Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a262217ab2 | ||
|
|
c5d06a2492 | ||
|
|
c93edde7d2 | ||
|
|
5103126001 | ||
|
|
b0c49c5141 | ||
|
|
2ed2cb3afd | ||
|
|
2bcda1f2ef | ||
|
|
ca20bbd298 | ||
|
|
c6da06cd4d | ||
|
|
50bf6c04c1 |
+18
-18
@@ -20,38 +20,38 @@
|
|||||||
"@radix-ui/react-switch": "^1.1.1",
|
"@radix-ui/react-switch": "^1.1.1",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "^1.1.3",
|
"@radix-ui/react-tooltip": "^1.1.3",
|
||||||
"@tanstack/query-broadcast-client-experimental": "^5.59.16",
|
"@tanstack/query-broadcast-client-experimental": "^5.59.20",
|
||||||
"@tanstack/query-persist-client-core": "^5.59.16",
|
"@tanstack/query-persist-client-core": "^5.59.20",
|
||||||
"@tanstack/react-query": "^5.59.16",
|
"@tanstack/react-query": "^5.59.20",
|
||||||
"@tanstack/react-router": "^1.77.5",
|
"@tanstack/react-router": "^1.81.0",
|
||||||
"@tauri-apps/api": "^2.0.3",
|
"@tauri-apps/api": "^2.1.0",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
|
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.0.1",
|
"@tauri-apps/plugin-dialog": "^2.0.1",
|
||||||
"@tauri-apps/plugin-fs": "^2.0.1",
|
"@tauri-apps/plugin-fs": "^2.0.2",
|
||||||
"@tauri-apps/plugin-http": "^2.0.1",
|
"@tauri-apps/plugin-http": "^2.0.1",
|
||||||
"@tauri-apps/plugin-os": "^2.0.0",
|
"@tauri-apps/plugin-os": "^2.0.0",
|
||||||
"@tauri-apps/plugin-process": "^2.0.0",
|
"@tauri-apps/plugin-process": "^2.0.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.1",
|
"@tauri-apps/plugin-shell": "^2.0.1",
|
||||||
"@tauri-apps/plugin-store": "github:tauri-apps/tauri-plugin-store#a564510",
|
"@tauri-apps/plugin-store": "github:tauri-apps/tauri-plugin-store#a564510",
|
||||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||||
"@tauri-apps/plugin-upload": "^2.0.0",
|
"@tauri-apps/plugin-upload": "^2.1.0",
|
||||||
"@tauri-apps/plugin-window-state": "^2.0.0",
|
"@tauri-apps/plugin-window-state": "^2.0.0",
|
||||||
"bitcoin-units": "^1.0.0",
|
"bitcoin-units": "^1.0.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"embla-carousel-react": "^8.3.0",
|
"embla-carousel-react": "^8.3.1",
|
||||||
"i18next": "^23.16.4",
|
"i18next": "^23.16.5",
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
"light-bolt11-decoder": "^3.2.0",
|
"light-bolt11-decoder": "^3.2.0",
|
||||||
"minidenticons": "^4.2.1",
|
"minidenticons": "^4.2.1",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.8",
|
||||||
"nostr-tools": "^2.9.4",
|
"nostr-tools": "^2.10.1",
|
||||||
"react": "19.0.0-rc-cae764ce-20241025",
|
"react": "19.0.0-rc-cae764ce-20241025",
|
||||||
"react-currency-input-field": "^3.8.0",
|
"react-currency-input-field": "^3.8.0",
|
||||||
"react-dom": "19.0.0-rc-cae764ce-20241025",
|
"react-dom": "19.0.0-rc-cae764ce-20241025",
|
||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.53.2",
|
||||||
"react-i18next": "^15.1.0",
|
"react-i18next": "^15.1.1",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"rich-textarea": "^0.26.3",
|
"rich-textarea": "^0.26.4",
|
||||||
"use-debounce": "^10.0.4",
|
"use-debounce": "^10.0.4",
|
||||||
"virtua": "^0.34.2"
|
"virtua": "^0.34.2"
|
||||||
},
|
},
|
||||||
@@ -59,9 +59,9 @@
|
|||||||
"@biomejs/biome": "^1.9.4",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@tanstack/router-devtools": "^1.77.5",
|
"@tanstack/router-devtools": "^1.81.0",
|
||||||
"@tanstack/router-plugin": "^1.76.4",
|
"@tanstack/router-plugin": "^1.79.0",
|
||||||
"@tauri-apps/cli": "^2.0.4",
|
"@tauri-apps/cli": "^2.1.0",
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"tailwindcss-content-visibility": "^1.0.0",
|
"tailwindcss-content-visibility": "^1.0.0",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"vite": "^5.4.10",
|
"vite": "^5.4.10",
|
||||||
"vite-tsconfig-paths": "^5.0.1"
|
"vite-tsconfig-paths": "5.0.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@types/react": "npm:types-react@rc",
|
"@types/react": "npm:types-react@rc",
|
||||||
|
|||||||
Generated
+300
-300
File diff suppressed because it is too large
Load Diff
Generated
+761
-767
File diff suppressed because it is too large
Load Diff
@@ -33,9 +33,6 @@ tauri-plugin-theme = "2.1.2"
|
|||||||
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
|
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
|
||||||
tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] }
|
tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] }
|
||||||
|
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
|
|
||||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
|
|
||||||
specta = "^2.0.0-rc.20"
|
specta = "^2.0.0-rc.20"
|
||||||
specta-typescript = "0.0.7"
|
specta-typescript = "0.0.7"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
@@ -49,10 +46,11 @@ regex = "1.10.4"
|
|||||||
keyring = { version = "3", features = ["apple-native", "windows-native"] }
|
keyring = { version = "3", features = ["apple-native", "windows-native"] }
|
||||||
keyring-search = { git = "https://github.com/reyamir/keyring-search" }
|
keyring-search = { git = "https://github.com/reyamir/keyring-search" }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
||||||
|
async-trait = "0.1.83"
|
||||||
|
webbrowser = "1.0.2"
|
||||||
|
|
||||||
[patch.'https://github.com/rust-nostr/nostr']
|
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
|
||||||
nostr-sdk = { git = "https://github.com/reyamir/nostr", branch = "feat/open-auth-url", features = ["lmdb", "webln", "all-nips"] }
|
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr-connect = { git = "https://github.com/reyamir/nostr", branch = "feat/open-auth-url" }
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
],
|
],
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Capability": {
|
"Capability": {
|
||||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
|
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"identifier",
|
"identifier",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
|
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/PermissionEntry"
|
"$ref": "#/definitions/PermissionEntry"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
],
|
],
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Capability": {
|
"Capability": {
|
||||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
|
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"identifier",
|
"identifier",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
|
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/PermissionEntry"
|
"$ref": "#/definitions/PermissionEntry"
|
||||||
|
|||||||
@@ -14,6 +14,17 @@ struct Account {
|
|||||||
nostr_connect: Option<String>,
|
nostr_connect: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct AuthHandler;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AuthUrlHandler for AuthHandler {
|
||||||
|
async fn on_auth_url(&self, auth_url: Url) -> Result<()> {
|
||||||
|
webbrowser::open(auth_url.as_str())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn get_accounts() -> Vec<String> {
|
pub fn get_accounts() -> Vec<String> {
|
||||||
@@ -94,19 +105,29 @@ pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result<Str
|
|||||||
let remote_npub = remote_user.to_bech32().map_err(|err| err.to_string())?;
|
let remote_npub = remote_user.to_bech32().map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
// Init nostr connect
|
// Init nostr connect
|
||||||
let nostr_connect = NostrConnect::new(bunker_uri, app_keys, Duration::from_secs(120), None)
|
let mut nostr_connect = NostrConnect::new(bunker_uri, app_keys, Duration::from_secs(120), None)
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
let bunker_uri = nostr_connect
|
// Handle auth url
|
||||||
|
nostr_connect.auth_url_handler(AuthHandler);
|
||||||
|
|
||||||
|
let keyring = Entry::new("Lume Safe Storage", &remote_npub).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let reuse_bunker = nostr_connect
|
||||||
.bunker_uri()
|
.bunker_uri()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
let keyring = Entry::new("Lume Safe Storage", &remote_npub).map_err(|err| err.to_string())?;
|
let mut reuse_uri = reuse_bunker.to_string();
|
||||||
|
|
||||||
|
if let Some(secret) = reuse_bunker.secret() {
|
||||||
|
let replace = format!("&secret={}", secret);
|
||||||
|
reuse_uri = reuse_uri.replace(replace.as_str(), "");
|
||||||
|
}
|
||||||
|
|
||||||
let account = Account {
|
let account = Account {
|
||||||
secret_key: app_secret,
|
secret_key: app_secret,
|
||||||
nostr_connect: Some(bunker_uri.to_string()),
|
nostr_connect: Some(reuse_uri),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save secret key to keyring
|
// Save secret key to keyring
|
||||||
@@ -217,7 +238,9 @@ pub async fn set_signer(
|
|||||||
let app_keys = Keys::from_str(&account.secret_key).map_err(|e| e.to_string())?;
|
let app_keys = Keys::from_str(&account.secret_key).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
match NostrConnect::new(uri, app_keys, Duration::from_secs(120), None) {
|
match NostrConnect::new(uri, app_keys, Duration::from_secs(120), None) {
|
||||||
Ok(signer) => {
|
Ok(mut signer) => {
|
||||||
|
// Handle auth url
|
||||||
|
signer.auth_url_handler(AuthHandler);
|
||||||
// Update signer
|
// Update signer
|
||||||
client.set_signer(signer).await;
|
client.set_signer(signer).await;
|
||||||
// Emit to front-end
|
// Emit to front-end
|
||||||
|
|||||||
@@ -71,11 +71,11 @@ pub async fn get_meta_from_event(content: String) -> Result<Meta, ()> {
|
|||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
|
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
|
||||||
|
|
||||||
|
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
|
.kind(Kind::Comment)
|
||||||
.event(event_id);
|
.custom_tag(SingleLetterTag::uppercase(Alphabet::E), [event_id]);
|
||||||
|
|
||||||
let mut events = Events::new(&[filter.clone()]);
|
let mut events = Events::new(&[filter.clone()]);
|
||||||
|
|
||||||
@@ -523,39 +523,31 @@ pub async fn reply(content: String, to: String, state: State<'_, Nostr>) -> Resu
|
|||||||
Err(e) => return Err(e.to_string()),
|
Err(e) => return Err(e.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect root event from reply
|
// Find root event from reply
|
||||||
let root_ids: Vec<&EventId> = reply_to
|
let root_tag = reply_to
|
||||||
.tags
|
.tags
|
||||||
.filter_standardized(TagKind::e())
|
.find(TagKind::SingleLetter(SingleLetterTag::uppercase(
|
||||||
.filter_map(|t| match t {
|
Alphabet::E,
|
||||||
TagStandard::Event {
|
)));
|
||||||
event_id, marker, ..
|
|
||||||
} => {
|
|
||||||
if let Some(mkr) = marker {
|
|
||||||
match mkr {
|
|
||||||
Marker::Root => Some(event_id),
|
|
||||||
Marker::Reply => Some(event_id),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Some(event_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Get root event if exist
|
// Get root event if exist
|
||||||
let root = match root_ids.first() {
|
let root = match root_tag {
|
||||||
Some(&id) => client
|
Some(tag) => match tag.content() {
|
||||||
|
Some(content) => {
|
||||||
|
let id = EventId::parse(content).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
client
|
||||||
.database()
|
.database()
|
||||||
.event_by_id(id)
|
.event_by_id(&id)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| err.to_string())?,
|
.map_err(|err| err.to_string())?
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let builder = EventBuilder::text_note_reply(content, &reply_to, root.as_ref(), None)
|
let builder = EventBuilder::comment(content, &reply_to, root.as_ref(), None)
|
||||||
.add_tags(tags)
|
.add_tags(tags)
|
||||||
.pow(DEFAULT_DIFFICULTY);
|
.pow(DEFAULT_DIFFICULTY);
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ pub mod account;
|
|||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod relay;
|
pub mod relay;
|
||||||
|
pub mod sync;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use tauri::State;
|
||||||
|
|
||||||
|
use crate::Nostr;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn sync_all(
|
||||||
|
state: State<'_, Nostr>,
|
||||||
|
reader: tauri::ipc::Channel<f64>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let client = &state.client;
|
||||||
|
|
||||||
|
// Create a filter for get all public keys
|
||||||
|
let filter = Filter::new().kinds(vec![
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::MuteList,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let events = client
|
||||||
|
.database()
|
||||||
|
.query(vec![filter])
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let public_keys: Vec<PublicKey> = events
|
||||||
|
.iter()
|
||||||
|
.flat_map(|ev| ev.tags.public_keys().copied())
|
||||||
|
.collect::<HashSet<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let (tx, mut rx) = SyncProgress::channel();
|
||||||
|
let opts = SyncOptions::default().progress(tx);
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
while rx.changed().await.is_ok() {
|
||||||
|
let progress = *rx.borrow_and_update();
|
||||||
|
|
||||||
|
if progress.total > 0 {
|
||||||
|
reader.send(progress.percentage() * 100.0).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for chunk in public_keys.chunks(200) {
|
||||||
|
let authors = chunk.to_owned();
|
||||||
|
let filter = Filter::new().authors(authors).kinds(vec![
|
||||||
|
Kind::Metadata,
|
||||||
|
Kind::ContactList,
|
||||||
|
Kind::FollowSet,
|
||||||
|
Kind::Interests,
|
||||||
|
Kind::InterestSet,
|
||||||
|
Kind::EventDeletion,
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::Comment,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let _ = client
|
||||||
|
.sync(filter, &opts)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -105,9 +105,6 @@ pub async fn create_column(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if let Ok(event_id) = EventId::parse(&id) {
|
} else if let Ok(event_id) = EventId::parse(&id) {
|
||||||
let is_thread = payload.url().to_string().contains("events");
|
|
||||||
|
|
||||||
if is_thread {
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = webview.state::<Nostr>();
|
let state = webview.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
@@ -115,8 +112,11 @@ pub async fn create_column(
|
|||||||
let subscription_id = SubscriptionId::new(webview.label());
|
let subscription_id = SubscriptionId::new(webview.label());
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.event(event_id)
|
.custom_tag(
|
||||||
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
|
SingleLetterTag::uppercase(Alphabet::E),
|
||||||
|
[event_id],
|
||||||
|
)
|
||||||
|
.kind(Kind::Comment)
|
||||||
.since(Timestamp::now());
|
.since(Timestamp::now());
|
||||||
|
|
||||||
if let Err(e) = client
|
if let Err(e) = client
|
||||||
@@ -129,7 +129,6 @@ pub async fn create_column(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
PageLoadEvent::Finished => {
|
PageLoadEvent::Finished => {
|
||||||
println!("{} finished loading", payload.url());
|
println!("{} finished loading", payload.url());
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-17
@@ -56,13 +56,6 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
// Get words
|
// Get words
|
||||||
let words: Vec<_> = content.split_whitespace().collect();
|
let words: Vec<_> = content.split_whitespace().collect();
|
||||||
|
|
||||||
// Get mentions
|
|
||||||
let mentions = words
|
|
||||||
.iter()
|
|
||||||
.filter(|&&word| ["nostr:", "@"].iter().any(|&el| word.starts_with(el)))
|
|
||||||
.map(|&s| s.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Get hashtags
|
// Get hashtags
|
||||||
let hashtags = words
|
let hashtags = words
|
||||||
.iter()
|
.iter()
|
||||||
@@ -70,6 +63,13 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
.map(|&s| s.to_string().replace("#", "").to_lowercase())
|
.map(|&s| s.to_string().replace("#", "").to_lowercase())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Get mentions
|
||||||
|
let mentions = words
|
||||||
|
.iter()
|
||||||
|
.filter(|&&word| ["nostr:", "@"].iter().any(|&el| word.starts_with(el)))
|
||||||
|
.map(|&s| s.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for mention in mentions {
|
for mention in mentions {
|
||||||
let entity = mention.replace("nostr:", "").replace('@', "");
|
let entity = mention.replace("nostr:", "").replace('@', "");
|
||||||
|
|
||||||
@@ -92,8 +92,11 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
}
|
}
|
||||||
if entity.starts_with("note") {
|
if entity.starts_with("note") {
|
||||||
if let Ok(event_id) = EventId::from_bech32(&entity) {
|
if let Ok(event_id) = EventId::from_bech32(&entity) {
|
||||||
let hex = event_id.to_hex();
|
let tag = Tag::from_standardized(TagStandard::Quote {
|
||||||
let tag = Tag::parse(&["e", &hex, "", "mention"]).unwrap();
|
event_id,
|
||||||
|
relay_url: None,
|
||||||
|
public_key: None,
|
||||||
|
});
|
||||||
tags.push(tag);
|
tags.push(tag);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
@@ -101,14 +104,12 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
}
|
}
|
||||||
if entity.starts_with("nevent") {
|
if entity.starts_with("nevent") {
|
||||||
if let Ok(event) = Nip19Event::from_bech32(&entity) {
|
if let Ok(event) = Nip19Event::from_bech32(&entity) {
|
||||||
let hex = event.event_id.to_hex();
|
let relay_url = event.relays.first().and_then(|i| Url::parse(i).ok());
|
||||||
let relay = event.clone().relays.into_iter().next().unwrap_or("".into());
|
let tag = Tag::from_standardized(TagStandard::Quote {
|
||||||
let tag = Tag::parse(&["e", &hex, &relay, "mention"]).unwrap();
|
event_id: event.event_id,
|
||||||
|
relay_url,
|
||||||
if let Some(author) = event.author {
|
public_key: event.author,
|
||||||
let tag = Tag::public_key(author);
|
});
|
||||||
tags.push(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
tags.push(tag);
|
tags.push(tag);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+12
-9
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
||||||
use commands::{account::*, event::*, metadata::*, relay::*, window::*};
|
use commands::{account::*, event::*, metadata::*, relay::*, sync::*, window::*};
|
||||||
use common::{get_all_accounts, parse_event};
|
use common::{get_all_accounts, parse_event};
|
||||||
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -76,6 +76,7 @@ fn main() {
|
|||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let builder = Builder::<tauri::Wry>::new().commands(collect_commands![
|
let builder = Builder::<tauri::Wry>::new().commands(collect_commands![
|
||||||
|
sync_all,
|
||||||
get_all_relays,
|
get_all_relays,
|
||||||
get_all_relay_lists,
|
get_all_relay_lists,
|
||||||
is_relay_connected,
|
is_relay_connected,
|
||||||
@@ -236,8 +237,8 @@ fn main() {
|
|||||||
// Config
|
// Config
|
||||||
let opts = Options::new()
|
let opts = Options::new()
|
||||||
.gossip(true)
|
.gossip(true)
|
||||||
.max_avg_latency(Duration::from_millis(500))
|
.max_avg_latency(Duration::from_secs(2))
|
||||||
.timeout(Duration::from_secs(5));
|
.timeout(Duration::from_secs(10));
|
||||||
|
|
||||||
// Setup nostr client
|
// Setup nostr client
|
||||||
let client = ClientBuilder::default()
|
let client = ClientBuilder::default()
|
||||||
@@ -365,6 +366,8 @@ fn main() {
|
|||||||
|
|
||||||
// Set interval
|
// Set interval
|
||||||
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(600));
|
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(600));
|
||||||
|
// Skip the first tick
|
||||||
|
interval.tick().await;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
@@ -475,7 +478,7 @@ fn main() {
|
|||||||
Kind::Repost,
|
Kind::Repost,
|
||||||
Kind::Reaction,
|
Kind::Reaction,
|
||||||
Kind::ZapReceipt,
|
Kind::ZapReceipt,
|
||||||
Kind::Custom(1111),
|
Kind::Comment,
|
||||||
])
|
])
|
||||||
.since(Timestamp::now());
|
.since(Timestamp::now());
|
||||||
|
|
||||||
@@ -532,7 +535,7 @@ fn main() {
|
|||||||
if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
|
if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
|
||||||
println!("Emit error: {}", e)
|
println!("Emit error: {}", e)
|
||||||
}
|
}
|
||||||
} else if event.kind == Kind::TextNote {
|
} else if event.kind == Kind::Comment {
|
||||||
let payload = RichEvent {
|
let payload = RichEvent {
|
||||||
raw: event.as_json(),
|
raw: event.as_json(),
|
||||||
parsed: if event.kind == Kind::TextNote {
|
parsed: if event.kind == Kind::TextNote {
|
||||||
@@ -544,7 +547,7 @@ fn main() {
|
|||||||
|
|
||||||
if let Err(e) = handle_clone.emit_to(
|
if let Err(e) = handle_clone.emit_to(
|
||||||
EventTarget::labeled(subscription_id.to_string()),
|
EventTarget::labeled(subscription_id.to_string()),
|
||||||
"event",
|
"comment",
|
||||||
payload,
|
payload,
|
||||||
) {
|
) {
|
||||||
println!("Emit error: {}", e)
|
println!("Emit error: {}", e)
|
||||||
@@ -603,7 +606,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
|
|||||||
if let Err(e) = handle
|
if let Err(e) = handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
.body("Mentioned you in a thread.")
|
.body("You're mentioned in a thread.")
|
||||||
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
||||||
.show()
|
.show()
|
||||||
{
|
{
|
||||||
@@ -614,7 +617,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
|
|||||||
if let Err(e) = handle
|
if let Err(e) = handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
.body("Reposted your note.")
|
.body("Your note has been reposted.")
|
||||||
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
||||||
.show()
|
.show()
|
||||||
{
|
{
|
||||||
@@ -625,7 +628,7 @@ fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppH
|
|||||||
if let Err(e) = handle
|
if let Err(e) = handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
.body("Zapped you.")
|
.body("You've received zap.")
|
||||||
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
|
||||||
.show()
|
.show()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "Lume",
|
"productName": "Lume",
|
||||||
"version": "24.11.5",
|
"version": "24.11.8",
|
||||||
"identifier": "nu.lume.Lume",
|
"identifier": "nu.lume.Lume",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
|
|||||||
@@ -5,6 +5,14 @@
|
|||||||
|
|
||||||
|
|
||||||
export const commands = {
|
export const commands = {
|
||||||
|
async syncAll(reader: TAURI_CHANNEL<number>) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("sync_all", { reader }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
async getAllRelays() : Promise<Result<string[], string>> {
|
async getAllRelays() : Promise<Result<string[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_all_relays") };
|
return { status: "ok", data: await TAURI_INVOKE("get_all_relays") };
|
||||||
@@ -554,6 +562,7 @@ export type Meta = { content: string; images: string[]; events: string[]; mentio
|
|||||||
export type NewWindow = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean; hidden_title: boolean; closable: boolean }
|
export type NewWindow = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean; hidden_title: boolean; closable: boolean }
|
||||||
export type RichEvent = { raw: string; parsed: Meta | null }
|
export type RichEvent = { raw: string; parsed: Meta | null }
|
||||||
export type Settings = { resize_service: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean }
|
export type Settings = { resize_service: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean }
|
||||||
|
export type TAURI_CHANNEL<TSend> = null
|
||||||
|
|
||||||
/** tauri-specta globals **/
|
/** tauri-specta globals **/
|
||||||
|
|
||||||
|
|||||||
+60
-35
@@ -1,12 +1,12 @@
|
|||||||
/* prettier-ignore-start */
|
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
// This file is auto-generated by TanStack Router
|
// This file was automatically generated by TanStack Router.
|
||||||
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
@@ -19,7 +19,6 @@ import { Route as AppIndexImport } from './routes/_app/index'
|
|||||||
import { Route as ZapIdImport } from './routes/zap.$id'
|
import { Route as ZapIdImport } from './routes/zap.$id'
|
||||||
import { Route as SettingsWalletImport } from './routes/settings/wallet'
|
import { Route as SettingsWalletImport } from './routes/settings/wallet'
|
||||||
import { Route as SettingsRelaysImport } from './routes/settings/relays'
|
import { Route as SettingsRelaysImport } from './routes/settings/relays'
|
||||||
import { Route as SettingsGeneralImport } from './routes/settings/general'
|
|
||||||
import { Route as ColumnsLayoutImport } from './routes/columns/_layout'
|
import { Route as ColumnsLayoutImport } from './routes/columns/_layout'
|
||||||
import { Route as IdSetProfileImport } from './routes/$id.set-profile'
|
import { Route as IdSetProfileImport } from './routes/$id.set-profile'
|
||||||
import { Route as IdSetInterestImport } from './routes/$id.set-interest'
|
import { Route as IdSetInterestImport } from './routes/$id.set-interest'
|
||||||
@@ -39,6 +38,8 @@ import { Route as ColumnsLayoutCreateNewsfeedF2fImport } from './routes/columns/
|
|||||||
const ColumnsImport = createFileRoute('/columns')()
|
const ColumnsImport = createFileRoute('/columns')()
|
||||||
const SettingsLazyImport = createFileRoute('/settings')()
|
const SettingsLazyImport = createFileRoute('/settings')()
|
||||||
const NewLazyImport = createFileRoute('/new')()
|
const NewLazyImport = createFileRoute('/new')()
|
||||||
|
const SettingsSyncLazyImport = createFileRoute('/settings/sync')()
|
||||||
|
const SettingsGeneralLazyImport = createFileRoute('/settings/general')()
|
||||||
const NewAccountWatchLazyImport = createFileRoute('/new-account/watch')()
|
const NewAccountWatchLazyImport = createFileRoute('/new-account/watch')()
|
||||||
const NewAccountImportLazyImport = createFileRoute('/new-account/import')()
|
const NewAccountImportLazyImport = createFileRoute('/new-account/import')()
|
||||||
const NewAccountConnectLazyImport = createFileRoute('/new-account/connect')()
|
const NewAccountConnectLazyImport = createFileRoute('/new-account/connect')()
|
||||||
@@ -118,6 +119,20 @@ const AppIndexRoute = AppIndexImport.update({
|
|||||||
getParentRoute: () => AppRoute,
|
getParentRoute: () => AppRoute,
|
||||||
} as any).lazy(() => import('./routes/_app/index.lazy').then((d) => d.Route))
|
} as any).lazy(() => import('./routes/_app/index.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
|
const SettingsSyncLazyRoute = SettingsSyncLazyImport.update({
|
||||||
|
id: '/sync',
|
||||||
|
path: '/sync',
|
||||||
|
getParentRoute: () => SettingsLazyRoute,
|
||||||
|
} as any).lazy(() => import('./routes/settings/sync.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
|
const SettingsGeneralLazyRoute = SettingsGeneralLazyImport.update({
|
||||||
|
id: '/general',
|
||||||
|
path: '/general',
|
||||||
|
getParentRoute: () => SettingsLazyRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/settings/general.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
const NewAccountWatchLazyRoute = NewAccountWatchLazyImport.update({
|
const NewAccountWatchLazyRoute = NewAccountWatchLazyImport.update({
|
||||||
id: '/new-account/watch',
|
id: '/new-account/watch',
|
||||||
path: '/new-account/watch',
|
path: '/new-account/watch',
|
||||||
@@ -164,14 +179,6 @@ const SettingsRelaysRoute = SettingsRelaysImport.update({
|
|||||||
import('./routes/settings/relays.lazy').then((d) => d.Route),
|
import('./routes/settings/relays.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
const SettingsGeneralRoute = SettingsGeneralImport.update({
|
|
||||||
id: '/general',
|
|
||||||
path: '/general',
|
|
||||||
getParentRoute: () => SettingsLazyRoute,
|
|
||||||
} as any).lazy(() =>
|
|
||||||
import('./routes/settings/general.lazy').then((d) => d.Route),
|
|
||||||
)
|
|
||||||
|
|
||||||
const ColumnsLayoutRoute = ColumnsLayoutImport.update({
|
const ColumnsLayoutRoute = ColumnsLayoutImport.update({
|
||||||
id: '/_layout',
|
id: '/_layout',
|
||||||
getParentRoute: () => ColumnsRoute,
|
getParentRoute: () => ColumnsRoute,
|
||||||
@@ -440,13 +447,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutImport
|
preLoaderRoute: typeof ColumnsLayoutImport
|
||||||
parentRoute: typeof ColumnsRoute
|
parentRoute: typeof ColumnsRoute
|
||||||
}
|
}
|
||||||
'/settings/general': {
|
|
||||||
id: '/settings/general'
|
|
||||||
path: '/general'
|
|
||||||
fullPath: '/settings/general'
|
|
||||||
preLoaderRoute: typeof SettingsGeneralImport
|
|
||||||
parentRoute: typeof SettingsLazyImport
|
|
||||||
}
|
|
||||||
'/settings/relays': {
|
'/settings/relays': {
|
||||||
id: '/settings/relays'
|
id: '/settings/relays'
|
||||||
path: '/relays'
|
path: '/relays'
|
||||||
@@ -489,6 +489,20 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof NewAccountWatchLazyImport
|
preLoaderRoute: typeof NewAccountWatchLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/settings/general': {
|
||||||
|
id: '/settings/general'
|
||||||
|
path: '/general'
|
||||||
|
fullPath: '/settings/general'
|
||||||
|
preLoaderRoute: typeof SettingsGeneralLazyImport
|
||||||
|
parentRoute: typeof SettingsLazyImport
|
||||||
|
}
|
||||||
|
'/settings/sync': {
|
||||||
|
id: '/settings/sync'
|
||||||
|
path: '/sync'
|
||||||
|
fullPath: '/settings/sync'
|
||||||
|
preLoaderRoute: typeof SettingsSyncLazyImport
|
||||||
|
parentRoute: typeof SettingsLazyImport
|
||||||
|
}
|
||||||
'/_app/': {
|
'/_app/': {
|
||||||
id: '/_app/'
|
id: '/_app/'
|
||||||
path: '/'
|
path: '/'
|
||||||
@@ -666,15 +680,17 @@ const AppRouteChildren: AppRouteChildren = {
|
|||||||
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
|
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
|
||||||
|
|
||||||
interface SettingsLazyRouteChildren {
|
interface SettingsLazyRouteChildren {
|
||||||
SettingsGeneralRoute: typeof SettingsGeneralRoute
|
|
||||||
SettingsRelaysRoute: typeof SettingsRelaysRoute
|
SettingsRelaysRoute: typeof SettingsRelaysRoute
|
||||||
SettingsWalletRoute: typeof SettingsWalletRoute
|
SettingsWalletRoute: typeof SettingsWalletRoute
|
||||||
|
SettingsGeneralLazyRoute: typeof SettingsGeneralLazyRoute
|
||||||
|
SettingsSyncLazyRoute: typeof SettingsSyncLazyRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsLazyRouteChildren: SettingsLazyRouteChildren = {
|
const SettingsLazyRouteChildren: SettingsLazyRouteChildren = {
|
||||||
SettingsGeneralRoute: SettingsGeneralRoute,
|
|
||||||
SettingsRelaysRoute: SettingsRelaysRoute,
|
SettingsRelaysRoute: SettingsRelaysRoute,
|
||||||
SettingsWalletRoute: SettingsWalletRoute,
|
SettingsWalletRoute: SettingsWalletRoute,
|
||||||
|
SettingsGeneralLazyRoute: SettingsGeneralLazyRoute,
|
||||||
|
SettingsSyncLazyRoute: SettingsSyncLazyRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsLazyRouteWithChildren = SettingsLazyRoute._addFileChildren(
|
const SettingsLazyRouteWithChildren = SettingsLazyRoute._addFileChildren(
|
||||||
@@ -768,13 +784,14 @@ export interface FileRoutesByFullPath {
|
|||||||
'/$id/set-interest': typeof IdSetInterestRoute
|
'/$id/set-interest': typeof IdSetInterestRoute
|
||||||
'/$id/set-profile': typeof IdSetProfileRoute
|
'/$id/set-profile': typeof IdSetProfileRoute
|
||||||
'/columns': typeof ColumnsLayoutRouteWithChildren
|
'/columns': typeof ColumnsLayoutRouteWithChildren
|
||||||
'/settings/general': typeof SettingsGeneralRoute
|
|
||||||
'/settings/relays': typeof SettingsRelaysRoute
|
'/settings/relays': typeof SettingsRelaysRoute
|
||||||
'/settings/wallet': typeof SettingsWalletRoute
|
'/settings/wallet': typeof SettingsWalletRoute
|
||||||
'/zap/$id': typeof ZapIdRoute
|
'/zap/$id': typeof ZapIdRoute
|
||||||
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
||||||
'/new-account/import': typeof NewAccountImportLazyRoute
|
'/new-account/import': typeof NewAccountImportLazyRoute
|
||||||
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
||||||
|
'/settings/general': typeof SettingsGeneralLazyRoute
|
||||||
|
'/settings/sync': typeof SettingsSyncLazyRoute
|
||||||
'/': typeof AppIndexRoute
|
'/': typeof AppIndexRoute
|
||||||
'/new-post': typeof NewPostIndexRoute
|
'/new-post': typeof NewPostIndexRoute
|
||||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
@@ -807,13 +824,14 @@ export interface FileRoutesByTo {
|
|||||||
'/$id/set-interest': typeof IdSetInterestRoute
|
'/$id/set-interest': typeof IdSetInterestRoute
|
||||||
'/$id/set-profile': typeof IdSetProfileRoute
|
'/$id/set-profile': typeof IdSetProfileRoute
|
||||||
'/columns': typeof ColumnsLayoutRouteWithChildren
|
'/columns': typeof ColumnsLayoutRouteWithChildren
|
||||||
'/settings/general': typeof SettingsGeneralRoute
|
|
||||||
'/settings/relays': typeof SettingsRelaysRoute
|
'/settings/relays': typeof SettingsRelaysRoute
|
||||||
'/settings/wallet': typeof SettingsWalletRoute
|
'/settings/wallet': typeof SettingsWalletRoute
|
||||||
'/zap/$id': typeof ZapIdRoute
|
'/zap/$id': typeof ZapIdRoute
|
||||||
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
||||||
'/new-account/import': typeof NewAccountImportLazyRoute
|
'/new-account/import': typeof NewAccountImportLazyRoute
|
||||||
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
||||||
|
'/settings/general': typeof SettingsGeneralLazyRoute
|
||||||
|
'/settings/sync': typeof SettingsSyncLazyRoute
|
||||||
'/': typeof AppIndexRoute
|
'/': typeof AppIndexRoute
|
||||||
'/new-post': typeof NewPostIndexRoute
|
'/new-post': typeof NewPostIndexRoute
|
||||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
@@ -849,13 +867,14 @@ export interface FileRoutesById {
|
|||||||
'/$id/set-profile': typeof IdSetProfileRoute
|
'/$id/set-profile': typeof IdSetProfileRoute
|
||||||
'/columns': typeof ColumnsRouteWithChildren
|
'/columns': typeof ColumnsRouteWithChildren
|
||||||
'/columns/_layout': typeof ColumnsLayoutRouteWithChildren
|
'/columns/_layout': typeof ColumnsLayoutRouteWithChildren
|
||||||
'/settings/general': typeof SettingsGeneralRoute
|
|
||||||
'/settings/relays': typeof SettingsRelaysRoute
|
'/settings/relays': typeof SettingsRelaysRoute
|
||||||
'/settings/wallet': typeof SettingsWalletRoute
|
'/settings/wallet': typeof SettingsWalletRoute
|
||||||
'/zap/$id': typeof ZapIdRoute
|
'/zap/$id': typeof ZapIdRoute
|
||||||
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
'/new-account/connect': typeof NewAccountConnectLazyRoute
|
||||||
'/new-account/import': typeof NewAccountImportLazyRoute
|
'/new-account/import': typeof NewAccountImportLazyRoute
|
||||||
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
'/new-account/watch': typeof NewAccountWatchLazyRoute
|
||||||
|
'/settings/general': typeof SettingsGeneralLazyRoute
|
||||||
|
'/settings/sync': typeof SettingsSyncLazyRoute
|
||||||
'/_app/': typeof AppIndexRoute
|
'/_app/': typeof AppIndexRoute
|
||||||
'/new-post/': typeof NewPostIndexRoute
|
'/new-post/': typeof NewPostIndexRoute
|
||||||
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
@@ -891,13 +910,14 @@ export interface FileRouteTypes {
|
|||||||
| '/$id/set-interest'
|
| '/$id/set-interest'
|
||||||
| '/$id/set-profile'
|
| '/$id/set-profile'
|
||||||
| '/columns'
|
| '/columns'
|
||||||
| '/settings/general'
|
|
||||||
| '/settings/relays'
|
| '/settings/relays'
|
||||||
| '/settings/wallet'
|
| '/settings/wallet'
|
||||||
| '/zap/$id'
|
| '/zap/$id'
|
||||||
| '/new-account/connect'
|
| '/new-account/connect'
|
||||||
| '/new-account/import'
|
| '/new-account/import'
|
||||||
| '/new-account/watch'
|
| '/new-account/watch'
|
||||||
|
| '/settings/general'
|
||||||
|
| '/settings/sync'
|
||||||
| '/'
|
| '/'
|
||||||
| '/new-post'
|
| '/new-post'
|
||||||
| '/columns/create-newsfeed'
|
| '/columns/create-newsfeed'
|
||||||
@@ -929,13 +949,14 @@ export interface FileRouteTypes {
|
|||||||
| '/$id/set-interest'
|
| '/$id/set-interest'
|
||||||
| '/$id/set-profile'
|
| '/$id/set-profile'
|
||||||
| '/columns'
|
| '/columns'
|
||||||
| '/settings/general'
|
|
||||||
| '/settings/relays'
|
| '/settings/relays'
|
||||||
| '/settings/wallet'
|
| '/settings/wallet'
|
||||||
| '/zap/$id'
|
| '/zap/$id'
|
||||||
| '/new-account/connect'
|
| '/new-account/connect'
|
||||||
| '/new-account/import'
|
| '/new-account/import'
|
||||||
| '/new-account/watch'
|
| '/new-account/watch'
|
||||||
|
| '/settings/general'
|
||||||
|
| '/settings/sync'
|
||||||
| '/'
|
| '/'
|
||||||
| '/new-post'
|
| '/new-post'
|
||||||
| '/columns/create-newsfeed'
|
| '/columns/create-newsfeed'
|
||||||
@@ -969,13 +990,14 @@ export interface FileRouteTypes {
|
|||||||
| '/$id/set-profile'
|
| '/$id/set-profile'
|
||||||
| '/columns'
|
| '/columns'
|
||||||
| '/columns/_layout'
|
| '/columns/_layout'
|
||||||
| '/settings/general'
|
|
||||||
| '/settings/relays'
|
| '/settings/relays'
|
||||||
| '/settings/wallet'
|
| '/settings/wallet'
|
||||||
| '/zap/$id'
|
| '/zap/$id'
|
||||||
| '/new-account/connect'
|
| '/new-account/connect'
|
||||||
| '/new-account/import'
|
| '/new-account/import'
|
||||||
| '/new-account/watch'
|
| '/new-account/watch'
|
||||||
|
| '/settings/general'
|
||||||
|
| '/settings/sync'
|
||||||
| '/_app/'
|
| '/_app/'
|
||||||
| '/new-post/'
|
| '/new-post/'
|
||||||
| '/columns/_layout/create-newsfeed'
|
| '/columns/_layout/create-newsfeed'
|
||||||
@@ -1036,8 +1058,6 @@ export const routeTree = rootRoute
|
|||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
._addFileTypes<FileRouteTypes>()
|
._addFileTypes<FileRouteTypes>()
|
||||||
|
|
||||||
/* prettier-ignore-end */
|
|
||||||
|
|
||||||
/* ROUTE_MANIFEST_START
|
/* ROUTE_MANIFEST_START
|
||||||
{
|
{
|
||||||
"routes": {
|
"routes": {
|
||||||
@@ -1070,9 +1090,10 @@ export const routeTree = rootRoute
|
|||||||
"/settings": {
|
"/settings": {
|
||||||
"filePath": "settings.lazy.tsx",
|
"filePath": "settings.lazy.tsx",
|
||||||
"children": [
|
"children": [
|
||||||
"/settings/general",
|
|
||||||
"/settings/relays",
|
"/settings/relays",
|
||||||
"/settings/wallet"
|
"/settings/wallet",
|
||||||
|
"/settings/general",
|
||||||
|
"/settings/sync"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/$id/set-group": {
|
"/$id/set-group": {
|
||||||
@@ -1115,10 +1136,6 @@ export const routeTree = rootRoute
|
|||||||
"/columns/_layout/users/$id"
|
"/columns/_layout/users/$id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/settings/general": {
|
|
||||||
"filePath": "settings/general.tsx",
|
|
||||||
"parent": "/settings"
|
|
||||||
},
|
|
||||||
"/settings/relays": {
|
"/settings/relays": {
|
||||||
"filePath": "settings/relays.tsx",
|
"filePath": "settings/relays.tsx",
|
||||||
"parent": "/settings"
|
"parent": "/settings"
|
||||||
@@ -1139,6 +1156,14 @@ export const routeTree = rootRoute
|
|||||||
"/new-account/watch": {
|
"/new-account/watch": {
|
||||||
"filePath": "new-account/watch.lazy.tsx"
|
"filePath": "new-account/watch.lazy.tsx"
|
||||||
},
|
},
|
||||||
|
"/settings/general": {
|
||||||
|
"filePath": "settings/general.lazy.tsx",
|
||||||
|
"parent": "/settings"
|
||||||
|
},
|
||||||
|
"/settings/sync": {
|
||||||
|
"filePath": "settings/sync.lazy.tsx",
|
||||||
|
"parent": "/settings"
|
||||||
|
},
|
||||||
"/_app/": {
|
"/_app/": {
|
||||||
"filePath": "_app/index.tsx",
|
"filePath": "_app/index.tsx",
|
||||||
"parent": "/_app"
|
"parent": "/_app"
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function Account({ pubkey }: { pubkey: string }) {
|
|||||||
const items = await Promise.all([
|
const items = await Promise.all([
|
||||||
MenuItem.new({
|
MenuItem.new({
|
||||||
text: "Unlock",
|
text: "Unlock",
|
||||||
enabled: !isActive || true,
|
enabled: !isActive,
|
||||||
action: async () => await commands.setSigner(pubkey),
|
action: async () => await commands.setSigner(pubkey),
|
||||||
}),
|
}),
|
||||||
PredefinedMenuItem.new({ item: "Separator" }),
|
PredefinedMenuItem.new({ item: "Separator" }),
|
||||||
@@ -183,7 +183,7 @@ function Account({ pubkey }: { pubkey: string }) {
|
|||||||
|
|
||||||
await menu.popup().catch((e) => console.error(e));
|
await menu.popup().catch((e) => console.error(e));
|
||||||
},
|
},
|
||||||
[pubkey],
|
[isActive, pubkey],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -99,13 +99,8 @@ function ReplyList() {
|
|||||||
const res = await commands.getReplies(id);
|
const res = await commands.getReplies(id);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const events = res.data
|
const events = res.data.map((item) =>
|
||||||
// Create Lume Events
|
LumeEvent.from(item.raw, item.parsed),
|
||||||
.map((item) => LumeEvent.from(item.raw, item.parsed))
|
|
||||||
// Filter quote
|
|
||||||
.filter(
|
|
||||||
(ev) =>
|
|
||||||
!ev.tags.filter((t) => t[0] === "q" || t[3] === "mention").length,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return events;
|
return events;
|
||||||
@@ -179,7 +174,7 @@ function ReplyList() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = getCurrentWindow().listen<EventPayload>(
|
const unlisten = getCurrentWindow().listen<EventPayload>(
|
||||||
"event",
|
"comment",
|
||||||
async (data) => {
|
async (data) => {
|
||||||
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
|
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
|
||||||
|
|
||||||
@@ -216,7 +211,7 @@ function ReplyList() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">Getting replies...</span>
|
<span className="text-sm font-medium">Loading replies...</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
import { commands } from "@/commands.gen";
|
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/columns/_layout/events/$id")({
|
export const Route = createFileRoute("/columns/_layout/events/$id")();
|
||||||
beforeLoad: async () => {
|
|
||||||
const accounts = await commands.getAccounts();
|
|
||||||
return { accounts };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ function Newsfeeds() {
|
|||||||
: item.tags.find((tag) => tag[0] === "title")?.[1] || "Unnamed";
|
: item.tags.find((tag) => tag[0] === "title")?.[1] || "Unnamed";
|
||||||
const label =
|
const label =
|
||||||
item.kind === 3
|
item.kind === 3
|
||||||
? `newsfeed-${id.slice(0, 5)}`
|
? `newsfeed-${item.pubkey.slice(0, 5)}`
|
||||||
: item.tags.find((tag) => tag[0] === "d")?.[1] || nanoid();
|
: item.tags.find((tag) => tag[0] === "d")?.[1] || nanoid();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -120,19 +120,19 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<Tabs.List className="h-11 shrink-0 flex items-center">
|
<Tabs.List className="h-11 shrink-0 flex items-center">
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50"
|
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
|
||||||
value="replies"
|
value="replies"
|
||||||
>
|
>
|
||||||
Replies
|
Replies
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50"
|
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
|
||||||
value="reactions"
|
value="reactions"
|
||||||
>
|
>
|
||||||
Reactions
|
Reactions
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50"
|
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
|
||||||
value="zaps"
|
value="zaps"
|
||||||
>
|
>
|
||||||
Zaps
|
Zaps
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export const Route = createLazyFileRoute("/new-post/")({
|
|||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { reply_to } = Route.useSearch();
|
const { reply_to } = Route.useSearch();
|
||||||
const { accounts, initialValue, queryClient } = Route.useRouteContext();
|
const { accounts, initialValue } = Route.useRouteContext();
|
||||||
const { deferMentionList } = Route.useLoaderData();
|
const { deferMentionList } = Route.useLoaderData();
|
||||||
const users = useAwaited({ promise: deferMentionList })[0];
|
const users = useAwaited({ promise: deferMentionList })[0];
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
import { cn } from '@/commons'
|
import { cn } from "@/commons";
|
||||||
import { CurrencyBtc, GearSix, HardDrives } from '@phosphor-icons/react'
|
import {
|
||||||
import * as ScrollArea from '@radix-ui/react-scroll-area'
|
CloudArrowDown,
|
||||||
import { Link } from '@tanstack/react-router'
|
CurrencyBtc,
|
||||||
import { Outlet, createLazyFileRoute } from '@tanstack/react-router'
|
GearSix,
|
||||||
|
HardDrives,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute('/settings')({
|
export const Route = createLazyFileRoute("/settings")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
})
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { platform } = Route.useRouteContext()
|
const { platform } = Route.useRouteContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full">
|
<div className="flex size-full">
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2',
|
"w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2",
|
||||||
platform === 'macos' ? 'pt-11' : '',
|
platform === "macos" ? "pt-11" : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="h-8 px-1.5">
|
<div className="h-8 px-1.5">
|
||||||
@@ -28,16 +33,33 @@ function Screen() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
|
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
|
||||||
isActive
|
isActive
|
||||||
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
|
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
|
||||||
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
|
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<GearSix className="size-5 shrink-0" />
|
<GearSix className="size-5 shrink-0" />
|
||||||
<p className="text-sm font-medium">General</p>
|
<p className="text-sm font-medium">General</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
}}
|
||||||
|
</Link>
|
||||||
|
<Link to="/settings/sync">
|
||||||
|
{({ isActive }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
|
||||||
|
isActive
|
||||||
|
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
|
||||||
|
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CloudArrowDown className="size-5 shrink-0" />
|
||||||
|
<p className="text-sm font-medium">Sync</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/settings/relays">
|
<Link to="/settings/relays">
|
||||||
@@ -45,16 +67,16 @@ function Screen() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
|
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
|
||||||
isActive
|
isActive
|
||||||
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
|
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
|
||||||
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
|
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HardDrives className="size-5 shrink-0" />
|
<HardDrives className="size-5 shrink-0" />
|
||||||
<p className="text-sm font-medium">Relays</p>
|
<p className="text-sm font-medium">Relays</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}}
|
}}
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/settings/wallet">
|
<Link to="/settings/wallet">
|
||||||
@@ -62,21 +84,21 @@ function Screen() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2',
|
"h-9 w-full inline-flex items-center gap-1.5 rounded-lg p-2",
|
||||||
isActive
|
isActive
|
||||||
? 'bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20'
|
? "bg-black/10 hover:bg-black/20 dark:bg-white/10 text-neutral-900 dark:text-neutral-100 dark:hover:bg-bg-white/20"
|
||||||
: 'text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10',
|
: "text-neutral-700 hover:bg-black/10 dark:text-neutral-300 dark:hover:bg-white/10",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CurrencyBtc className="size-5 shrink-0" />
|
<CurrencyBtc className="size-5 shrink-0" />
|
||||||
<p className="text-sm font-medium">Wallet</p>
|
<p className="text-sm font-medium">Wallet</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}}
|
}}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={'scroll'}
|
type={"scroll"}
|
||||||
scrollHideDelay={300}
|
scrollHideDelay={300}
|
||||||
className="flex-1 overflow-hidden size-full"
|
className="flex-1 overflow-hidden size-full"
|
||||||
>
|
>
|
||||||
@@ -92,5 +114,5 @@ function Screen() {
|
|||||||
<ScrollArea.Corner className="bg-transparent" />
|
<ScrollArea.Corner className="bg-transparent" />
|
||||||
</ScrollArea.Root>
|
</ScrollArea.Root>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,9 +69,7 @@ function Screen() {
|
|||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<div className="flex flex-col gap-6 px-3 pb-3">
|
<div className="flex flex-col gap-6 px-3 pb-3">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<h2 className="text-sm font-semibold">General</h2>
|
||||||
General
|
|
||||||
</h2>
|
|
||||||
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
<Setting
|
<Setting
|
||||||
name="Content Warning"
|
name="Content Warning"
|
||||||
@@ -92,9 +90,7 @@ function Screen() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<h2 className="text-sm font-semibold">Appearance</h2>
|
||||||
Appearance
|
|
||||||
</h2>
|
|
||||||
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
<div className="flex items-start justify-between w-full gap-4 py-3">
|
<div className="flex items-start justify-between w-full gap-4 py-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -140,9 +136,7 @@ function Screen() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<h2 className="text-sm font-semibold">Privacy & Performance</h2>
|
||||||
Privacy & Performance
|
|
||||||
</h2>
|
|
||||||
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
<Setting
|
<Setting
|
||||||
name="Resize Service"
|
name="Resize Service"
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/settings/general")();
|
|
||||||
@@ -53,9 +53,20 @@ function Screen() {
|
|||||||
<div className="w-full px-3 pb-3">
|
<div className="w-full px-3 pb-3">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<div>
|
||||||
Connected Relays
|
<h2 className="text-sm font-semibold">Connected Relays</h2>
|
||||||
</h2>
|
<p className="text-sm text-neutral-500">
|
||||||
|
Learn more about Relays{" "}
|
||||||
|
<a
|
||||||
|
href="https://nostr.how/en/relays"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 !underline"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
<div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
<div className="flex items-center h-14">
|
<div className="flex items-center h-14">
|
||||||
<div className="flex items-center w-full gap-2 mb-0">
|
<div className="flex items-center w-full gap-2 mb-0">
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useEffect, useState, useTransition } from "react";
|
||||||
|
import * as Progress from "@radix-ui/react-progress";
|
||||||
|
import { Channel } from "@tauri-apps/api/core";
|
||||||
|
import { commands } from "@/commands.gen";
|
||||||
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
|
import { Spinner } from "@/components";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/settings/sync")({
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const [channel, _setChannel] = useState<Channel<number>>(
|
||||||
|
() => new Channel<number>(),
|
||||||
|
);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const runSync = () => {
|
||||||
|
startTransition(async () => {
|
||||||
|
const res = await commands.syncAll(channel);
|
||||||
|
|
||||||
|
if (res.status === "error") {
|
||||||
|
await message(res.error, { kind: "error" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
channel.onmessage = (message) => {
|
||||||
|
setProgress(message);
|
||||||
|
};
|
||||||
|
}, [channel]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full px-3 pb-3">
|
||||||
|
<div className="h-full flex flex-col w-full gap-2">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-sm font-semibold">Sync events with Negentropy</h2>
|
||||||
|
<p className="text-sm text-neutral-500">
|
||||||
|
Learn more about negentropy{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/hoytech/strfry/blob/nextneg/docs/negentropy.md"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 !underline"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm flex flex-col gap-2">
|
||||||
|
<h5 className="font-semibold">Data will be sync:</h5>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
Metadata of all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
Contact list of all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
Follow and interest sets of all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
All notes and reposts of all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-9 inline-flex items-center px-2 bg-black/5 dark:bg-white/5 rounded-lg text-neutral-700 dark:text-neutral-300">
|
||||||
|
All comments all public keys that found in database.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative mt-auto flex items-center gap-4 justify-between">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Progress.Root
|
||||||
|
className="relative overflow-hidden bg-black/20 dark:bg-white/20 rounded-full w-full h-1"
|
||||||
|
style={{
|
||||||
|
transform: "translateZ(0)",
|
||||||
|
}}
|
||||||
|
value={progress}
|
||||||
|
>
|
||||||
|
<Progress.Indicator
|
||||||
|
className="bg-blue-500 size-full rounded-full transition-transform duration-[660ms] ease-[cubic-bezier(0.65, 0, 0.35, 1)]"
|
||||||
|
style={{ transform: `translateX(-${100 - progress}%)` }}
|
||||||
|
/>
|
||||||
|
</Progress.Root>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={() => runSync()}
|
||||||
|
className="shrink-0 w-20 h-8 rounded-lg inline-flex items-center justify-center bg-blue-500 hover:bg-blue-600 text-white text-sm font-semibold"
|
||||||
|
>
|
||||||
|
{isPending ? <Spinner className="size-4" /> : "Sync"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -33,9 +33,20 @@ function Screen() {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full px-3 pb-3">
|
<div className="w-full px-3 pb-3">
|
||||||
<div className="flex flex-col w-full gap-2">
|
<div className="flex flex-col w-full gap-2">
|
||||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
<div>
|
||||||
Wallet
|
<h2 className="text-sm font-semibold">Bitcoin Wallet</h2>
|
||||||
</h2>
|
<p className="text-sm text-neutral-500">
|
||||||
|
Learn more about Zap{" "}
|
||||||
|
<a
|
||||||
|
href="https://nostr.how/en/zaps"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 !underline"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="w-full h-44 flex items-center justify-center bg-black/5 dark:bg-white/5 rounded-xl">
|
<div className="w-full h-44 flex items-center justify-center bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
<Button
|
<Button
|
||||||
onConnected={(provider) =>
|
onConnected={(provider) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user