forked from coracle/flotilla
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5931a268cf | |||
| 268028a968 | |||
| d6669f42c1 | |||
| 19d69005a1 | |||
| 9917970760 | |||
| 814c5974c4 | |||
| f5dced433a | |||
| 9e96d5e483 | |||
| 420dfc41f3 | |||
| 23ae530cd4 | |||
| 8dfbc99a34 | |||
| 75bca31c14 | |||
| 0c9109f387 | |||
| 7a17dc772f | |||
| 7bd98270f8 | |||
| c15f57c9a5 | |||
| 0f311c45c0 | |||
| 055d539b88 | |||
| b8e23c47d4 | |||
| 39c72a61ce |
@@ -0,0 +1,12 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
# 0.2.1
|
||||||
|
|
||||||
|
* Improve performance, as well as scrolling and loading
|
||||||
|
* Integrate @welshman/editor
|
||||||
|
* Improve NIP 29 compatibility
|
||||||
|
* Fix incorrect connection errors
|
||||||
|
* Refine notifications
|
||||||
|
* Add room menu to space homepage
|
||||||
|
* Fix storage bugs
|
||||||
|
* Add join space CTA
|
||||||
@@ -8,7 +8,7 @@ android {
|
|||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "0.2.2"
|
||||||
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.
|
||||||
|
|||||||
Generated
+478
-155
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "0.1.0",
|
"version": "0.2.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "0.1.0",
|
"version": "0.2.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^6.1.2",
|
"@capacitor/android": "^6.1.2",
|
||||||
"@capacitor/cli": "^6.1.2",
|
"@capacitor/cli": "^6.1.2",
|
||||||
@@ -30,15 +30,16 @@
|
|||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@vite-pwa/assets-generator": "^0.2.6",
|
"@vite-pwa/assets-generator": "^0.2.6",
|
||||||
"@vite-pwa/sveltekit": "^0.6.6",
|
"@vite-pwa/sveltekit": "^0.6.6",
|
||||||
"@welshman/app": "~0.0.34",
|
"@welshman/app": "~0.0.36",
|
||||||
"@welshman/content": "~0.0.14",
|
"@welshman/content": "~0.0.15",
|
||||||
"@welshman/dvm": "~0.0.12",
|
"@welshman/dvm": "~0.0.13",
|
||||||
"@welshman/feeds": "~0.0.27",
|
"@welshman/editor": "~0.0.4",
|
||||||
"@welshman/lib": "~0.0.33",
|
"@welshman/feeds": "~0.0.30",
|
||||||
"@welshman/net": "~0.0.43",
|
"@welshman/lib": "~0.0.37",
|
||||||
"@welshman/signer": "~0.0.17",
|
"@welshman/net": "~0.0.45",
|
||||||
"@welshman/store": "~0.0.14",
|
"@welshman/signer": "~0.0.19",
|
||||||
"@welshman/util": "~0.0.52",
|
"@welshman/store": "~0.0.15",
|
||||||
|
"@welshman/util": "~0.0.55",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
"date-picker-svelte": "^2.13.0",
|
"date-picker-svelte": "^2.13.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/assets": "^3.0.5",
|
"@capacitor/assets": "^3.0.5",
|
||||||
|
"@sentry/cli": "^2.40.0",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@types/eslint": "^9.6.0",
|
"@types/eslint": "^9.6.0",
|
||||||
@@ -3118,22 +3120,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@noble/curves": {
|
"node_modules/@noble/curves": {
|
||||||
"version": "1.5.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz",
|
||||||
"integrity": "sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==",
|
"integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/hashes": "1.4.0"
|
"@noble/hashes": "1.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@noble/hashes": {
|
"node_modules/@noble/hashes": {
|
||||||
"version": "1.4.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz",
|
||||||
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
|
"integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16"
|
"node": "^14.21.3 || >=16"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
@@ -3648,6 +3655,158 @@
|
|||||||
"node": ">=14.18"
|
"node": ">=14.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sentry/cli": {
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.40.0.tgz",
|
||||||
|
"integrity": "sha512-yo+ZfrrpVyu/2Q9r4XI84VeC6xTNzTharSJB2D0BNkreL+c16I1ykG1uc/GmmFnYVBq+HHAaYqXVfSUV14IdHw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"https-proxy-agent": "^5.0.0",
|
||||||
|
"node-fetch": "^2.6.7",
|
||||||
|
"progress": "^2.0.3",
|
||||||
|
"proxy-from-env": "^1.1.0",
|
||||||
|
"which": "^2.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sentry-cli": "bin/sentry-cli"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@sentry/cli-darwin": "2.40.0",
|
||||||
|
"@sentry/cli-linux-arm": "2.40.0",
|
||||||
|
"@sentry/cli-linux-arm64": "2.40.0",
|
||||||
|
"@sentry/cli-linux-i686": "2.40.0",
|
||||||
|
"@sentry/cli-linux-x64": "2.40.0",
|
||||||
|
"@sentry/cli-win32-i686": "2.40.0",
|
||||||
|
"@sentry/cli-win32-x64": "2.40.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-darwin": {
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.40.0.tgz",
|
||||||
|
"integrity": "sha512-GmPGvPU9tjM1Ps/pkUGQa7rImveo4delb2Dc5l8129i1MyD2ugJ5zjeNhIdBHkaObpuude9rUS7sHC4HTU2Wqw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-linux-arm": {
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.40.0.tgz",
|
||||||
|
"integrity": "sha512-LUdwh3shYXZThkBvmKFUkQvmsCIQu76ZVqU7NXcEWHRF9gITijnSyHKCBPCbcGkb1SqQ92BW/1cJq84Dy0/DRw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-linux-arm64": {
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.40.0.tgz",
|
||||||
|
"integrity": "sha512-b8gDORhkhP/g1CTYVKzBlbYlmC3BqkgEzAXP8ViFxX1NNS7dK9Hr84cVnDGxhSIfCP8TW1d5V3AGeHwQr5EwEg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-linux-i686": {
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.40.0.tgz",
|
||||||
|
"integrity": "sha512-sZo3QykQRpMkrz0Eb07ViyK++C6Iir1j7Rpsj/97y5WDncR8TrpGTn6ceuuVRt4clA09/ZIvwuS7amfeKN6jQw==",
|
||||||
|
"cpu": [
|
||||||
|
"x86",
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-linux-x64": {
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.40.0.tgz",
|
||||||
|
"integrity": "sha512-ctpBFuyk2fP97FkxWTD9olI1BM1cy+rUIfnUqmrjXneTaUi3RFIFBB4koYhh1UT6OCWIRvChRIq40Rd9R3Pw8A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-win32-i686": {
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.40.0.tgz",
|
||||||
|
"integrity": "sha512-4SYD40zJS7hVbFzAwXvXcVIoc7xsWa6L1RW1SQlt+Woh5MTPk7FMMSGft8021OSGTljiuqQzx4ecnXMO0K/gOw==",
|
||||||
|
"cpu": [
|
||||||
|
"x86",
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-win32-x64": {
|
||||||
|
"version": "2.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.40.0.tgz",
|
||||||
|
"integrity": "sha512-QEW2Ra4Wsr4y6AwcxOk2hL0zMlCK+adTSTaptTMmcm52el8XjdMwsNo7d/416HUYNcND0YZGih7D+KERepyQSw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sentry/core": {
|
"node_modules/@sentry/core": {
|
||||||
"version": "8.35.0",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.35.0.tgz",
|
||||||
@@ -3782,9 +3941,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/core": {
|
"node_modules/@tiptap/core": {
|
||||||
"version": "2.7.2",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.0.tgz",
|
||||||
"integrity": "sha512-rGAH90LPMR5OIG7vuTDRw8WxDYxPXSxuGtu++mxPF+Bv7V2ijPOy3P1oyV1G3KGoS0pPiNugLh+tVLsElcx/9Q==",
|
"integrity": "sha512-0S3AWx6E2QqwdQqb6z0/q6zq2u9lA9oL3BLyAaITGSC9zt8OwjloS2k1zN6wLa9hp2rO0c0vDnWsTPeFaEaMdw==",
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -3812,53 +3972,57 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-code": {
|
"node_modules/@tiptap/extension-code": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.0.tgz",
|
||||||
"integrity": "sha512-JrEFKsZiLvfvOFhOnnrpA0TzCuJjDeysfbMeuKUZNV4+DhYOL28d39H1++rEtJAX0LcbBU60oC5/PrlU9SpvRQ==",
|
"integrity": "sha512-2roNZxcny1bGjyZ8x6VmGTuKbwfJyTZ1hiqPc/CRTQ1u42yOhbjF4ziA5kfyUoQlzygZrWH9LR5IMYGzPQ1N3w==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6"
|
"@tiptap/core": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-code-block": {
|
"node_modules/@tiptap/extension-code-block": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.0.tgz",
|
||||||
"integrity": "sha512-1YLp/zHMHSkE2xzht8nPR6T4sQJJ3ket798czxWuQEbetFv/l0U/mpiPpYSLObj6oTAoqYZ0kWXZj5eQSpPB8Q==",
|
"integrity": "sha512-8of3qTOLjpveHBrrk8KVliSUVd6R2i2TNrBj0f/21HcFVAy0fP++02p6vI6UPOhwM3+p3CprGdSM48DFCu1rqw==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6",
|
"@tiptap/core": "^2.7.0",
|
||||||
"@tiptap/pm": "^2.6.6"
|
"@tiptap/pm": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-document": {
|
"node_modules/@tiptap/extension-document": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.0.tgz",
|
||||||
"integrity": "sha512-6qlH5VWzLHHRVeeciRC6C4ZHpMsAGPNG16EF53z0GeMSaaFD/zU3B239QlmqXmLsAl8bpf8Bn93N0t2ABUvScw==",
|
"integrity": "sha512-9YI0AT3mxyUZD7NHECHyV1uAjQ8KwxOS5ACwvrK1MU8TqY084LmodYNTXPKwpqbr51yvt3qZq1R7UIVu4/22Cg==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6"
|
"@tiptap/core": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-dropcursor": {
|
"node_modules/@tiptap/extension-dropcursor": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.0.tgz",
|
||||||
"integrity": "sha512-O6CeKriA9uyHsg7Ui4z5ZjEWXQxrIL+1zDekffW0wenGC3G4LUsCzAiFS4LSrR9a3u7tnwqGApW10rdkmCGF4w==",
|
"integrity": "sha512-p7tUtlz7KzBa+06+7W2LJ8AEiHG5chdnUIapojZ7SqQCrFRVw70R+orpkzkoictxNNHsun0A9FCUy4rz8L0+nQ==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6",
|
"@tiptap/core": "^2.7.0",
|
||||||
"@tiptap/pm": "^2.6.6"
|
"@tiptap/pm": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-floating-menu": {
|
"node_modules/@tiptap/extension-floating-menu": {
|
||||||
@@ -3879,41 +4043,44 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-gapcursor": {
|
"node_modules/@tiptap/extension-gapcursor": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.0.tgz",
|
||||||
"integrity": "sha512-O2lQ2t0X0Vsbn3yLWxFFHrXY6C2N9Y6ZF/M7LWzpcDTUZeWuhoNkFE/1yOM0h6ZX1DO2A9hNIrKpi5Ny8yx+QA==",
|
"integrity": "sha512-1TVOthPkUYwTQnQwP0BzuIHVz09epOiXJQ3GqgNZsmTehwcMzz2vGCpx1JXhZ5DoMaREHNLCdraXb1n2FdhDNA==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6",
|
"@tiptap/core": "^2.7.0",
|
||||||
"@tiptap/pm": "^2.6.6"
|
"@tiptap/pm": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-hard-break": {
|
"node_modules/@tiptap/extension-hard-break": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.0.tgz",
|
||||||
"integrity": "sha512-bsUuyYBrMDEiudx1dOQSr9MzKv13m0xHWrOK+DYxuIDYJb5g+c9un5cK7Js+et/HEYYSPOoH/iTW6h+4I5YeUg==",
|
"integrity": "sha512-7pMgPNk2FnPT0LcWaWNNxOLK3LQnRSYFgrdBGMXec3sy+y3Lit3hM+EZhbZcHpTIQTbWWs+eskh1waRMIt0ZaQ==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6"
|
"@tiptap/core": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-history": {
|
"node_modules/@tiptap/extension-history": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.0.tgz",
|
||||||
"integrity": "sha512-tPTzAmPGqMX5Bd5H8lzRpmsaMvB9DvI5Dy2za/VQuFtxgXmDiFVgHRkRXIuluSkPTuANu84XBOQ0cBijqY8x4w==",
|
"integrity": "sha512-eEUEDoOtS17AHVEPbGfZ+x2L5A87SiIsppWYTkpfIH/8EnVQmzu+3i1tcT9cWvHC31d9JTG7TDptVuuHr30TJw==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6",
|
"@tiptap/core": "^2.7.0",
|
||||||
"@tiptap/pm": "^2.6.6"
|
"@tiptap/pm": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-image": {
|
"node_modules/@tiptap/extension-image": {
|
||||||
@@ -3947,15 +4114,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-paragraph": {
|
"node_modules/@tiptap/extension-paragraph": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.0.tgz",
|
||||||
"integrity": "sha512-fD/onCr16UQWx+/xEmuFC2MccZZ7J5u4YaENh8LMnAnBXf78iwU7CAcmuc9rfAEO3qiLoYGXgLKiHlh2ZfD4wA==",
|
"integrity": "sha512-xLNC05An3SQq0bVHJtOTLa8As5r6NxDZFpK0NZqO2hTq/fAIRL/9VPeZ8E0tziXULwIvIPp+L0Taw3TvaUkRUg==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6"
|
"@tiptap/core": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-placeholder": {
|
"node_modules/@tiptap/extension-placeholder": {
|
||||||
@@ -3973,41 +4141,43 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-text": {
|
"node_modules/@tiptap/extension-text": {
|
||||||
"version": "2.6.6",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.0.tgz",
|
||||||
"integrity": "sha512-e84uILnRzNzcwK1DVQNpXVmBG1Cq3BJipTOIDl1LHifOok7MBjhI/X+/NR0bd3N2t6gmDTWi63+4GuJ5EeDmsg==",
|
"integrity": "sha512-LcyrP+7ZEVx3YaKzjMAeujq+4xRt4mZ3ITGph2CQ4vOKFaMI8bzSR909q18t7Qyyvek0a9VydEU1NHSaq4G5jw==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.6"
|
"@tiptap/core": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/pm": {
|
"node_modules/@tiptap/pm": {
|
||||||
"version": "2.7.2",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.0.tgz",
|
||||||
"integrity": "sha512-RiRPlwpuE6IHDJytE0tglbFlWELOaqeyGRGv25wBTjzV1plnqC5B3U65XY/8kKuuLjdd3NpRfR68DXBafusSBg==",
|
"integrity": "sha512-4RU6bpODkMY+ZshzdRFcuUc5jWlMW82LWXR6UOsHK/X/Mav41ZFS0Cyf+hQM6gxxTB09YFIICmGpEpULb+/CuA==",
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-changeset": "^2.2.1",
|
"prosemirror-changeset": "^2.2.1",
|
||||||
"prosemirror-collab": "^1.3.1",
|
"prosemirror-collab": "^1.3.1",
|
||||||
"prosemirror-commands": "^1.6.0",
|
"prosemirror-commands": "^1.6.2",
|
||||||
"prosemirror-dropcursor": "^1.8.1",
|
"prosemirror-dropcursor": "^1.8.1",
|
||||||
"prosemirror-gapcursor": "^1.3.2",
|
"prosemirror-gapcursor": "^1.3.2",
|
||||||
"prosemirror-history": "^1.4.1",
|
"prosemirror-history": "^1.4.1",
|
||||||
"prosemirror-inputrules": "^1.4.0",
|
"prosemirror-inputrules": "^1.4.0",
|
||||||
"prosemirror-keymap": "^1.2.2",
|
"prosemirror-keymap": "^1.2.2",
|
||||||
"prosemirror-markdown": "^1.13.0",
|
"prosemirror-markdown": "^1.13.1",
|
||||||
"prosemirror-menu": "^1.2.4",
|
"prosemirror-menu": "^1.2.4",
|
||||||
"prosemirror-model": "^1.22.3",
|
"prosemirror-model": "^1.23.0",
|
||||||
"prosemirror-schema-basic": "^1.2.3",
|
"prosemirror-schema-basic": "^1.2.3",
|
||||||
"prosemirror-schema-list": "^1.4.1",
|
"prosemirror-schema-list": "^1.4.1",
|
||||||
"prosemirror-state": "^1.4.3",
|
"prosemirror-state": "^1.4.3",
|
||||||
"prosemirror-tables": "^1.4.0",
|
"prosemirror-tables": "^1.6.1",
|
||||||
"prosemirror-trailing-node": "^3.0.0",
|
"prosemirror-trailing-node": "^3.0.0",
|
||||||
"prosemirror-transform": "^1.10.0",
|
"prosemirror-transform": "^1.10.2",
|
||||||
"prosemirror-view": "^1.33.10"
|
"prosemirror-view": "^1.37.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -4015,16 +4185,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/suggestion": {
|
"node_modules/@tiptap/suggestion": {
|
||||||
"version": "2.6.4",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.11.0.tgz",
|
||||||
"integrity": "sha512-t4GOEcsVSCwTlugHjZdK5Swe6or/tBej5E3ZWYOFHxkNLDod76Q7hvAeBPYrLeDo6m3sPnxrazfdqSeVclk72g==",
|
"integrity": "sha512-f+KcczhzEEy2f7/0N/RSID+Z6NjxCX6ab26NLfWZxdaEm/J+vQ2Pqh/e5Z59vMfKiC0DJXVcO0rdv2LBh23qDw==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/core": "^2.6.4",
|
"@tiptap/core": "^2.7.0",
|
||||||
"@tiptap/pm": "^2.6.4"
|
"@tiptap/pm": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@trapezedev/gradle-parse": {
|
"node_modules/@trapezedev/gradle-parse": {
|
||||||
@@ -4292,7 +4463,8 @@
|
|||||||
"node_modules/@types/events": {
|
"node_modules/@types/events": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz",
|
||||||
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g=="
|
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/fs-extra": {
|
"node_modules/@types/fs-extra": {
|
||||||
"version": "8.1.5",
|
"version": "8.1.5",
|
||||||
@@ -4394,6 +4566,15 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.5.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
||||||
|
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0.tgz",
|
||||||
@@ -4662,19 +4843,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@welshman/app": {
|
"node_modules/@welshman/app": {
|
||||||
"version": "0.0.34",
|
"version": "0.0.36",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/app/-/app-0.0.34.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/app/-/app-0.0.36.tgz",
|
||||||
"integrity": "sha512-wxCWvoal/ctRvImK8dIgg7IajA4eXPheUAXPwPmO6ZuYPV+ytIYzmKGalsYMDMDweQrWVKOf0gLEMOap8LS0iQ==",
|
"integrity": "sha512-ECUaBiDE896P6LXdE3yN49z0I2MCvjA0lO6FOd2BCRfmnmdbTnC+FLcoPGTS262/uDuJz+rr3utBjq8DylugaQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/throttle-debounce": "^5.0.2",
|
"@types/throttle-debounce": "^5.0.2",
|
||||||
"@welshman/dvm": "~0.0.11",
|
"@welshman/dvm": "~0.0.13",
|
||||||
"@welshman/feeds": "~0.0.26",
|
"@welshman/feeds": "~0.0.29",
|
||||||
"@welshman/lib": "~0.0.33",
|
"@welshman/lib": "~0.0.37",
|
||||||
"@welshman/net": "~0.0.41",
|
"@welshman/net": "~0.0.45",
|
||||||
"@welshman/signer": "~0.0.16",
|
"@welshman/signer": "~0.0.19",
|
||||||
"@welshman/store": "~0.0.13",
|
"@welshman/store": "~0.0.15",
|
||||||
"@welshman/util": "~0.0.50",
|
"@welshman/util": "~0.0.54",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"idb": "^8.0.0",
|
"idb": "^8.0.0",
|
||||||
"svelte": "^4.2.18",
|
"svelte": "^4.2.18",
|
||||||
@@ -4682,42 +4863,84 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@welshman/content": {
|
"node_modules/@welshman/content": {
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/content/-/content-0.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/content/-/content-0.0.15.tgz",
|
||||||
"integrity": "sha512-LwdBJOF5n2EIdmLgn4tJliTKEmTEDZz68zvcmjVhpn34vkc/7lQvHz5pfsQK/CRjPGFsMEdjrSepGXD8v2JAwA==",
|
"integrity": "sha512-y0f0iLIaHUqEJJ0ziRWbGw13mg0tOLTKpHQNgIXJ03PD3xGHBaQ5xPWiOI8XeUt35KgrayvQZHsaqfAsOWkwag==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^7.0.2",
|
"@braintree/sanitize-url": "^7.0.2",
|
||||||
"@welshman/lib": "~0.0.33",
|
"@welshman/lib": "~0.0.34",
|
||||||
"nostr-tools": "^2.7.2"
|
"nostr-tools": "^2.7.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@welshman/dvm": {
|
"node_modules/@welshman/dvm": {
|
||||||
"version": "0.0.12",
|
"version": "0.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/dvm/-/dvm-0.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/dvm/-/dvm-0.0.13.tgz",
|
||||||
"integrity": "sha512-6VqDJzzsfw2UxxIVbJB3+xw6o3qg29+Kkrp1Ei537oXnqmH4W4fGVyoGSdJQQfhEbzdDzT4u/OCUX+z85wfNNA==",
|
"integrity": "sha512-C8y4s7wDJTJ6DVuzQoRLAhMpFD+kBoRHlc7kCTmjzh62VOmDSY+46xKttK/WaEnypPiPbIbBg3hd3+tO2A9KoQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@welshman/lib": "~0.0.33",
|
"@noble/hashes": "^1.6.1",
|
||||||
"@welshman/net": "~0.0.41",
|
"@welshman/lib": "~0.0.34",
|
||||||
"@welshman/util": "~0.0.50",
|
"@welshman/net": "~0.0.43",
|
||||||
|
"@welshman/util": "~0.0.52",
|
||||||
"nostr-tools": "^2.7.2"
|
"nostr-tools": "^2.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@welshman/dvm/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@welshman/editor": {
|
||||||
|
"version": "0.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@welshman/editor/-/editor-0.0.4.tgz",
|
||||||
|
"integrity": "sha512-tcMwLuBaBtT2JgON5f+Fd4Cg9oM7QMnXW9voGP+RqH1gJt0W6rjjQCtpqEcgdVtHhmaSL1P+tM4ORmOinoCv+A==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/core": "^2.9.1",
|
||||||
|
"@tiptap/extension-code": "^2.9.1",
|
||||||
|
"@tiptap/extension-code-block": "^2.9.1",
|
||||||
|
"@tiptap/extension-document": "^2.9.1",
|
||||||
|
"@tiptap/extension-dropcursor": "^2.9.1",
|
||||||
|
"@tiptap/extension-gapcursor": "^2.9.1",
|
||||||
|
"@tiptap/extension-hard-break": "^2.9.1",
|
||||||
|
"@tiptap/extension-history": "^2.9.1",
|
||||||
|
"@tiptap/extension-paragraph": "^2.9.1",
|
||||||
|
"@tiptap/extension-placeholder": "^2.9.1",
|
||||||
|
"@tiptap/extension-text": "^2.9.1",
|
||||||
|
"@tiptap/pm": "^2.9.1",
|
||||||
|
"@tiptap/suggestion": "^2.9.1",
|
||||||
|
"@welshman/lib": "~0.0.36",
|
||||||
|
"@welshman/util": "~0.0.53",
|
||||||
|
"nostr-editor": "github:cesardeazevedo/nostr-editor#a211491c",
|
||||||
|
"nostr-tools": "^2.8.1",
|
||||||
|
"svelte": "^4.0.0",
|
||||||
|
"svelte-tiptap": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@welshman/feeds": {
|
"node_modules/@welshman/feeds": {
|
||||||
"version": "0.0.27",
|
"version": "0.0.30",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/feeds/-/feeds-0.0.27.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/feeds/-/feeds-0.0.30.tgz",
|
||||||
"integrity": "sha512-AMm3v3mJlCYMI8C86/LhF/hNx2dkBqdHVWebTX69NNIK24Srbd67YduOdIeuIAIYlBZj93BMi8lvOL5YY2RWPw==",
|
"integrity": "sha512-Zcex2uJVeYM55zDI1Dhb5I41lYGD4BURWl95nbFaWbbMYDwoAFIS2cPXBsaGNrITzsz8qByvRs2RnplrmZwSzA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@welshman/lib": "~0.0.33",
|
"@welshman/lib": "~0.0.37",
|
||||||
"@welshman/util": "~0.0.50"
|
"@welshman/util": "~0.0.54"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@welshman/lib": {
|
"node_modules/@welshman/lib": {
|
||||||
"version": "0.0.33",
|
"version": "0.0.37",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/lib/-/lib-0.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/lib/-/lib-0.0.37.tgz",
|
||||||
"integrity": "sha512-otaTKItm0DDR+/IHI5puYo1hU3ssd0R9LTxS+DcIKL6H+0fxtn6OLUmhcHROQukqZ6Jf7l7sfj9MX50KqPicjQ==",
|
"integrity": "sha512-qnEjdGIb/QVIYML0EQgGAhucds00hiX8/4rJ9OcqoRUXPS2cDD47BcgYap+kG/OKSfgSL7EH64voAdoprZtuvg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scure/base": "^1.1.6",
|
"@scure/base": "^1.1.6",
|
||||||
@@ -4726,51 +4949,72 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@welshman/net": {
|
"node_modules/@welshman/net": {
|
||||||
"version": "0.0.43",
|
"version": "0.0.45",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/net/-/net-0.0.43.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/net/-/net-0.0.45.tgz",
|
||||||
"integrity": "sha512-qcQPl944ArM9+GvwXb0nsWaMKQgsBCKtGvLOWJZs24S+n+8LiW1hmUP71evp5hFoLFTbws/l3OXAML9jO+W7Lw==",
|
"integrity": "sha512-sXYmfGdqvrj1ssr5xaSUxmJAFo+ScJtodBpzgya0CTLYorKRGoeQRJsyPWdh5VBVtoldPclzGhfvZ11d8d8Lyw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@welshman/lib": "~0.0.33",
|
"@welshman/lib": "~0.0.37",
|
||||||
"@welshman/util": "~0.0.50",
|
"@welshman/util": "~0.0.54",
|
||||||
"isomorphic-ws": "^5.0.0",
|
"isomorphic-ws": "^5.0.0",
|
||||||
"ws": "^8.16.0"
|
"ws": "^8.16.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@welshman/signer": {
|
"node_modules/@welshman/signer": {
|
||||||
"version": "0.0.17",
|
"version": "0.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/signer/-/signer-0.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/signer/-/signer-0.0.19.tgz",
|
||||||
"integrity": "sha512-mZhOKTmtEgAFI2D0KJrZIX5A6WDnHk1+YwIlzL3FyZwdxYnqg4Hx/MPHtAyZoMVt19iodKeZ+Fis/sLblXsXgg==",
|
"integrity": "sha512-+pKkm5HeaSJB6ET456w0zVnbVAjRzYuDagYUns1BQ6Co22nUSp2CntWFFckwbH2eQ9bjdlC65LjRfIP9qhNSrg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@welshman/lib": "~0.0.33",
|
"@noble/curves": "^1.7.0",
|
||||||
"@welshman/net": "~0.0.41",
|
"@noble/hashes": "^1.6.1",
|
||||||
"@welshman/util": "~0.0.50",
|
"@welshman/lib": "~0.0.37",
|
||||||
|
"@welshman/net": "~0.0.45",
|
||||||
|
"@welshman/util": "~0.0.54",
|
||||||
"nostr-tools": "^2.7.2"
|
"nostr-tools": "^2.7.2"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.x"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"nostr-signer-capacitor-plugin": "^0.0.3"
|
"nostr-signer-capacitor-plugin": "^0.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@welshman/signer/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@welshman/store": {
|
"node_modules/@welshman/store": {
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/store/-/store-0.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/store/-/store-0.0.15.tgz",
|
||||||
"integrity": "sha512-6y5c+/5yLcRGbVr7doOys0KJH/dnxCjjrUm0iQIvp/JEdSbGKcNhPjfWqs2mnh51PZIF9UlR9eDSjlE0nUxfgg==",
|
"integrity": "sha512-xapI9cqmpf6ot90T0Z+gFH2HSAby/N8oyLl7u+JASYbNDS3pkK26SviNlTciMC+VBuJChEr1zX5l8RHuLPtw5Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@welshman/lib": "~0.0.33",
|
"@welshman/lib": "~0.0.34",
|
||||||
"@welshman/util": "~0.0.50",
|
"@welshman/util": "~0.0.52",
|
||||||
"svelte": "^4.2.18"
|
"svelte": "^4.2.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@welshman/util": {
|
"node_modules/@welshman/util": {
|
||||||
"version": "0.0.52",
|
"version": "0.0.55",
|
||||||
"resolved": "https://registry.npmjs.org/@welshman/util/-/util-0.0.52.tgz",
|
"resolved": "https://registry.npmjs.org/@welshman/util/-/util-0.0.55.tgz",
|
||||||
"integrity": "sha512-w17nJ9T8mhwy010WnSjGzRn9kPerZvtG6Ay5fGHw13ZC0hnOD8fkWi85r4/sI+FbCaMLAeKM57P9XD8rIkOfpw==",
|
"integrity": "sha512-eqb2522Y/9oPaf+qd+qnsqZh4tDT8TZj29G/XvXCsGuFxXBpOzJ2uOuEVclXD4AeFdy0CgMRKe7kZ7741ZRCgg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@welshman/lib": "~0.0.33",
|
"@types/ws": "^8.5.13",
|
||||||
|
"@welshman/lib": "~0.0.37",
|
||||||
"nostr-tools": "^2.7.2"
|
"nostr-tools": "^2.7.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@xml-tools/parser": {
|
"node_modules/@xml-tools/parser": {
|
||||||
@@ -4832,6 +5076,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/agent-base": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/aggregate-error": {
|
"node_modules/aggregate-error": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
||||||
@@ -7099,6 +7356,7 @@
|
|||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.x"
|
"node": ">=0.8.x"
|
||||||
}
|
}
|
||||||
@@ -8071,6 +8329,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/https-proxy-agent": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "6",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/husky": {
|
"node_modules/husky": {
|
||||||
"version": "9.1.6",
|
"version": "9.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz",
|
||||||
@@ -8623,6 +8895,7 @@
|
|||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
||||||
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
|
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ws": "*"
|
"ws": "*"
|
||||||
}
|
}
|
||||||
@@ -9741,8 +10014,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/nostr-editor": {
|
"node_modules/nostr-editor": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/nostr-editor/-/nostr-editor-0.0.3.tgz",
|
"resolved": "git+ssh://git@github.com/cesardeazevedo/nostr-editor.git#a211491c7cfeb792ae58ba91d295fe747c151ded",
|
||||||
"integrity": "sha512-ODfwzebBRweaYt8l0pz8EbV4OqbEKZpDAVdoU+j7ubmfjhqIyk1PcQoikEZ8UasqkBcZjEQMAPl776F8nb55fQ==",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"light-bolt11-decoder": "^3.1.1"
|
"light-bolt11-decoder": "^3.1.1"
|
||||||
@@ -9773,9 +10045,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nostr-tools": {
|
"node_modules/nostr-tools": {
|
||||||
"version": "2.7.2",
|
"version": "2.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.10.4.tgz",
|
||||||
"integrity": "sha512-Bq3Ug0SZFtgtL1+0wCnAe8AJtI7yx/00/a2nUug9SkhfOwlKS92Tef12iCK9FdwXw+oFZWMtRnSwcLayQso+xA==",
|
"integrity": "sha512-biU7sk+jxHgVASfobg2T5ttxOGGSt69wEVBC51sHHOEaKAAdzHBLV/I2l9Rf61UzClhliZwNouYhqIso4a3HYg==",
|
||||||
|
"license": "Unlicense",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/ciphers": "^0.5.1",
|
"@noble/ciphers": "^0.5.1",
|
||||||
"@noble/curves": "1.2.0",
|
"@noble/curves": "1.2.0",
|
||||||
@@ -9785,7 +10058,7 @@
|
|||||||
"@scure/bip39": "1.2.1"
|
"@scure/bip39": "1.2.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"nostr-wasm": "v0.1.0"
|
"nostr-wasm": "0.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
@@ -10546,6 +10819,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/progress": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prompts": {
|
"node_modules/prompts": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||||
@@ -10587,14 +10870,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prosemirror-commands": {
|
"node_modules/prosemirror-commands": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.2.tgz",
|
||||||
"integrity": "sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==",
|
"integrity": "sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==",
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-model": "^1.0.0",
|
"prosemirror-model": "^1.0.0",
|
||||||
"prosemirror-state": "^1.0.0",
|
"prosemirror-state": "^1.0.0",
|
||||||
"prosemirror-transform": "^1.0.0"
|
"prosemirror-transform": "^1.10.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prosemirror-dropcursor": {
|
"node_modules/prosemirror-dropcursor": {
|
||||||
@@ -10653,15 +10937,42 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prosemirror-markdown": {
|
"node_modules/prosemirror-markdown": {
|
||||||
"version": "1.13.0",
|
"version": "1.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz",
|
||||||
"integrity": "sha512-UziddX3ZYSYibgx8042hfGKmukq5Aljp2qoBiJRejD/8MH70siQNz5RB1TrdTPheqLMy4aCe4GYNF10/3lQS5g==",
|
"integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==",
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/markdown-it": "^14.0.0",
|
||||||
"markdown-it": "^14.0.0",
|
"markdown-it": "^14.0.0",
|
||||||
"prosemirror-model": "^1.20.0"
|
"prosemirror-model": "^1.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prosemirror-markdown/node_modules/@types/linkify-it": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-markdown/node_modules/@types/markdown-it": {
|
||||||
|
"version": "14.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
||||||
|
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/linkify-it": "^5",
|
||||||
|
"@types/mdurl": "^2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-markdown/node_modules/@types/mdurl": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/prosemirror-menu": {
|
"node_modules/prosemirror-menu": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz",
|
||||||
@@ -10675,9 +10986,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prosemirror-model": {
|
"node_modules/prosemirror-model": {
|
||||||
"version": "1.22.3",
|
"version": "1.24.1",
|
||||||
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.24.1.tgz",
|
||||||
"integrity": "sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==",
|
"integrity": "sha512-YM053N+vTThzlWJ/AtPtF1j0ebO36nvbmDy4U7qA2XQB8JVaQp1FmB9Jhrps8s+z+uxhhVTny4m20ptUvhk0Mg==",
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"orderedmap": "^2.0.0"
|
"orderedmap": "^2.0.0"
|
||||||
@@ -10715,16 +11027,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prosemirror-tables": {
|
"node_modules/prosemirror-tables": {
|
||||||
"version": "1.4.0",
|
"version": "1.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.2.tgz",
|
||||||
"integrity": "sha512-fxryZZkQG12fSCNuZDrYx6Xvo2rLYZTbKLRd8rglOPgNJGMKIS8uvTt6gGC38m7UCu/ENnXIP9pEz5uDaPc+cA==",
|
"integrity": "sha512-97dKocVLrEVTQjZ4GBLdrrMw7Gv3no8H8yMwf5IRM9OoHrzbWpcH5jJxYgNQIRCtdIqwDctT1HdMHrGTiwp1dQ==",
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-keymap": "^1.1.2",
|
"prosemirror-keymap": "^1.2.2",
|
||||||
"prosemirror-model": "^1.8.1",
|
"prosemirror-model": "^1.24.1",
|
||||||
"prosemirror-state": "^1.3.1",
|
"prosemirror-state": "^1.4.3",
|
||||||
"prosemirror-transform": "^1.2.1",
|
"prosemirror-transform": "^1.10.2",
|
||||||
"prosemirror-view": "^1.13.3"
|
"prosemirror-view": "^1.37.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prosemirror-trailing-node": {
|
"node_modules/prosemirror-trailing-node": {
|
||||||
@@ -10743,18 +11056,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prosemirror-transform": {
|
"node_modules/prosemirror-transform": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz",
|
||||||
"integrity": "sha512-9UOgFSgN6Gj2ekQH5CTDJ8Rp/fnKR2IkYfGdzzp5zQMFsS4zDllLVx/+jGcX86YlACpG7UR5fwAXiWzxqWtBTg==",
|
"integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==",
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-model": "^1.21.0"
|
"prosemirror-model": "^1.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prosemirror-view": {
|
"node_modules/prosemirror-view": {
|
||||||
"version": "1.33.11",
|
"version": "1.37.1",
|
||||||
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.11.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.37.1.tgz",
|
||||||
"integrity": "sha512-K0z9oMf6EI2ZifS9yW8PUPjEw2o1ZoFAaNzvcuyfcjIzsU6pJMo3tk9r26MyzEsuGHXZwmKPEmrjgFd78biTGA==",
|
"integrity": "sha512-MEAnjOdXU1InxEmhjgmEzQAikaS6lF3hD64MveTPpjOGNTl87iRLA1HupC/DEV6YuK7m4Q9DHFNTjwIVtqz5NA==",
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-model": "^1.20.0",
|
"prosemirror-model": "^1.20.0",
|
||||||
@@ -10762,6 +11077,13 @@
|
|||||||
"prosemirror-transform": "^1.1.0"
|
"prosemirror-transform": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pump": {
|
"node_modules/pump": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||||
@@ -14068,6 +14390,7 @@
|
|||||||
"version": "8.18.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
+12
-10
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "0.1.0",
|
"version": "0.2.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/assets": "^3.0.5",
|
"@capacitor/assets": "^3.0.5",
|
||||||
|
"@sentry/cli": "^2.40.0",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@types/eslint": "^9.6.0",
|
"@types/eslint": "^9.6.0",
|
||||||
@@ -58,15 +59,16 @@
|
|||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@vite-pwa/assets-generator": "^0.2.6",
|
"@vite-pwa/assets-generator": "^0.2.6",
|
||||||
"@vite-pwa/sveltekit": "^0.6.6",
|
"@vite-pwa/sveltekit": "^0.6.6",
|
||||||
"@welshman/app": "~0.0.34",
|
"@welshman/app": "~0.0.36",
|
||||||
"@welshman/content": "~0.0.14",
|
"@welshman/content": "~0.0.15",
|
||||||
"@welshman/dvm": "~0.0.12",
|
"@welshman/dvm": "~0.0.13",
|
||||||
"@welshman/feeds": "~0.0.27",
|
"@welshman/editor": "~0.0.4",
|
||||||
"@welshman/lib": "~0.0.33",
|
"@welshman/feeds": "~0.0.30",
|
||||||
"@welshman/net": "~0.0.43",
|
"@welshman/lib": "~0.0.37",
|
||||||
"@welshman/signer": "~0.0.17",
|
"@welshman/net": "~0.0.45",
|
||||||
"@welshman/store": "~0.0.14",
|
"@welshman/signer": "~0.0.19",
|
||||||
"@welshman/util": "~0.0.52",
|
"@welshman/store": "~0.0.15",
|
||||||
|
"@welshman/util": "~0.0.55",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
"date-picker-svelte": "^2.13.0",
|
"date-picker-svelte": "^2.13.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
|||||||
+3
-16
@@ -1,5 +1,5 @@
|
|||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {ctx, sample, uniq, sleep, chunk, equals, choice} from "@welshman/lib"
|
import {ctx, sample, uniq, sleep, chunk, equals} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
DELETE,
|
DELETE,
|
||||||
PROFILE,
|
PROFILE,
|
||||||
@@ -85,19 +85,6 @@ export const getPubkeyPetname = (pubkey: string) => {
|
|||||||
return display
|
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(" ")),
|
|
||||||
]
|
|
||||||
|
|
||||||
export const getThunkError = async (thunk: Thunk) => {
|
export const getThunkError = async (thunk: Thunk) => {
|
||||||
const result = await thunk.result
|
const result = await thunk.result
|
||||||
const [{status, message}] = Object.values(result) as any
|
const [{status, message}] = Object.values(result) as any
|
||||||
@@ -121,7 +108,7 @@ export const loginWithNip46 = async ({
|
|||||||
connectSecret?: string
|
connectSecret?: string
|
||||||
}) => {
|
}) => {
|
||||||
const broker = Nip46Broker.get({relays, clientSecret, signerPubkey})
|
const broker = Nip46Broker.get({relays, clientSecret, signerPubkey})
|
||||||
const result = await broker.connect("", connectSecret, NIP46_PERMS)
|
const result = await broker.connect(connectSecret, NIP46_PERMS)
|
||||||
|
|
||||||
// TODO: remove ack result
|
// TODO: remove ack result
|
||||||
if (!["ack", connectSecret].includes(result)) return false
|
if (!["ack", connectSecret].includes(result)) return false
|
||||||
@@ -342,7 +329,7 @@ export const checkRelayAccess = async (url: string, claim = "") => {
|
|||||||
|
|
||||||
const result = await thunk.result
|
const result = await thunk.result
|
||||||
|
|
||||||
if (result[url].status !== PublishStatus.Success) {
|
if (result[url].status === PublishStatus.Failure) {
|
||||||
const message =
|
const message =
|
||||||
connection.auth.message?.replace(/^.*: /, "") ||
|
connection.auth.message?.replace(/^.*: /, "") ||
|
||||||
result[url].message?.replace(/^.*: /, "") ||
|
result[url].message?.replace(/^.*: /, "") ||
|
||||||
|
|||||||
@@ -1,56 +1,54 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {createEditor, EditorContent} from "svelte-tiptap"
|
import {writable} from "svelte/store"
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
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 {getEditorOptions, getEditorTags} from "@lib/editor"
|
import {getEditor} from "@app/editor"
|
||||||
import {getPubkeyHints} from "@app/commands"
|
|
||||||
|
|
||||||
export let onSubmit: any
|
export let onSubmit: any
|
||||||
export let content = ""
|
export let content = ""
|
||||||
export let editor = createEditor(
|
export let editor: ReturnType<typeof getEditor> | undefined = undefined
|
||||||
getEditorOptions({
|
|
||||||
submit,
|
|
||||||
getPubkeyHints,
|
|
||||||
submitOnEnter: true,
|
|
||||||
autofocus: !isMobile,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
function submit() {
|
const uploading = writable(false)
|
||||||
if ($loading) return
|
|
||||||
|
let element: HTMLElement
|
||||||
|
|
||||||
|
const uploadFiles = () => $editor!.chain().selectFiles().run()
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if ($uploading) return
|
||||||
|
|
||||||
onSubmit({
|
onSubmit({
|
||||||
content: $editor.getText({blockSeparator: "\n"}),
|
content: $editor!.getText({blockSeparator: "\n"}).trim(),
|
||||||
tags: getEditorTags($editor),
|
tags: $editor!.storage.nostr.getEditorTags(),
|
||||||
})
|
})
|
||||||
|
|
||||||
$editor.chain().clearContent().run()
|
$editor!.chain().clearContent().run()
|
||||||
}
|
}
|
||||||
|
|
||||||
$: loading = $editor?.storage.fileUpload.loading
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
$editor.commands.setContent(content)
|
editor = getEditor({autofocus: !isMobile, aggressive: true, element, submit, uploading})
|
||||||
|
|
||||||
|
$editor!.chain().setContent(content).run()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
class="relative z-feature flex gap-2 p-2"
|
class="relative z-feature flex gap-2 p-2"
|
||||||
on:submit|preventDefault={$loading ? undefined : submit}>
|
on:submit|preventDefault={$uploading ? undefined : submit}>
|
||||||
<Button
|
<Button
|
||||||
data-tip="Add an image"
|
data-tip="Add an image"
|
||||||
class="center tooltip tooltip-right h-10 w-10 min-w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200"
|
class="center tooltip tooltip-right h-10 w-10 min-w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200"
|
||||||
disabled={$loading}
|
disabled={$uploading}
|
||||||
on:click={$editor.commands.selectFiles}>
|
on:click={uploadFiles}>
|
||||||
{#if $loading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="gallery-send" />
|
<Icon icon="gallery-send" />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
<div class="chat-editor flex-grow overflow-hidden">
|
<div class="chat-editor flex-grow overflow-hidden">
|
||||||
<EditorContent editor={$editor} />
|
<div bind:this={element} />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
const expand = () => pushModal(ContentLinkDetail, {url}, {fullscreen: true})
|
const expand = () => pushModal(ContentLinkDetail, {url}, {fullscreen: true})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link external href={url} class="my-2 flex">
|
<Link external href={url} class="my-2 inline-block">
|
||||||
<div class="overflow-hidden rounded-box leading-[0]">
|
<div class="overflow-hidden rounded-box leading-[0]">
|
||||||
{#if url.match(/\.(mov|webm|mp4)$/)}
|
{#if url.match(/\.(mov|webm|mp4)$/)}
|
||||||
<video controls src={url} class="max-h-96 object-contain object-center">
|
<video controls src={url} class="max-h-96 object-contain object-center">
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import type {Readable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import {createEditor, type Editor, EditorContent} from "svelte-tiptap"
|
|
||||||
import {randomId} from "@welshman/lib"
|
import {randomId} from "@welshman/lib"
|
||||||
import {createEvent, EVENT_DATE, EVENT_TIME} from "@welshman/util"
|
import {createEvent, EVENT_TIME} from "@welshman/util"
|
||||||
import {publishThunk, dateToSeconds} from "@welshman/app"
|
import {publishThunk, dateToSeconds} from "@welshman/app"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
@@ -12,16 +11,17 @@
|
|||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
|
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
|
||||||
import {PROTECTED} from "@app/state"
|
import {PROTECTED} from "@app/state"
|
||||||
import {getPubkeyHints} from "@app/commands"
|
import {getEditor} from "@app/editor"
|
||||||
import {getEditorOptions, getEditorTags} from "@lib/editor"
|
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
export let url
|
export let url
|
||||||
|
|
||||||
|
const uploading = writable(false)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if ($loading) return
|
if ($uploading) return
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
@@ -37,16 +37,15 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const kind = isAllDay ? EVENT_DATE : EVENT_TIME
|
const event = createEvent(EVENT_TIME, {
|
||||||
const event = createEvent(kind, {
|
content: $editor.getText({blockSeparator: "\n"}).trim(),
|
||||||
content: $editor.getText({blockSeparator: "\n"}),
|
|
||||||
tags: [
|
tags: [
|
||||||
["d", randomId()],
|
["d", randomId()],
|
||||||
["title", title],
|
["title", title],
|
||||||
["location", location],
|
["location", location],
|
||||||
["start", dateToSeconds(start).toString()],
|
["start", dateToSeconds(start).toString()],
|
||||||
["end", dateToSeconds(end).toString()],
|
["end", dateToSeconds(end).toString()],
|
||||||
...getEditorTags($editor),
|
...$editor.storage.nostr.getEditorTags(),
|
||||||
PROTECTED,
|
PROTECTED,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -55,17 +54,15 @@
|
|||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
let editor: Readable<Editor>
|
let element: HTMLElement
|
||||||
const isAllDay = false
|
let editor: ReturnType<typeof getEditor>
|
||||||
let title = ""
|
let title = ""
|
||||||
let location = ""
|
let location = ""
|
||||||
let start: Date
|
let start: Date
|
||||||
let end: Date
|
let end: Date
|
||||||
|
|
||||||
$: loading = $editor?.storage.fileUpload.loading
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor = createEditor(getEditorOptions({submit, getPubkeyHints}))
|
editor = getEditor({submit, element, uploading})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -86,13 +83,13 @@
|
|||||||
slot="input"
|
slot="input"
|
||||||
class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
|
class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
|
||||||
<div class="input-editor flex-grow overflow-hidden">
|
<div class="input-editor flex-grow overflow-hidden">
|
||||||
<EditorContent editor={$editor} />
|
<div bind:this={element} />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
data-tip="Add an image"
|
data-tip="Add an image"
|
||||||
class="center btn tooltip"
|
class="center btn tooltip"
|
||||||
on:click={$editor.commands.selectFiles}>
|
on:click={() => $editor.chain().selectFiles().run()}>
|
||||||
{#if $loading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="gallery-send" />
|
<Icon icon="gallery-send" />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
||||||
import ChannelName from "@app/components/ChannelName.svelte"
|
import ChannelName from "@app/components/ChannelName.svelte"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeRoomPath} from "@app/routes"
|
||||||
import {deriveChannel, channelIsLocked} from "@app/state"
|
import {deriveChannel, channelIsLocked} from "@app/state"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/notifications"
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
export let room
|
export let room
|
||||||
export let notify = false
|
export let notify = false
|
||||||
|
|
||||||
const path = makeSpacePath(url, room)
|
const path = makeRoomPath(url, room)
|
||||||
const channel = deriveChannel(url, room)
|
const channel = deriveChannel(url, room)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
import {type Instance} from "tippy.js"
|
import {type Instance} from "tippy.js"
|
||||||
import {append, remove, uniq} from "@welshman/lib"
|
import {append, remove, uniq} from "@welshman/lib"
|
||||||
import {profileSearch} from "@welshman/app"
|
import {profileSearch} from "@welshman/app"
|
||||||
|
import {Suggestions} from "@welshman/editor"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Suggestions from "@lib/editor/Suggestions.svelte"
|
import ProfileSuggestion from "@app/editor/ProfileSuggestion.svelte"
|
||||||
import SuggestionProfile from "@lib/editor/SuggestionProfile.svelte"
|
|
||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
term,
|
term,
|
||||||
select: selectPubkey,
|
select: selectPubkey,
|
||||||
search: profileSearch,
|
search: profileSearch,
|
||||||
component: SuggestionProfile,
|
component: ProfileSuggestion,
|
||||||
class: "rounded-box",
|
class: "rounded-box",
|
||||||
style: `left: 4px; width: ${input?.clientWidth + 12}px`,
|
style: `left: 4px; width: ${input?.clientWidth + 12}px`,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
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 Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
@@ -47,7 +48,9 @@
|
|||||||
<div slot="title">Access Error</div>
|
<div slot="title">Access Error</div>
|
||||||
<div slot="info">We couldn't connect you to this space.</div>
|
<div slot="info">We couldn't connect you to this space.</div>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<p>We received an error from the relay indicating you don't have access to this space.</p>
|
<p>
|
||||||
|
We received an error from the relay indicating you don't have access to {displayRelayUrl(url)}.
|
||||||
|
</p>
|
||||||
<p class="border-l border-solid border-error pl-4 text-error">
|
<p class="border-l border-solid border-error pl-4 text-error">
|
||||||
{error}
|
{error}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import type {Readable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import {createEditor, type Editor, EditorContent} from "svelte-tiptap"
|
|
||||||
import {createEvent, THREAD} from "@welshman/util"
|
import {createEvent, THREAD} from "@welshman/util"
|
||||||
import {publishThunk} from "@welshman/app"
|
import {publishThunk} from "@welshman/app"
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
@@ -12,15 +11,16 @@
|
|||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import {GENERAL, tagRoom, PROTECTED} from "@app/state"
|
import {GENERAL, tagRoom, PROTECTED} from "@app/state"
|
||||||
import {getPubkeyHints} from "@app/commands"
|
import {getEditor} from "@app/editor"
|
||||||
import {getEditorOptions, getEditorTags} from "@lib/editor"
|
|
||||||
|
|
||||||
export let url
|
export let url
|
||||||
|
|
||||||
|
const uploading = writable(false)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if ($loading) return
|
if ($uploading) return
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = $editor.getText({blockSeparator: "\n"})
|
const content = $editor.getText({blockSeparator: "\n"}).trim()
|
||||||
|
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
@@ -38,7 +38,12 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = [["title", title], tagRoom(GENERAL, url), ...getEditorTags($editor), PROTECTED]
|
const tags = [
|
||||||
|
...$editor.storage.nostr.getEditorTags(),
|
||||||
|
tagRoom(GENERAL, url),
|
||||||
|
["title", title],
|
||||||
|
PROTECTED,
|
||||||
|
]
|
||||||
|
|
||||||
publishThunk({
|
publishThunk({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
@@ -49,14 +54,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let title: string
|
let title: string
|
||||||
let editor: Readable<Editor>
|
let element: HTMLElement
|
||||||
|
let editor: ReturnType<typeof getEditor>
|
||||||
$: loading = $editor?.storage.fileUpload.loading
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor = createEditor(
|
editor = getEditor({submit, element, uploading, placeholder: "What's on your mind?"})
|
||||||
getEditorOptions({submit, getPubkeyHints, placeholder: "What's on your mind?"}),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -81,14 +83,14 @@
|
|||||||
<Field>
|
<Field>
|
||||||
<p slot="label">Message*</p>
|
<p slot="label">Message*</p>
|
||||||
<div slot="input" class="note-editor flex-grow overflow-hidden">
|
<div slot="input" class="note-editor flex-grow overflow-hidden">
|
||||||
<EditorContent editor={$editor} />
|
<div bind:this={element} />
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
</Field>
|
||||||
<Button
|
<Button
|
||||||
data-tip="Add an image"
|
data-tip="Add an image"
|
||||||
class="tooltip tooltip-left absolute bottom-1 right-2"
|
class="tooltip tooltip-left absolute bottom-1 right-2"
|
||||||
on:click={$editor.commands.selectFiles}>
|
on:click={$editor.commands.selectFiles}>
|
||||||
{#if $loading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="paperclip" size={3} />
|
<Icon icon="paperclip" size={3} />
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="-mb-3 h-0 text-end text-xs opacity-75">
|
<p class="mb-3 h-0 text-xs opacity-75">
|
||||||
{formatTimestamp(event.created_at)}
|
{formatTimestamp(event.created_at)}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import type {Readable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import {createEditor, type Editor, EditorContent} from "svelte-tiptap"
|
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
import {fly, slideAndFade} from "@lib/transition"
|
import {fly, slideAndFade} from "@lib/transition"
|
||||||
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 ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import {getEditorOptions, getEditorTags} from "@lib/editor"
|
import {publishComment} from "@app/commands"
|
||||||
import {getPubkeyHints, publishComment} from "@app/commands"
|
|
||||||
import {tagRoom, GENERAL, PROTECTED} from "@app/state"
|
import {tagRoom, GENERAL, PROTECTED} from "@app/state"
|
||||||
|
import {getEditor} from "@app/editor"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
export let url
|
export let url
|
||||||
@@ -17,13 +16,15 @@
|
|||||||
export let onClose
|
export let onClose
|
||||||
export let onSubmit
|
export let onSubmit
|
||||||
|
|
||||||
|
const uploading = writable(false)
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if ($loading) return
|
if ($uploading) return
|
||||||
|
|
||||||
const content = $editor.getText({blockSeparator: "\n"})
|
const content = $editor.getText({blockSeparator: "\n"}).trim()
|
||||||
const tags = [...getEditorTags($editor), tagRoom(GENERAL, url), PROTECTED]
|
const tags = [...$editor.storage.nostr.getEditorTags(), tagRoom(GENERAL, url), PROTECTED]
|
||||||
|
|
||||||
if (!content.trim()) {
|
if (!content) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
theme: "error",
|
theme: "error",
|
||||||
message: "Please provide a message for your reply.",
|
message: "Please provide a message for your reply.",
|
||||||
@@ -33,12 +34,11 @@
|
|||||||
onSubmit(publishComment({event, content, tags, relays: [url]}))
|
onSubmit(publishComment({event, content, tags, relays: [url]}))
|
||||||
}
|
}
|
||||||
|
|
||||||
let editor: Readable<Editor>
|
let editor: ReturnType<typeof getEditor>
|
||||||
|
let element: HTMLElement
|
||||||
$: loading = $editor?.storage.fileUpload.loading
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor = createEditor(getEditorOptions({submit, getPubkeyHints, autofocus: !isMobile}))
|
editor = getEditor({element, submit, uploading, autofocus: !isMobile})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -49,13 +49,13 @@
|
|||||||
class="card2 sticky bottom-2 z-feature mx-2 mt-4 bg-neutral">
|
class="card2 sticky bottom-2 z-feature mx-2 mt-4 bg-neutral">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="note-editor flex-grow overflow-hidden">
|
<div class="note-editor flex-grow overflow-hidden">
|
||||||
<EditorContent editor={$editor} />
|
<div bind:this={element} />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
data-tip="Add an image"
|
data-tip="Add an image"
|
||||||
class="tooltip tooltip-left absolute bottom-1 right-2"
|
class="tooltip tooltip-left absolute bottom-1 right-2"
|
||||||
on:click={$editor.commands.selectFiles}>
|
on:click={$editor.commands.selectFiles}>
|
||||||
{#if $loading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="paperclip" size={3} />
|
<Icon icon="paperclip" size={3} />
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
|
import {NodeViewWrapper} from "svelte-tiptap"
|
||||||
|
import {deriveProfileDisplay} from "@welshman/app"
|
||||||
|
|
||||||
|
export let node: NodeViewProps["node"]
|
||||||
|
export let selected: NodeViewProps["selected"]
|
||||||
|
|
||||||
|
const display = deriveProfileDisplay(node.attrs.pubkey)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NodeViewWrapper as="span">
|
||||||
|
<button class="tiptap-object {selected ? 'tiptap-active' : ''}">
|
||||||
|
@{$display}
|
||||||
|
</button>
|
||||||
|
</NodeViewWrapper>
|
||||||
@@ -3,18 +3,16 @@
|
|||||||
import {
|
import {
|
||||||
userFollows,
|
userFollows,
|
||||||
deriveUserWotScore,
|
deriveUserWotScore,
|
||||||
deriveProfile,
|
|
||||||
deriveHandleForPubkey,
|
deriveHandleForPubkey,
|
||||||
displayHandle,
|
displayHandle,
|
||||||
deriveProfileDisplay,
|
deriveProfileDisplay,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
|
||||||
import WotScore from "@lib/components/WotScore.svelte"
|
import WotScore from "@lib/components/WotScore.svelte"
|
||||||
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
const pubkey = value
|
const pubkey = value
|
||||||
const profile = deriveProfile(pubkey)
|
|
||||||
const profileDisplay = deriveProfileDisplay(pubkey)
|
const profileDisplay = deriveProfileDisplay(pubkey)
|
||||||
const handle = deriveHandleForPubkey(pubkey)
|
const handle = deriveHandleForPubkey(pubkey)
|
||||||
const score = deriveUserWotScore(pubkey)
|
const score = deriveUserWotScore(pubkey)
|
||||||
@@ -24,11 +22,11 @@
|
|||||||
|
|
||||||
<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} />
|
||||||
</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">
|
||||||
<div class="text-bold overflow-hidden text-ellipsis">
|
<div class="text-bold overflow-hidden text-ellipsis text-base">
|
||||||
{$profileDisplay}
|
{$profileDisplay}
|
||||||
</div>
|
</div>
|
||||||
<WotScore score={$score} active={following} />
|
<WotScore score={$score} active={following} />
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import type {Writable} from "svelte/store"
|
||||||
|
import {derived} from "svelte/store"
|
||||||
|
import {createEditor, SvelteNodeViewRenderer} from "svelte-tiptap"
|
||||||
|
import {ctx} from "@welshman/lib"
|
||||||
|
import type {StampedEvent} from "@welshman/util"
|
||||||
|
import {signer, profileSearch} from "@welshman/app"
|
||||||
|
import {MentionSuggestion, WelshmanExtension} from "@welshman/editor"
|
||||||
|
import {getSetting, userSettingValues} from "@app/state"
|
||||||
|
import ProfileSuggestion from "./ProfileSuggestion.svelte"
|
||||||
|
import EditMention from "./EditMention.svelte"
|
||||||
|
|
||||||
|
export const getUploadType = () => getSetting<"nip96" | "blossom">("upload_type")
|
||||||
|
|
||||||
|
export const getUploadUrl = () => {
|
||||||
|
const {upload_type, nip96_urls, blossom_urls} = userSettingValues.get()
|
||||||
|
|
||||||
|
return upload_type === "nip96"
|
||||||
|
? nip96_urls[0] || "https://nostr.build"
|
||||||
|
: blossom_urls[0] || "https://cdn.satellite.earth"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const signWithAssert = async (template: StampedEvent) => {
|
||||||
|
const event = await signer.get().sign(template)
|
||||||
|
|
||||||
|
return event!
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEditor = ({
|
||||||
|
aggressive = false,
|
||||||
|
autofocus = false,
|
||||||
|
charCount,
|
||||||
|
content = "",
|
||||||
|
element,
|
||||||
|
placeholder = "",
|
||||||
|
submit,
|
||||||
|
uploading,
|
||||||
|
wordCount,
|
||||||
|
}: {
|
||||||
|
aggressive?: boolean
|
||||||
|
autofocus?: boolean
|
||||||
|
charCount?: Writable<number>
|
||||||
|
content?: string
|
||||||
|
element: HTMLElement
|
||||||
|
placeholder?: string
|
||||||
|
submit: () => void
|
||||||
|
uploading?: Writable<boolean>
|
||||||
|
wordCount?: Writable<number>
|
||||||
|
}) =>
|
||||||
|
createEditor({
|
||||||
|
element,
|
||||||
|
content,
|
||||||
|
autofocus,
|
||||||
|
extensions: [
|
||||||
|
WelshmanExtension.configure({
|
||||||
|
submit,
|
||||||
|
sign: signWithAssert,
|
||||||
|
defaultUploadType: getUploadType(),
|
||||||
|
defaultUploadUrl: getUploadUrl(),
|
||||||
|
extensions: {
|
||||||
|
placeholder: {
|
||||||
|
config: {
|
||||||
|
placeholder,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
breakOrSubmit: {
|
||||||
|
config: {
|
||||||
|
aggressive,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fileUpload: {
|
||||||
|
config: {
|
||||||
|
onDrop() {
|
||||||
|
uploading?.set(true)
|
||||||
|
},
|
||||||
|
onComplete() {
|
||||||
|
uploading?.set(false)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nprofile: {
|
||||||
|
extend: {
|
||||||
|
addNodeView: () => SvelteNodeViewRenderer(EditMention),
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [
|
||||||
|
MentionSuggestion({
|
||||||
|
editor: (this as any).editor,
|
||||||
|
search: derived(profileSearch, s => s.searchValues),
|
||||||
|
getRelays: (pubkey: string) => ctx.app.router.FromPubkeys([pubkey]).getUrls(),
|
||||||
|
component: ProfileSuggestion,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
onUpdate({editor}) {
|
||||||
|
wordCount?.set(editor.storage.wordCount.words)
|
||||||
|
charCount?.set(editor.storage.wordCount.chars)
|
||||||
|
},
|
||||||
|
})
|
||||||
+20
-15
@@ -1,7 +1,7 @@
|
|||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {synced} from "@welshman/store"
|
import {synced, throttled} from "@welshman/store"
|
||||||
import {pubkey} from "@welshman/app"
|
import {pubkey} from "@welshman/app"
|
||||||
import {prop, sortBy, now} from "@welshman/lib"
|
import {prop, identity, now} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {MESSAGE} from "@welshman/util"
|
import {MESSAGE} from "@welshman/util"
|
||||||
import {makeSpacePath, makeChatPath, makeThreadPath, makeRoomPath} from "@app/routes"
|
import {makeSpacePath, makeChatPath, makeThreadPath, makeRoomPath} from "@app/routes"
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
THREAD_FILTER,
|
THREAD_FILTER,
|
||||||
COMMENT_FILTER,
|
COMMENT_FILTER,
|
||||||
chats,
|
chats,
|
||||||
getEventsForUrl,
|
getUrlsForEvent,
|
||||||
userRoomsByUrl,
|
userRoomsByUrl,
|
||||||
repositoryStore,
|
repositoryStore,
|
||||||
} from "@app/state"
|
} from "@app/state"
|
||||||
@@ -25,11 +25,12 @@ export const setChecked = (key: string) => checked.update(state => ({...state, [
|
|||||||
// Derived notifications state
|
// Derived notifications state
|
||||||
|
|
||||||
export const notifications = derived(
|
export const notifications = derived(
|
||||||
[pubkey, checked, chats, userRoomsByUrl, repositoryStore],
|
throttled(
|
||||||
([$pubkey, $checked, $chats, $userRoomsByUrl, $repository]) => {
|
1000,
|
||||||
const hasNotification = (path: string, events: TrustedEvent[]) => {
|
derived([pubkey, checked, chats, userRoomsByUrl, repositoryStore, getUrlsForEvent], identity),
|
||||||
const [latestEvent] = sortBy($e => -$e.created_at, events)
|
),
|
||||||
|
([$pubkey, $checked, $chats, $userRoomsByUrl, $repository, $getUrlsForEvent]) => {
|
||||||
|
const hasNotification = (path: string, latestEvent: TrustedEvent | undefined) => {
|
||||||
if (!latestEvent || latestEvent.pubkey === $pubkey) {
|
if (!latestEvent || latestEvent.pubkey === $pubkey) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -50,29 +51,33 @@ export const notifications = derived(
|
|||||||
for (const {pubkeys, messages} of $chats) {
|
for (const {pubkeys, messages} of $chats) {
|
||||||
const chatPath = makeChatPath(pubkeys)
|
const chatPath = makeChatPath(pubkeys)
|
||||||
|
|
||||||
if (hasNotification(chatPath, messages)) {
|
if (hasNotification(chatPath, messages[0])) {
|
||||||
paths.add("/chat")
|
paths.add("/chat")
|
||||||
paths.add(chatPath)
|
paths.add(chatPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allThreadEvents = $repository.query([THREAD_FILTER, COMMENT_FILTER])
|
||||||
|
const allMessageEvents = $repository.query([{kinds: [MESSAGE]}])
|
||||||
|
|
||||||
for (const [url, rooms] of $userRoomsByUrl.entries()) {
|
for (const [url, rooms] of $userRoomsByUrl.entries()) {
|
||||||
const spacePath = makeSpacePath(url)
|
const spacePath = makeSpacePath(url)
|
||||||
const threadPath = makeThreadPath(url)
|
const threadPath = makeThreadPath(url)
|
||||||
const threadFilters = [THREAD_FILTER, COMMENT_FILTER]
|
const latestEvent = allThreadEvents.find(e => $getUrlsForEvent(e.id).includes(url))
|
||||||
const threadEvents = getEventsForUrl($repository, url, threadFilters)
|
|
||||||
|
|
||||||
if (hasNotification(threadPath, threadEvents)) {
|
if (hasNotification(threadPath, latestEvent)) {
|
||||||
paths.add(spacePath)
|
paths.add(spacePath)
|
||||||
paths.add(threadPath)
|
paths.add(threadPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const room of rooms) {
|
for (const room of rooms) {
|
||||||
const roomPath = makeRoomPath(url, room)
|
const roomPath = makeRoomPath(url, room)
|
||||||
const roomFilters = [{kinds: [MESSAGE], "#h": [room]}]
|
const latestEvent = allMessageEvents.find(
|
||||||
const roomEvents = getEventsForUrl($repository, url, roomFilters)
|
e =>
|
||||||
|
$getUrlsForEvent(e.id).includes(url) && e.tags.find(t => t[0] === "h" && t[1] === room),
|
||||||
|
)
|
||||||
|
|
||||||
if (hasNotification(roomPath, roomEvents)) {
|
if (hasNotification(roomPath, latestEvent)) {
|
||||||
paths.add(spacePath)
|
paths.add(spacePath)
|
||||||
paths.add(roomPath)
|
paths.add(roomPath)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-24
@@ -1,20 +1,23 @@
|
|||||||
|
import {get} from "svelte/store"
|
||||||
import {partition, assoc, now} from "@welshman/lib"
|
import {partition, assoc, now} from "@welshman/lib"
|
||||||
import {MESSAGE, REACTION, DELETE, THREAD, COMMENT} from "@welshman/util"
|
import {MESSAGE, THREAD, COMMENT} from "@welshman/util"
|
||||||
import type {Subscription} from "@welshman/net"
|
import type {Subscription} from "@welshman/net"
|
||||||
import type {AppSyncOpts} from "@welshman/app"
|
import type {AppSyncOpts} from "@welshman/app"
|
||||||
import {subscribe, repository, load, pull, hasNegentropy} from "@welshman/app"
|
import {subscribe, load, repository, pull, hasNegentropy} from "@welshman/app"
|
||||||
import {userRoomsByUrl, LEGACY_MESSAGE, GENERAL, getEventsForUrl} from "@app/state"
|
import {userRoomsByUrl, getUrlsForEvent} from "@app/state"
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
export const pullConservatively = ({relays, filters}: AppSyncOpts) => {
|
export const pullConservatively = ({relays, filters}: AppSyncOpts) => {
|
||||||
|
const $getUrlsForEvent = get(getUrlsForEvent)
|
||||||
const [smart, dumb] = partition(hasNegentropy, relays)
|
const [smart, dumb] = partition(hasNegentropy, relays)
|
||||||
const promises = [pull({relays: smart, filters})]
|
const promises = [pull({relays: smart, filters})]
|
||||||
|
const allEvents = repository.query(filters, {shouldSort: false})
|
||||||
|
|
||||||
// Since pulling from relays without negentropy is expensive, limit how many
|
// Since pulling from relays without negentropy is expensive, limit how many
|
||||||
// duplicates we repeatedly download
|
// duplicates we repeatedly download
|
||||||
for (const url of dumb) {
|
for (const url of dumb) {
|
||||||
const events = getEventsForUrl(repository, url, filters)
|
const events = allEvents.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||||
|
|
||||||
if (events.length > 100) {
|
if (events.length > 100) {
|
||||||
filters = filters.map(assoc("since", events[10]!.created_at))
|
filters = filters.map(assoc("since", events[10]!.created_at))
|
||||||
@@ -29,7 +32,6 @@ export const pullConservatively = ({relays, filters}: AppSyncOpts) => {
|
|||||||
// Application requests
|
// Application requests
|
||||||
|
|
||||||
export const listenForNotifications = () => {
|
export const listenForNotifications = () => {
|
||||||
const since = now()
|
|
||||||
const subs: Subscription[] = []
|
const subs: Subscription[] = []
|
||||||
|
|
||||||
for (const [url, rooms] of userRoomsByUrl.get()) {
|
for (const [url, rooms] of userRoomsByUrl.get()) {
|
||||||
@@ -46,9 +48,9 @@ export const listenForNotifications = () => {
|
|||||||
subscribe({
|
subscribe({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
filters: [
|
filters: [
|
||||||
{kinds: [THREAD], since},
|
{kinds: [THREAD], since: now()},
|
||||||
{kinds: [COMMENT], "#K": [String(THREAD)], since},
|
{kinds: [COMMENT], "#K": [String(THREAD)], since: now()},
|
||||||
{kinds: [MESSAGE], "#h": Array.from(rooms), since},
|
{kinds: [MESSAGE], "#h": Array.from(rooms), since: now()},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -60,19 +62,3 @@ export const listenForNotifications = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listenForChannelMessages = (url: string, room: string) => {
|
|
||||||
const since = now()
|
|
||||||
const relays = [url]
|
|
||||||
const kinds = [MESSAGE, REACTION, DELETE]
|
|
||||||
const legacyRoom = room === GENERAL ? "general" : room
|
|
||||||
|
|
||||||
// Load legacy immediate so our request doesn't get rejected by nip29 relays
|
|
||||||
load({relays, filters: [{kinds: [LEGACY_MESSAGE], "#~": [legacyRoom]}], delay: 0})
|
|
||||||
|
|
||||||
// Load historical state with negentropy if available
|
|
||||||
pullConservatively({relays, filters: [{kinds, "#h": [room]}]})
|
|
||||||
|
|
||||||
// Listen for new messages
|
|
||||||
return subscribe({relays, filters: [{kinds, "#h": [room], since}]})
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -607,7 +607,7 @@ export const userSettingValues = withGetter(
|
|||||||
derived(userSettings, $s => $s?.values || defaultSettings),
|
derived(userSettings, $s => $s?.values || defaultSettings),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getSetting = (key: keyof Settings["values"]) => userSettingValues.get()[key]
|
export const getSetting = <T>(key: keyof Settings["values"]) => userSettingValues.get()[key] as T
|
||||||
|
|
||||||
export const userMembership = withGetter(
|
export const userMembership = withGetter(
|
||||||
derived([pubkey, membershipByPubkey], ([$pubkey, $membershipByPubkey]) => {
|
derived([pubkey, membershipByPubkey], ([$pubkey, $membershipByPubkey]) => {
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
import {type Instance} from "tippy.js"
|
import {type Instance} from "tippy.js"
|
||||||
import {identity} from "@welshman/lib"
|
import {identity} from "@welshman/lib"
|
||||||
import {createSearch} from "@welshman/app"
|
import {createSearch} from "@welshman/app"
|
||||||
|
import {Suggestions, SuggestionString} from "@welshman/editor"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
import Suggestions from "@lib/editor/Suggestions.svelte"
|
|
||||||
import SuggestionString from "@lib/editor/SuggestionString.svelte"
|
|
||||||
|
|
||||||
export let value: string
|
export let value: string
|
||||||
export let options: string[]
|
export let options: string[]
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
<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">
|
|
||||||
<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>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import cx from "classnames"
|
|
||||||
import type {NodeViewProps} from "@tiptap/core"
|
|
||||||
import {NodeViewWrapper} from "svelte-tiptap"
|
|
||||||
import {always, nthEq} from "@welshman/lib"
|
|
||||||
import {parse, renderAsText, ParsedType} from "@welshman/content"
|
|
||||||
import {type TrustedEvent, fromNostrURI, Address} from "@welshman/util"
|
|
||||||
import Link from "@lib/components/Link.svelte"
|
|
||||||
import {deriveEvent, entityLink} from "@app/state"
|
|
||||||
|
|
||||||
export let node: NodeViewProps["node"]
|
|
||||||
export let selected: NodeViewProps["selected"]
|
|
||||||
|
|
||||||
const renderLink = (href: string, display: string) => display
|
|
||||||
|
|
||||||
const displayEvent = (e: TrustedEvent) => {
|
|
||||||
const content = e?.tags.find(nthEq(0, "alt"))?.[1] || e?.content || ""
|
|
||||||
|
|
||||||
if (content.length < 1) {
|
|
||||||
return fromNostrURI(nevent || naddr).slice(0, 16) + "..."
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = parse({...e, content})
|
|
||||||
|
|
||||||
// Try stripping entities, but if we get nothing back go ahead and show them
|
|
||||||
const renderEntity = always(parsed.find(p => p.type === ParsedType.Text) ? "" : "[quote]")
|
|
||||||
|
|
||||||
return renderAsText(parsed, {renderLink, renderEntity})
|
|
||||||
}
|
|
||||||
|
|
||||||
$: ({identifier, pubkey, kind, id, relays = [], nevent, naddr} = node.attrs)
|
|
||||||
$: event = deriveEvent(id || new Address(kind, pubkey, identifier).toString(), relays)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<NodeViewWrapper class="inline">
|
|
||||||
<Link
|
|
||||||
external
|
|
||||||
href={entityLink(node.attrs.nevent)}
|
|
||||||
class={cx("link-content", {"link-content-selected": selected})}>
|
|
||||||
{displayEvent($event)}
|
|
||||||
</Link>
|
|
||||||
</NodeViewWrapper>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<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"
|
|
||||||
|
|
||||||
export let node: NodeViewProps["node"]
|
|
||||||
export let selected: NodeViewProps["selected"]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<NodeViewWrapper class={cx("link-content inline", {"link-content-selected": selected})}>
|
|
||||||
{#if node.attrs.uploading}
|
|
||||||
<span class="loading loading-spinner loading-xs translate-y-[2px] scale-75" />
|
|
||||||
{:else}
|
|
||||||
<Icon icon="paperclip" size={3} class="inline-block translate-y-px" />
|
|
||||||
{/if}
|
|
||||||
{node.attrs.file.name}
|
|
||||||
</NodeViewWrapper>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import cx from "classnames"
|
|
||||||
import type {NodeViewProps} from "@tiptap/core"
|
|
||||||
import {NodeViewWrapper} from "svelte-tiptap"
|
|
||||||
import {displayProfile} from "@welshman/util"
|
|
||||||
import {deriveProfile} from "@welshman/app"
|
|
||||||
import Link from "@lib/components/Link.svelte"
|
|
||||||
import {pubkeyLink} 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">
|
|
||||||
<Link
|
|
||||||
external
|
|
||||||
href={pubkeyLink(node.attrs.pubkey, node.attrs.relays)}
|
|
||||||
class={cx("link-content", {"link-content-selected": selected})}>
|
|
||||||
@{displayProfile($profile)}
|
|
||||||
</Link>
|
|
||||||
</NodeViewWrapper>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type {NodeViewProps} from "@tiptap/core"
|
|
||||||
import {NodeViewWrapper} from "svelte-tiptap"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
|
||||||
|
|
||||||
export let node: NodeViewProps["node"]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<NodeViewWrapper class="link-content inline">
|
|
||||||
{#if node.attrs.uploading}
|
|
||||||
<span class="loading loading-spinner loading-xs translate-y-[2px] scale-75" />
|
|
||||||
{:else}
|
|
||||||
<Icon icon="paperclip" size={3} class="inline-block translate-y-px" />
|
|
||||||
{/if}
|
|
||||||
{node.attrs.file.name}
|
|
||||||
</NodeViewWrapper>
|
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
import type {CommandProps, Editor} from "@tiptap/core"
|
|
||||||
import {Extension} from "@tiptap/core"
|
|
||||||
import {now} from "@welshman/lib"
|
|
||||||
import type {StampedEvent, SignedEvent} from "@welshman/util"
|
|
||||||
import type {ImageAttributes, VideoAttributes} from "nostr-editor"
|
|
||||||
import {readServerConfig, uploadFile} from "nostr-tools/nip96"
|
|
||||||
import {getToken} from "nostr-tools/nip98"
|
|
||||||
import type {Node} from "prosemirror-model"
|
|
||||||
import {Plugin, PluginKey} from "prosemirror-state"
|
|
||||||
import {writable} from "svelte/store"
|
|
||||||
|
|
||||||
declare module "@tiptap/core" {
|
|
||||||
interface Commands<ReturnType> {
|
|
||||||
uploadFile: {
|
|
||||||
selectFiles: () => ReturnType
|
|
||||||
uploadFiles: () => ReturnType
|
|
||||||
getMetaTags: () => string[][]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileUploadOptions {
|
|
||||||
allowedMimeTypes: string[]
|
|
||||||
expiration: number
|
|
||||||
immediateUpload: boolean
|
|
||||||
hash: (file: File) => Promise<string>
|
|
||||||
sign?: (event: StampedEvent) => Promise<SignedEvent | undefined>
|
|
||||||
onDrop: (currentEditor: Editor, file: File, pos: number) => void
|
|
||||||
onComplete: (currentEditor: Editor) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UploadTask {
|
|
||||||
url?: string
|
|
||||||
sha256?: string
|
|
||||||
error?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function bufferToHex(buffer: ArrayBuffer) {
|
|
||||||
return Array.from(new Uint8Array(buffer))
|
|
||||||
.map(b => b.toString(16).padStart(2, "0"))
|
|
||||||
.join("")
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FileUploadExtension = Extension.create<FileUploadOptions>({
|
|
||||||
name: "fileUpload",
|
|
||||||
|
|
||||||
addStorage() {
|
|
||||||
return {
|
|
||||||
loading: writable(false),
|
|
||||||
tags: [] as string[][],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addOptions() {
|
|
||||||
return {
|
|
||||||
allowedMimeTypes: [
|
|
||||||
"image/jpeg",
|
|
||||||
"image/png",
|
|
||||||
"image/gif",
|
|
||||||
"video/mp4",
|
|
||||||
"video/mpeg",
|
|
||||||
"video/webm",
|
|
||||||
],
|
|
||||||
immediateUpload: true,
|
|
||||||
expiration: 60000,
|
|
||||||
async hash(file: File) {
|
|
||||||
return bufferToHex(await crypto.subtle.digest("SHA-256", await file.arrayBuffer()))
|
|
||||||
},
|
|
||||||
onDrop() {},
|
|
||||||
onComplete() {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addCommands() {
|
|
||||||
return {
|
|
||||||
selectFiles: () => props => {
|
|
||||||
props.tr.setMeta("selectFiles", true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
uploadFiles: () => (props: CommandProps) => {
|
|
||||||
props.tr.setMeta("uploadFiles", true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
getMetaTags: () =>
|
|
||||||
((props: CommandProps) => {
|
|
||||||
const tags: string[][] = []
|
|
||||||
// make sure the file uploaded is still in the editor content
|
|
||||||
props.editor.state.doc.descendants(node => {
|
|
||||||
if (!(node.type.name === "image" || node.type.name === "video")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const tag = props.editor.storage.fileUpload.tags.find((t: string[]) =>
|
|
||||||
t[1].includes(node.attrs.src),
|
|
||||||
)
|
|
||||||
if (tag) {
|
|
||||||
tags.push(tag)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return tags
|
|
||||||
}) as any,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addProseMirrorPlugins() {
|
|
||||||
const uploader = new Uploader(this.editor, this.options)
|
|
||||||
return [
|
|
||||||
new Plugin({
|
|
||||||
key: new PluginKey("fileUploadPlugin"),
|
|
||||||
state: {
|
|
||||||
init() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
apply(tr) {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (tr.getMeta("selectFiles")) {
|
|
||||||
uploader.selectFiles()
|
|
||||||
tr.setMeta("selectFiles", null)
|
|
||||||
} else if (tr.getMeta("uploadFiles")) {
|
|
||||||
uploader.uploadFiles()
|
|
||||||
tr.setMeta("uploadFiles", null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
handleDrop: (_, event) => {
|
|
||||||
return uploader.handleDrop(event)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
class Uploader {
|
|
||||||
constructor(
|
|
||||||
public editor: Editor,
|
|
||||||
private options: FileUploadOptions,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
get view() {
|
|
||||||
return this.editor.view
|
|
||||||
}
|
|
||||||
|
|
||||||
addFile(file: File, pos: number) {
|
|
||||||
if (
|
|
||||||
!this.options.allowedMimeTypes.some(amt => amt.split("*").every(s => file.type.includes(s)))
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const {tr} = this.view.state
|
|
||||||
const [mimetype] = file.type.split("/")
|
|
||||||
const node = this.view.state.schema.nodes[mimetype].create({
|
|
||||||
file,
|
|
||||||
src: URL.createObjectURL(file),
|
|
||||||
alt: "",
|
|
||||||
uploading: false,
|
|
||||||
uploadError: null,
|
|
||||||
})
|
|
||||||
tr.insert(pos, node)
|
|
||||||
this.view.dispatch(tr)
|
|
||||||
|
|
||||||
if (this.options.immediateUpload) {
|
|
||||||
this.editor.storage.fileUpload.loading.set(true)
|
|
||||||
this.upload(node).then(() => this.editor.storage.fileUpload.loading.set(false))
|
|
||||||
}
|
|
||||||
this.options.onDrop(this.editor, file, pos)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
findNodePosition(node: Node) {
|
|
||||||
let pos = -1
|
|
||||||
this.view.state.doc.descendants((n, p) => {
|
|
||||||
if (n === node) {
|
|
||||||
pos = p
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return pos
|
|
||||||
}
|
|
||||||
|
|
||||||
findNodes(uploading: boolean) {
|
|
||||||
const nodes = [] as [Node, number][]
|
|
||||||
this.view.state.doc.descendants((node, pos) => {
|
|
||||||
if (!(node.type.name === "image" || node.type.name === "video")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (node.attrs.sha256) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ((node.attrs.uploading || false) !== uploading) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nodes.push([node, pos])
|
|
||||||
})
|
|
||||||
return nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNodeAttributes(nodeRef: Node, attrs: Record<string, unknown>) {
|
|
||||||
const {tr} = this.editor.view.state
|
|
||||||
|
|
||||||
const pos = this.findNodePosition(nodeRef)
|
|
||||||
if (pos === -1) return
|
|
||||||
|
|
||||||
Object.entries(attrs).forEach(
|
|
||||||
([key, value]) => value !== undefined && tr.setNodeAttribute(pos, key, value),
|
|
||||||
)
|
|
||||||
this.view.dispatch(tr)
|
|
||||||
}
|
|
||||||
|
|
||||||
onUploadDone(nodeRef: Node, response: UploadTask) {
|
|
||||||
this.findNodes(true).forEach(([node, pos]) => {
|
|
||||||
if (node.attrs.src === nodeRef.attrs.src) {
|
|
||||||
this.updateNodeAttributes(node, {
|
|
||||||
uploading: false,
|
|
||||||
src: response.url,
|
|
||||||
sha256: response.sha256,
|
|
||||||
uploadError: response.error,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async upload(node: Node) {
|
|
||||||
const {sign, hash, expiration} = this.options
|
|
||||||
|
|
||||||
const {
|
|
||||||
file,
|
|
||||||
alt,
|
|
||||||
uploadType,
|
|
||||||
uploadUrl: serverUrl,
|
|
||||||
} = node.attrs as ImageAttributes | VideoAttributes
|
|
||||||
|
|
||||||
this.updateNodeAttributes(node, {uploading: true, uploadError: null})
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (uploadType === "nip96") {
|
|
||||||
const res = (await uploadNIP96({file, alt, sign: sign!, serverUrl}))!
|
|
||||||
|
|
||||||
// add the tags as received from nip-96 to the storage
|
|
||||||
this.editor.storage.fileUpload.tags.push(["imeta", ...res.tags!])
|
|
||||||
this.onUploadDone(node, res)
|
|
||||||
} else {
|
|
||||||
const res = await uploadBlossom({file, serverUrl, hash, sign, expiration})
|
|
||||||
this.editor.storage.fileUpload.tags.push([
|
|
||||||
"imeta",
|
|
||||||
`url ${res.url}`,
|
|
||||||
`size ${res.size}`,
|
|
||||||
`m ${res.type}`,
|
|
||||||
`x ${res.sha256}`,
|
|
||||||
])
|
|
||||||
this.onUploadDone(node, res)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const msg = error as string
|
|
||||||
this.onUploadDone(node, {error: msg})
|
|
||||||
throw new Error(msg as string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async uploadFiles() {
|
|
||||||
const tasks = this.findNodes(false).map(([node]) => {
|
|
||||||
return this.upload(node)
|
|
||||||
})
|
|
||||||
try {
|
|
||||||
this.editor.storage.fileUpload.loading.set(true)
|
|
||||||
await Promise.all(tasks)
|
|
||||||
this.options.onComplete(this.editor)
|
|
||||||
} finally {
|
|
||||||
this.editor.storage.fileUpload.loading.set(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectFiles() {
|
|
||||||
const input = document.createElement("input")
|
|
||||||
input.type = "file"
|
|
||||||
input.multiple = true
|
|
||||||
input.accept = this.options.allowedMimeTypes.join(",")
|
|
||||||
input.onchange = event => {
|
|
||||||
const files = (event.target as HTMLInputElement).files
|
|
||||||
if (files) {
|
|
||||||
Array.from(files).forEach(file => {
|
|
||||||
if (file) {
|
|
||||||
const pos = this.view.state.selection.from + 1
|
|
||||||
this.addFile(file, pos)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDrop(event: DragEvent) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
const pos = this.view.posAtCoords({left: event.clientX, top: event.clientY})?.pos
|
|
||||||
|
|
||||||
if (pos === undefined) return false
|
|
||||||
|
|
||||||
const file = event.dataTransfer?.files?.[0]
|
|
||||||
if (file) {
|
|
||||||
this.addFile(file, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NIP96Options {
|
|
||||||
file: File
|
|
||||||
alt?: string
|
|
||||||
serverUrl: string
|
|
||||||
expiration?: number
|
|
||||||
sign: (event: StampedEvent) => Promise<SignedEvent | undefined>
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uploadNIP96(options: NIP96Options) {
|
|
||||||
try {
|
|
||||||
const server = await readServerConfig(options.serverUrl)
|
|
||||||
const authorization = await getToken(server.api_url, "POST", options.sign as any, true)
|
|
||||||
const res = await uploadFile(options.file, server.api_url, authorization, {
|
|
||||||
alt: options.alt || "",
|
|
||||||
expiration: options.expiration?.toString() || "",
|
|
||||||
content_type: options.file.type,
|
|
||||||
})
|
|
||||||
if (res.status === "error") {
|
|
||||||
throw new Error(res.message)
|
|
||||||
}
|
|
||||||
const url = res.nip94_event?.tags.find(x => x[0] === "url")?.[1] || ""
|
|
||||||
const sha256 = res.nip94_event?.tags.find(x => x[0] === "x")?.[1] || ""
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
sha256,
|
|
||||||
tags: res.nip94_event?.tags.flatMap(item => item.join(" ")),
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BlossomOptions {
|
|
||||||
file: File
|
|
||||||
serverUrl: string
|
|
||||||
expiration?: number
|
|
||||||
hash?: (file: File) => Promise<string>
|
|
||||||
sign?: (event: StampedEvent) => Promise<SignedEvent | undefined>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BlossomResponse {
|
|
||||||
sha256: string
|
|
||||||
size: number
|
|
||||||
type: string
|
|
||||||
uploaded: number
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BlossomResponseError {
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uploadBlossom(options: BlossomOptions) {
|
|
||||||
if (!options.hash) {
|
|
||||||
throw new Error("No hash function provided")
|
|
||||||
}
|
|
||||||
if (!options.sign) {
|
|
||||||
throw new Error("No signer provided")
|
|
||||||
}
|
|
||||||
const created_at = now()
|
|
||||||
const hash = await options.hash(options.file)
|
|
||||||
const event = await options.sign({
|
|
||||||
kind: 24242,
|
|
||||||
content: `Upload ${options.file.name}`,
|
|
||||||
created_at,
|
|
||||||
tags: [
|
|
||||||
["t", "upload"],
|
|
||||||
["x", hash],
|
|
||||||
["size", options.file.size.toString()],
|
|
||||||
["expiration", (created_at + (options.expiration || 60000)).toString()],
|
|
||||||
],
|
|
||||||
})
|
|
||||||
const data = JSON.stringify(event)
|
|
||||||
const base64 = btoa(data)
|
|
||||||
const authorization = `Nostr ${base64}`
|
|
||||||
const res = await fetch(options.serverUrl + "/upload", {
|
|
||||||
method: "PUT",
|
|
||||||
body: options.file,
|
|
||||||
headers: {
|
|
||||||
authorization,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const json = await res.json()
|
|
||||||
if (res.status === 200) {
|
|
||||||
return json as BlossomResponse
|
|
||||||
}
|
|
||||||
throw new Error((json as BlossomResponseError).message)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let value
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{value}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
<svelte:options accessors />
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {fly, slide} from "svelte/transition"
|
|
||||||
import {clamp, throttle} from "@welshman/lib"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
|
||||||
import {theme} from "@app/theme"
|
|
||||||
|
|
||||||
export let term
|
|
||||||
export let search
|
|
||||||
export let select
|
|
||||||
export let component
|
|
||||||
export let loading = false
|
|
||||||
export let allowCreate = false
|
|
||||||
|
|
||||||
let index = 0
|
|
||||||
let element: Element
|
|
||||||
let items: string[] = []
|
|
||||||
|
|
||||||
$: populateItems(term)
|
|
||||||
|
|
||||||
const populateItems = throttle(300, term => {
|
|
||||||
items = $search.searchValues(term).slice(0, 5)
|
|
||||||
})
|
|
||||||
|
|
||||||
const setIndex = (newIndex: number, block: any) => {
|
|
||||||
index = clamp([0, items.length - 1], newIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const onKeyDown = (e: any) => {
|
|
||||||
if (["Enter", "Tab"].includes(e.code)) {
|
|
||||||
const value = items[index]
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
select(value)
|
|
||||||
return true
|
|
||||||
} else if (term && allowCreate) {
|
|
||||||
select(term)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.code === "Space" && term && allowCreate) {
|
|
||||||
select(term)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.code === "ArrowUp") {
|
|
||||||
setIndex(index - 1, "start")
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.code === "ArrowDown") {
|
|
||||||
setIndex(index + 1, "start")
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if term}
|
|
||||||
<div
|
|
||||||
data-theme={$theme}
|
|
||||||
bind:this={element}
|
|
||||||
transition:fly|local={{duration: 200}}
|
|
||||||
class="mt-2 max-h-[350px] overflow-y-auto overflow-x-hidden shadow-xl {$$props.class} bg-alt"
|
|
||||||
style={$$props.style}>
|
|
||||||
{#if term && allowCreate && !items.includes(term)}
|
|
||||||
<button
|
|
||||||
class="white-space-nowrap block w-full min-w-0 cursor-pointer overflow-x-hidden text-ellipsis px-4 py-2 text-left transition-all hover:brightness-150"
|
|
||||||
on:mousedown|preventDefault
|
|
||||||
on:click|preventDefault={() => select(term)}>
|
|
||||||
Use "<svelte:component this={component} value={term} />"
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{#each items as value, i (value)}
|
|
||||||
<button
|
|
||||||
class="white-space-nowrap block flex w-full min-w-0 cursor-pointer items-center overflow-x-hidden text-ellipsis px-4 py-2 text-left transition-all hover:brightness-150"
|
|
||||||
on:mousedown|preventDefault
|
|
||||||
on:click|preventDefault={() => select(value)}>
|
|
||||||
{#if index === i}
|
|
||||||
<div transition:slide|local={{axis: "x"}} class="flex items-center pr-2">
|
|
||||||
<Icon icon="alt-arrow-right" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<svelte:component this={component} {value} />
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{#if loading}
|
|
||||||
<div transition:slide|local class="flex gap-2 px-4 py-2">
|
|
||||||
<div>
|
|
||||||
<i class="fa fa-circle-notch fa-spin" />
|
|
||||||
</div>
|
|
||||||
Loading more options...
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import type {SvelteComponent, ComponentType} from "svelte"
|
|
||||||
import type {Readable} from "svelte/store"
|
|
||||||
import tippy, {type Instance} from "tippy.js"
|
|
||||||
import type {Editor} from "@tiptap/core"
|
|
||||||
import {PluginKey} from "@tiptap/pm/state"
|
|
||||||
import Suggestion from "@tiptap/suggestion"
|
|
||||||
import type {Search} from "@welshman/app"
|
|
||||||
|
|
||||||
export type SuggestionsOptions = {
|
|
||||||
char: string
|
|
||||||
name: string
|
|
||||||
editor: Editor
|
|
||||||
search: Readable<Search<any, any>>
|
|
||||||
select: (value: any, props: any) => void
|
|
||||||
allowCreate?: boolean
|
|
||||||
suggestionComponent: ComponentType
|
|
||||||
suggestionsComponent: ComponentType
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createSuggestions = (options: SuggestionsOptions) =>
|
|
||||||
Suggestion({
|
|
||||||
char: options.char,
|
|
||||||
editor: options.editor,
|
|
||||||
pluginKey: new PluginKey(`suggest-${options.name}`),
|
|
||||||
command: ({editor, range, props}) => {
|
|
||||||
// increase range.to by one when the next node is of type "text"
|
|
||||||
// and starts with a space character
|
|
||||||
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
|
||||||
const overrideSpace = nodeAfter?.text?.startsWith(" ")
|
|
||||||
|
|
||||||
if (overrideSpace) {
|
|
||||||
range.to += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
editor
|
|
||||||
.chain()
|
|
||||||
.focus()
|
|
||||||
.insertContentAt(range, [
|
|
||||||
{type: options.name, attrs: props},
|
|
||||||
{type: "text", text: " "},
|
|
||||||
])
|
|
||||||
.run()
|
|
||||||
|
|
||||||
window.getSelection()?.collapseToEnd()
|
|
||||||
},
|
|
||||||
allow: ({state, range}) => {
|
|
||||||
const $from = state.doc.resolve(range.from)
|
|
||||||
const type = state.schema.nodes[options.name]
|
|
||||||
|
|
||||||
return !!$from.parent.type.contentMatch.matchType(type)
|
|
||||||
},
|
|
||||||
render: () => {
|
|
||||||
let popover: Instance[]
|
|
||||||
let suggestions: SvelteComponent
|
|
||||||
|
|
||||||
const mapProps = (props: any) => ({
|
|
||||||
term: props.query,
|
|
||||||
search: options.search,
|
|
||||||
allowCreate: options.allowCreate,
|
|
||||||
component: options.suggestionComponent,
|
|
||||||
select: (value: string) => options.select(value, props),
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
onStart: props => {
|
|
||||||
const target = document.createElement("div")
|
|
||||||
|
|
||||||
popover = tippy("body", {
|
|
||||||
getReferenceClientRect: props.clientRect as any,
|
|
||||||
appendTo: document.querySelector("dialog[open]") || document.body,
|
|
||||||
content: target,
|
|
||||||
showOnCreate: true,
|
|
||||||
interactive: true,
|
|
||||||
trigger: "manual",
|
|
||||||
placement: "bottom-start",
|
|
||||||
})
|
|
||||||
|
|
||||||
suggestions = new options.suggestionsComponent({target, props: mapProps(props)})
|
|
||||||
},
|
|
||||||
onUpdate: props => {
|
|
||||||
suggestions.$set(mapProps(props))
|
|
||||||
|
|
||||||
if (props.clientRect) {
|
|
||||||
popover[0].setProps({
|
|
||||||
getReferenceClientRect: props.clientRect as any,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyDown: props => {
|
|
||||||
if (props.event.key === "Escape") {
|
|
||||||
popover[0].hide()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return Boolean(suggestions.onKeyDown?.(props.event))
|
|
||||||
},
|
|
||||||
onExit: () => {
|
|
||||||
popover[0].destroy()
|
|
||||||
suggestions.$destroy()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
import {nprofileEncode} from "nostr-tools/nip19"
|
|
||||||
import {SvelteNodeViewRenderer} from "svelte-tiptap"
|
|
||||||
import Placeholder from "@tiptap/extension-placeholder"
|
|
||||||
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,
|
|
||||||
TagExtension,
|
|
||||||
} from "nostr-editor"
|
|
||||||
import type {StampedEvent} from "@welshman/util"
|
|
||||||
import {signer, profileSearch} from "@welshman/app"
|
|
||||||
import {FileUploadExtension} from "./FileUpload"
|
|
||||||
import {createSuggestions} from "./Suggestions"
|
|
||||||
import EditMention from "./EditMention.svelte"
|
|
||||||
import EditEvent from "./EditEvent.svelte"
|
|
||||||
import EditImage from "./EditImage.svelte"
|
|
||||||
import EditBolt11 from "./EditBolt11.svelte"
|
|
||||||
import EditVideo from "./EditVideo.svelte"
|
|
||||||
import Suggestions from "./Suggestions.svelte"
|
|
||||||
import SuggestionProfile from "./SuggestionProfile.svelte"
|
|
||||||
import {asInline} from "./util"
|
|
||||||
import {getSetting} from "@app/state"
|
|
||||||
|
|
||||||
export {
|
|
||||||
createSuggestions,
|
|
||||||
EditMention,
|
|
||||||
EditEvent,
|
|
||||||
EditImage,
|
|
||||||
EditBolt11,
|
|
||||||
EditVideo,
|
|
||||||
Suggestions,
|
|
||||||
SuggestionProfile,
|
|
||||||
}
|
|
||||||
export * from "./util"
|
|
||||||
|
|
||||||
type UploadType = "nip96" | "blossom"
|
|
||||||
|
|
||||||
type EditorOptions = {
|
|
||||||
submit: () => void
|
|
||||||
getPubkeyHints: (pubkey: string) => string[]
|
|
||||||
submitOnEnter?: boolean
|
|
||||||
placeholder?: string
|
|
||||||
autofocus?: boolean
|
|
||||||
uploadType?: UploadType
|
|
||||||
defaultUploadUrl?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getEditorOptions = ({
|
|
||||||
submit,
|
|
||||||
getPubkeyHints,
|
|
||||||
submitOnEnter,
|
|
||||||
placeholder = "",
|
|
||||||
autofocus = false,
|
|
||||||
uploadType = getSetting("upload_type") as UploadType,
|
|
||||||
defaultUploadUrl = getSetting("upload_type") == "nip96"
|
|
||||||
? (getSetting("nip96_urls") as string[])[0] || "https://nostr.build"
|
|
||||||
: (getSetting("blossom_urls") as string[])[0] || "https://cdn.satellite.earth",
|
|
||||||
}: EditorOptions) => ({
|
|
||||||
autofocus,
|
|
||||||
content: "",
|
|
||||||
extensions: [
|
|
||||||
Code,
|
|
||||||
CodeBlock,
|
|
||||||
Document,
|
|
||||||
Dropcursor,
|
|
||||||
Gapcursor,
|
|
||||||
History,
|
|
||||||
Paragraph,
|
|
||||||
Text,
|
|
||||||
TagExtension,
|
|
||||||
Placeholder.configure({placeholder}),
|
|
||||||
HardBreakExtension.extend({
|
|
||||||
addKeyboardShortcuts() {
|
|
||||||
return {
|
|
||||||
"Shift-Enter": () => this.editor.commands.setHardBreak(),
|
|
||||||
"Mod-Enter": () => {
|
|
||||||
if (this.editor.getText().trim()) {
|
|
||||||
submit()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.editor.commands.setHardBreak()
|
|
||||||
},
|
|
||||||
Enter: () => {
|
|
||||||
if (submitOnEnter && this.editor.getText().trim()) {
|
|
||||||
submit()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.editor.commands.setHardBreak()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Bolt11Extension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditBolt11)})),
|
|
||||||
NProfileExtension.extend({
|
|
||||||
addNodeView: () => SvelteNodeViewRenderer(EditMention),
|
|
||||||
addProseMirrorPlugins() {
|
|
||||||
return [
|
|
||||||
createSuggestions({
|
|
||||||
char: "@",
|
|
||||||
name: "nprofile",
|
|
||||||
editor: this.editor,
|
|
||||||
search: profileSearch,
|
|
||||||
select: (pubkey: string, props: any) => {
|
|
||||||
const relays = getPubkeyHints(pubkey)
|
|
||||||
const nprofile = nprofileEncode({pubkey, relays})
|
|
||||||
|
|
||||||
return props.command({pubkey, nprofile, relays})
|
|
||||||
},
|
|
||||||
suggestionComponent: SuggestionProfile,
|
|
||||||
suggestionsComponent: Suggestions,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})),
|
|
||||||
NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})),
|
|
||||||
ImageExtension.extend(
|
|
||||||
asInline({addNodeView: () => SvelteNodeViewRenderer(EditImage)}),
|
|
||||||
).configure({defaultUploadUrl, defaultUploadType: uploadType}),
|
|
||||||
VideoExtension.extend(
|
|
||||||
asInline({addNodeView: () => SvelteNodeViewRenderer(EditVideo)}),
|
|
||||||
).configure({defaultUploadUrl, defaultUploadType: uploadType}),
|
|
||||||
FileUploadExtension.configure({
|
|
||||||
immediateUpload: true,
|
|
||||||
allowedMimeTypes: ["image/*", "video/*"],
|
|
||||||
sign: (event: StampedEvent) => signer.get()!.sign(event),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import type {JSONContent, PasteRuleMatch, InputRuleMatch} from "@tiptap/core"
|
|
||||||
import {Editor} from "@tiptap/core"
|
|
||||||
import {ctx} from "@welshman/lib"
|
|
||||||
import {Address} from "@welshman/util"
|
|
||||||
|
|
||||||
export const asInline = (extend: Record<string, any>) => ({
|
|
||||||
inline: true,
|
|
||||||
group: "inline",
|
|
||||||
...extend,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const createInputRuleMatch = <T extends Record<string, unknown>>(
|
|
||||||
match: RegExpMatchArray,
|
|
||||||
data: T,
|
|
||||||
): InputRuleMatch => ({index: match.index!, text: match[0], match, data})
|
|
||||||
|
|
||||||
export const createPasteRuleMatch = <T extends Record<string, unknown>>(
|
|
||||||
match: RegExpMatchArray,
|
|
||||||
data: T,
|
|
||||||
): PasteRuleMatch => ({index: match.index!, text: match[0], match, data})
|
|
||||||
|
|
||||||
export const findNodes = (type: string, json: JSONContent) => {
|
|
||||||
const results: JSONContent[] = []
|
|
||||||
|
|
||||||
for (const node of json.content || []) {
|
|
||||||
if (node.type === type) {
|
|
||||||
results.push(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const result of findNodes(type, node)) {
|
|
||||||
results.push(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
export const findMarks = (type: string, json: JSONContent) => {
|
|
||||||
const results: JSONContent[] = []
|
|
||||||
|
|
||||||
for (const node of json.content || []) {
|
|
||||||
for (const mark of node.marks || []) {
|
|
||||||
if (mark.type === type) {
|
|
||||||
results.push(mark)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const result of findMarks(type, node)) {
|
|
||||||
results.push(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getEditorTags = (editor: Editor) => {
|
|
||||||
const json = editor.getJSON()
|
|
||||||
|
|
||||||
const topicTags = findMarks("tag", json).map(({attrs}: any) => [
|
|
||||||
"t",
|
|
||||||
attrs.tag.replace(/^#/, "").toLowerCase(),
|
|
||||||
])
|
|
||||||
|
|
||||||
const naddrTags = findNodes("naddr", json).map(
|
|
||||||
({attrs: {kind, pubkey, identifier, relays = []}}: any) => {
|
|
||||||
const address = new Address(kind, pubkey, identifier).toString()
|
|
||||||
|
|
||||||
return ["q", address, ctx.app.router.FromRelays(relays).getUrl(), pubkey]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const neventTags = findNodes("nevent", json).map(({attrs: {id, author, relays = []}}: any) => [
|
|
||||||
"q",
|
|
||||||
id,
|
|
||||||
ctx.app.router.FromRelays(relays).getUrl(),
|
|
||||||
author || "",
|
|
||||||
])
|
|
||||||
|
|
||||||
const mentionTags = findNodes("nprofile", json).map(({attrs: {pubkey, relays = []}}: any) => [
|
|
||||||
"p",
|
|
||||||
pubkey,
|
|
||||||
ctx.app.router.FromRelays(relays).getUrl(),
|
|
||||||
"",
|
|
||||||
])
|
|
||||||
|
|
||||||
const imetaTags = findNodes("image", json).map(({attrs: {src, sha256}}: any) => [
|
|
||||||
"imeta",
|
|
||||||
`url ${src}`,
|
|
||||||
`x ${sha256}`,
|
|
||||||
`ox ${sha256}`,
|
|
||||||
])
|
|
||||||
|
|
||||||
return [...topicTags, ...naddrTags, ...neventTags, ...mentionTags, ...imetaTags]
|
|
||||||
}
|
|
||||||
+59
-66
@@ -5,10 +5,12 @@
|
|||||||
import {get, derived} from "svelte/store"
|
import {get, derived} from "svelte/store"
|
||||||
import {dev} from "$app/environment"
|
import {dev} from "$app/environment"
|
||||||
import {bytesToHex, hexToBytes} from "@noble/hashes/utils"
|
import {bytesToHex, hexToBytes} from "@noble/hashes/utils"
|
||||||
import {identity, sleep, take, sortBy, ago, now, HOUR, WEEK, Worker} from "@welshman/lib"
|
import {identity, sleep, take, sortBy, ago, now, HOUR, WEEK, MONTH, Worker} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
|
MESSAGE,
|
||||||
PROFILE,
|
PROFILE,
|
||||||
|
DELETE,
|
||||||
REACTION,
|
REACTION,
|
||||||
ZAP_RESPONSE,
|
ZAP_RESPONSE,
|
||||||
FOLLOWS,
|
FOLLOWS,
|
||||||
@@ -18,7 +20,6 @@
|
|||||||
getPubkeyTagValues,
|
getPubkeyTagValues,
|
||||||
getListTags,
|
getListTags,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {throttled} from "@welshman/store"
|
|
||||||
import {
|
import {
|
||||||
relays,
|
relays,
|
||||||
handles,
|
handles,
|
||||||
@@ -80,80 +81,72 @@
|
|||||||
...notifications,
|
...notifications,
|
||||||
})
|
})
|
||||||
|
|
||||||
const getScoreEvent = () => {
|
|
||||||
const ALWAYS_KEEP = Infinity
|
|
||||||
const NEVER_KEEP = 0
|
|
||||||
|
|
||||||
const reactionKinds = [REACTION, ZAP_RESPONSE]
|
|
||||||
const metaKinds = [PROFILE, FOLLOWS, RELAYS, INBOX_RELAYS]
|
|
||||||
const $sessionKeys = new Set(Object.keys(app.sessions.get()))
|
|
||||||
const $userFollows = new Set(getPubkeyTagValues(getListTags(get(app.userFollows))))
|
|
||||||
const $maxWot = get(app.maxWot)
|
|
||||||
|
|
||||||
return (e: TrustedEvent) => {
|
|
||||||
const isFollowing = $userFollows.has(e.pubkey)
|
|
||||||
|
|
||||||
// No need to keep a record of everyone who follows the current user
|
|
||||||
if (e.kind === FOLLOWS && !isFollowing) return NEVER_KEEP
|
|
||||||
|
|
||||||
// Always keep stuff by or tagging a signed in user
|
|
||||||
if ($sessionKeys.has(e.pubkey)) return ALWAYS_KEEP
|
|
||||||
if (e.tags.some(t => $sessionKeys.has(t[1]))) return ALWAYS_KEEP
|
|
||||||
|
|
||||||
// Get rid of irrelevant messages, reactions, and likes
|
|
||||||
if (e.wrap || e.kind === 4 || e.kind === WRAP) return NEVER_KEEP
|
|
||||||
if (reactionKinds.includes(e.kind)) return NEVER_KEEP
|
|
||||||
|
|
||||||
// If the user follows this person, use max wot score
|
|
||||||
let score = isFollowing ? $maxWot : app.getUserWotScore(e.pubkey)
|
|
||||||
|
|
||||||
// Inflate the score for profiles/relays/follows to avoid redundant fetches
|
|
||||||
// Demote non-metadata type events, and introduce recency bias
|
|
||||||
score *= metaKinds.includes(e.kind) ? 2 : e.created_at / now()
|
|
||||||
|
|
||||||
return score
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const migrateFreshness = (data: {key: string; value: number}[]) => {
|
|
||||||
const cutoff = ago(HOUR)
|
|
||||||
|
|
||||||
return data.filter(({value}) => value > cutoff)
|
|
||||||
}
|
|
||||||
|
|
||||||
const migratePlaintext = (data: {key: string; value: number}[]) => data.slice(0, 10_000)
|
|
||||||
|
|
||||||
const migrateEvents = (events: TrustedEvent[]) => {
|
|
||||||
if (events.length < 50_000) {
|
|
||||||
return events
|
|
||||||
}
|
|
||||||
|
|
||||||
const scoreEvent = getScoreEvent()
|
|
||||||
|
|
||||||
return take(
|
|
||||||
30_000,
|
|
||||||
sortBy(e => -scoreEvent(e), events),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!db) {
|
if (!db) {
|
||||||
setupTracking()
|
setupTracking()
|
||||||
setupAnalytics()
|
setupAnalytics()
|
||||||
|
|
||||||
ready = initStorage("flotilla", 4, {
|
ready = initStorage("flotilla", 4, {
|
||||||
relays: {keyPath: "url", store: throttled(3000, relays)},
|
relays: storageAdapters.fromCollectionStore("url", relays, {throttle: 3000}),
|
||||||
handles: {keyPath: "nip05", store: throttled(3000, handles)},
|
handles: storageAdapters.fromCollectionStore("nip05", handles, {throttle: 3000}),
|
||||||
freshness: storageAdapters.fromObjectStore(freshness, {
|
freshness: storageAdapters.fromObjectStore(freshness, {
|
||||||
throttle: 3000,
|
throttle: 3000,
|
||||||
migrate: migrateFreshness,
|
migrate: (data: {key: string; value: number}[]) => {
|
||||||
|
const cutoff = ago(HOUR)
|
||||||
|
|
||||||
|
return data.filter(({value}) => value > cutoff)
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
plaintext: storageAdapters.fromObjectStore(plaintext, {
|
plaintext: storageAdapters.fromObjectStore(plaintext, {
|
||||||
throttle: 3000,
|
throttle: 3000,
|
||||||
migrate: migratePlaintext,
|
migrate: (data: {key: string; value: number}[]) => data.slice(0, 10_000),
|
||||||
}),
|
}),
|
||||||
events: storageAdapters.fromRepositoryAndTracker(repository, tracker, {
|
events: storageAdapters.fromRepositoryAndTracker(repository, tracker, {
|
||||||
throttle: 3000,
|
throttle: 3000,
|
||||||
migrate: migrateEvents,
|
migrate: (events: TrustedEvent[]) => {
|
||||||
|
if (events.length < 15_000) {
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
const NEVER_KEEP = 0
|
||||||
|
const ALWAYS_KEEP = Infinity
|
||||||
|
const reactionKinds = [REACTION, ZAP_RESPONSE, DELETE]
|
||||||
|
const metaKinds = [PROFILE, FOLLOWS, RELAYS, INBOX_RELAYS]
|
||||||
|
const $sessionKeys = new Set(Object.keys(app.sessions.get()))
|
||||||
|
const $userFollows = new Set(getPubkeyTagValues(getListTags(get(app.userFollows))))
|
||||||
|
const $maxWot = get(app.maxWot)
|
||||||
|
|
||||||
|
const scoreEvent = (e: TrustedEvent) => {
|
||||||
|
const isFollowing = $userFollows.has(e.pubkey)
|
||||||
|
|
||||||
|
// No need to keep a record of everyone who follows the current user
|
||||||
|
if (e.kind === FOLLOWS && !isFollowing) return NEVER_KEEP
|
||||||
|
|
||||||
|
// Drop room messages after a month, re-load on demand
|
||||||
|
if (e.kind === MESSAGE && e.created_at < ago(MONTH)) return NEVER_KEEP
|
||||||
|
|
||||||
|
// Always keep stuff by or tagging a signed in user
|
||||||
|
if ($sessionKeys.has(e.pubkey)) return ALWAYS_KEEP
|
||||||
|
if (e.tags.some(t => $sessionKeys.has(t[1]))) return ALWAYS_KEEP
|
||||||
|
|
||||||
|
// Get rid of irrelevant messages, reactions, and likes
|
||||||
|
if (e.wrap || e.kind === 4 || e.kind === WRAP) return NEVER_KEEP
|
||||||
|
if (reactionKinds.includes(e.kind)) return NEVER_KEEP
|
||||||
|
|
||||||
|
// If the user follows this person, use max wot score
|
||||||
|
let score = isFollowing ? $maxWot : app.getUserWotScore(e.pubkey)
|
||||||
|
|
||||||
|
// Inflate the score for profiles/relays/follows to avoid redundant fetches
|
||||||
|
// Demote non-metadata type events, and introduce recency bias
|
||||||
|
score *= metaKinds.includes(e.kind) ? 2 : e.created_at / now()
|
||||||
|
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
return take(
|
||||||
|
10_000,
|
||||||
|
sortBy(e => -scoreEvent(e), events),
|
||||||
|
)
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}).then(() => sleep(300))
|
}).then(() => sleep(300))
|
||||||
|
|
||||||
@@ -195,11 +188,11 @@
|
|||||||
// Listen for chats, populate chat-based notifications
|
// Listen for chats, populate chat-based notifications
|
||||||
let chatsSub: any
|
let chatsSub: any
|
||||||
|
|
||||||
derived([pubkey, userInboxRelaySelections], identity).subscribe(
|
derived([pubkey, canDecrypt, userInboxRelaySelections], identity).subscribe(
|
||||||
([$pubkey, $userInboxRelaySelections]) => {
|
([$pubkey, $canDecrypt, $userInboxRelaySelections]) => {
|
||||||
chatsSub?.close()
|
chatsSub?.close()
|
||||||
|
|
||||||
if ($pubkey) {
|
if ($pubkey && $canDecrypt) {
|
||||||
chatsSub = subscribe({
|
chatsSub = subscribe({
|
||||||
filters: [
|
filters: [
|
||||||
{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)},
|
{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)},
|
||||||
|
|||||||
@@ -40,14 +40,14 @@
|
|||||||
<CardButton>
|
<CardButton>
|
||||||
<div slot="icon"><Icon icon="compass" size={7} /></div>
|
<div slot="icon"><Icon icon="compass" size={7} /></div>
|
||||||
<div slot="title">Browse the network</div>
|
<div slot="title">Browse the network</div>
|
||||||
<div slot="info">Find your people on the nostr network</div>
|
<div slot="info">Find your people on the nostr network.</div>
|
||||||
</CardButton>
|
</CardButton>
|
||||||
</Link>
|
</Link>
|
||||||
<Button on:click={startChat}>
|
<Button on:click={startChat}>
|
||||||
<CardButton>
|
<CardButton>
|
||||||
<div slot="icon"><Icon icon="chat-round" size={7} /></div>
|
<div slot="icon"><Icon icon="chat-round" size={7} /></div>
|
||||||
<div slot="title">Start a conversation</div>
|
<div slot="title">Start a conversation</div>
|
||||||
<div slot="info">Use nostr's encrypted group chats to stay in touch</div>
|
<div slot="info">Use nostr's encrypted group chats to stay in touch.</div>
|
||||||
</CardButton>
|
</CardButton>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {now} from "@welshman/lib"
|
import {ago, WEEK} from "@welshman/lib"
|
||||||
|
import {GROUPS, MESSAGE, DELETE} from "@welshman/util"
|
||||||
import {subscribe} from "@welshman/app"
|
import {subscribe} from "@welshman/app"
|
||||||
import {DELETE, REACTION, GROUPS} from "@welshman/util"
|
|
||||||
import Page from "@lib/components/Page.svelte"
|
import Page from "@lib/components/Page.svelte"
|
||||||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||||
import MenuSpace from "@app/components/MenuSpace.svelte"
|
import MenuSpace from "@app/components/MenuSpace.svelte"
|
||||||
@@ -12,11 +12,14 @@
|
|||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {setChecked} from "@app/notifications"
|
import {setChecked} from "@app/notifications"
|
||||||
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/commands"
|
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/commands"
|
||||||
import {decodeRelay} from "@app/state"
|
import {decodeRelay, userRoomsByUrl, THREAD_FILTER, COMMENT_FILTER} from "@app/state"
|
||||||
|
import {pullConservatively} from "@app/requests"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/notifications"
|
||||||
|
|
||||||
const url = decodeRelay($page.params.relay)
|
const url = decodeRelay($page.params.relay)
|
||||||
|
|
||||||
|
const rooms = Array.from($userRoomsByUrl.get(url) || [])
|
||||||
|
|
||||||
const checkConnection = async () => {
|
const checkConnection = async () => {
|
||||||
const connectionError = await checkRelayConnection(url)
|
const connectionError = await checkRelayConnection(url)
|
||||||
|
|
||||||
@@ -43,11 +46,27 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
checkConnection()
|
checkConnection()
|
||||||
|
|
||||||
const sub = subscribe({
|
const relays = [url]
|
||||||
relays: [url],
|
const since = ago(WEEK)
|
||||||
filters: [{kinds: [GROUPS]}, {kinds: [DELETE, REACTION], since: now()}],
|
|
||||||
|
// Load all groups for this space to populate navigation
|
||||||
|
pullConservatively({relays, filters: [{kinds: [GROUPS]}]})
|
||||||
|
|
||||||
|
// Load threads and comments
|
||||||
|
pullConservatively({
|
||||||
|
relays,
|
||||||
|
filters: [
|
||||||
|
{...THREAD_FILTER, since},
|
||||||
|
{...COMMENT_FILTER, since},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Load recent messages for user rooms to help with a quick page transition
|
||||||
|
pullConservatively({relays, filters: rooms.map(r => ({kinds: [MESSAGE], "#h": [r], since}))})
|
||||||
|
|
||||||
|
// Listen for deletes that would apply to messages we already have
|
||||||
|
const sub = subscribe({relays, filters: [{kinds: [DELETE], since}]})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
sub.close()
|
sub.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,25 +131,23 @@
|
|||||||
</Link>
|
</Link>
|
||||||
{#each $userRooms as room (room)}
|
{#each $userRooms as room (room)}
|
||||||
{@const roomPath = makeRoomPath(url, room)}
|
{@const roomPath = makeRoomPath(url, room)}
|
||||||
<Link href={roomPath} class="btn btn-neutral">
|
<Link href={roomPath} class="btn btn-neutral relative">
|
||||||
<div class="relative flex min-w-0 items-center gap-2 overflow-hidden">
|
<div class="flex min-w-0 items-center gap-2 overflow-hidden text-nowrap">
|
||||||
{#if channelIsLocked($channelsById.get(makeChannelId(url, room)))}
|
{#if channelIsLocked($channelsById.get(makeChannelId(url, room)))}
|
||||||
<Icon icon="lock" size={4} />
|
<Icon icon="lock" size={4} />
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="hashtag" />
|
<Icon icon="hashtag" />
|
||||||
{/if}
|
{/if}
|
||||||
<ChannelName {url} {room} />
|
<ChannelName {url} {room} />
|
||||||
{#if $notifications.has(roomPath)}
|
|
||||||
<div
|
|
||||||
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary"
|
|
||||||
transition:fade />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
{#if $notifications.has(roomPath)}
|
||||||
|
<div class="absolute right-1 top-1 h-2 w-2 rounded-full bg-primary" transition:fade />
|
||||||
|
{/if}
|
||||||
</Link>
|
</Link>
|
||||||
{/each}
|
{/each}
|
||||||
{#each $otherRooms as room (room)}
|
{#each $otherRooms as room (room)}
|
||||||
<Link href={makeRoomPath(url, room)} class="btn btn-neutral">
|
<Link href={makeRoomPath(url, room)} class="btn btn-neutral">
|
||||||
<div class="relative flex min-w-0 items-center gap-2 overflow-hidden">
|
<div class="relative flex min-w-0 items-center gap-2 overflow-hidden text-nowrap">
|
||||||
{#if channelIsLocked($channelsById.get(makeChannelId(url, room)))}
|
{#if channelIsLocked($channelsById.get(makeChannelId(url, room)))}
|
||||||
<Icon icon="lock" size={4} />
|
<Icon icon="lock" size={4} />
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {nip19} from "nostr-tools"
|
import {nip19} from "nostr-tools"
|
||||||
import {onMount, onDestroy} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import type {Readable} from "svelte/store"
|
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import type {Editor} from "svelte-tiptap"
|
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {sleep, ctx} from "@welshman/lib"
|
import {sleep, now, ctx} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {throttled} from "@welshman/store"
|
import {throttled} from "@welshman/store"
|
||||||
import {createEvent, MESSAGE} from "@welshman/util"
|
import {feedsFromFilter, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
||||||
import type {Subscription} from "@welshman/net"
|
import {createEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
|
||||||
import {formatTimestampAsDate, publishThunk, deriveRelay} from "@welshman/app"
|
import {
|
||||||
|
formatTimestampAsDate,
|
||||||
|
createFeedController,
|
||||||
|
subscribe,
|
||||||
|
publishThunk,
|
||||||
|
deriveRelay,
|
||||||
|
} from "@welshman/app"
|
||||||
import {slide} from "@lib/transition"
|
import {slide} from "@lib/transition"
|
||||||
import {createScroller, type Scroller} from "@lib/html"
|
import {createScroller, type Scroller} from "@lib/html"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
@@ -18,6 +22,7 @@
|
|||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import PageBar from "@lib/components/PageBar.svelte"
|
import PageBar from "@lib/components/PageBar.svelte"
|
||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
|
import type {getEditor} from "@app/editor"
|
||||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||||
import ChannelName from "@app/components/ChannelName.svelte"
|
import ChannelName from "@app/components/ChannelName.svelte"
|
||||||
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
||||||
@@ -34,7 +39,6 @@
|
|||||||
} from "@app/state"
|
} from "@app/state"
|
||||||
import {setChecked} from "@app/notifications"
|
import {setChecked} from "@app/notifications"
|
||||||
import {nip29, addRoomMembership, removeRoomMembership, getThunkError} from "@app/commands"
|
import {nip29, addRoomMembership, removeRoomMembership, getThunkError} from "@app/commands"
|
||||||
import {listenForChannelMessages} from "@app/requests"
|
|
||||||
import {PROTECTED, hasNip29} from "@app/state"
|
import {PROTECTED, hasNip29} from "@app/state"
|
||||||
import {popKey} from "@app/implicit"
|
import {popKey} from "@app/implicit"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
@@ -44,6 +48,8 @@
|
|||||||
const url = decodeRelay($page.params.relay)
|
const url = decodeRelay($page.params.relay)
|
||||||
const relay = deriveRelay(url)
|
const relay = deriveRelay(url)
|
||||||
const legacyRoom = room === GENERAL ? "general" : room
|
const legacyRoom = room === GENERAL ? "general" : room
|
||||||
|
const feeds = feedsFromFilter({kinds: [MESSAGE], "#h": [room]})
|
||||||
|
|
||||||
const events = throttled(
|
const events = throttled(
|
||||||
300,
|
300,
|
||||||
deriveEventsForUrl(url, [
|
deriveEventsForUrl(url, [
|
||||||
@@ -52,6 +58,14 @@
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ctrl = createFeedController({
|
||||||
|
useWindowing: true,
|
||||||
|
feed: makeIntersectionFeed(makeRelayFeed(url), ...feeds),
|
||||||
|
onExhausted: () => {
|
||||||
|
loading = false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const assertEvent = (e: any) => e as TrustedEvent
|
const assertEvent = (e: any) => e as TrustedEvent
|
||||||
|
|
||||||
const joinRoom = async () => {
|
const joinRoom = async () => {
|
||||||
@@ -90,12 +104,12 @@
|
|||||||
delay: $userSettingValues.send_delay,
|
delay: $userSettingValues.send_delay,
|
||||||
})
|
})
|
||||||
|
|
||||||
let limit = 30
|
let limit = 100
|
||||||
let loading = sleep(5000)
|
let loading = true
|
||||||
let sub: Subscription
|
let unmounted = false
|
||||||
let element: HTMLElement
|
let element: HTMLElement
|
||||||
let scroller: Scroller
|
let scroller: Scroller
|
||||||
let editor: Readable<Editor>
|
let editor: ReturnType<typeof getEditor>
|
||||||
|
|
||||||
const elements = derived(events, $events => {
|
const elements = derived(events, $events => {
|
||||||
const $elements = []
|
const $elements = []
|
||||||
@@ -122,30 +136,39 @@
|
|||||||
previousPubkey = pubkey
|
previousPubkey = pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
return $elements.reverse().slice(0, limit)
|
return $elements.reverse()
|
||||||
})
|
})
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
// Sveltekiiit
|
// Element is frequently not defined. I don't know why
|
||||||
await sleep(100)
|
sleep(1000).then(() => {
|
||||||
|
if (!unmounted) {
|
||||||
|
scroller = createScroller({
|
||||||
|
element,
|
||||||
|
delay: 300,
|
||||||
|
threshold: 10_000,
|
||||||
|
onScroll: () => {
|
||||||
|
limit += 100
|
||||||
|
|
||||||
scroller = createScroller({
|
if ($events.length - limit < 100) {
|
||||||
element,
|
ctrl.load(200)
|
||||||
delay: 300,
|
}
|
||||||
threshold: 3000,
|
},
|
||||||
onScroll: () => {
|
})
|
||||||
limit += 30
|
}
|
||||||
loading = sleep(5000)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
sub = listenForChannelMessages(url, room)
|
const sub = subscribe({
|
||||||
})
|
relays: [url],
|
||||||
|
filters: [{kinds: [DELETE, REACTION, MESSAGE], "#h": [room], since: now()}],
|
||||||
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
return () => {
|
||||||
setChecked($page.url.pathname)
|
unmounted = true
|
||||||
scroller?.stop()
|
setChecked($page.url.pathname)
|
||||||
sub?.close()
|
scroller?.stop()
|
||||||
|
sub.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -177,7 +200,7 @@
|
|||||||
<div
|
<div
|
||||||
class="scroll-container -mt-2 flex flex-grow flex-col-reverse overflow-auto py-2"
|
class="scroll-container -mt-2 flex flex-grow flex-col-reverse overflow-auto py-2"
|
||||||
bind:this={element}>
|
bind:this={element}>
|
||||||
{#each $elements as { type, id, value, showPubkey } (id)}
|
{#each $elements.slice(0, limit) as { type, id, value, showPubkey } (id)}
|
||||||
{#if type === "date"}
|
{#if type === "date"}
|
||||||
<Divider>{value}</Divider>
|
<Divider>{value}</Divider>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -187,11 +210,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<p class="flex h-10 items-center justify-center py-20">
|
<p class="flex h-10 items-center justify-center py-20">
|
||||||
{#await loading}
|
{#if loading}
|
||||||
<Spinner loading>Looking for messages...</Spinner>
|
<Spinner loading>Looking for messages...</Spinner>
|
||||||
{:then}
|
{:else}
|
||||||
<Spinner>End of message history</Spinner>
|
<Spinner>End of message history</Spinner>
|
||||||
{/await}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ChannelCompose bind:editor {content} {onSubmit} />
|
<ChannelCompose bind:editor {content} {onSubmit} />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import {sortBy, min, nthEq, sleep} from "@welshman/lib"
|
import {sortBy, min, nthEq, sleep} from "@welshman/lib"
|
||||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
||||||
import {throttled} from "@welshman/store"
|
import {throttled} from "@welshman/store"
|
||||||
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
import {feedFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
||||||
import {createFeedController, userMutes} from "@welshman/app"
|
import {createFeedController, userMutes} from "@welshman/app"
|
||||||
import {createScroller, type Scroller} from "@lib/html"
|
import {createScroller, type Scroller} from "@lib/html"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
const url = decodeRelay($page.params.relay)
|
const url = decodeRelay($page.params.relay)
|
||||||
const feeds = feedsFromFilters([THREAD_FILTER, COMMENT_FILTER])
|
const feed = feedFromFilters([THREAD_FILTER, COMMENT_FILTER])
|
||||||
const threads = deriveEventsForUrl(url, [THREAD_FILTER])
|
const threads = deriveEventsForUrl(url, [THREAD_FILTER])
|
||||||
const comments = deriveEventsForUrl(url, [COMMENT_FILTER])
|
const comments = deriveEventsForUrl(url, [COMMENT_FILTER])
|
||||||
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
|
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
const ctrl = createFeedController({
|
const ctrl = createFeedController({
|
||||||
useWindowing: true,
|
useWindowing: true,
|
||||||
feed: makeIntersectionFeed(makeRelayFeed(url), feeds),
|
feed: makeIntersectionFeed(makeRelayFeed(url), feed),
|
||||||
onExhausted: () => {
|
onExhausted: () => {
|
||||||
loading = false
|
loading = false
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user