core: shared notes on the desktop and phone, and Share from the desktop
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 4s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 12s
CI & Build / integration (push) Successful in 1m14s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Failing after 1m26s
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Canceled after 9m20s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 4s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 12s
CI & Build / integration (push) Successful in 1m14s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Failing after 1m26s
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Canceled after 9m20s
The core pulls with shares from a server offering them (protocol 6): a note says how it is held (owner, edit, view) and who shared it, and a revoked note leaves the device. The first such pull starts the feed over once, so notes shared before this build arrive. The store refuses what a share doesn't allow (view: everything; edit: anything but the text), push sends only the text of someone else's note, and their notes stay out of trash, reminders and reordering. Unlinking drops them. The Share dialog's calls go to the linked server over the device token, as Tauri commands and through the FFI. The desktop now offers Share and "Shared with me"; unlinked, the dialog says sharing needs a server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+52
-3
@@ -40,11 +40,11 @@ use std::sync::Arc;
|
||||
|
||||
use inkwell_core::local::{self, Db};
|
||||
use inkwell_core::sync::blobs::BlobStore;
|
||||
use inkwell_core::sync::{client, compat, engine, push, state};
|
||||
use inkwell_core::sync::{client, compat, engine, push, sharing, state};
|
||||
|
||||
use models::{
|
||||
patch_from, BodyItem, BodyTag, ClientUpdate, Identity, Label, Note, NoteDraft, NoteEdit,
|
||||
NoteQuery, ProbeResult, RevokeOutcome, SyncOutcome, SyncStatus,
|
||||
patch_from, BodyItem, BodyTag, ClientUpdate, Identity, Label, Member, Note, NoteDraft,
|
||||
NoteEdit, NoteQuery, NoteShare, ProbeResult, RevokeOutcome, SyncOutcome, SyncStatus,
|
||||
};
|
||||
|
||||
uniffi::setup_scaffolding!();
|
||||
@@ -606,6 +606,55 @@ impl Inkwell {
|
||||
.map(SyncOutcome::from)
|
||||
.map_err(CoreError::network)
|
||||
}
|
||||
|
||||
// ────────────────────────────── sharing ──────────────────────────────
|
||||
//
|
||||
// The Share dialog asks the server directly (#5175). Unlinked, each answers
|
||||
// `NotLinked`, which the dialog turns into "sharing needs a server".
|
||||
|
||||
/// Everyone on the instance a note can be shared with.
|
||||
pub async fn share_directory(&self) -> Result<Vec<Member>, CoreError> {
|
||||
self.credentials()?;
|
||||
let members = sharing::directory(&self.db)
|
||||
.await
|
||||
.map_err(CoreError::network)?;
|
||||
Ok(members.into_iter().map(Member::from).collect())
|
||||
}
|
||||
|
||||
/// Who this note is shared with.
|
||||
pub async fn note_shares(&self, note_id: String) -> Result<Vec<NoteShare>, CoreError> {
|
||||
self.credentials()?;
|
||||
let shares = sharing::list(&self.db, ¬e_id)
|
||||
.await
|
||||
.map_err(CoreError::network)?;
|
||||
Ok(shares.into_iter().map(NoteShare::from).collect())
|
||||
}
|
||||
|
||||
/// Share with one member at "view" or "edit", or change their permission.
|
||||
pub async fn share_note(
|
||||
&self,
|
||||
note_id: String,
|
||||
user_id: String,
|
||||
permission: String,
|
||||
) -> Result<Vec<NoteShare>, CoreError> {
|
||||
self.credentials()?;
|
||||
let shares = sharing::share(&self.db, ¬e_id, &user_id, &permission)
|
||||
.await
|
||||
.map_err(CoreError::network)?;
|
||||
Ok(shares.into_iter().map(NoteShare::from).collect())
|
||||
}
|
||||
|
||||
pub async fn unshare_note(
|
||||
&self,
|
||||
note_id: String,
|
||||
share_id: String,
|
||||
) -> Result<Vec<NoteShare>, CoreError> {
|
||||
self.credentials()?;
|
||||
let shares = sharing::unshare(&self.db, ¬e_id, &share_id)
|
||||
.await
|
||||
.map_err(CoreError::network)?;
|
||||
Ok(shares.into_iter().map(NoteShare::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
// ── checklist text, as pure functions ───────────────────────────────────────
|
||||
|
||||
@@ -46,6 +46,20 @@ pub struct Note {
|
||||
pub previews: Vec<LinkPreview>,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
/// How this account holds the note (#5175): "owner", or "edit"/"view" for one
|
||||
/// someone shared with it.
|
||||
pub permission: String,
|
||||
/// Whether the owner has shared it with anyone.
|
||||
pub shared: bool,
|
||||
/// Who shared it with us; absent on our own notes.
|
||||
pub shared_by: Option<SharedBy>,
|
||||
}
|
||||
|
||||
/// The owner of a note shared with this account.
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct SharedBy {
|
||||
pub id: String,
|
||||
pub display_name: String,
|
||||
}
|
||||
|
||||
/// A checklist item as it sits in a note's body.
|
||||
@@ -201,6 +215,9 @@ impl From<core_models::Note> for Note {
|
||||
previews,
|
||||
created_at,
|
||||
updated_at,
|
||||
permission,
|
||||
shared,
|
||||
shared_by,
|
||||
} = value;
|
||||
Note {
|
||||
id,
|
||||
@@ -219,6 +236,12 @@ impl From<core_models::Note> for Note {
|
||||
previews: previews.into_iter().map(LinkPreview::from).collect(),
|
||||
created_at,
|
||||
updated_at,
|
||||
permission,
|
||||
shared,
|
||||
shared_by: shared_by.map(|by| SharedBy {
|
||||
id: by.id,
|
||||
display_name: by.display_name,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -350,6 +373,8 @@ pub struct NoteFacets {
|
||||
pub has_attachment: Option<bool>,
|
||||
pub created_after: Option<String>,
|
||||
pub created_before: Option<String>,
|
||||
/// "with_me": only notes someone else shared with this account.
|
||||
pub shared: Option<String>,
|
||||
}
|
||||
|
||||
impl From<NoteQuery> for core_models::ListQuery {
|
||||
@@ -378,6 +403,7 @@ impl From<NoteFacets> for core_models::Facets {
|
||||
has_attachment,
|
||||
created_after,
|
||||
created_before,
|
||||
shared,
|
||||
} = value;
|
||||
core_models::Facets {
|
||||
q,
|
||||
@@ -386,6 +412,7 @@ impl From<NoteFacets> for core_models::Facets {
|
||||
has_attachment,
|
||||
created_after,
|
||||
created_before,
|
||||
shared,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,6 +613,54 @@ impl From<core_client::Identity> for Identity {
|
||||
}
|
||||
}
|
||||
|
||||
/// Someone on the instance a note can be shared with (#5175).
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct Member {
|
||||
pub id: String,
|
||||
pub display_name: String,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
impl From<core_client::Member> for Member {
|
||||
fn from(value: core_client::Member) -> Self {
|
||||
let core_client::Member {
|
||||
id,
|
||||
display_name,
|
||||
email,
|
||||
} = value;
|
||||
Member {
|
||||
id,
|
||||
display_name,
|
||||
email,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One person a note is shared with, at "view" or "edit".
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct NoteShare {
|
||||
pub id: String,
|
||||
pub member: Member,
|
||||
pub permission: String,
|
||||
}
|
||||
|
||||
impl From<core_client::NoteShare> for NoteShare {
|
||||
fn from(value: core_client::NoteShare) -> Self {
|
||||
// `created_at` stays behind: nothing on the phone orders or shows by it.
|
||||
let core_client::NoteShare {
|
||||
id,
|
||||
member,
|
||||
permission,
|
||||
created_at: _,
|
||||
} = value;
|
||||
NoteShare {
|
||||
id,
|
||||
member: member.into(),
|
||||
permission,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What became of this device's token on the server during an unlink.
|
||||
///
|
||||
/// Separate from the local result because the local half always succeeds and the
|
||||
|
||||
Reference in New Issue
Block a user