mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 10:57:04 +00:00
Improve join/leave, publish messages
This commit is contained in:
244
package-lock.json
generated
244
package-lock.json
generated
@@ -12,7 +12,15 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||
"@sveltejs/adapter-static": "^3.0.4",
|
||||
"@tiptap/starter-kit": "^2.6.4",
|
||||
"@tiptap/extension-code": "^2.6.6",
|
||||
"@tiptap/extension-code-block": "^2.6.6",
|
||||
"@tiptap/extension-document": "^2.6.6",
|
||||
"@tiptap/extension-dropcursor": "^2.6.6",
|
||||
"@tiptap/extension-gapcursor": "^2.6.6",
|
||||
"@tiptap/extension-hard-break": "^2.6.6",
|
||||
"@tiptap/extension-history": "^2.6.6",
|
||||
"@tiptap/extension-paragraph": "^2.6.6",
|
||||
"@tiptap/extension-text": "^2.6.6",
|
||||
"@tiptap/suggestion": "^2.6.4",
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@welshman/lib": "^0.0.15",
|
||||
@@ -1116,39 +1124,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/core": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.6.4.tgz",
|
||||
"integrity": "sha512-lv+JyBI+5C6C7BMLYg2bloB00HvAZkcvgO3CzmFia28Vtt1P9yhS44elvBemhUf7IP7Hu12FUzDWY+2GQqiqkw==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.6.6.tgz",
|
||||
"integrity": "sha512-VO5qTsjt6rwworkuo0s5AqYMfDA0ZwiTiH6FHKFSu2G/6sS7HKcc/LjPq+5Legzps4QYdBDl3W28wGsGuS1GdQ==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-blockquote": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.6.4.tgz",
|
||||
"integrity": "sha512-BzeQ52qHL4AEryPqgvPNRJ2siSTfSi2s3k7hVC29QYUTOidLSSDWVihn7lzJoBnqDMAOYj7yUhnEUEdjvOFGqw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bold": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.6.4.tgz",
|
||||
"integrity": "sha512-DIKUiO2aqO9D3dAQngBacWk/vYwDY13+q3t5dlawRTCIHxgV571vGb+YbcLswbWPQjOziIBc5QgwUVZLjA8OkA==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
"@tiptap/pm": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bubble-menu": {
|
||||
@@ -1168,66 +1152,54 @@
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bullet-list": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.6.4.tgz",
|
||||
"integrity": "sha512-SsEqWNvbcLjgPYQXWT+gm8Mdtd6SnM9kr5xdfOvfe9W1RCYi7U7SQjaYGLGQXuy3E8NDugNiG+ss2POMj4RaUQ==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-code": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.6.4.tgz",
|
||||
"integrity": "sha512-qCt/CRhV+s1E9XVCDxGgFwyQRjcLsqBuY5UTwH3Zp8MIBniyLyJDD0Rv9DgvVqalzRC8RoRxVey9Al3YhYNqsw==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.6.6.tgz",
|
||||
"integrity": "sha512-JrEFKsZiLvfvOFhOnnrpA0TzCuJjDeysfbMeuKUZNV4+DhYOL28d39H1++rEtJAX0LcbBU60oC5/PrlU9SpvRQ==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-code-block": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.6.4.tgz",
|
||||
"integrity": "sha512-dnZYiKVNdHfqZqYgoCElLk8ETLlV3Q0rw3IVDKDTwrhanSSooGfkVts/Gn/jtJUIulRdu8lH/0qZCgM4ihznfw==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.6.6.tgz",
|
||||
"integrity": "sha512-1YLp/zHMHSkE2xzht8nPR6T4sQJJ3ket798czxWuQEbetFv/l0U/mpiPpYSLObj6oTAoqYZ0kWXZj5eQSpPB8Q==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4",
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6",
|
||||
"@tiptap/pm": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-document": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.6.4.tgz",
|
||||
"integrity": "sha512-fEQzou6J/w7GWiMqxxiwX2TEB6hgjBsImkHCxU05a4IOnIkzC8C9pV+NWa8u1LGvbERmVPBQqWYJG6phDhtYkg==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.6.6.tgz",
|
||||
"integrity": "sha512-6qlH5VWzLHHRVeeciRC6C4ZHpMsAGPNG16EF53z0GeMSaaFD/zU3B239QlmqXmLsAl8bpf8Bn93N0t2ABUvScw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-dropcursor": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.6.4.tgz",
|
||||
"integrity": "sha512-maTQi2R63i1S3CCJTjyuHMpk0BvnFuUxq7krZ3LBCOJgUeS78PF/XPirbbR7s2jOVsHK77LYsgdoS3ApDu1zdQ==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.6.6.tgz",
|
||||
"integrity": "sha512-O6CeKriA9uyHsg7Ui4z5ZjEWXQxrIL+1zDekffW0wenGC3G4LUsCzAiFS4LSrR9a3u7tnwqGApW10rdkmCGF4w==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4",
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6",
|
||||
"@tiptap/pm": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-floating-menu": {
|
||||
@@ -1261,66 +1233,41 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-gapcursor": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.6.4.tgz",
|
||||
"integrity": "sha512-g5fa1RLNpFZoiE5PIvG/pFIz88CvtiWkBUp5OOYrPxNzByazcbBsBI8Sa5ptDVrbDqerayUZYAVFPhXnq7MSlQ==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.6.6.tgz",
|
||||
"integrity": "sha512-O2lQ2t0X0Vsbn3yLWxFFHrXY6C2N9Y6ZF/M7LWzpcDTUZeWuhoNkFE/1yOM0h6ZX1DO2A9hNIrKpi5Ny8yx+QA==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4",
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6",
|
||||
"@tiptap/pm": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-hard-break": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.6.4.tgz",
|
||||
"integrity": "sha512-kBGGSBtp9oQlRBH7PfRvhbrauEphiJEuFUP9n/amAbrrNSabwmvBgyMl6wFXgMdfHF6CSv2YDgndE1sk8SjPSg==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.6.6.tgz",
|
||||
"integrity": "sha512-bsUuyYBrMDEiudx1dOQSr9MzKv13m0xHWrOK+DYxuIDYJb5g+c9un5cK7Js+et/HEYYSPOoH/iTW6h+4I5YeUg==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-heading": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.6.4.tgz",
|
||||
"integrity": "sha512-GHwDguzRXRrB5htGPx6T0f0uN9RPAkjbjrl28T7LFXX5Lb2XO+Esr1l4LNsTU49H4wR9nL/89ZjEcd36BUWkog==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-history": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.6.4.tgz",
|
||||
"integrity": "sha512-Hr3SrvMsyDHKcsF4u3QPdY/NBYG9V0g5pPmZs/tdysXot3NUdkEYowjs9K9o5osKom364KjxQS0c9mOjyeKu1g==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.6.6.tgz",
|
||||
"integrity": "sha512-tPTzAmPGqMX5Bd5H8lzRpmsaMvB9DvI5Dy2za/VQuFtxgXmDiFVgHRkRXIuluSkPTuANu84XBOQ0cBijqY8x4w==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4",
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-horizontal-rule": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.6.4.tgz",
|
||||
"integrity": "sha512-lL29Hxsj1qFwRqtg41JlBOK/hmN+qnwIWvNCyZpKEVHs7d0iELj2REB/7R1KKAAdsvYo7pJrgqwBd1Ph6xRLpw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4",
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6",
|
||||
"@tiptap/pm": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-image": {
|
||||
@@ -1335,18 +1282,6 @@
|
||||
"@tiptap/core": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-italic": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.6.4.tgz",
|
||||
"integrity": "sha512-XG/zaKVuorKr1vGEWEgLQTnQwOpNn/JyGxO7oC7wfYx5eYpbbCtMTEMvuqNvkm7kpvVAUx3ugi/D8DWyWZEtYg==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-link": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.6.4.tgz",
|
||||
@@ -1363,70 +1298,34 @@
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-list-item": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.6.4.tgz",
|
||||
"integrity": "sha512-NLP0nshX8eCZMLospdCsUApUQHPL1+T/MIi/Hhr0aNeaAg7KwBNH8/rFPuxPNs4BQkHOCuYq4Fm+klkebkFYJA==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-ordered-list": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.6.4.tgz",
|
||||
"integrity": "sha512-ecAEFpRKZc+b3f54EGvaRp7hsVza2i1nRhxHoPElqVR5DiCCSuSgAPCsKhUUT1rKweK9h56HiC4xswAyFrU5Ag==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-paragraph": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.6.4.tgz",
|
||||
"integrity": "sha512-JVlvhZPzjz0Q+29KmnrmLr3A3SvAMfKOZxbZZVnzee6vtI6rqjdYGBOtyyyWwrAliNQB6GkHiKmT3GxH76dz7A==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.6.6.tgz",
|
||||
"integrity": "sha512-fD/onCr16UQWx+/xEmuFC2MccZZ7J5u4YaENh8LMnAnBXf78iwU7CAcmuc9rfAEO3qiLoYGXgLKiHlh2ZfD4wA==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-strike": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.6.4.tgz",
|
||||
"integrity": "sha512-EV4hEA5qnRtKViaLKcucFvXP9xEUJOFgpFeOrp2xIgSXJLSmutkaDfz7nxJ2RLzwwYvPfWUL7ay97JSCzSuaIA==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.6.4.tgz",
|
||||
"integrity": "sha512-QfspuCTTpmFrSLbDs2z/0W7GLaoNanwj4OCKPSPz5XcraZJgFLsWAqZxZE4aLgZbJH2hcGWMe5ZHmvLf5dJogw==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.6.6.tgz",
|
||||
"integrity": "sha512-e84uILnRzNzcwK1DVQNpXVmBG1Cq3BJipTOIDl1LHifOok7MBjhI/X+/NR0bd3N2t6gmDTWi63+4GuJ5EeDmsg==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.6.4"
|
||||
"@tiptap/core": "^2.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/pm": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.6.4.tgz",
|
||||
"integrity": "sha512-k/AyigUioZVxFTcF7kWcUh5xeOV0bdGzHz+wmtP33md2jo8SJP29yEZ4Kshvk0IcFnVFEDrsfKiGhLRWpKx+YQ==",
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.6.6.tgz",
|
||||
"integrity": "sha512-56FGLPn3fwwUlIbLs+BO21bYfyqP9fKyZQbQyY0zWwA/AG2kOwoXaRn7FOVbjP6CylyWpFJnpRRmgn694QKHEg==",
|
||||
"dependencies": {
|
||||
"prosemirror-changeset": "^2.2.1",
|
||||
"prosemirror-collab": "^1.3.1",
|
||||
@@ -1452,37 +1351,6 @@
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/starter-kit": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.6.4.tgz",
|
||||
"integrity": "sha512-uvGXOI6h+AjyyOgJOmBSFrDR7xJ841+gtwzGbAolVM2a7LCEkocyHjLBWFYVfQu2vvMIqA63+0+yAsw6ghwUgw==",
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^2.6.4",
|
||||
"@tiptap/extension-blockquote": "^2.6.4",
|
||||
"@tiptap/extension-bold": "^2.6.4",
|
||||
"@tiptap/extension-bullet-list": "^2.6.4",
|
||||
"@tiptap/extension-code": "^2.6.4",
|
||||
"@tiptap/extension-code-block": "^2.6.4",
|
||||
"@tiptap/extension-document": "^2.6.4",
|
||||
"@tiptap/extension-dropcursor": "^2.6.4",
|
||||
"@tiptap/extension-gapcursor": "^2.6.4",
|
||||
"@tiptap/extension-hard-break": "^2.6.4",
|
||||
"@tiptap/extension-heading": "^2.6.4",
|
||||
"@tiptap/extension-history": "^2.6.4",
|
||||
"@tiptap/extension-horizontal-rule": "^2.6.4",
|
||||
"@tiptap/extension-italic": "^2.6.4",
|
||||
"@tiptap/extension-list-item": "^2.6.4",
|
||||
"@tiptap/extension-ordered-list": "^2.6.4",
|
||||
"@tiptap/extension-paragraph": "^2.6.4",
|
||||
"@tiptap/extension-strike": "^2.6.4",
|
||||
"@tiptap/extension-text": "^2.6.4",
|
||||
"@tiptap/pm": "^2.6.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/suggestion": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.6.4.tgz",
|
||||
|
||||
10
package.json
10
package.json
@@ -37,7 +37,15 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||
"@sveltejs/adapter-static": "^3.0.4",
|
||||
"@tiptap/starter-kit": "^2.6.4",
|
||||
"@tiptap/extension-code": "^2.6.6",
|
||||
"@tiptap/extension-code-block": "^2.6.6",
|
||||
"@tiptap/extension-document": "^2.6.6",
|
||||
"@tiptap/extension-dropcursor": "^2.6.6",
|
||||
"@tiptap/extension-gapcursor": "^2.6.6",
|
||||
"@tiptap/extension-hard-break": "^2.6.6",
|
||||
"@tiptap/extension-history": "^2.6.6",
|
||||
"@tiptap/extension-paragraph": "^2.6.6",
|
||||
"@tiptap/extension-text": "^2.6.6",
|
||||
"@tiptap/suggestion": "^2.6.4",
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@welshman/lib": "^0.0.15",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {derived} from "svelte/store"
|
||||
import {memoize, assoc} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import type {TrustedEvent, HashedEvent} from "@welshman/util"
|
||||
import {Repository, createEvent, Relay} from "@welshman/util"
|
||||
import {withGetter} from "@welshman/store"
|
||||
import {NetworkContext, Tracker} from "@welshman/net"
|
||||
@@ -8,13 +8,17 @@ import {Nip46Broker, Nip46Signer, Nip07Signer, Nip01Signer} from "@welshman/sign
|
||||
import {synced} from "@lib/util"
|
||||
import type {Session} from "@app/types"
|
||||
|
||||
export const DEFAULT_RELAYS = ["wss://groups.fiatjaf.com/"]
|
||||
export const DEFAULT_RELAYS = [
|
||||
"wss://groups.fiatjaf.com/",
|
||||
"wss://relay29.galaxoidlabs.com/",
|
||||
"wss://devrelay.highlighter.com/",
|
||||
]
|
||||
|
||||
export const INDEXER_RELAYS = ["wss://purplepag.es/", "wss://relay.damus.io/", "wss://nos.lol/"]
|
||||
|
||||
export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
|
||||
|
||||
export const repository = new Repository()
|
||||
export const repository = new Repository<HashedEvent>()
|
||||
|
||||
export const relay = new Relay(repository)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {uniqBy, uniq, now} from "@welshman/lib"
|
||||
import {uniqBy, uniq, now, choice} from "@welshman/lib"
|
||||
import {
|
||||
GROUPS,
|
||||
GROUP_JOIN,
|
||||
asDecryptedEvent,
|
||||
getGroupTags,
|
||||
getRelayTagValues,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
makeList,
|
||||
createList,
|
||||
createEvent,
|
||||
displayProfile,
|
||||
} from "@welshman/util"
|
||||
import {pk, signer, repository, INDEXER_RELAYS} from "@app/base"
|
||||
import {
|
||||
@@ -23,8 +25,34 @@ import {
|
||||
makeThunk,
|
||||
publishThunk,
|
||||
ensurePlaintext,
|
||||
getProfilesByPubkey,
|
||||
} from "@app/state"
|
||||
|
||||
// Utils
|
||||
|
||||
export const getPubkeyHints = (pubkey: string) => {
|
||||
const selections = getRelaySelectionsByPubkey().get(pubkey)
|
||||
const relays = selections ? getWriteRelayUrls(selections) : []
|
||||
const hints = relays.length ? relays : INDEXER_RELAYS
|
||||
|
||||
return hints
|
||||
}
|
||||
|
||||
export const getPubkeyPetname = (pubkey: string) => {
|
||||
const profile = getProfilesByPubkey().get(pubkey)
|
||||
const display = displayProfile(profile)
|
||||
|
||||
return display
|
||||
}
|
||||
|
||||
export const makeMention = (pubkey: string, hints?: string[]) =>
|
||||
["p", pubkey, choice(hints || getPubkeyHints(pubkey)), getPubkeyPetname(pubkey)]
|
||||
|
||||
export const makeIMeta = (url: string, data: Record<string, string>) =>
|
||||
["imeta", `url ${url}`, ...Object.entries(data).map(([k, v]) => [k, v].join(' '))]
|
||||
|
||||
// Loaders
|
||||
|
||||
export const loadUserData = async (pubkey: string, hints: string[] = []) => {
|
||||
const relaySelections = await loadRelaySelections(pubkey, INDEXER_RELAYS)
|
||||
const relays = uniq([
|
||||
@@ -46,6 +74,8 @@ export const loadUserData = async (pubkey: string, hints: string[] = []) => {
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
// Updates
|
||||
|
||||
export type ModifyTags = (tags: string[][]) => string[][]
|
||||
|
||||
export const updateList = async (kind: number, modifyTags: ModifyTags) => {
|
||||
@@ -67,3 +97,10 @@ export const addGroupMemberships = (newTags: string[][]) =>
|
||||
|
||||
export const removeGroupMemberships = (noms: string[]) =>
|
||||
updateList(GROUPS, (tags: string[][]) => tags.filter(t => !noms.includes(t[1])))
|
||||
|
||||
export const sendJoinRequest = async (nom: string, url: string) => {
|
||||
const event = createEvent(GROUP_JOIN, {tags: [["h", nom]]})
|
||||
const result = await publishThunk(makeThunk({event, relays: [url]}))
|
||||
|
||||
return result[url]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from 'svelte'
|
||||
import type {Readable} from 'svelte/store'
|
||||
import {nprofileEncode} from 'nostr-tools/nip19'
|
||||
import {createEditor, type Editor, EditorContent, SvelteNodeViewRenderer} from 'svelte-tiptap'
|
||||
import {Extension} from '@tiptap/core'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Code from '@tiptap/extension-code'
|
||||
import CodeBlock from '@tiptap/extension-code-block'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||
import Gapcursor from '@tiptap/extension-gapcursor'
|
||||
import History from '@tiptap/extension-history'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import HardBreakExtension from '@tiptap/extension-hard-break'
|
||||
import {Bolt11Extension, NProfileExtension, NEventExtension, NAddrExtension, ImageExtension, VideoExtension, FileUploadExtension} from 'nostr-editor'
|
||||
import type {StampedEvent} from '@welshman/util'
|
||||
@@ -21,8 +29,11 @@
|
||||
import GroupComposeSuggestions from '@app/components/GroupComposeSuggestions.svelte'
|
||||
import GroupComposeTopicSuggestion from '@app/components/GroupComposeTopicSuggestion.svelte'
|
||||
import GroupComposeProfileSuggestion from '@app/components/GroupComposeProfileSuggestion.svelte'
|
||||
import {signer} from '@app/base'
|
||||
import {searchProfiles, searchTopics, displayProfileByPubkey} from '@app/state'
|
||||
import {signer, INDEXER_RELAYS} from '@app/base'
|
||||
import {searchProfiles, publishThunk, makeThunk, searchTopics, userRelayUrlsByNom, getWriteRelayUrls, displayProfileByPubkey, getRelaySelectionsByPubkey} from '@app/state'
|
||||
import {getPubkeyHints, makeMention, makeIMeta} from '@app/commands'
|
||||
|
||||
export let nom
|
||||
|
||||
let editor: Readable<Editor>
|
||||
let uploading = false
|
||||
@@ -34,31 +45,36 @@
|
||||
|
||||
const uploadFiles = () => $editor.chain().uploadFiles().run()
|
||||
|
||||
const sendMessage = () => {
|
||||
console.log($editor.getJSON())
|
||||
$editor.chain().clearContent().run()
|
||||
createEvent(CHAT_MESSAGE, {
|
||||
content: '',
|
||||
tags: [],
|
||||
const sendMessage = async () => {
|
||||
const json = $editor.getJSON()
|
||||
const relays = $userRelayUrlsByNom.get(nom)
|
||||
const event = createEvent(CHAT_MESSAGE, {
|
||||
content: $editor.getText(),
|
||||
tags: [
|
||||
["h", nom],
|
||||
...findNodes(TopicExtension.name, json).map(t => ["t", t.attrs!.name.toLowerCase()]),
|
||||
...findNodes(NProfileExtension.name, json).map(m => makeMention(m.attrs!.pubkey, m.attrs!.relays)),
|
||||
...findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) => makeIMeta(src, {x, ox: x})),
|
||||
],
|
||||
})
|
||||
|
||||
publishThunk(makeThunk({event, relays}))
|
||||
|
||||
$editor.chain().clearContent().run()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
editor = createEditor({
|
||||
autofocus: true,
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
blockquote: false,
|
||||
bold: false,
|
||||
bulletList: false,
|
||||
heading: false,
|
||||
horizontalRule: false,
|
||||
italic: false,
|
||||
listItem: false,
|
||||
orderedList: false,
|
||||
strike: false,
|
||||
hardBreak: false,
|
||||
}),
|
||||
Code,
|
||||
CodeBlock,
|
||||
Document,
|
||||
Dropcursor,
|
||||
Gapcursor,
|
||||
History,
|
||||
Paragraph,
|
||||
Text,
|
||||
HardBreakExtension.extend({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
@@ -79,7 +95,7 @@
|
||||
LinkExtension.extend({
|
||||
addNodeView: () => SvelteNodeViewRenderer(GroupComposeLink),
|
||||
}),
|
||||
Bolt11Extension.extend({addNodeView: () => SvelteNodeViewRenderer(GroupComposeBolt11)}),
|
||||
Bolt11Extension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeBolt11)})),
|
||||
NProfileExtension.extend({
|
||||
addNodeView: () => SvelteNodeViewRenderer(GroupComposeMention),
|
||||
addProseMirrorPlugins() {
|
||||
@@ -89,7 +105,12 @@
|
||||
name: 'nprofile',
|
||||
editor: this.editor,
|
||||
search: searchProfiles,
|
||||
select: (pubkey: string, props: any) => props.command({pubkey}),
|
||||
select: (pubkey: string, props: any) => {
|
||||
const relays = getPubkeyHints(pubkey)
|
||||
const nprofile = nprofileEncode({pubkey, relays})
|
||||
|
||||
return props.command({pubkey, nprofile, relays})
|
||||
},
|
||||
suggestionComponent: GroupComposeProfileSuggestion,
|
||||
suggestionsComponent: GroupComposeSuggestions,
|
||||
}),
|
||||
@@ -135,11 +156,7 @@
|
||||
}),
|
||||
],
|
||||
content: '',
|
||||
onUpdate: () => {
|
||||
// console.log('update', $editor.getJSON(), $editor.getText())
|
||||
},
|
||||
})
|
||||
console.log($editor)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
<script lang="ts">
|
||||
import cx from 'classnames'
|
||||
import type {NodeViewProps} from '@tiptap/core'
|
||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
||||
import Icon from '@lib/components/Icon.svelte'
|
||||
import Button from '@lib/components/Button.svelte'
|
||||
import {clip} from '@app/toast'
|
||||
|
||||
export let node: NodeViewProps['node']
|
||||
export let selected: NodeViewProps['selected']
|
||||
|
||||
const copy = () => clip(node.attrs.lnbc)
|
||||
</script>
|
||||
|
||||
<NodeViewWrapper class="inline link-content">
|
||||
{node.attrs.lnbc.slice(0, 16)}...
|
||||
<NodeViewWrapper class="inline">
|
||||
<Button on:click={copy} class={cx("link-content", {'link-content-selected': selected})}>
|
||||
<Icon icon="bolt" size={3} class="inline-block translate-y-px" />
|
||||
{node.attrs.lnbc.slice(0, 16)}...
|
||||
</Button>
|
||||
</NodeViewWrapper>
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
import {deriveProfile} from '@app/state'
|
||||
|
||||
export let node: NodeViewProps['node']
|
||||
export let selected: NodeViewProps['selected']
|
||||
|
||||
$: profile = deriveProfile(node.attrs.pubkey, node.attrs.relays)
|
||||
</script>
|
||||
|
||||
<NodeViewWrapper class="inline">
|
||||
<span class="text-primary">@</span><Link external href="https://njump.me/{node.attrs.nprofile}">{displayProfile($profile)}</Link>
|
||||
<Link external href="https://njump.me/{node.attrs.nprofile}" class={cx("link-content", {'link-content-selected': selected})}>
|
||||
@{displayProfile($profile)}
|
||||
</Link>
|
||||
</NodeViewWrapper>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script lang="ts">
|
||||
import cx from 'classnames'
|
||||
import type {NodeViewProps} from '@tiptap/core'
|
||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
||||
import Link from '@lib/components/Link.svelte'
|
||||
|
||||
export let node: NodeViewProps['node']
|
||||
export let selected: NodeViewProps['selected']
|
||||
</script>
|
||||
|
||||
<NodeViewWrapper class="inline text-primary">
|
||||
#<span class="underline">{node.attrs.name}</span>
|
||||
<NodeViewWrapper class="inline">
|
||||
<Link external href="https://coracle.social/topics/{node.attrs.name.toLowerCase()}" class={cx("link-content", {'link-content-selected': selected})}>
|
||||
#{node.attrs.name}
|
||||
</Link>
|
||||
</NodeViewWrapper>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script lang="ts">
|
||||
import twColors from "tailwindcss/colors"
|
||||
import {readable} from "svelte/store"
|
||||
import {readable, derived} from "svelte/store"
|
||||
import {hash} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {GROUP_REPLY, getAncestorTags, displayPubkey} from "@welshman/util"
|
||||
import {fly} from "@lib/transition"
|
||||
import {PublishStatus} from "@welshman/net"
|
||||
import {GROUP_REPLY, displayRelayUrl, getAncestorTags, displayPubkey} from "@welshman/util"
|
||||
import {fly, fade} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import {deriveProfile, deriveProfileDisplay, deriveEvent} from "@app/state"
|
||||
import type {PublishStatusData} from "@app/state"
|
||||
import {deriveProfile, deriveProfileDisplay, deriveEvent, publishStatusData} from "@app/state"
|
||||
|
||||
export let event: TrustedEvent
|
||||
export let showPubkey: boolean
|
||||
@@ -41,10 +43,17 @@
|
||||
const parentHints = [replies[0]?.[2]].filter(Boolean)
|
||||
const parentEvent = parentId ? deriveEvent(parentId, parentHints) : readable(null)
|
||||
const [colorName, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
|
||||
const ps = derived(publishStatusData, $m => Object.values($m[event.id] || {}))
|
||||
|
||||
const findStatus = ($ps: PublishStatusData[], statuses: PublishStatus[]) =>
|
||||
$ps.find(({status}) => statuses.includes(status))
|
||||
|
||||
$: parentPubkey = $parentEvent?.pubkey || replies[0]?.[4]
|
||||
$: parentProfile = deriveProfile(parentPubkey)
|
||||
$: parentProfileDisplay = deriveProfileDisplay(parentPubkey)
|
||||
$: isPublished = findStatus($ps, [PublishStatus.Success])
|
||||
$: isPending = findStatus($ps, [PublishStatus.Pending])
|
||||
$: failure = !isPending && !isPublished && findStatus($ps, [PublishStatus.Failure, PublishStatus.Timeout])
|
||||
</script>
|
||||
|
||||
<div in:fly class="group relative flex flex-col gap-1 p-2 transition-colors hover:bg-base-300">
|
||||
@@ -65,13 +74,28 @@
|
||||
{#if showPubkey}
|
||||
<Avatar src={$profile?.picture} class="border border-solid border-base-content" size={10} />
|
||||
{:else}
|
||||
<div class="w-10" />
|
||||
<div class="min-w-10 max-w-10 w-10" />
|
||||
{/if}
|
||||
<div class="-mt-1">
|
||||
{#if showPubkey}
|
||||
<strong class="text-sm" style="color: {colorValue}" data-color={colorName}>{$profileDisplay}</strong>
|
||||
{/if}
|
||||
<p class="text-sm">{event.content}</p>
|
||||
<p class="text-sm">
|
||||
{event.content}
|
||||
{#if isPending}
|
||||
<span class="ml-1 flex-inline gap-1">
|
||||
<span class="loading loading-spinner h-3 w-3 mx-1 translate-y-px" />
|
||||
<span class="opacity-50">Sending...</span>
|
||||
</span>
|
||||
{:else if failure}
|
||||
<span
|
||||
class="ml-1 flex-inline gap-1 tooltip cursor-pointer"
|
||||
data-tip="{failure.message} ({displayRelayUrl(failure.url)})">
|
||||
<Icon icon="danger" class="translate-y-px" size={3} />
|
||||
<span class="opacity-50">Failed to send!</span>
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import {displayRelayUrl} from '@welshman/util'
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import {DEFAULT_RELAYS} from "@app/base"
|
||||
import {clip} from "@app/toast"
|
||||
</script>
|
||||
|
||||
@@ -21,14 +23,16 @@
|
||||
>. If you do decide to join someone else's, make sure to follow their directions for registering
|
||||
as a user.
|
||||
</p>
|
||||
<div class="alert !flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon="remote-controller-minimalistic" />
|
||||
groups.fiatjaf.com
|
||||
{#each DEFAULT_RELAYS as url}
|
||||
<div class="alert !flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon="remote-controller-minimalistic" />
|
||||
{displayRelayUrl(url)}
|
||||
</div>
|
||||
<Button on:click={() => clip(url)}>
|
||||
<Icon icon="copy" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button on:click={() => clip("groups.fiatjaf.com")}>
|
||||
<Icon icon="copy" />
|
||||
</Button>
|
||||
</div>
|
||||
{/each}
|
||||
<Button class="btn btn-primary" on:click={() => history.back()}>Got it</Button>
|
||||
</div>
|
||||
|
||||
@@ -52,9 +52,12 @@
|
||||
{#each $userGroupsByNom.entries() as [nom, qualifiedGroups] (nom)}
|
||||
{@const qualifiedGroup = qualifiedGroups[0]}
|
||||
<PrimaryNavItem title={displayGroup(qualifiedGroup?.group)} href="/spaces/{nom}">
|
||||
<div class="w-10 rounded-full border border-solid border-base-300">
|
||||
<img alt={displayGroup(qualifiedGroup?.group)} src={qualifiedGroup?.group.picture} />
|
||||
</div>
|
||||
<Avatar
|
||||
icon="ghost"
|
||||
class="!h-10 !w-10 border border-solid border-base-300"
|
||||
alt={displayGroup(qualifiedGroup?.group)}
|
||||
src={qualifiedGroup?.group.picture}
|
||||
size={7} />
|
||||
</PrimaryNavItem>
|
||||
{/each}
|
||||
<PrimaryNavItem title="Add Space" on:click={addSpace}>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<script lang="ts">
|
||||
import {append, remove} from "@welshman/lib"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {PublishStatus} from "@welshman/net"
|
||||
import {goto} from "$app/navigation"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import InfoNip29 from "@app/components/InfoNip29.svelte"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {pushModal, clearModal} from "@app/modal"
|
||||
import {pushToast} from "@app/toast"
|
||||
import type {PublishStatusData} from "@app/state"
|
||||
import {deriveGroup, displayGroup, relayUrlsByNom} from "@app/state"
|
||||
import {addGroupMemberships} from "@app/commands"
|
||||
import {sendJoinRequest, addGroupMemberships} from "@app/commands"
|
||||
|
||||
export let nom
|
||||
|
||||
@@ -22,20 +25,35 @@
|
||||
: append(e.target.value, urls)
|
||||
}
|
||||
|
||||
const tryJoin = async () => {
|
||||
for (const url of urls) {
|
||||
const {status, message} = await sendJoinRequest(nom, url)
|
||||
|
||||
if (status !== PublishStatus.Success) {
|
||||
return pushToast({
|
||||
theme: 'error',
|
||||
message: `Failed to join relay: ${message || status}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await addGroupMemberships(urls.map(url => ["group", nom, url]))
|
||||
|
||||
clearModal()
|
||||
}
|
||||
|
||||
const join = async () => {
|
||||
loading = true
|
||||
|
||||
try {
|
||||
await addGroupMemberships(urls.map(url => ["group", nom, url]))
|
||||
await tryJoin()
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
goto(`/spaces/${nom}`)
|
||||
}
|
||||
|
||||
let urls: string[] = $relayUrlsByNom.get(nom) || []
|
||||
let loading = false
|
||||
let urls: string[] = $relayUrlsByNom.get(nom) || []
|
||||
|
||||
$: hasUrls = urls.length > 0
|
||||
$: urlOptions = $relayUrlsByNom.get(nom)?.toSorted() || []
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
</script>
|
||||
|
||||
{#if $toast}
|
||||
{@const theme = $toast.theme || "info"}
|
||||
{#key $toast.id}
|
||||
<div transition:fly class="toast z-toast">
|
||||
<div
|
||||
role="alert"
|
||||
class="alert flex justify-center"
|
||||
class:alert-error={$toast.theme === "error"}>
|
||||
class:alert-info={theme === "info"}
|
||||
class:alert-error={theme === "error"}>
|
||||
{$toast.message}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {get, writable, readable, derived} from "svelte/store"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import {
|
||||
max,
|
||||
append,
|
||||
between,
|
||||
uniqBy,
|
||||
groupBy,
|
||||
@@ -38,6 +39,8 @@ import {
|
||||
displayPubkey,
|
||||
GROUP_JOIN,
|
||||
GROUP_ADD_USER,
|
||||
isStampedEvent,
|
||||
isEventTemplate,
|
||||
} from "@welshman/util"
|
||||
import type {SignedEvent, HashedEvent, EventTemplate, TrustedEvent, PublishedProfile, PublishedList} from "@welshman/util"
|
||||
import type {SubscribeRequest, PublishRequest} from "@welshman/net"
|
||||
@@ -129,9 +132,13 @@ export type Thunk = {
|
||||
relays: string[]
|
||||
}
|
||||
|
||||
export const thunkWorker = new Worker<Thunk>()
|
||||
export type ThunkWithResolve = Thunk & {
|
||||
resolve: (data: PublishStatusDataByUrl) => void
|
||||
}
|
||||
|
||||
thunkWorker.addGlobalHandler(async ({event, relays}: Thunk) => {
|
||||
export const thunkWorker = new Worker<ThunkWithResolve>()
|
||||
|
||||
thunkWorker.addGlobalHandler(async ({event, relays, resolve}: ThunkWithResolve) => {
|
||||
const session = getSession(event.pubkey)
|
||||
|
||||
if (!session) {
|
||||
@@ -139,26 +146,20 @@ thunkWorker.addGlobalHandler(async ({event, relays}: Thunk) => {
|
||||
}
|
||||
|
||||
const signedEvent = await getSigner(session)!.sign(event)
|
||||
const savedEvent = repository.getEvent(signedEvent.id)
|
||||
const pub = basePublish({event: signedEvent, relays})
|
||||
|
||||
// Copy the signature over since we had deferred it
|
||||
if (savedEvent) {
|
||||
savedEvent.sig = signedEvent.sig
|
||||
}
|
||||
;(repository.getEvent(signedEvent.id) as SignedEvent).sig = signedEvent.sig
|
||||
|
||||
const failures = new Set<string>()
|
||||
// Track publish success
|
||||
const {id} = event
|
||||
const statusByUrl: PublishStatusDataByUrl = {}
|
||||
|
||||
// Watch for failures
|
||||
pub.emitter.on('*', (status: PublishStatus, url: string) => {
|
||||
console.log('pub status', status, url)
|
||||
pub.emitter.on('*', (status: PublishStatus, url: string, message: string) => {
|
||||
publishStatusData.update(assoc(id, Object.assign(statusByUrl, {[url]: {id, url, status, message}})))
|
||||
|
||||
if ([PublishStatus.Failure, PublishStatus.Timeout].includes(status)) {
|
||||
failures.add(url)
|
||||
}
|
||||
|
||||
if (failures.size === relays.length) {
|
||||
console.warn("Failed to publish", pub)
|
||||
if (Object.values(statusByUrl).filter(s => s.status !== PublishStatus.Pending).length === relays.length) {
|
||||
resolve(statusByUrl)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -178,17 +179,20 @@ export const makeThunk = ({event, relays}: ThunkParams) => {
|
||||
return {event: hash(own(stamp(event), $pk)), relays}
|
||||
}
|
||||
|
||||
export const publishThunk = (thunk: Thunk) => {
|
||||
thunkWorker.push(thunk)
|
||||
repository.publish(thunk.event)
|
||||
}
|
||||
export const publishThunk = (thunk: Thunk) =>
|
||||
new Promise<PublishStatusDataByUrl>(resolve => {
|
||||
thunkWorker.push({...thunk, resolve})
|
||||
repository.publish(thunk.event)
|
||||
})
|
||||
|
||||
// Subscribe
|
||||
|
||||
export const subscribe = (request: SubscribeRequest) => {
|
||||
const sub = baseSubscribe({delay: 50, authTimeout: 3000, ...request})
|
||||
|
||||
sub.emitter.on("event", (url: string, e: SignedEvent) => repository.publish(e))
|
||||
sub.emitter.on("event", (url: string, e: SignedEvent) => {
|
||||
repository.publish(e)
|
||||
})
|
||||
|
||||
return sub
|
||||
}
|
||||
@@ -198,14 +202,27 @@ export const load = (request: SubscribeRequest) =>
|
||||
const sub = subscribe({closeOnEose: true, timeout: 3000, ...request})
|
||||
const events: TrustedEvent[] = []
|
||||
|
||||
sub.emitter.on("event", (url: string, e: SignedEvent) => {
|
||||
repository.publish(e)
|
||||
events.push(e)
|
||||
})
|
||||
sub.emitter.on("event", (url: string, e: SignedEvent) => events.push(e))
|
||||
|
||||
sub.emitter.on("complete", () => resolve(events))
|
||||
})
|
||||
|
||||
// Publish status
|
||||
|
||||
export type PublishStatusData = {
|
||||
id: string
|
||||
url: string
|
||||
message: string
|
||||
status: PublishStatus
|
||||
}
|
||||
|
||||
|
||||
export type PublishStatusDataByUrl = Record<string, PublishStatusData>
|
||||
|
||||
export type PublishStatusDataByUrlById = Record<string, PublishStatusDataByUrl>
|
||||
|
||||
export const publishStatusData = writable<PublishStatusDataByUrlById>({})
|
||||
|
||||
// Freshness
|
||||
|
||||
export const freshness = withGetter(writable<Record<string, number>>({}))
|
||||
@@ -255,7 +272,7 @@ export type Topic = {
|
||||
count: number
|
||||
}
|
||||
|
||||
export const topics = custom<Topic[]>(setter => {
|
||||
export const topics = custom<Topic[]>(setter => {
|
||||
const getTopics = () => {
|
||||
const topics = new Map<string, number>()
|
||||
for (const tagString of repository.eventsByTag.keys()) {
|
||||
@@ -290,7 +307,7 @@ export const searchTopics = derived(topics, $topics =>
|
||||
export const relays = writable<Relay[]>([])
|
||||
|
||||
export const relaysByPubkey = derived(relays, $relays =>
|
||||
groupBy(($relay: Relay) => $relay.pubkey, $relays),
|
||||
groupBy(($relay: Relay) => $relay.pubkey, $relays.filter(r => r.pubkey)),
|
||||
)
|
||||
|
||||
export const {
|
||||
@@ -508,7 +525,7 @@ export const getGroupName = (e?: TrustedEvent) => e?.tags.find(nthEq(0, "name"))
|
||||
|
||||
export const getGroupPicture = (e?: TrustedEvent) => e?.tags.find(nthEq(0, "picture"))?.[1]
|
||||
|
||||
export const displayGroup = (group?: Group) => group?.name || "[no name]"
|
||||
export const displayGroup = (group?: Group) => group?.name || group?.nom || "[no name]"
|
||||
|
||||
export type Group = {
|
||||
nom: string
|
||||
@@ -524,8 +541,8 @@ export type PublishedGroup = Omit<Group, "event"> & {
|
||||
|
||||
export const readGroup = (event: TrustedEvent) => {
|
||||
const nom = getIdentifier(event)!
|
||||
const name = event?.tags.find(nthEq(0, "name"))?.[1] || "[no name]"
|
||||
const about = event?.tags.find(nthEq(0, "about"))?.[1] || ""
|
||||
const name = event?.tags.find(nthEq(0, "name"))?.[1]
|
||||
const about = event?.tags.find(nthEq(0, "about"))?.[1]
|
||||
const picture = event?.tags.find(nthEq(0, "picture"))?.[1]
|
||||
|
||||
return {nom, name, about, picture, event}
|
||||
@@ -759,3 +776,18 @@ export const userGroupsByNom = withGetter(
|
||||
return $userGroupsByNom
|
||||
}),
|
||||
)
|
||||
|
||||
export const userRelayUrlsByNom = derived(
|
||||
userGroupsByNom,
|
||||
$userGroupsByNom => {
|
||||
const $userRelayUrlsByNom = new Map()
|
||||
|
||||
for (const [nom, groups] of $userGroupsByNom.entries()) {
|
||||
for (const group of groups) {
|
||||
pushToMapKey($userRelayUrlsByNom, nom, group.relay.url)
|
||||
}
|
||||
}
|
||||
|
||||
return $userRelayUrlsByNom
|
||||
}
|
||||
)
|
||||
|
||||
@@ -21,8 +21,6 @@ export const subs: Unsubscriber[] = []
|
||||
|
||||
export const DB_NAME = "flotilla"
|
||||
|
||||
export const DB_VERSION = 1
|
||||
|
||||
export const getAll = async (name: string) => {
|
||||
const tx = db.transaction(name, "readwrite")
|
||||
const store = tx.objectStore(name)
|
||||
@@ -81,12 +79,12 @@ export const initIndexedDbAdapter = async (name: string, adapter: IndexedDbAdapt
|
||||
)
|
||||
}
|
||||
|
||||
export const initStorage = async (adapters: Record<string, IndexedDbAdapter>) => {
|
||||
export const initStorage = async (version: number, adapters: Record<string, IndexedDbAdapter>) => {
|
||||
if (!window.indexedDB) return
|
||||
|
||||
window.addEventListener("beforeunload", () => closeStorage())
|
||||
|
||||
db = await openDB(DB_NAME, DB_VERSION, {
|
||||
db = await openDB(DB_NAME, version, {
|
||||
upgrade(db: IDBPDatabase) {
|
||||
const names = Object.keys(adapters)
|
||||
|
||||
|
||||
3
src/assets/icons/Bolt.svg
Normal file
3
src/assets/icons/Bolt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.66953 9.91436L8.73167 5.77133C10.711 3.09327 11.7007 1.75425 12.6241 2.03721C13.5474 2.32018 13.5474 3.96249 13.5474 7.24712V7.55682C13.5474 8.74151 13.5474 9.33386 13.926 9.70541L13.946 9.72466C14.3327 10.0884 14.9492 10.0884 16.1822 10.0884C18.4011 10.0884 19.5106 10.0884 19.8855 10.7613C19.8917 10.7724 19.8977 10.7837 19.9036 10.795C20.2576 11.4784 19.6152 12.3475 18.3304 14.0857L15.2683 18.2287C13.2889 20.9067 12.2992 22.2458 11.3758 21.9628C10.4525 21.6798 10.4525 20.0375 10.4525 16.7528L10.4526 16.4433C10.4526 15.2585 10.4526 14.6662 10.074 14.2946L10.054 14.2754C9.6673 13.9117 9.05079 13.9117 7.81775 13.9117C5.59888 13.9117 4.48945 13.9117 4.1145 13.2387C4.10829 13.2276 4.10225 13.2164 4.09639 13.205C3.74244 12.5217 4.3848 11.6526 5.66953 9.91436Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 919 B |
5
src/assets/icons/Danger.svg
Normal file
5
src/assets/icons/Danger.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 7V13" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<circle cx="12" cy="16" r="1" fill="#1C274C"/>
|
||||
<path d="M7.84308 3.80211C9.8718 2.6007 10.8862 2 12 2C13.1138 2 14.1282 2.6007 16.1569 3.80211L16.8431 4.20846C18.8718 5.40987 19.8862 6.01057 20.4431 7C21 7.98943 21 9.19084 21 11.5937V12.4063C21 14.8092 21 16.0106 20.4431 17C19.8862 17.9894 18.8718 18.5901 16.8431 19.7915L16.1569 20.1979C14.1282 21.3993 13.1138 22 12 22C10.8862 22 9.8718 21.3993 7.84308 20.1979L7.15692 19.7915C5.1282 18.5901 4.11384 17.9894 3.55692 17C3 16.0106 3 14.8092 3 12.4063V11.5937C3 9.19084 3 7.98943 3.55692 7C4.11384 6.01057 5.1282 5.40987 7.15692 4.20846L7.84308 3.80211Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 826 B |
@@ -3,15 +3,17 @@
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
|
||||
export let src
|
||||
export let alt = ""
|
||||
export let size = 7
|
||||
export let icon = "user-rounded"
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cx($$props.class, "!flex items-center justify-center overflow-hidden rounded-full")}
|
||||
style={`width: ${size * 4}px; height: ${size * 4}px;`}>
|
||||
{#if src}
|
||||
<img alt="" {src} />
|
||||
<img {alt} {src} />
|
||||
{:else}
|
||||
<Icon icon="user-rounded" size={Math.round(size * 0.7)} />
|
||||
<Icon {icon} size={Math.round(size * 0.7)} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import cx from 'classnames'
|
||||
import {switcher} from "@welshman/lib"
|
||||
import AddSquare from "@assets/icons/Add Square.svg?dataurl"
|
||||
import AddCircle from "@assets/icons/Add Circle.svg?dataurl"
|
||||
@@ -14,6 +15,7 @@
|
||||
import AltArrowLeft from "@assets/icons/Alt Arrow Left.svg?dataurl"
|
||||
import ArrowRight from "@assets/icons/Arrow Right.svg?dataurl"
|
||||
import Bag from "@assets/icons/Bag.svg?dataurl"
|
||||
import Bolt from "@assets/icons/Bolt.svg?dataurl"
|
||||
import CalendarMinimalistic from "@assets/icons/Calendar Minimalistic.svg?dataurl"
|
||||
import ChatRound from "@assets/icons/Chat Round.svg?dataurl"
|
||||
import CheckCircle from "@assets/icons/Check Circle.svg?dataurl"
|
||||
@@ -22,6 +24,7 @@
|
||||
import Copy from "@assets/icons/Copy.svg?dataurl"
|
||||
import Compass from "@assets/icons/Compass.svg?dataurl"
|
||||
import CompassBig from "@assets/icons/Compass Big.svg?dataurl"
|
||||
import Danger from "@assets/icons/Danger.svg?dataurl"
|
||||
import Exit from "@assets/icons/Exit.svg?dataurl"
|
||||
import FireMinimalistic from "@assets/icons/Fire Minimalistic.svg?dataurl"
|
||||
import GallerySend from "@assets/icons/Gallery Send.svg?dataurl"
|
||||
@@ -65,6 +68,7 @@
|
||||
"alt-arrow-left": AltArrowLeft,
|
||||
"arrow-right": ArrowRight,
|
||||
bag: Bag,
|
||||
bolt: Bolt,
|
||||
"calendar-minimalistic": CalendarMinimalistic,
|
||||
"chat-round": ChatRound,
|
||||
"check-circle": CheckCircle,
|
||||
@@ -73,6 +77,7 @@
|
||||
copy: Copy,
|
||||
compass: Compass,
|
||||
"compass-big": CompassBig,
|
||||
danger: Danger,
|
||||
exit: Exit,
|
||||
"fire-minimalistic": FireMinimalistic,
|
||||
"gallery-send": GallerySend,
|
||||
@@ -110,5 +115,5 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={$$props.class}
|
||||
class={cx("inline-block", $$props.class)}
|
||||
style="mask-image: url({data}); width: {px}px; height: {px}px; min-width: {px}px; min-height: {px}px; background-color: currentcolor;" />
|
||||
|
||||
@@ -5,7 +5,7 @@ export const createPasteRuleMatch = <T extends Record<string, unknown>>(
|
||||
data: T,
|
||||
): PasteRuleMatch => ({ index: match.index!, replaceWith: match[2], text: match[0], match, data })
|
||||
|
||||
export const findNodes = (json: JSONContent, type: string) => {
|
||||
export const findNodes = (type: string, json: JSONContent) => {
|
||||
const results: JSONContent[] = []
|
||||
|
||||
for (const node of json.content || []) {
|
||||
@@ -13,7 +13,7 @@ export const findNodes = (json: JSONContent, type: string) => {
|
||||
results.push(node)
|
||||
}
|
||||
|
||||
for (const result of findNodes(node, type)) {
|
||||
for (const result of findNodes(type, node)) {
|
||||
results.push(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type {FlyParams} from "svelte/transition"
|
||||
import {fly as baseFly} from "svelte/transition"
|
||||
|
||||
export {fade} from 'svelte/transition'
|
||||
|
||||
export const fly = (node: Element, params?: FlyParams | undefined) =>
|
||||
baseFly(node, {y: 20, ...params})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import "@src/app.css"
|
||||
import {onMount} from "svelte"
|
||||
import {get, derived} from 'svelte/store'
|
||||
import {page} from "$app/stores"
|
||||
import {goto} from "$app/navigation"
|
||||
import {browser} from "$app/environment"
|
||||
import {createEventStore} from "@welshman/store"
|
||||
import {createEventStore, adapter} from "@welshman/store"
|
||||
import ModalBox from "@lib/components/ModalBox.svelte"
|
||||
import Toast from "@app/components/Toast.svelte"
|
||||
import Landing from "@app/components/Landing.svelte"
|
||||
@@ -12,9 +13,12 @@
|
||||
import {modals, clearModal} from "@app/modal"
|
||||
import {theme} from "@app/theme"
|
||||
import {pk, session, repository, DEFAULT_RELAYS} from "@app/base"
|
||||
import {relays, handles, loadRelay} from "@app/state"
|
||||
import type {PublishStatusData, PublishStatusDataByUrlById} from "@app/state"
|
||||
import {relays, freshness, plaintext, handles, loadRelay, publishStatusData} from "@app/state"
|
||||
import {initStorage} from "@app/storage"
|
||||
import {loadUserData} from "@app/commands"
|
||||
import * as base from "@app/base"
|
||||
import * as state from "@app/state"
|
||||
|
||||
let ready: Promise<void>
|
||||
let dialog: HTMLDialogElement
|
||||
@@ -43,7 +47,9 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
ready = initStorage({
|
||||
Object.assign(window, {get, base, state})
|
||||
|
||||
ready = initStorage(3, {
|
||||
events: {
|
||||
keyPath: "id",
|
||||
store: createEventStore(repository),
|
||||
@@ -56,6 +62,65 @@
|
||||
keyPath: "nip05",
|
||||
store: handles,
|
||||
},
|
||||
publishStatus: {
|
||||
keyPath: "id",
|
||||
store: adapter({
|
||||
store: publishStatusData,
|
||||
forward: ($psd: PublishStatusDataByUrlById) => {
|
||||
const data = []
|
||||
|
||||
for (const [id, itemsByUrl] of Object.entries($psd)) {
|
||||
for (const [url, item] of Object.entries(itemsByUrl)) {
|
||||
data.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
},
|
||||
backward: (data: PublishStatusData[]) => {
|
||||
const result: PublishStatusDataByUrlById = {}
|
||||
|
||||
for (const item of data) {
|
||||
result[item.id] = result[item.id] || {}
|
||||
result[item.id][item.url] = item
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
}),
|
||||
},
|
||||
freshness: {
|
||||
keyPath: "key",
|
||||
store: adapter({
|
||||
store: freshness,
|
||||
forward: ($freshness: Record<string, number>) => Object.entries($freshness).map(([key, ts]) => ({key, ts})),
|
||||
backward: (data: any[]) => {
|
||||
const result: Record<string, number> = {}
|
||||
|
||||
for (const {key, ts} of data) {
|
||||
result[key] = ts
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
}),
|
||||
},
|
||||
plaintext: {
|
||||
keyPath: "id",
|
||||
store: adapter({
|
||||
store: plaintext,
|
||||
forward: ($plaintext: Record<string, string>) => Object.entries($plaintext).map(([id, plaintext]) => ({id, plaintext})),
|
||||
backward: (data: any[]) => {
|
||||
const result: Record<string, string> = {}
|
||||
|
||||
for (const {id, plaintext} of data) {
|
||||
result[id] = plaintext
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
dialog.addEventListener("close", () => {
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
userMembership,
|
||||
} from "@app/state"
|
||||
|
||||
const getRelayUrls = (nom: string): string[] => $relayUrlsByNom.get(nom) || []
|
||||
|
||||
let term = ""
|
||||
|
||||
$: groups = $searchGroups.searchOptions(term).filter(g => $relayUrlsByNom.get(g.nom)?.length > 0)
|
||||
|
||||
onMount(() => {
|
||||
load({
|
||||
relays: [...DEFAULT_RELAYS, ...$relays.map(r => r.url)],
|
||||
@@ -35,7 +35,7 @@
|
||||
</label>
|
||||
<Masonry
|
||||
animate={false}
|
||||
items={$searchGroups.searchOptions(term)}
|
||||
items={groups}
|
||||
minColWidth={250}
|
||||
maxColWidth={800}
|
||||
gap={16}
|
||||
@@ -57,7 +57,7 @@
|
||||
{#if $userMembership?.noms.has(group.nom)}
|
||||
<div class="center absolute flex w-full">
|
||||
<div
|
||||
class="tooltip relative left-8 top-[38px] rounded-full bg-primary"
|
||||
class="tooltip relative left-8 w-5 h-5 top-[38px] rounded-full bg-primary"
|
||||
data-tip="You are already a member of this space.">
|
||||
<Icon icon="check-circle" class="scale-110" />
|
||||
</div>
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="card-body">
|
||||
<h2 class="card-title justify-center">{displayGroup(group)}</h2>
|
||||
<div class="text-center text-sm">
|
||||
{#each getRelayUrls(group.nom) as url}
|
||||
{#each $relayUrlsByNom.get(group.nom) || [] as url}
|
||||
<div class="badge badge-neutral">{displayRelayUrl(url)}</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -90,5 +90,5 @@
|
||||
</Spinner>
|
||||
</p>
|
||||
</div>
|
||||
<GroupCompose />
|
||||
<GroupCompose {nom} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user