v0.3.4: Comment edit/delete, native notifications, forward-compatible protocol, UI fixes
Comment edit & delete: - EditComment/DeleteComment BlobHeaderDiffOps with upstream+downstream propagation - Trust-based: comment author can edit/delete, post author can delete - Storage: edit_comment(), delete_comment() methods - Frontend: inline edit (Enter/Escape), delete with confirm Native notifications: - tauri-plugin-notification for system notifications on all platforms - Triggers for messages, new posts, reactions, and comments - notif_reacts setting added, button-group toggles replace dropdowns - _notifReady flag prevents startup spam Protocol hardening: - BlobHeaderDiffOp::Unknown variant with #[serde(other)] for forward compatibility - Old nodes silently skip unknown ops instead of crashing UI fixes: - Self removed from Following list - Offline follows in lightbox popup (auto-show if no one online) - Sent DMs filtered from My Posts - Comment threading scoped to closest .post (fixes duplicate ID issue) - Select dropdown text legible in WebKitGTK (black on white options) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ce176a2299
commit
0abc244ee9
18 changed files with 1616 additions and 67 deletions
435
Cargo.lock
generated
435
Cargo.lock
generated
|
|
@ -102,6 +102,30 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-broadcast"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compat"
|
name = "async-compat"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
|
@ -115,6 +139,102 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-executor"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
|
||||||
|
dependencies = [
|
||||||
|
"async-task",
|
||||||
|
"concurrent-queue",
|
||||||
|
"fastrand",
|
||||||
|
"futures-lite",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-io"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"parking",
|
||||||
|
"polling",
|
||||||
|
"rustix",
|
||||||
|
"slab",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-lock"
|
||||||
|
version = "3.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-process"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-signal",
|
||||||
|
"async-task",
|
||||||
|
"blocking",
|
||||||
|
"cfg-if",
|
||||||
|
"event-listener",
|
||||||
|
"futures-lite",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-recursion"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-signal"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"atomic-waker",
|
||||||
|
"cfg-if",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"rustix",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"slab",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-task"
|
||||||
|
version = "4.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.89"
|
version = "0.1.89"
|
||||||
|
|
@ -284,6 +404,19 @@ dependencies = [
|
||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blocking"
|
||||||
|
version = "1.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-task",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"piper",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli"
|
name = "brotli"
|
||||||
version = "8.0.2"
|
version = "8.0.2"
|
||||||
|
|
@ -514,6 +647,15 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
|
|
@ -1135,6 +1277,12 @@ version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "endi"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enum-as-inner"
|
name = "enum-as-inner"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
|
@ -1158,6 +1306,27 @@ dependencies = [
|
||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||||
|
dependencies = [
|
||||||
|
"enumflags2_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2_derive"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -1185,6 +1354,27 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener-strategy"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -1849,6 +2039,12 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
@ -2550,7 +2746,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itsgoin-desktop"
|
name = "itsgoin-desktop"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
|
@ -2562,6 +2758,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-notification",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|
@ -2813,6 +3010,18 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f"
|
checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mac-notification-sys"
|
||||||
|
version = "0.6.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29a16783dd1a47849b8c8133c9cd3eb2112cfbc6901670af3dba47c8bbfb07d3"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"objc2",
|
||||||
|
"objc2-foundation",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markup5ever"
|
name = "markup5ever"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
|
|
@ -3146,6 +3355,20 @@ version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-rust"
|
||||||
|
version = "4.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21af20a1b50be5ac5861f74af1a863da53a11c38684d9818d82f1c42f7fdc6c2"
|
||||||
|
dependencies = [
|
||||||
|
"futures-lite",
|
||||||
|
"log",
|
||||||
|
"mac-notification-sys",
|
||||||
|
"serde",
|
||||||
|
"tauri-winrt-notification",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntimestamp"
|
name = "ntimestamp"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
@ -3477,6 +3700,16 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-stream"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.18.3"
|
version = "0.18.3"
|
||||||
|
|
@ -3744,6 +3977,17 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "piper"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"fastrand",
|
||||||
|
"futures-io",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkarr"
|
name = "pkarr"
|
||||||
version = "5.0.2"
|
version = "5.0.2"
|
||||||
|
|
@ -3799,7 +4043,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"indexmap 2.13.0",
|
"indexmap 2.13.0",
|
||||||
"quick-xml",
|
"quick-xml 0.38.4",
|
||||||
"serde",
|
"serde",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
@ -3817,6 +4061,20 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polling"
|
||||||
|
version = "3.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"hermit-abi",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "poly1305"
|
name = "poly1305"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -3987,6 +4245,15 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.37.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.38.4"
|
version = "0.38.4"
|
||||||
|
|
@ -5292,6 +5559,42 @@ dependencies = [
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin"
|
||||||
|
version = "2.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"glob",
|
||||||
|
"plist",
|
||||||
|
"schemars 0.8.22",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri-utils",
|
||||||
|
"toml 0.9.11+spec-1.1.0",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-notification"
|
||||||
|
version = "2.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"notify-rust",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_repr",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
|
|
@ -5346,9 +5649,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "2.8.2"
|
version = "2.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e"
|
checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"brotli",
|
"brotli",
|
||||||
|
|
@ -5393,6 +5696,18 @@ dependencies = [
|
||||||
"toml 0.9.11+spec-1.1.0",
|
"toml 0.9.11+spec-1.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-winrt-notification"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
|
||||||
|
dependencies = [
|
||||||
|
"quick-xml 0.37.5",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"windows 0.61.3",
|
||||||
|
"windows-version",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.25.0"
|
version = "3.25.0"
|
||||||
|
|
@ -5854,6 +6169,17 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uds_windows"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||||
|
dependencies = [
|
||||||
|
"memoffset",
|
||||||
|
"tempfile",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unic-char-property"
|
name = "unic-char-property"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
@ -7055,6 +7381,67 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f"
|
checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus"
|
||||||
|
version = "5.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc"
|
||||||
|
dependencies = [
|
||||||
|
"async-broadcast",
|
||||||
|
"async-executor",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-process",
|
||||||
|
"async-recursion",
|
||||||
|
"async-task",
|
||||||
|
"async-trait",
|
||||||
|
"blocking",
|
||||||
|
"enumflags2",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
"futures-lite",
|
||||||
|
"hex",
|
||||||
|
"libc",
|
||||||
|
"ordered-stream",
|
||||||
|
"rustix",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"tracing",
|
||||||
|
"uds_windows",
|
||||||
|
"uuid",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
"winnow 0.7.14",
|
||||||
|
"zbus_macros",
|
||||||
|
"zbus_names",
|
||||||
|
"zvariant",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_macros"
|
||||||
|
version = "5.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate 3.4.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
"zbus_names",
|
||||||
|
"zvariant",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_names"
|
||||||
|
version = "4.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"winnow 0.7.14",
|
||||||
|
"zvariant",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.39"
|
version = "0.8.39"
|
||||||
|
|
@ -7154,3 +7541,43 @@ name = "zmij"
|
||||||
version = "1.0.19"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
|
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant"
|
||||||
|
version = "5.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b"
|
||||||
|
dependencies = [
|
||||||
|
"endi",
|
||||||
|
"enumflags2",
|
||||||
|
"serde",
|
||||||
|
"winnow 0.7.14",
|
||||||
|
"zvariant_derive",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_derive"
|
||||||
|
version = "5.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate 3.4.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_utils"
|
||||||
|
version = "3.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"syn 2.0.114",
|
||||||
|
"winnow 0.7.14",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -5493,6 +5493,18 @@ impl ConnectionManager {
|
||||||
}
|
}
|
||||||
let _ = storage.store_comment(comment);
|
let _ = storage.store_comment(comment);
|
||||||
}
|
}
|
||||||
|
BlobHeaderDiffOp::EditComment { author, post_id, timestamp_ms, new_content } => {
|
||||||
|
// Trust-based: only the comment author can edit
|
||||||
|
if *author == sender || sender == payload.author {
|
||||||
|
let _ = storage.edit_comment(author, post_id, *timestamp_ms, new_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlobHeaderDiffOp::DeleteComment { author, post_id, timestamp_ms } => {
|
||||||
|
// Trust-based: comment author or post author can delete
|
||||||
|
if *author == sender || sender == payload.author {
|
||||||
|
let _ = storage.delete_comment(author, post_id, *timestamp_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
BlobHeaderDiffOp::SetPolicy(new_policy) => {
|
BlobHeaderDiffOp::SetPolicy(new_policy) => {
|
||||||
if sender == payload.author {
|
if sender == payload.author {
|
||||||
let _ = storage.set_comment_policy(&payload.post_id, new_policy);
|
let _ = storage.set_comment_policy(&payload.post_id, new_policy);
|
||||||
|
|
@ -5504,6 +5516,7 @@ impl ConnectionManager {
|
||||||
parent_post_id: payload.post_id,
|
parent_post_id: payload.post_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
BlobHeaderDiffOp::Unknown => {} // future ops — silently skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3229,6 +3229,88 @@ impl Node {
|
||||||
Ok(comment)
|
Ok(comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Edit one of your own comments on a post.
|
||||||
|
pub async fn edit_comment(
|
||||||
|
&self,
|
||||||
|
post_id: PostId,
|
||||||
|
timestamp_ms: u64,
|
||||||
|
new_content: String,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let our_node_id = self.node_id;
|
||||||
|
let now = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)?
|
||||||
|
.as_millis() as u64;
|
||||||
|
|
||||||
|
let storage = self.storage.lock().await;
|
||||||
|
storage.edit_comment(&our_node_id, &post_id, timestamp_ms, &new_content)?;
|
||||||
|
drop(storage);
|
||||||
|
|
||||||
|
// Propagate via BlobHeaderDiff
|
||||||
|
{
|
||||||
|
let network = &self.network;
|
||||||
|
let diff = crate::protocol::BlobHeaderDiffPayload {
|
||||||
|
post_id,
|
||||||
|
author: our_node_id,
|
||||||
|
ops: vec![crate::types::BlobHeaderDiffOp::EditComment {
|
||||||
|
author: our_node_id,
|
||||||
|
post_id,
|
||||||
|
timestamp_ms,
|
||||||
|
new_content,
|
||||||
|
}],
|
||||||
|
timestamp_ms: now,
|
||||||
|
};
|
||||||
|
network.propagate_engagement_diff(&post_id, &diff, &our_node_id).await;
|
||||||
|
let upstream = {
|
||||||
|
let storage = self.storage.lock().await;
|
||||||
|
storage.get_post_upstream(&post_id).ok().flatten()
|
||||||
|
};
|
||||||
|
if let Some(up) = upstream {
|
||||||
|
let _ = network.send_to_peer_uni(&up, crate::protocol::MessageType::BlobHeaderDiff, &diff).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete one of your own comments on a post.
|
||||||
|
pub async fn delete_comment(
|
||||||
|
&self,
|
||||||
|
post_id: PostId,
|
||||||
|
timestamp_ms: u64,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let our_node_id = self.node_id;
|
||||||
|
let now = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)?
|
||||||
|
.as_millis() as u64;
|
||||||
|
|
||||||
|
let storage = self.storage.lock().await;
|
||||||
|
storage.delete_comment(&our_node_id, &post_id, timestamp_ms)?;
|
||||||
|
drop(storage);
|
||||||
|
|
||||||
|
// Propagate via BlobHeaderDiff
|
||||||
|
{
|
||||||
|
let network = &self.network;
|
||||||
|
let diff = crate::protocol::BlobHeaderDiffPayload {
|
||||||
|
post_id,
|
||||||
|
author: our_node_id,
|
||||||
|
ops: vec![crate::types::BlobHeaderDiffOp::DeleteComment {
|
||||||
|
author: our_node_id,
|
||||||
|
post_id,
|
||||||
|
timestamp_ms,
|
||||||
|
}],
|
||||||
|
timestamp_ms: now,
|
||||||
|
};
|
||||||
|
network.propagate_engagement_diff(&post_id, &diff, &our_node_id).await;
|
||||||
|
let upstream = {
|
||||||
|
let storage = self.storage.lock().await;
|
||||||
|
storage.get_post_upstream(&post_id).ok().flatten()
|
||||||
|
};
|
||||||
|
if let Some(up) = upstream {
|
||||||
|
let _ = network.send_to_peer_uni(&up, crate::protocol::MessageType::BlobHeaderDiff, &diff).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all comments for a post.
|
/// Get all comments for a post.
|
||||||
pub async fn get_comments(&self, post_id: PostId) -> anyhow::Result<Vec<crate::types::InlineComment>> {
|
pub async fn get_comments(&self, post_id: PostId) -> anyhow::Result<Vec<crate::types::InlineComment>> {
|
||||||
let storage = self.storage.lock().await;
|
let storage = self.storage.lock().await;
|
||||||
|
|
|
||||||
|
|
@ -3918,6 +3918,24 @@ impl Storage {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Edit a comment (must match author + post_id + timestamp_ms).
|
||||||
|
pub fn edit_comment(&self, author: &NodeId, post_id: &PostId, timestamp_ms: u64, new_content: &str) -> anyhow::Result<bool> {
|
||||||
|
let updated = self.conn.execute(
|
||||||
|
"UPDATE comments SET content = ?4 WHERE author = ?1 AND post_id = ?2 AND timestamp_ms = ?3",
|
||||||
|
params![author.as_slice(), post_id.as_slice(), timestamp_ms as i64, new_content],
|
||||||
|
)?;
|
||||||
|
Ok(updated > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a comment (must match author + post_id + timestamp_ms).
|
||||||
|
pub fn delete_comment(&self, author: &NodeId, post_id: &PostId, timestamp_ms: u64) -> anyhow::Result<bool> {
|
||||||
|
let deleted = self.conn.execute(
|
||||||
|
"DELETE FROM comments WHERE author = ?1 AND post_id = ?2 AND timestamp_ms = ?3",
|
||||||
|
params![author.as_slice(), post_id.as_slice(), timestamp_ms as i64],
|
||||||
|
)?;
|
||||||
|
Ok(deleted > 0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all comments for a post, ordered by timestamp.
|
/// Get all comments for a post, ordered by timestamp.
|
||||||
pub fn get_comments(&self, post_id: &PostId) -> anyhow::Result<Vec<InlineComment>> {
|
pub fn get_comments(&self, post_id: &PostId) -> anyhow::Result<Vec<InlineComment>> {
|
||||||
let mut stmt = self.conn.prepare(
|
let mut stmt = self.conn.prepare(
|
||||||
|
|
|
||||||
|
|
@ -817,8 +817,13 @@ pub enum BlobHeaderDiffOp {
|
||||||
AddReaction(Reaction),
|
AddReaction(Reaction),
|
||||||
RemoveReaction { reactor: NodeId, emoji: String, post_id: PostId },
|
RemoveReaction { reactor: NodeId, emoji: String, post_id: PostId },
|
||||||
AddComment(InlineComment),
|
AddComment(InlineComment),
|
||||||
|
EditComment { author: NodeId, post_id: PostId, timestamp_ms: u64, new_content: String },
|
||||||
|
DeleteComment { author: NodeId, post_id: PostId, timestamp_ms: u64 },
|
||||||
SetPolicy(CommentPolicy),
|
SetPolicy(CommentPolicy),
|
||||||
ThreadSplit { new_post_id: PostId },
|
ThreadSplit { new_post_id: PostId },
|
||||||
|
/// Unknown ops from newer protocol versions — silently ignored
|
||||||
|
#[serde(other)]
|
||||||
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aggregated engagement header for a post (stored locally, propagated as diffs)
|
/// Aggregated engagement header for a post (stored locally, propagated as diffs)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "itsgoin-desktop"
|
name = "itsgoin-desktop"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
@ -23,3 +23,4 @@ anyhow = "1"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
dirs = "5"
|
dirs = "5"
|
||||||
open = "5"
|
open = "5"
|
||||||
|
tauri-plugin-notification = "2"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@
|
||||||
"description": "Default capability for the main window",
|
"description": "Default capability for the main window",
|
||||||
"windows": ["main"],
|
"windows": ["main"],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default"
|
"core:default",
|
||||||
|
"notification:default",
|
||||||
|
"notification:allow-is-permission-granted",
|
||||||
|
"notification:allow-request-permission",
|
||||||
|
"notification:allow-notify",
|
||||||
|
"notification:allow-check-permissions",
|
||||||
|
"notification:allow-show"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -2143,6 +2143,204 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "core:window:deny-unminimize",
|
"const": "core:window:deny-unminimize",
|
||||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:default",
|
||||||
|
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-batch",
|
||||||
|
"markdownDescription": "Enables the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-cancel",
|
||||||
|
"markdownDescription": "Enables the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-check-permissions",
|
||||||
|
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-create-channel",
|
||||||
|
"markdownDescription": "Enables the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-delete-channel",
|
||||||
|
"markdownDescription": "Enables the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-active",
|
||||||
|
"markdownDescription": "Enables the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-pending",
|
||||||
|
"markdownDescription": "Enables the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-is-permission-granted",
|
||||||
|
"markdownDescription": "Enables the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-list-channels",
|
||||||
|
"markdownDescription": "Enables the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-notify",
|
||||||
|
"markdownDescription": "Enables the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-permission-state",
|
||||||
|
"markdownDescription": "Enables the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-action-types",
|
||||||
|
"markdownDescription": "Enables the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-listener",
|
||||||
|
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-remove-active",
|
||||||
|
"markdownDescription": "Enables the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-request-permission",
|
||||||
|
"markdownDescription": "Enables the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-show",
|
||||||
|
"markdownDescription": "Enables the show command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-batch",
|
||||||
|
"markdownDescription": "Denies the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-cancel",
|
||||||
|
"markdownDescription": "Denies the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-check-permissions",
|
||||||
|
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-create-channel",
|
||||||
|
"markdownDescription": "Denies the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-delete-channel",
|
||||||
|
"markdownDescription": "Denies the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-active",
|
||||||
|
"markdownDescription": "Denies the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-pending",
|
||||||
|
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-is-permission-granted",
|
||||||
|
"markdownDescription": "Denies the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-list-channels",
|
||||||
|
"markdownDescription": "Denies the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-notify",
|
||||||
|
"markdownDescription": "Denies the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-permission-state",
|
||||||
|
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-action-types",
|
||||||
|
"markdownDescription": "Denies the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-listener",
|
||||||
|
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-remove-active",
|
||||||
|
"markdownDescription": "Denies the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-request-permission",
|
||||||
|
"markdownDescription": "Denies the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-show",
|
||||||
|
"markdownDescription": "Denies the show command without any pre-configured scope."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"default":{"identifier":"default","description":"Default capability for the main window","local":true,"windows":["main"],"permissions":["core:default"]}}
|
{"default":{"identifier":"default","description":"Default capability for the main window","local":true,"windows":["main"],"permissions":["core:default","notification:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:allow-notify","notification:allow-check-permissions","notification:allow-show"]}}
|
||||||
|
|
@ -2143,6 +2143,204 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "core:window:deny-unminimize",
|
"const": "core:window:deny-unminimize",
|
||||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:default",
|
||||||
|
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-batch",
|
||||||
|
"markdownDescription": "Enables the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-cancel",
|
||||||
|
"markdownDescription": "Enables the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-check-permissions",
|
||||||
|
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-create-channel",
|
||||||
|
"markdownDescription": "Enables the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-delete-channel",
|
||||||
|
"markdownDescription": "Enables the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-active",
|
||||||
|
"markdownDescription": "Enables the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-pending",
|
||||||
|
"markdownDescription": "Enables the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-is-permission-granted",
|
||||||
|
"markdownDescription": "Enables the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-list-channels",
|
||||||
|
"markdownDescription": "Enables the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-notify",
|
||||||
|
"markdownDescription": "Enables the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-permission-state",
|
||||||
|
"markdownDescription": "Enables the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-action-types",
|
||||||
|
"markdownDescription": "Enables the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-listener",
|
||||||
|
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-remove-active",
|
||||||
|
"markdownDescription": "Enables the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-request-permission",
|
||||||
|
"markdownDescription": "Enables the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-show",
|
||||||
|
"markdownDescription": "Enables the show command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-batch",
|
||||||
|
"markdownDescription": "Denies the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-cancel",
|
||||||
|
"markdownDescription": "Denies the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-check-permissions",
|
||||||
|
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-create-channel",
|
||||||
|
"markdownDescription": "Denies the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-delete-channel",
|
||||||
|
"markdownDescription": "Denies the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-active",
|
||||||
|
"markdownDescription": "Denies the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-pending",
|
||||||
|
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-is-permission-granted",
|
||||||
|
"markdownDescription": "Denies the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-list-channels",
|
||||||
|
"markdownDescription": "Denies the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-notify",
|
||||||
|
"markdownDescription": "Denies the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-permission-state",
|
||||||
|
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-action-types",
|
||||||
|
"markdownDescription": "Denies the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-listener",
|
||||||
|
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-remove-active",
|
||||||
|
"markdownDescription": "Denies the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-request-permission",
|
||||||
|
"markdownDescription": "Denies the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-show",
|
||||||
|
"markdownDescription": "Denies the show command without any pre-configured scope."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2143,6 +2143,204 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "core:window:deny-unminimize",
|
"const": "core:window:deny-unminimize",
|
||||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:default",
|
||||||
|
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-batch",
|
||||||
|
"markdownDescription": "Enables the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-cancel",
|
||||||
|
"markdownDescription": "Enables the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-check-permissions",
|
||||||
|
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-create-channel",
|
||||||
|
"markdownDescription": "Enables the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-delete-channel",
|
||||||
|
"markdownDescription": "Enables the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-active",
|
||||||
|
"markdownDescription": "Enables the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-pending",
|
||||||
|
"markdownDescription": "Enables the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-is-permission-granted",
|
||||||
|
"markdownDescription": "Enables the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-list-channels",
|
||||||
|
"markdownDescription": "Enables the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-notify",
|
||||||
|
"markdownDescription": "Enables the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-permission-state",
|
||||||
|
"markdownDescription": "Enables the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-action-types",
|
||||||
|
"markdownDescription": "Enables the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-listener",
|
||||||
|
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-remove-active",
|
||||||
|
"markdownDescription": "Enables the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-request-permission",
|
||||||
|
"markdownDescription": "Enables the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-show",
|
||||||
|
"markdownDescription": "Enables the show command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-batch",
|
||||||
|
"markdownDescription": "Denies the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-cancel",
|
||||||
|
"markdownDescription": "Denies the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-check-permissions",
|
||||||
|
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-create-channel",
|
||||||
|
"markdownDescription": "Denies the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-delete-channel",
|
||||||
|
"markdownDescription": "Denies the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-active",
|
||||||
|
"markdownDescription": "Denies the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-pending",
|
||||||
|
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-is-permission-granted",
|
||||||
|
"markdownDescription": "Denies the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-list-channels",
|
||||||
|
"markdownDescription": "Denies the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-notify",
|
||||||
|
"markdownDescription": "Denies the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-permission-state",
|
||||||
|
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-action-types",
|
||||||
|
"markdownDescription": "Denies the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-listener",
|
||||||
|
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-remove-active",
|
||||||
|
"markdownDescription": "Denies the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-request-permission",
|
||||||
|
"markdownDescription": "Denies the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-show",
|
||||||
|
"markdownDescription": "Denies the show command without any pre-configured scope."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2143,6 +2143,204 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "core:window:deny-unminimize",
|
"const": "core:window:deny-unminimize",
|
||||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:default",
|
||||||
|
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-batch",
|
||||||
|
"markdownDescription": "Enables the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-cancel",
|
||||||
|
"markdownDescription": "Enables the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-check-permissions",
|
||||||
|
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-create-channel",
|
||||||
|
"markdownDescription": "Enables the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-delete-channel",
|
||||||
|
"markdownDescription": "Enables the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-active",
|
||||||
|
"markdownDescription": "Enables the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-get-pending",
|
||||||
|
"markdownDescription": "Enables the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-is-permission-granted",
|
||||||
|
"markdownDescription": "Enables the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-list-channels",
|
||||||
|
"markdownDescription": "Enables the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-notify",
|
||||||
|
"markdownDescription": "Enables the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-permission-state",
|
||||||
|
"markdownDescription": "Enables the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-action-types",
|
||||||
|
"markdownDescription": "Enables the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-register-listener",
|
||||||
|
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-remove-active",
|
||||||
|
"markdownDescription": "Enables the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-request-permission",
|
||||||
|
"markdownDescription": "Enables the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:allow-show",
|
||||||
|
"markdownDescription": "Enables the show command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-batch",
|
||||||
|
"markdownDescription": "Denies the batch command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-cancel",
|
||||||
|
"markdownDescription": "Denies the cancel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-check-permissions",
|
||||||
|
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-create-channel",
|
||||||
|
"markdownDescription": "Denies the create_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-delete-channel",
|
||||||
|
"markdownDescription": "Denies the delete_channel command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-active",
|
||||||
|
"markdownDescription": "Denies the get_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-get-pending",
|
||||||
|
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the is_permission_granted command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-is-permission-granted",
|
||||||
|
"markdownDescription": "Denies the is_permission_granted command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-list-channels",
|
||||||
|
"markdownDescription": "Denies the list_channels command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the notify command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-notify",
|
||||||
|
"markdownDescription": "Denies the notify command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-permission-state",
|
||||||
|
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_action_types command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-action-types",
|
||||||
|
"markdownDescription": "Denies the register_action_types command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-register-listener",
|
||||||
|
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-remove-active",
|
||||||
|
"markdownDescription": "Denies the remove_active command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the request_permission command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-request-permission",
|
||||||
|
"markdownDescription": "Denies the request_permission command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "notification:deny-show",
|
||||||
|
"markdownDescription": "Denies the show command without any pre-configured scope."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1574,6 +1574,29 @@ async fn comment_on_post(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn edit_comment(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
post_id: String,
|
||||||
|
timestamp_ms: u64,
|
||||||
|
new_content: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let node = state.inner();
|
||||||
|
let pid = hex_to_postid(&post_id)?;
|
||||||
|
node.edit_comment(pid, timestamp_ms, new_content).await.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn delete_comment(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
post_id: String,
|
||||||
|
timestamp_ms: u64,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let node = state.inner();
|
||||||
|
let pid = hex_to_postid(&post_id)?;
|
||||||
|
node.delete_comment(pid, timestamp_ms).await.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_comments(
|
async fn get_comments(
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
|
|
@ -1703,6 +1726,7 @@ pub fn run() {
|
||||||
|
|
||||||
info!("Starting Tauri app");
|
info!("Starting Tauri app");
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_notification::init())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
// Desktop: store data next to the AppImage/executable so each copy
|
// Desktop: store data next to the AppImage/executable so each copy
|
||||||
// gets its own identity. Mobile: use the standard app data dir.
|
// gets its own identity. Mobile: use the standard app data dir.
|
||||||
|
|
@ -1818,6 +1842,8 @@ pub fn run() {
|
||||||
get_reactions,
|
get_reactions,
|
||||||
get_reaction_counts,
|
get_reaction_counts,
|
||||||
comment_on_post,
|
comment_on_post,
|
||||||
|
edit_comment,
|
||||||
|
delete_comment,
|
||||||
get_comments,
|
get_comments,
|
||||||
set_comment_policy,
|
set_comment_policy,
|
||||||
get_comment_policy,
|
get_comment_policy,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"productName": "itsgoin",
|
"productName": "itsgoin",
|
||||||
"version": "0.3.3",
|
"version": "0.3.4",
|
||||||
"identifier": "com.itsgoin.app",
|
"identifier": "com.itsgoin.app",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../../frontend",
|
"frontendDist": "../../frontend",
|
||||||
|
|
|
||||||
230
frontend/app.js
230
frontend/app.js
|
|
@ -357,16 +357,30 @@ function toast(msg) {
|
||||||
setTimeout(() => toastEl.classList.add('hidden'), 3000);
|
setTimeout(() => toastEl.classList.add('hidden'), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Notifications ---
|
// --- Notifications (Tauri plugin) ---
|
||||||
let _notifiedMessages = new Set();
|
let _notifiedMessages = new Set();
|
||||||
|
let _notifiedReacts = new Set();
|
||||||
|
let _notifiedComments = new Set();
|
||||||
|
let _notifiedPosts = new Set();
|
||||||
|
let _notifReady = false;
|
||||||
async function maybeNotify(title, body, tag) {
|
async function maybeNotify(title, body, tag) {
|
||||||
if (!('Notification' in window)) return;
|
try {
|
||||||
if (Notification.permission === 'default') {
|
if (window.__TAURI__?.notification) {
|
||||||
await Notification.requestPermission();
|
const { isPermissionGranted, requestPermission, sendNotification } = window.__TAURI__.notification;
|
||||||
}
|
let granted = await isPermissionGranted();
|
||||||
if (Notification.permission === 'granted') {
|
if (!granted) {
|
||||||
new Notification(title, { body, tag, silent: false });
|
const perm = await requestPermission();
|
||||||
}
|
granted = perm === 'granted';
|
||||||
|
}
|
||||||
|
if (granted) {
|
||||||
|
sendNotification({ title, body, channelId: 'default' });
|
||||||
|
}
|
||||||
|
} else if ('Notification' in window) {
|
||||||
|
// Fallback for browsers
|
||||||
|
if (Notification.permission === 'default') await Notification.requestPermission();
|
||||||
|
if (Notification.permission === 'granted') new Notification(title, { body, tag, silent: false });
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Popover helpers ---
|
// --- Popover helpers ---
|
||||||
|
|
@ -618,7 +632,48 @@ async function loadFeed(force) {
|
||||||
// Fingerprint: post IDs + reaction counts + comment counts
|
// Fingerprint: post IDs + reaction counts + comment counts
|
||||||
const fp = posts.map(p => `${p.id}:${(p.reactionCounts||[]).map(r=>r.emoji+r.count).join(',')}:${p.commentCount||0}`).join('|');
|
const fp = posts.map(p => `${p.id}:${(p.reactionCounts||[]).map(r=>r.emoji+r.count).join(',')}:${p.commentCount||0}`).join('|');
|
||||||
if (!force && fp === _feedFingerprint) return;
|
if (!force && fp === _feedFingerprint) return;
|
||||||
|
const oldFp = _feedFingerprint;
|
||||||
_feedFingerprint = fp;
|
_feedFingerprint = fp;
|
||||||
|
|
||||||
|
// Notify on new posts and engagement (skip first load)
|
||||||
|
if (_notifReady && oldFp) {
|
||||||
|
try {
|
||||||
|
const notifPosts = await invoke('get_setting', { key: 'notif_posts' }).catch(() => null) || 'off';
|
||||||
|
const notifReacts = await invoke('get_setting', { key: 'notif_reacts' }).catch(() => null) || 'on';
|
||||||
|
for (const p of posts) {
|
||||||
|
// New post notifications
|
||||||
|
if (!p.isMe && notifPosts !== 'off' && !_notifiedPosts.has(p.id)) {
|
||||||
|
_notifiedPosts.add(p.id);
|
||||||
|
if (_notifiedPosts.size > posts.length) { // skip initial bulk
|
||||||
|
maybeNotify(`New post from ${p.authorName || p.author.slice(0,8)}`, (p.content || '').slice(0, 80), `post-${p.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reaction notifications on our posts
|
||||||
|
if (p.isMe && notifReacts !== 'off' && p.reactionCounts) {
|
||||||
|
for (const r of p.reactionCounts) {
|
||||||
|
const key = `${p.id}-${r.emoji}-${r.count}`;
|
||||||
|
if (!_notifiedReacts.has(key)) {
|
||||||
|
_notifiedReacts.add(key);
|
||||||
|
if (_notifiedReacts.size > 1) {
|
||||||
|
maybeNotify(`${r.emoji} on your post`, `${r.count} ${r.emoji} reactions`, `react-${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Comment notifications on our posts
|
||||||
|
if (p.isMe && notifReacts !== 'off' && p.commentCount > 0) {
|
||||||
|
const key = `${p.id}-comments-${p.commentCount}`;
|
||||||
|
if (!_notifiedComments.has(key)) {
|
||||||
|
_notifiedComments.add(key);
|
||||||
|
if (_notifiedComments.size > 1) {
|
||||||
|
maybeNotify('New comment on your post', (p.content || '').slice(0, 40), `comment-${p.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
// Preserve expanded comment threads
|
// Preserve expanded comment threads
|
||||||
const expandedComments = new Set();
|
const expandedComments = new Set();
|
||||||
feedList.querySelectorAll('.comment-thread:not(.hidden)').forEach(el => {
|
feedList.querySelectorAll('.comment-thread:not(.hidden)').forEach(el => {
|
||||||
|
|
@ -650,7 +705,7 @@ async function loadFeed(force) {
|
||||||
async function loadMyPosts(force) {
|
async function loadMyPosts(force) {
|
||||||
try {
|
try {
|
||||||
const posts = await invoke('get_all_posts');
|
const posts = await invoke('get_all_posts');
|
||||||
const mine = posts.filter(p => p.isMe && p.visibility !== 'encrypted-for-me');
|
const mine = posts.filter(p => p.isMe && p.visibility !== 'encrypted-for-me' && !(p.recipients && p.recipients.length > 0));
|
||||||
const fp = mine.map(p => `${p.id}:${(p.reactionCounts||[]).map(r=>r.emoji+r.count).join(',')}:${p.commentCount||0}`).join('|');
|
const fp = mine.map(p => `${p.id}:${(p.reactionCounts||[]).map(r=>r.emoji+r.count).join(',')}:${p.commentCount||0}`).join('|');
|
||||||
if (!force && fp === _myPostsFingerprint) return;
|
if (!force && fp === _myPostsFingerprint) return;
|
||||||
_myPostsFingerprint = fp;
|
_myPostsFingerprint = fp;
|
||||||
|
|
@ -993,7 +1048,9 @@ async function loadFollows() {
|
||||||
const approvedSet = new Set(outbound.filter(r => r.status === 'approved').map(r => r.nodeId));
|
const approvedSet = new Set(outbound.filter(r => r.status === 'approved').map(r => r.nodeId));
|
||||||
const inboundApprovedSet = new Set(inbound.filter(r => r.status === 'approved').map(r => r.nodeId));
|
const inboundApprovedSet = new Set(inbound.filter(r => r.status === 'approved').map(r => r.nodeId));
|
||||||
|
|
||||||
if (follows.length === 0) {
|
// Filter out self before rendering
|
||||||
|
const others = follows.filter(f => f.nodeId !== myNodeId);
|
||||||
|
if (others.length === 0) {
|
||||||
followsList.innerHTML = `<div>${renderEmptyState('Not following anyone', 'Follow suggested peers or connect manually.')}</div>`;
|
followsList.innerHTML = `<div>${renderEmptyState('Not following anyone', 'Follow suggested peers or connect manually.')}</div>`;
|
||||||
} else {
|
} else {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
@ -1037,8 +1094,14 @@ async function loadFollows() {
|
||||||
</div>`;
|
</div>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const online = follows.filter(f => f.isOnline || (f.lastActivityMs > 0 && (now - f.lastActivityMs) < ONLINE_THRESHOLD));
|
// If isOnline field isn't available (old build), show all as online
|
||||||
const offline = follows.filter(f => !online.includes(f));
|
const hasOnlineField = others.some(f => f.isOnline !== undefined);
|
||||||
|
const online = hasOnlineField
|
||||||
|
? others.filter(f => f.isOnline || (f.lastActivityMs > 0 && (now - f.lastActivityMs) < ONLINE_THRESHOLD))
|
||||||
|
: others;
|
||||||
|
const offline = hasOnlineField
|
||||||
|
? others.filter(f => !online.includes(f))
|
||||||
|
: [];
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
if (online.length > 0) {
|
if (online.length > 0) {
|
||||||
|
|
@ -1046,11 +1109,36 @@ async function loadFollows() {
|
||||||
html += online.map(renderFollowCard).join('');
|
html += online.map(renderFollowCard).join('');
|
||||||
}
|
}
|
||||||
if (offline.length > 0) {
|
if (offline.length > 0) {
|
||||||
html += `<div class="follows-section-header">Following: Offline (${offline.length})</div>`;
|
html += `<div class="follows-section-header follows-offline-header" style="cursor:pointer">Following: Offline (${offline.length})</div>`;
|
||||||
html += offline.map(renderFollowCard).join('');
|
|
||||||
}
|
}
|
||||||
followsList.innerHTML = html;
|
followsList.innerHTML = html;
|
||||||
|
|
||||||
|
// Open offline follows in lightbox
|
||||||
|
if (offline.length > 0) {
|
||||||
|
followsList.querySelectorAll('.follows-offline-header').forEach(hdr => {
|
||||||
|
hdr.addEventListener('click', () => {
|
||||||
|
const existing = document.querySelector('.offline-lightbox');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'offline-lightbox';
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="offline-lightbox-content">
|
||||||
|
<div class="offline-lightbox-header">
|
||||||
|
<h3>Following: Offline (${offline.length})</h3>
|
||||||
|
<button class="offline-lightbox-close">x</button>
|
||||||
|
</div>
|
||||||
|
<div class="offline-lightbox-list">${offline.map(renderFollowCard).join('')}</div>
|
||||||
|
</div>`;
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
overlay.querySelector('.offline-lightbox-close').onclick = () => overlay.remove();
|
||||||
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||||
|
|
||||||
|
// Wire up buttons inside the lightbox
|
||||||
|
attachFollowHandlers(overlay);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Attach unfollow handlers
|
// Attach unfollow handlers
|
||||||
followsList.querySelectorAll('.unfollow-btn').forEach(btn => {
|
followsList.querySelectorAll('.unfollow-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', async () => {
|
btn.addEventListener('click', async () => {
|
||||||
|
|
@ -1924,7 +2012,8 @@ document.addEventListener('click', async (e) => {
|
||||||
// Comment toggle → expand/collapse thread
|
// Comment toggle → expand/collapse thread
|
||||||
if (e.target.classList.contains('comment-toggle-btn')) {
|
if (e.target.classList.contains('comment-toggle-btn')) {
|
||||||
const postId = e.target.dataset.postId;
|
const postId = e.target.dataset.postId;
|
||||||
const threadEl = document.getElementById('comments-' + postId);
|
const postEl = e.target.closest('.post');
|
||||||
|
const threadEl = postEl ? postEl.querySelector('.comment-thread') : document.getElementById('comments-' + postId);
|
||||||
if (!threadEl) return;
|
if (!threadEl) return;
|
||||||
if (threadEl.classList.contains('hidden')) {
|
if (threadEl.classList.contains('hidden')) {
|
||||||
threadEl.classList.remove('hidden');
|
threadEl.classList.remove('hidden');
|
||||||
|
|
@ -1945,7 +2034,8 @@ document.addEventListener('click', async (e) => {
|
||||||
try {
|
try {
|
||||||
await invoke('comment_on_post', { postId, content });
|
await invoke('comment_on_post', { postId, content });
|
||||||
input.value = '';
|
input.value = '';
|
||||||
const threadEl = document.getElementById('comments-' + postId);
|
const postEl = e.target.closest('.post');
|
||||||
|
const threadEl = postEl ? postEl.querySelector('.comment-thread') : document.getElementById('comments-' + postId);
|
||||||
if (threadEl) await loadCommentThread(postId, threadEl);
|
if (threadEl) await loadCommentThread(postId, threadEl);
|
||||||
refreshPostEngagement(postId);
|
refreshPostEngagement(postId);
|
||||||
} catch (err) { toast('Error: ' + err); }
|
} catch (err) { toast('Error: ' + err); }
|
||||||
|
|
@ -1953,6 +2043,56 @@ document.addEventListener('click', async (e) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Edit comment
|
||||||
|
if (e.target.classList.contains('comment-edit-btn')) {
|
||||||
|
const postId = e.target.dataset.postId;
|
||||||
|
const ts = parseInt(e.target.dataset.ts);
|
||||||
|
const bubble = e.target.closest('.comment-bubble');
|
||||||
|
const textEl = bubble.querySelector('.comment-text');
|
||||||
|
const oldContent = textEl.textContent;
|
||||||
|
textEl.innerHTML = `<input class="comment-edit-input" value="${escapeHtml(oldContent)}" style="width:100%;background:#1a1a2e;color:#e0e0e0;border:1px solid #444;border-radius:3px;padding:0.2rem;font-size:0.8rem" />`;
|
||||||
|
const input = textEl.querySelector('.comment-edit-input');
|
||||||
|
input.focus();
|
||||||
|
input.addEventListener('keydown', async (ev) => {
|
||||||
|
if (ev.key === 'Enter') {
|
||||||
|
const newContent = input.value.trim();
|
||||||
|
if (newContent && newContent !== oldContent) {
|
||||||
|
try {
|
||||||
|
await invoke('edit_comment', { postId, timestampMs: ts, newContent });
|
||||||
|
const postEl = bubble.closest('.post');
|
||||||
|
const threadEl = postEl ? postEl.querySelector('.comment-thread') : null;
|
||||||
|
if (threadEl) await loadCommentThread(postId, threadEl);
|
||||||
|
toast('Comment edited');
|
||||||
|
} catch (err) { toast('Error: ' + err); }
|
||||||
|
} else {
|
||||||
|
textEl.textContent = oldContent;
|
||||||
|
}
|
||||||
|
} else if (ev.key === 'Escape') {
|
||||||
|
textEl.textContent = oldContent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
input.addEventListener('blur', () => {
|
||||||
|
if (textEl.querySelector('.comment-edit-input')) textEl.textContent = oldContent;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete comment
|
||||||
|
if (e.target.classList.contains('comment-delete-btn')) {
|
||||||
|
const postId = e.target.dataset.postId;
|
||||||
|
const ts = parseInt(e.target.dataset.ts);
|
||||||
|
if (!confirm('Delete this comment?')) return;
|
||||||
|
try {
|
||||||
|
await invoke('delete_comment', { postId, timestampMs: ts });
|
||||||
|
const postEl = e.target.closest('.post');
|
||||||
|
const threadEl = postEl ? postEl.querySelector('.comment-thread') : null;
|
||||||
|
if (threadEl) await loadCommentThread(postId, threadEl);
|
||||||
|
refreshPostEngagement(postId);
|
||||||
|
toast('Comment deleted');
|
||||||
|
} catch (err) { toast('Error: ' + err); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Close emoji picker on outside click
|
// Close emoji picker on outside click
|
||||||
closeEmojiPicker();
|
closeEmojiPicker();
|
||||||
});
|
});
|
||||||
|
|
@ -1968,10 +2108,15 @@ async function loadCommentThread(postId, container) {
|
||||||
for (const c of comments) {
|
for (const c of comments) {
|
||||||
const name = c.authorName || c.author.substring(0, 12);
|
const name = c.authorName || c.author.substring(0, 12);
|
||||||
const time = relativeTime(c.timestampMs);
|
const time = relativeTime(c.timestampMs);
|
||||||
html += `<div class="comment-bubble">
|
const isMyComment = c.author === myNodeId;
|
||||||
|
const editDeleteBtns = isMyComment
|
||||||
|
? `<button class="comment-edit-btn" data-post-id="${c.postId}" data-ts="${c.timestampMs}" title="Edit">edit</button>
|
||||||
|
<button class="comment-delete-btn" data-post-id="${c.postId}" data-ts="${c.timestampMs}" title="Delete">del</button>`
|
||||||
|
: '';
|
||||||
|
html += `<div class="comment-bubble" data-post-id="${c.postId}" data-ts="${c.timestampMs}">
|
||||||
<span class="comment-author">${escapeHtml(name)}</span>
|
<span class="comment-author">${escapeHtml(name)}</span>
|
||||||
<span class="comment-text">${escapeHtml(c.content)}</span>
|
<span class="comment-text">${escapeHtml(c.content)}</span>
|
||||||
<span class="comment-time">${time}</span>
|
<span class="comment-time">${time} ${editDeleteBtns}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
html += `<div class="comment-compose">
|
html += `<div class="comment-compose">
|
||||||
|
|
@ -2593,38 +2738,36 @@ $('#notifications-btn').addEventListener('click', async () => {
|
||||||
// Load current settings
|
// Load current settings
|
||||||
const msgVal = await invoke('get_setting', { key: 'notif_messages' }).catch(() => null) || 'on';
|
const msgVal = await invoke('get_setting', { key: 'notif_messages' }).catch(() => null) || 'on';
|
||||||
const postVal = await invoke('get_setting', { key: 'notif_posts' }).catch(() => null) || 'off';
|
const postVal = await invoke('get_setting', { key: 'notif_posts' }).catch(() => null) || 'off';
|
||||||
|
const reactVal = await invoke('get_setting', { key: 'notif_reacts' }).catch(() => null) || 'on';
|
||||||
const nearbyVal = await invoke('get_setting', { key: 'notif_nearby' }).catch(() => null) || 'on';
|
const nearbyVal = await invoke('get_setting', { key: 'notif_nearby' }).catch(() => null) || 'on';
|
||||||
|
|
||||||
|
function btnGroup(label, key, value, options) {
|
||||||
|
const btns = options.map(([v, text]) =>
|
||||||
|
`<button class="notif-opt${v === value ? ' active' : ''}" data-key="${key}" data-value="${v}">${text}</button>`
|
||||||
|
).join('');
|
||||||
|
return `<div class="notif-row"><span class="notif-label">${label}</span><div class="notif-opts">${btns}</div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
<label for="notif-messages">Messages</label>
|
${btnGroup('Messages', 'notif_messages', msgVal, [['off','Off'],['on','On'],['preview','Preview']])}
|
||||||
<select id="notif-messages">
|
${btnGroup('Posts', 'notif_posts', postVal, [['off','Off'],['follows','Follows'],['recommended','Recommended'],['popular','Popular']])}
|
||||||
<option value="off"${msgVal === 'off' ? ' selected' : ''}>Off</option>
|
${btnGroup('Reactions & Comments', 'notif_reacts', reactVal, [['off','Off'],['on','On']])}
|
||||||
<option value="on"${msgVal === 'on' ? ' selected' : ''}>On (no preview)</option>
|
${btnGroup('Nearby Users', 'notif_nearby', nearbyVal, [['off','Off'],['on','On']])}
|
||||||
<option value="preview"${msgVal === 'preview' ? ' selected' : ''}>On (with preview)</option>
|
<p class="empty-hint" style="margin-top:0.75rem">Changes are saved automatically.</p>`;
|
||||||
</select>
|
|
||||||
<label for="notif-posts">Posts</label>
|
|
||||||
<select id="notif-posts">
|
|
||||||
<option value="off"${postVal === 'off' ? ' selected' : ''}>Off</option>
|
|
||||||
<option value="follows"${postVal === 'follows' ? ' selected' : ''}>From follows & audience</option>
|
|
||||||
<option value="recommended"${postVal === 'recommended' ? ' selected' : ''}>Recommended by follows</option>
|
|
||||||
<option value="popular"${postVal === 'popular' ? ' selected' : ''}>Popular (100+)</option>
|
|
||||||
</select>
|
|
||||||
<label for="notif-nearby">Nearby Users</label>
|
|
||||||
<select id="notif-nearby">
|
|
||||||
<option value="off"${nearbyVal === 'off' ? ' selected' : ''}>Off</option>
|
|
||||||
<option value="on"${nearbyVal === 'on' ? ' selected' : ''}>On</option>
|
|
||||||
</select>
|
|
||||||
<p class="empty-hint" style="margin-top:0.5rem">Changes are saved automatically.</p>`;
|
|
||||||
|
|
||||||
openPopover('Notifications', html, {
|
openPopover('Notifications', html, {
|
||||||
onOpen() {
|
onOpen() {
|
||||||
for (const id of ['notif-messages', 'notif-posts', 'notif-nearby']) {
|
document.querySelectorAll('.notif-opt').forEach(btn => {
|
||||||
$(`#${id}`).addEventListener('change', async (e) => {
|
btn.addEventListener('click', async () => {
|
||||||
const key = id.replace('-', '_');
|
const key = btn.dataset.key;
|
||||||
await invoke('set_setting', { key, value: e.target.value });
|
const value = btn.dataset.value;
|
||||||
|
// Update active state
|
||||||
|
btn.closest('.notif-opts').querySelectorAll('.notif-opt').forEach(b => b.classList.remove('active'));
|
||||||
|
btn.classList.add('active');
|
||||||
|
await invoke('set_setting', { key, value });
|
||||||
toast('Setting saved');
|
toast('Setting saved');
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -2665,6 +2808,9 @@ async function init() {
|
||||||
const info = await loadNodeInfo();
|
const info = await loadNodeInfo();
|
||||||
await loadStats();
|
await loadStats();
|
||||||
await loadFeed();
|
await loadFeed();
|
||||||
|
await loadMessages();
|
||||||
|
// Now safe to fire notifications (initial data loaded, won't spam)
|
||||||
|
_notifReady = true;
|
||||||
|
|
||||||
// Show setup overlay if no profile exists
|
// Show setup overlay if no profile exists
|
||||||
if (info && !info.hasProfile) {
|
if (info && !info.hasProfile) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 0 auto; padding: 1rem; background: #1a1a2e; color: #e0e0e0; }
|
select option { color: #000 !important; }
|
||||||
|
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 0 auto; padding: 1rem; background: #1a1a2e; color: #e0e0e0; color-scheme: dark; }
|
||||||
header { border-bottom: 1px solid #333; padding-bottom: 0.5rem; margin-bottom: 1rem; }
|
header { border-bottom: 1px solid #333; padding-bottom: 0.5rem; margin-bottom: 1rem; }
|
||||||
header h1 { font-size: 1.4rem; color: #7fdbca; }
|
header h1 { font-size: 1.4rem; color: #7fdbca; }
|
||||||
#stats-bar { font-size: 0.8rem; color: #bbc; margin-top: 0.25rem; }
|
#stats-bar { font-size: 0.8rem; color: #bbc; margin-top: 0.25rem; }
|
||||||
|
|
@ -21,8 +22,15 @@ header h1 { font-size: 1.4rem; color: #7fdbca; }
|
||||||
.overlay-close { background: none; border: none; color: #889; font-size: 1.4rem; cursor: pointer; padding: 0 0.3rem; line-height: 1; transition: color 0.15s; }
|
.overlay-close { background: none; border: none; color: #889; font-size: 1.4rem; cursor: pointer; padding: 0 0.3rem; line-height: 1; transition: color 0.15s; }
|
||||||
.overlay-close:hover { color: #e74c3c; }
|
.overlay-close:hover { color: #e74c3c; }
|
||||||
.overlay-body { padding: 1rem; max-height: 70vh; overflow-y: auto; }
|
.overlay-body { padding: 1rem; max-height: 70vh; overflow-y: auto; }
|
||||||
.overlay-body select { width: 100%; background: #1a1a2e; color: #e0e0e0; border: 1px solid #444; border-radius: 4px; padding: 0.4rem; font-size: 0.85rem; font-family: inherit; margin-bottom: 0.75rem; }
|
.overlay-body select { width: 100%; background: #1a1a2e; border: 1px solid #444; border-radius: 4px; padding: 0.4rem; font-size: 0.85rem; font-family: inherit; margin-bottom: 0.75rem; }
|
||||||
.overlay-body select:focus { outline: none; border-color: #7fdbca; }
|
.overlay-body select:focus { outline: none; border-color: #7fdbca; }
|
||||||
|
.overlay-body select option { background: #fff; color: #000; }
|
||||||
|
.notif-row { margin-bottom: 0.6rem; }
|
||||||
|
.notif-label { display: block; font-size: 0.8rem; color: #bbc; margin-bottom: 0.3rem; }
|
||||||
|
.notif-opts { display: flex; gap: 0.3rem; flex-wrap: wrap; }
|
||||||
|
.notif-opt { background: #1a1a2e; color: #999; border: 1px solid #333; border-radius: 4px; padding: 0.25rem 0.6rem; font-size: 0.75rem; cursor: pointer; transition: all 0.15s; }
|
||||||
|
.notif-opt:hover { border-color: #555; color: #ccc; }
|
||||||
|
.notif-opt.active { background: #3b82f6; color: #fff; border-color: #3b82f6; }
|
||||||
.overlay-body label { display: block; font-size: 0.8rem; color: #bbc; margin-bottom: 0.25rem; }
|
.overlay-body label { display: block; font-size: 0.8rem; color: #bbc; margin-bottom: 0.25rem; }
|
||||||
|
|
||||||
/* Clickable activity peer IDs */
|
/* Clickable activity peer IDs */
|
||||||
|
|
@ -189,9 +197,9 @@ header h1 { font-size: 1.4rem; color: #7fdbca; }
|
||||||
|
|
||||||
/* Visibility selector */
|
/* Visibility selector */
|
||||||
#visibility-row { display: flex; gap: 0.4rem; margin-top: 0.35rem; align-items: center; }
|
#visibility-row { display: flex; gap: 0.4rem; margin-top: 0.35rem; align-items: center; }
|
||||||
#visibility-select, #circle-select { background: #1a1a2e; color: #fff; border: 1px solid #444; border-radius: 3px; padding: 0.2rem 0.4rem; font-size: 0.75rem; font-family: inherit; -webkit-appearance: none; appearance: none; color-scheme: dark; }
|
#visibility-select, #circle-select { background: #1a1a2e; border: 1px solid #444; border-radius: 3px; padding: 0.2rem 0.4rem; font-size: 0.75rem; font-family: inherit; -webkit-appearance: none; appearance: none; }
|
||||||
#visibility-select:focus, #circle-select:focus { outline: none; border-color: #7fdbca; }
|
#visibility-select:focus, #circle-select:focus { outline: none; border-color: #7fdbca; }
|
||||||
#visibility-select option, #circle-select option { background: #1a1a2e; color: #fff; }
|
#visibility-select option, #circle-select option { background: #fff; color: #000; }
|
||||||
|
|
||||||
/* Visibility badges on posts */
|
/* Visibility badges on posts */
|
||||||
.vis-badge { font-size: 0.6rem; padding: 0.1rem 0.35rem; border-radius: 3px; margin-left: 0.4rem; font-family: system-ui, sans-serif; vertical-align: middle; }
|
.vis-badge { font-size: 0.6rem; padding: 0.1rem 0.35rem; border-radius: 3px; margin-left: 0.4rem; font-family: system-ui, sans-serif; vertical-align: middle; }
|
||||||
|
|
@ -237,7 +245,8 @@ header h1 { font-size: 1.4rem; color: #7fdbca; }
|
||||||
.circle-members { font-size: 0.75rem; font-family: monospace; color: #bbc; display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
.circle-members { font-size: 0.75rem; font-family: monospace; color: #bbc; display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
||||||
.circle-member { background: #1e2040; padding: 0.2rem 0.5rem; border-radius: 3px; display: inline-flex; align-items: center; gap: 0.35rem; }
|
.circle-member { background: #1e2040; padding: 0.2rem 0.5rem; border-radius: 3px; display: inline-flex; align-items: center; gap: 0.35rem; }
|
||||||
.circle-add-row { display: flex; gap: 0.3rem; margin-top: 0.4rem; }
|
.circle-add-row { display: flex; gap: 0.3rem; margin-top: 0.4rem; }
|
||||||
.add-member-select { flex: 1; background: #1a1a2e; color: #e0e0e0; border: 1px solid #444; border-radius: 3px; padding: 0.2rem; font-size: 0.75rem; }
|
.add-member-select { flex: 1; background: #1a1a2e; border: 1px solid #444; border-radius: 3px; padding: 0.2rem; font-size: 0.75rem; }
|
||||||
|
.add-member-select option { background: #fff; color: #000; }
|
||||||
.empty-hint { color: #667; font-size: 0.8rem; font-style: italic; }
|
.empty-hint { color: #667; font-size: 0.8rem; font-style: italic; }
|
||||||
.hidden { display: none !important; }
|
.hidden { display: none !important; }
|
||||||
|
|
||||||
|
|
@ -246,14 +255,16 @@ header h1 { font-size: 1.4rem; color: #7fdbca; }
|
||||||
.anchor-item { display: flex; align-items: center; gap: 0.35rem; padding: 0.3rem 0; border-bottom: 1px solid #1e2040; }
|
.anchor-item { display: flex; align-items: center; gap: 0.35rem; padding: 0.3rem 0; border-bottom: 1px solid #1e2040; }
|
||||||
.anchor-item:last-child { border-bottom: none; }
|
.anchor-item:last-child { border-bottom: none; }
|
||||||
.anchor-item .peer-label { flex: 1; display: flex; align-items: center; gap: 0.35rem; }
|
.anchor-item .peer-label { flex: 1; display: flex; align-items: center; gap: 0.35rem; }
|
||||||
#anchor-add-select { flex: 1; background: #1a1a2e; color: #e0e0e0; border: 1px solid #444; border-radius: 3px; padding: 0.2rem; font-size: 0.75rem; }
|
#anchor-add-select { flex: 1; background: #1a1a2e; border: 1px solid #444; border-radius: 3px; padding: 0.2rem; font-size: 0.75rem; }
|
||||||
|
#anchor-add-select option { background: #fff; color: #000; }
|
||||||
|
|
||||||
/* DM compose */
|
/* DM compose */
|
||||||
#dm-compose textarea { width: 100%; padding: 0.5rem; background: #1a1a2e; color: #e0e0e0; border: 1px solid #333; border-radius: 4px; resize: none; font-family: inherit; font-size: 0.9rem; min-height: 60px; line-height: 1.5; transition: border-color 0.15s; margin-top: 0.5rem; }
|
#dm-compose textarea { width: 100%; padding: 0.5rem; background: #1a1a2e; color: #e0e0e0; border: 1px solid #333; border-radius: 4px; resize: none; font-family: inherit; font-size: 0.9rem; min-height: 60px; line-height: 1.5; transition: border-color 0.15s; margin-top: 0.5rem; }
|
||||||
#dm-compose textarea:focus { outline: none; border-color: #7fdbca; }
|
#dm-compose textarea:focus { outline: none; border-color: #7fdbca; }
|
||||||
#dm-compose textarea::placeholder { color: #778; }
|
#dm-compose textarea::placeholder { color: #778; }
|
||||||
.dm-compose-row { margin-bottom: 0; }
|
.dm-compose-row { margin-bottom: 0; }
|
||||||
#dm-recipient-select { width: 100%; background: #1a1a2e; color: #e0e0e0; border: 1px solid #444; border-radius: 4px; padding: 0.4rem; font-size: 0.85rem; font-family: inherit; }
|
#dm-recipient-select { width: 100%; background: #1a1a2e; border: 1px solid #444; border-radius: 4px; padding: 0.4rem; font-size: 0.85rem; font-family: inherit; }
|
||||||
|
#dm-recipient-select option { background: #fff; color: #000; }
|
||||||
#dm-recipient-select:focus { outline: none; border-color: #7fdbca; }
|
#dm-recipient-select:focus { outline: none; border-color: #7fdbca; }
|
||||||
.dm-compose-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #2a2a40; }
|
.dm-compose-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #2a2a40; }
|
||||||
|
|
||||||
|
|
@ -294,6 +305,14 @@ header h1 { font-size: 1.4rem; color: #7fdbca; }
|
||||||
.last-seen { color: #666; }
|
.last-seen { color: #666; }
|
||||||
.follows-section-header { font-size: 0.8rem; font-weight: 600; color: #888; padding: 0.5rem 0 0.25rem; border-bottom: 1px solid #2a2a40; margin-bottom: 0.3rem; margin-top: 0.5rem; }
|
.follows-section-header { font-size: 0.8rem; font-weight: 600; color: #888; padding: 0.5rem 0 0.25rem; border-bottom: 1px solid #2a2a40; margin-bottom: 0.3rem; margin-top: 0.5rem; }
|
||||||
.follows-section-header:first-child { margin-top: 0; }
|
.follows-section-header:first-child { margin-top: 0; }
|
||||||
|
.follows-offline-header { cursor: pointer; color: #5b8def !important; }
|
||||||
|
.follows-offline-header:hover { color: #7fdbca !important; }
|
||||||
|
.offline-lightbox { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 999999; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; }
|
||||||
|
.offline-lightbox-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 1rem; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; }
|
||||||
|
.offline-lightbox-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; }
|
||||||
|
.offline-lightbox-header h3 { color: #e0e0e0; font-size: 1rem; margin: 0; }
|
||||||
|
.offline-lightbox-close { background: none; border: none; color: #889; font-size: 1.4rem; cursor: pointer; }
|
||||||
|
.offline-lightbox-close:hover { color: #e74c3c; }
|
||||||
.peer-card-actions { display: flex; gap: 0.3rem; margin-top: 0.35rem; }
|
.peer-card-actions { display: flex; gap: 0.3rem; margin-top: 0.35rem; }
|
||||||
.peer-card-highlight { border-color: #7fdbca; animation: peerHighlight 2s ease-out; }
|
.peer-card-highlight { border-color: #7fdbca; animation: peerHighlight 2s ease-out; }
|
||||||
@keyframes peerHighlight { 0% { border-color: #7fdbca; box-shadow: 0 0 8px rgba(127, 219, 202, 0.3); } 100% { border-color: #2a2a40; box-shadow: none; } }
|
@keyframes peerHighlight { 0% { border-color: #7fdbca; box-shadow: 0 0 8px rgba(127, 219, 202, 0.3); } 100% { border-color: #2a2a40; box-shadow: none; } }
|
||||||
|
|
@ -390,7 +409,10 @@ header h1 { font-size: 1.4rem; color: #7fdbca; }
|
||||||
.comment-bubble { display: flex; gap: 0.35rem; align-items: baseline; padding: 0.2rem 0; font-size: 0.82rem; }
|
.comment-bubble { display: flex; gap: 0.35rem; align-items: baseline; padding: 0.2rem 0; font-size: 0.82rem; }
|
||||||
.comment-author { color: #7fdbca; font-weight: 600; white-space: nowrap; font-size: 0.78rem; }
|
.comment-author { color: #7fdbca; font-weight: 600; white-space: nowrap; font-size: 0.78rem; }
|
||||||
.comment-text { color: #ddd; }
|
.comment-text { color: #ddd; }
|
||||||
.comment-time { color: #777; font-size: 0.7rem; white-space: nowrap; }
|
.comment-time { color: #777; font-size: 0.7rem; white-space: nowrap; display: flex; align-items: center; gap: 0.3rem; }
|
||||||
|
.comment-edit-btn, .comment-delete-btn { background: none; border: none; color: #556; font-size: 0.65rem; cursor: pointer; padding: 0; }
|
||||||
|
.comment-edit-btn:hover { color: #5b8def; }
|
||||||
|
.comment-delete-btn:hover { color: #e74c3c; }
|
||||||
.comment-compose { display: flex; gap: 0.35rem; margin-top: 0.4rem; align-items: center; }
|
.comment-compose { display: flex; gap: 0.35rem; margin-top: 0.4rem; align-items: center; }
|
||||||
.comment-input { flex: 1; background: #1a1a2e; border: 1px solid #3a3a5a; border-radius: 0.35rem; padding: 0.3rem 0.5rem; color: #e0e0e0; font-size: 0.82rem; }
|
.comment-input { flex: 1; background: #1a1a2e; border: 1px solid #3a3a5a; border-radius: 0.35rem; padding: 0.3rem 0.5rem; color: #e0e0e0; font-size: 0.82rem; }
|
||||||
.comment-input:focus { border-color: #7fdbca; outline: none; }
|
.comment-input:focus { border-color: #7fdbca; outline: none; }
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,16 @@
|
||||||
<section>
|
<section>
|
||||||
<h1 style="font-size: 2rem; font-weight: 800; letter-spacing: -0.03em; margin-bottom: 0.25rem;">Download ItsGoin</h1>
|
<h1 style="font-size: 2rem; font-weight: 800; letter-spacing: -0.03em; margin-bottom: 0.25rem;">Download ItsGoin</h1>
|
||||||
<p>Available for Android and Linux. Free and open source.</p>
|
<p>Available for Android and Linux. Free and open source.</p>
|
||||||
<p style="color: var(--text-muted); font-size: 0.85rem;">Version 0.3.3 — March 15, 2026</p>
|
<p style="color: var(--text-muted); font-size: 0.85rem;">Version 0.3.4 — March 15, 2026</p>
|
||||||
|
|
||||||
<div class="downloads">
|
<div class="downloads">
|
||||||
<a href="itsgoin-0.3.3.apk" class="download-btn btn-android">
|
<a href="itsgoin-0.3.4.apk" class="download-btn btn-android">
|
||||||
Android APK
|
Android APK
|
||||||
<span class="sub">v0.3.3</span>
|
<span class="sub">v0.3.4</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="itsgoin_0.3.3_amd64.AppImage" class="download-btn btn-linux">
|
<a href="itsgoin_0.3.4_amd64.AppImage" class="download-btn btn-linux">
|
||||||
Linux AppImage
|
Linux AppImage
|
||||||
<span class="sub">v0.3.3</span>
|
<span class="sub">v0.3.4</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
<h3 style="color: var(--accent);">Android</h3>
|
<h3 style="color: var(--accent);">Android</h3>
|
||||||
<ol class="steps">
|
<ol class="steps">
|
||||||
<li><strong>Download the APK</strong> — Tap the button above. Your browser may warn that this type of file can be harmful — tap <strong>Download anyway</strong>.</li>
|
<li><strong>Download the APK</strong> — Tap the button above. Your browser may warn that this type of file can be harmful — tap <strong>Download anyway</strong>.</li>
|
||||||
<li><strong>Open the file</strong> — When the download finishes, tap the notification or find <code>itsgoin-0.3.3.apk</code> in your Downloads folder and tap it.</li>
|
<li><strong>Open the file</strong> — When the download finishes, tap the notification or find <code>itsgoin-0.3.4.apk</code> in your Downloads folder and tap it.</li>
|
||||||
<li><strong>Allow installation</strong> — Android will ask you to allow installs from this source. Tap <strong>Settings</strong>, toggle <strong>"Allow from this source"</strong>, then go back and tap <strong>Install</strong>.</li>
|
<li><strong>Allow installation</strong> — Android will ask you to allow installs from this source. Tap <strong>Settings</strong>, toggle <strong>"Allow from this source"</strong>, then go back and tap <strong>Install</strong>.</li>
|
||||||
<li><strong>Launch the app</strong> — Once installed, tap <strong>Open</strong> or find ItsGoin in your app drawer.</li>
|
<li><strong>Launch the app</strong> — Once installed, tap <strong>Open</strong> or find ItsGoin in your app drawer.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
@ -58,8 +58,8 @@
|
||||||
<h3 style="color: var(--green);">Linux (AppImage)</h3>
|
<h3 style="color: var(--green);">Linux (AppImage)</h3>
|
||||||
<ol class="steps">
|
<ol class="steps">
|
||||||
<li><strong>Download the AppImage</strong> — Click the button above to download.</li>
|
<li><strong>Download the AppImage</strong> — Click the button above to download.</li>
|
||||||
<li><strong>Make it executable</strong> — Open a terminal and run:<br><code>chmod +x itsgoin_0.3.3_amd64.AppImage</code></li>
|
<li><strong>Make it executable</strong> — Open a terminal and run:<br><code>chmod +x itsgoin_0.3.4_amd64.AppImage</code></li>
|
||||||
<li><strong>Run it</strong> — Double-click the file, or from the terminal:<br><code>./itsgoin_0.3.3_amd64.AppImage</code></li>
|
<li><strong>Run it</strong> — Double-click the file, or from the terminal:<br><code>./itsgoin_0.3.4_amd64.AppImage</code></li>
|
||||||
</ol>
|
</ol>
|
||||||
<div class="note">
|
<div class="note">
|
||||||
<strong>Note:</strong> If it doesn't launch, you may need to install FUSE:<br><code>sudo apt install libfuse2</code> (Debian/Ubuntu) or <code>sudo dnf install fuse</code> (Fedora).
|
<strong>Note:</strong> If it doesn't launch, you may need to install FUSE:<br><code>sudo apt install libfuse2</code> (Debian/Ubuntu) or <code>sudo dnf install fuse</code> (Fedora).
|
||||||
|
|
@ -70,6 +70,17 @@
|
||||||
<section>
|
<section>
|
||||||
<h2>Changelog</h2>
|
<h2>Changelog</h2>
|
||||||
<div class="changelog">
|
<div class="changelog">
|
||||||
|
<div class="changelog-date">v0.3.4 — March 18, 2026</div>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Comment edit & delete</strong> — Edit or delete your own comments. Trust-based: post authors can also delete comments on their posts. Propagates via BlobHeaderDiff to all holders.</li>
|
||||||
|
<li><strong>Native notifications</strong> — System notifications via Tauri plugin (works on all platforms). Notifications for messages, new posts, reactions, and comments. Configurable in Settings with button-group toggles.</li>
|
||||||
|
<li><strong>Forward-compatible protocol</strong> — Unknown BlobHeaderDiffOp variants are silently ignored instead of crashing deserialization. Prevents old nodes from breaking when new features are added.</li>
|
||||||
|
<li><strong>Following: Online/Offline</strong> — Self removed from following list. Offline follows hidden behind lightbox popup. Shows expanded if no one is online.</li>
|
||||||
|
<li><strong>DM filter fix</strong> — Sent direct messages no longer appear in My Posts tab.</li>
|
||||||
|
<li><strong>Comment threading fix</strong> — Comments now work correctly in My Posts tab (duplicate element ID scoping fix).</li>
|
||||||
|
<li><strong>Dropdown text fix</strong> — Select dropdowns across the app now have legible text in WebKitGTK native popups.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="changelog-date">v0.3.3 — March 16, 2026</div>
|
<div class="changelog-date">v0.3.3 — March 16, 2026</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>IPv6 HTTP address fix</strong> — Nodes with public IPv6 now correctly advertise their real address for direct browser access, instead of <code>0.0.0.0</code>. Fixes share link video/image serving for IPv6-reachable nodes.</li>
|
<li><strong>IPv6 HTTP address fix</strong> — Nodes with public IPv6 now correctly advertise their real address for direct browser access, instead of <code>0.0.0.0</code>. Fixes share link video/image serving for IPv6-reachable nodes.</li>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue