chore: update gpui and nostr sdk
This commit is contained in:
371
Cargo.lock
generated
371
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,16 +11,17 @@ publish = false
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
coop = { path = "crates/*" }
|
coop = { path = "crates/*" }
|
||||||
|
|
||||||
# UI
|
# GPUI
|
||||||
gpui = { git = "https://github.com/zed-industries/zed" }
|
gpui = { git = "https://github.com/zed-industries/zed" }
|
||||||
reqwest_client = { git = "https://github.com/zed-industries/zed" }
|
reqwest_client = { git = "https://github.com/zed-industries/zed" }
|
||||||
|
|
||||||
# Nostr
|
# Nostr
|
||||||
nostr = { git = "https://github.com/rust-nostr/nostr" }
|
nostr = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "nip96", "nip59", "nip49", "nip44", "nip05"] }
|
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "nip96", "nip59", "nip49", "nip44"] }
|
||||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
|
|
||||||
# Others
|
# Others
|
||||||
|
reqwest = { version = "0.12", features = ["stream"] }
|
||||||
emojis = "0.6.4"
|
emojis = "0.6.4"
|
||||||
smol = "2"
|
smol = "2"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ gpui.workspace = true
|
|||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
reqwest.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
||||||
tempfile = "3.19.1"
|
tempfile = "3.19.1"
|
||||||
reqwest = { version = "0.12", features = ["stream"] }
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use std::sync::Arc;
|
|||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use chrono::{Local, TimeZone};
|
use chrono::{Local, TimeZone};
|
||||||
use common::profile::RenderProfile;
|
use common::profile::RenderProfile;
|
||||||
use common::{compare, room_hash};
|
|
||||||
use global::shared_state;
|
use global::shared_state;
|
||||||
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task, Window};
|
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task, Window};
|
||||||
use identity::Identity;
|
use identity::Identity;
|
||||||
@@ -79,7 +78,7 @@ impl Room {
|
|||||||
///
|
///
|
||||||
/// A new Room instance with information extracted from the event
|
/// A new Room instance with information extracted from the event
|
||||||
pub fn new(event: &Event) -> Self {
|
pub fn new(event: &Event) -> Self {
|
||||||
let id = room_hash(event);
|
let id = common::room_hash(event);
|
||||||
let created_at = event.created_at;
|
let created_at = event.created_at;
|
||||||
|
|
||||||
// Get all pubkeys from the event's tags
|
// Get all pubkeys from the event's tags
|
||||||
@@ -405,7 +404,7 @@ impl Room {
|
|||||||
let mut other_pubkeys = ev.tags.public_keys().copied().collect::<Vec<_>>();
|
let mut other_pubkeys = ev.tags.public_keys().copied().collect::<Vec<_>>();
|
||||||
other_pubkeys.push(ev.pubkey);
|
other_pubkeys.push(ev.pubkey);
|
||||||
// Check if the event is from a member of the room
|
// Check if the event is from a member of the room
|
||||||
compare(&other_pubkeys, &pubkeys)
|
common::compare(&other_pubkeys, &pubkeys)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ global = { path = "../global" }
|
|||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
nostr-connect.workspace = true
|
nostr-connect.workspace = true
|
||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
|
nostr.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
reqwest.workspace = true
|
||||||
|
|
||||||
webbrowser = "1.0.4"
|
webbrowser = "1.0.4"
|
||||||
qrcode-generator = "5.0.0"
|
qrcode-generator = "5.0.0"
|
||||||
|
|||||||
@@ -2,23 +2,43 @@ use std::collections::HashSet;
|
|||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Error, Result};
|
||||||
use gpui::{Image, ImageFormat};
|
use gpui::{Image, ImageFormat};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use qrcode_generator::QrCodeEcc;
|
use qrcode_generator::QrCodeEcc;
|
||||||
|
use reqwest::Client as ReqClient;
|
||||||
|
|
||||||
pub mod debounced_delay;
|
pub mod debounced_delay;
|
||||||
pub mod handle_auth;
|
pub mod handle_auth;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
|
|
||||||
pub async fn nip96_upload(
|
pub async fn verify_nip05(public_key: PublicKey, address: &str) -> Result<bool, Error> {
|
||||||
client: &Client,
|
let req_client = ReqClient::new();
|
||||||
upload_to: Url,
|
let address = Nip05Address::parse(address)?;
|
||||||
file: Vec<u8>,
|
let res = req_client.get(address.url().to_string()).send().await?;
|
||||||
) -> anyhow::Result<Url, anyhow::Error> {
|
let json: Value = res.json().await?;
|
||||||
let signer = client.signer().await?;
|
let verify = nip05::verify_from_json(&public_key, &address, &json);
|
||||||
|
|
||||||
let config: ServerConfig = nip96::get_server_config(upload_to.to_owned(), None).await?;
|
Ok(verify)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn nip05_profile(address: &str) -> Result<Nip05Profile, Error> {
|
||||||
|
let req_client = ReqClient::new();
|
||||||
|
let address = Nip05Address::parse(address)?;
|
||||||
|
let res = req_client.get(address.url().to_string()).send().await?;
|
||||||
|
let json: Value = res.json().await?;
|
||||||
|
|
||||||
|
if let Ok(profile) = Nip05Profile::from_json(&address, &json) {
|
||||||
|
Ok(profile)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Failed to get NIP-05 profile"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn nip96_upload(client: &Client, server: Url, file: Vec<u8>) -> Result<Url, Error> {
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let config = nip96::get_server_config(server.to_owned(), None).await?;
|
||||||
let url = nip96::upload_data(&signer, &config, file, None, None).await?;
|
let url = nip96::upload_data(&signer, &config, file, None, None).await?;
|
||||||
|
|
||||||
Ok(url)
|
Ok(url)
|
||||||
@@ -26,10 +46,10 @@ pub async fn nip96_upload(
|
|||||||
|
|
||||||
pub fn room_hash(event: &Event) -> u64 {
|
pub fn room_hash(event: &Event) -> u64 {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
let mut pubkeys: Vec<&PublicKey> = vec![];
|
let mut pubkeys: Vec<PublicKey> = vec![];
|
||||||
|
|
||||||
// Add all public keys from event
|
// Add all public keys from event
|
||||||
pubkeys.push(&event.pubkey);
|
pubkeys.push(event.pubkey);
|
||||||
pubkeys.extend(event.tags.public_keys().collect::<Vec<_>>());
|
pubkeys.extend(event.tags.public_keys().collect::<Vec<_>>());
|
||||||
|
|
||||||
// Generate unique hash
|
// Generate unique hash
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ reqwest_client.workspace = true
|
|||||||
|
|
||||||
nostr-connect.workspace = true
|
nostr-connect.workspace = true
|
||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
|
nostr.workspace = true
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use global::constants::{DEFAULT_MODAL_WIDTH, DEFAULT_SIDEBAR_WIDTH};
|
|||||||
use global::shared_state;
|
use global::shared_state;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, impl_internal_actions, px, relative, App, AppContext, Axis, Context, Entity, IntoElement,
|
div, px, relative, Action, App, AppContext, Axis, Context, Entity, IntoElement, ParentElement,
|
||||||
ParentElement, Render, Styled, Subscription, Task, Window,
|
Render, Styled, Subscription, Task, Window,
|
||||||
};
|
};
|
||||||
use identity::Identity;
|
use identity::Identity;
|
||||||
use nostr_connect::prelude::*;
|
use nostr_connect::prelude::*;
|
||||||
@@ -25,8 +25,6 @@ use ui::{ContextModal, IconName, Root, Sizable, StyledExt, TitleBar};
|
|||||||
use crate::views::chat::{self, Chat};
|
use crate::views::chat::{self, Chat};
|
||||||
use crate::views::{login, new_account, onboarding, preferences, sidebar, startup, welcome};
|
use crate::views::{login, new_account, onboarding, preferences, sidebar, startup, welcome};
|
||||||
|
|
||||||
impl_internal_actions!(dock, [ToggleModal]);
|
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<ChatSpace> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<ChatSpace> {
|
||||||
ChatSpace::new(window, cx)
|
ChatSpace::new(window, cx)
|
||||||
}
|
}
|
||||||
@@ -56,7 +54,8 @@ pub enum ModalKind {
|
|||||||
SetupRelay,
|
SetupRelay,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[action(namespace = modal, no_json)]
|
||||||
pub struct ToggleModal {
|
pub struct ToggleModal {
|
||||||
pub modal: ModalKind,
|
pub modal: ModalKind,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ use common::profile::RenderProfile;
|
|||||||
use global::shared_state;
|
use global::shared_state;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, impl_internal_actions, list, px, red, relative, rems, svg, white, AnyElement, App,
|
div, img, list, px, red, relative, rems, svg, white, Action, AnyElement, App, AppContext,
|
||||||
AppContext, ClipboardItem, Context, Div, Element, Empty, Entity, EventEmitter, Flatten,
|
ClipboardItem, Context, Div, Element, Empty, Entity, EventEmitter, Flatten, FocusHandle,
|
||||||
FocusHandle, Focusable, InteractiveElement, IntoElement, ListAlignment, ListState, ObjectFit,
|
Focusable, InteractiveElement, IntoElement, ListAlignment, ListState, ObjectFit, ParentElement,
|
||||||
ParentElement, PathPromptOptions, Render, RetainAllImageCache, SharedString,
|
PathPromptOptions, Render, RetainAllImageCache, SharedString, StatefulInteractiveElement,
|
||||||
StatefulInteractiveElement, Styled, StyledImage, Subscription, Window,
|
Styled, StyledImage, Subscription, Window,
|
||||||
};
|
};
|
||||||
use identity::Identity;
|
use identity::Identity;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@@ -38,11 +38,10 @@ use ui::{
|
|||||||
|
|
||||||
use crate::views::subject;
|
use crate::views::subject;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[action(namespace = chat, no_json)]
|
||||||
pub struct ChangeSubject(pub String);
|
pub struct ChangeSubject(pub String);
|
||||||
|
|
||||||
impl_internal_actions!(chat, [ChangeSubject]);
|
|
||||||
|
|
||||||
pub fn init(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Arc<Entity<Chat>> {
|
pub fn init(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Arc<Entity<Chat>> {
|
||||||
Arc::new(Chat::new(room, window, cx))
|
Arc::new(Chat::new(room, window, cx))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ use std::time::Duration;
|
|||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use chats::room::{Room, RoomKind};
|
use chats::room::{Room, RoomKind};
|
||||||
use chats::ChatRegistry;
|
use chats::ChatRegistry;
|
||||||
|
use common::nip05_profile;
|
||||||
use common::profile::RenderProfile;
|
use common::profile::RenderProfile;
|
||||||
use global::shared_state;
|
use global::shared_state;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, impl_internal_actions, px, red, relative, uniform_list, App, AppContext, Context,
|
div, img, px, red, relative, uniform_list, App, AppContext, Context, Entity,
|
||||||
Entity, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||||
StatefulInteractiveElement, Styled, Subscription, Task, TextAlign, Window,
|
StatefulInteractiveElement, Styled, Subscription, Task, TextAlign, Window,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::Deserialize;
|
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
@@ -28,11 +28,6 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Compose> {
|
|||||||
cx.new(|cx| Compose::new(window, cx))
|
cx.new(|cx| Compose::new(window, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
|
||||||
struct SelectContact(PublicKey);
|
|
||||||
|
|
||||||
impl_internal_actions!(contacts, [SelectContact]);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Contact {
|
struct Contact {
|
||||||
profile: Profile,
|
profile: Profile,
|
||||||
@@ -245,15 +240,14 @@ impl Compose {
|
|||||||
|
|
||||||
let task: Task<Result<Contact, anyhow::Error>> = if content.contains("@") {
|
let task: Task<Result<Contact, anyhow::Error>> = if content.contains("@") {
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let (tx, rx) = oneshot::channel::<Nip05Profile>();
|
let (tx, rx) = oneshot::channel::<Option<Nip05Profile>>();
|
||||||
|
|
||||||
nostr_sdk::async_utility::task::spawn(async move {
|
nostr_sdk::async_utility::task::spawn(async move {
|
||||||
if let Ok(profile) = nip05::profile(&content, None).await {
|
let profile = nip05_profile(&content).await.ok();
|
||||||
tx.send(profile).ok();
|
tx.send(profile).ok();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Ok(profile) = rx.await {
|
if let Ok(Some(profile)) = rx.await {
|
||||||
let public_key = profile.public_key;
|
let public_key = profile.public_key;
|
||||||
let metadata = shared_state()
|
let metadata = shared_state()
|
||||||
.client()
|
.client()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use chats::room::{Room, RoomKind};
|
|||||||
use chats::{ChatRegistry, RoomEmitter};
|
use chats::{ChatRegistry, RoomEmitter};
|
||||||
use common::debounced_delay::DebouncedDelay;
|
use common::debounced_delay::DebouncedDelay;
|
||||||
use common::profile::RenderProfile;
|
use common::profile::RenderProfile;
|
||||||
|
use common::verify_nip05;
|
||||||
use element::DisplayRoom;
|
use element::DisplayRoom;
|
||||||
use global::constants::{DEFAULT_MODAL_WIDTH, SEARCH_RELAYS};
|
use global::constants::{DEFAULT_MODAL_WIDTH, SEARCH_RELAYS};
|
||||||
use global::shared_state;
|
use global::shared_state;
|
||||||
@@ -184,13 +185,8 @@ impl Sidebar {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(verified) = nip05::verify(&event.pubkey, target, None).await else {
|
if !verify_nip05(event.pubkey, target).await.unwrap_or(false) {
|
||||||
// Skip if NIP-05 verification fails
|
// Skip if NIP-05 is not valid or failed to verify
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !verified {
|
|
||||||
// Skip if NIP-05 is not valid
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use gpui::{actions, impl_internal_actions};
|
use gpui::{actions, Action};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Action, PartialEq, Eq, Deserialize)]
|
||||||
|
#[action(namespace = list, no_json)]
|
||||||
pub struct Confirm {
|
pub struct Confirm {
|
||||||
/// Is confirm with secondary.
|
/// Is confirm with secondary.
|
||||||
pub secondary: bool,
|
pub secondary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(list, [Cancel, SelectPrev, SelectNext]);
|
actions!(list, [Cancel, SelectPrev, SelectNext]);
|
||||||
impl_internal_actions!(list, [Confirm]);
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, impl_internal_actions, px, App, AppContext, Corner, Element, InteractiveElement,
|
div, px, Action, App, AppContext, Corner, Element, InteractiveElement, IntoElement,
|
||||||
IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled,
|
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WeakEntity,
|
||||||
WeakEntity, Window,
|
Window,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
@@ -14,11 +14,10 @@ use crate::input::InputState;
|
|||||||
use crate::popover::{Popover, PopoverContent};
|
use crate::popover::{Popover, PopoverContent};
|
||||||
use crate::Icon;
|
use crate::Icon;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, Deserialize)]
|
#[derive(Action, PartialEq, Clone, Debug, Deserialize)]
|
||||||
|
#[action(namespace = emoji, no_json)]
|
||||||
pub struct EmitEmoji(pub SharedString);
|
pub struct EmitEmoji(pub SharedString);
|
||||||
|
|
||||||
impl_internal_actions!(emoji, [EmitEmoji]);
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct EmojiPicker {
|
pub struct EmojiPicker {
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, impl_internal_actions, point, px, App, AppContext, Bounds, ClipboardItem,
|
actions, div, point, px, Action, App, AppContext, Bounds, ClipboardItem, Context,
|
||||||
Context, DefiniteLength, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable,
|
DefiniteLength, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable,
|
||||||
InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent,
|
InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent,
|
||||||
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, ScrollHandle,
|
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, ScrollHandle,
|
||||||
ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, WrappedLine,
|
ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, WrappedLine,
|
||||||
@@ -24,14 +24,13 @@ use crate::history::History;
|
|||||||
use crate::scroll::ScrollbarState;
|
use crate::scroll::ScrollbarState;
|
||||||
use crate::Root;
|
use crate::Root;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[action(namespace = input, no_json)]
|
||||||
pub struct Enter {
|
pub struct Enter {
|
||||||
/// Is confirm with secondary.
|
/// Is confirm with secondary.
|
||||||
pub secondary: bool,
|
pub secondary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_internal_actions!(input, [Enter]);
|
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
input,
|
input,
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user