web, core, desktop: share a note with a group, and Settings → Groups

The Share dialog lists people and groups in one picker and shows a group share
as its name and member count. Settings gains a Groups section for the admin:
create, rename, delete, and add or remove people.

The core client reads the directory's groups and group shares (ShareTarget:
a member or a group); the desktop command takes user_id or group_id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 18:25:16 -04:00
co-authored by Claude Opus 5.5
parent 5f3cfe8bb7
commit 442cca4398
9 changed files with 413 additions and 57 deletions
+9 -3
View File
@@ -16,7 +16,7 @@ import type { Device } from "../stores/devices";
import type { TitleEntry } from "../stores/titles";
import type { User } from "../stores/session";
import type { PublicConfig } from "../stores/config";
import type { DeviceToken, ImportResult, Member, NoteShare, Repo } from "./repo";
import type { DeviceToken, Directory, ImportResult, NoteShare, Repo } from "./repo";
const NEEDS_SERVER = "That's not available offline — connect a server to use it.";
@@ -95,9 +95,15 @@ export const local: Repo = {
// Sharing is between accounts on a server, so these go to the linked one (#5175).
// Unlinked, each rejects with the core's explanation, which the dialog shows.
shares: {
directory: () => invoke<Member[]>("shares_directory"),
directory: () => invoke<Directory>("shares_directory"),
list: (noteId) => invoke<NoteShare[]>("shares_list", { noteId }),
share: (noteId, userId, permission) => invoke<NoteShare[]>("shares_share", { noteId, userId, permission }),
share: (noteId, target, permission) =>
invoke<NoteShare[]>("shares_share", {
noteId,
userId: "user_id" in target ? target.user_id : null,
groupId: "group_id" in target ? target.group_id : null,
permission,
}),
unshare: (noteId, shareId) => invoke<NoteShare[]>("shares_unshare", { noteId, shareId }),
},
};