feat(quick-261009-dkv): Links im Teilen-Dialog, Von mir geteilt und Mit mir geteilt, Oberflaeche

- Link-Formular: Berechtigung (Ansehen, Bearbeiten, Nur hochladen), Passwort verlangt oder
  freiwillig mit Passwort erzeugen, Ablaufdatum verlangt (vorbelegt, begrenzt) oder frei,
  Bezeichnung; Link kopieren, aendern, loeschen mit Rueckfrage
- Ansichten Von mir geteilt und Mit mir geteilt mit offenen Freigaben (Annehmen, Ablehnen),
  Oeffnen in der Dateiansicht (initialPath/initialFocus), Freigabe verlassen
- Reiter fuer jeden verbundenen Benutzer, ausgeblendet nur wenn die Nextcloud das Teilen ausschaltet
- Hilfen (Passwortmodus, Ablaufregel, Datumsrechnung, Aenderungsunterschied), Web-Client, Meldungen de/en

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-09 11:19:39 +02:00
parent fe429dbe01
commit c3adf851b8
17 changed files with 2452 additions and 37 deletions
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
acceptShare,
checkDownload,
checkZip,
createFolder,
@@ -9,6 +10,8 @@ import {
downloadUrl,
getSharePolicy,
listFolder,
listMyShares,
listReceivedShares,
listSharesForPath,
moveEntry,
NextcloudFilesRequestError,
@@ -226,4 +229,43 @@ describe('nextcloud-files-api — Teilen', () => {
expect(err.code).toBe('shareRejected');
expect(err.extra).toEqual({ ncMessage: 'Nein' });
});
it('createShare fuer einen Link sendet Passwort und Ablauf nur im JSON-Koerper', async () => {
const fetchMock = mockFetch(201, { id: '2' });
await createShare({
path: '/P',
kind: 'link',
access: 'view',
password: 'Geheim-1',
expireDate: '',
label: 'Kunde',
});
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
expect(url).toBe(`${API}/modules/nextcloud-files/shares`);
expect(init.body).toBe(
'{"path":"/P","kind":"link","access":"view","password":"Geheim-1","expireDate":"","label":"Kunde"}',
);
});
it('listMyShares und listReceivedShares: GET auf die festen Pfade', async () => {
const mine = mockFetch(200, { shares: [], hidden: 0, truncated: false });
await listMyShares();
expect((mine.mock.calls[0] as [string, RequestInit])[0]).toBe(
`${API}/modules/nextcloud-files/shares/mine`,
);
const received = mockFetch(200, { shares: [], pending: [], hidden: 0, truncated: false });
await listReceivedShares();
expect((received.mock.calls[0] as [string, RequestInit])[0]).toBe(
`${API}/modules/nextcloud-files/shares/received`,
);
});
it('acceptShare: POST auf shares/<Kennung>/accept ohne Koerper', async () => {
const fetchMock = mockFetch(200, { accepted: true });
await acceptShare('17');
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
expect(url).toBe(`${API}/modules/nextcloud-files/shares/17/accept`);
expect(init.method).toBe('POST');
expect(init.body).toBeUndefined();
});
});
+40 -2
View File
@@ -384,15 +384,38 @@ export interface NcShareList {
truncated: boolean;
}
/**
* Neue Freigabe. Bei Links: `password` (nie in einer Adresse), `expireDate` (`YYYY-MM-DD` oder
* `''` fuer "ohne Ablauf") und `label`; bei Personen und Gruppen `shareWith`.
*/
export interface CreateShareInput {
path: string;
kind: 'user' | 'group';
shareWith: string;
kind: NcShareKind;
shareWith?: string;
access: 'view' | 'edit' | 'upload';
password?: string;
expireDate?: string;
label?: string;
}
/** Leere Zeichenkette heisst: Passwort entfernen, Ablaufdatum entfernen, Bezeichnung leeren. */
export interface UpdateShareInput {
access?: 'view' | 'edit' | 'upload';
password?: string;
expireDate?: string;
label?: string;
}
/** Eigene Freigaben: Personen, Gruppen, Links. */
export interface NcMyShares {
shares: NcShare[];
hidden: number;
truncated: boolean;
}
/** Mit mir geteilt: angenommene und noch offene (`pending`) Freigaben. */
export interface NcReceivedShares extends NcMyShares {
pending: NcShare[];
}
export function getSharePolicy(): Promise<NcSharePolicy> {
@@ -425,3 +448,18 @@ export function updateShare(id: string, input: UpdateShareInput): Promise<NcShar
export function deleteShare(id: string): Promise<{ deleted: true }> {
return request<{ deleted: true }>(`/shares/${encodeURIComponent(id)}`, { method: 'DELETE' });
}
export function listMyShares(): Promise<NcMyShares> {
return request<NcMyShares>('/shares/mine');
}
export function listReceivedShares(): Promise<NcReceivedShares> {
return request<NcReceivedShares>('/shares/received');
}
/** Eine offene Freigabe annehmen; Ablehnen und Verlassen laufen ueber `deleteShare`. */
export function acceptShare(id: string): Promise<{ accepted: true }> {
return request<{ accepted: true }>(`/shares/${encodeURIComponent(id)}/accept`, {
method: 'POST',
});
}