feat(fileDto, folderDto): add is_favorite + is_shared
- provide is_shared and is_favorite information in DTO, information propagated as badge/buttons per items
regarding performances I try to be minimalis on SQL to prevent any perf regression
- remove old set of sharedids and favoriteids (was not functionnal anymore)
- fix date picker (no past selection) in grant
- fix contextmenu close on /files section
This commit is contained in:
@@ -29,9 +29,7 @@ function fakeRes(opts: { status: number; body?: ResourcePage }): Response {
|
||||
|
||||
const emptyListing = (): FolderListing => ({
|
||||
folders: [],
|
||||
files: [],
|
||||
favoriteIds: [],
|
||||
sharedIds: []
|
||||
files: []
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -56,7 +54,6 @@ describe('fetchFolderListing (cursor-paginated /resources)', () => {
|
||||
expect(r.status).toBe(200);
|
||||
expect(r.listing?.folders.map((f) => f.id)).toEqual(['d1']);
|
||||
expect(r.listing?.files.map((f) => f.id)).toEqual(['x1']);
|
||||
expect(r.listing?.favoriteIds).toEqual([]);
|
||||
expect(vi.mocked(apiFetch).mock.calls[0][0]).toContain('/api/folders/f1/resources');
|
||||
});
|
||||
|
||||
@@ -124,9 +121,7 @@ describe('folder name cache (breadcrumbs)', () => {
|
||||
it("learns its children's names from a cached listing", () => {
|
||||
cacheFolder('nc-parent', {
|
||||
folders: [folder('nc-a', 'Alpha'), folder('nc-b', 'Beta')],
|
||||
files: [],
|
||||
favoriteIds: [],
|
||||
sharedIds: []
|
||||
files: []
|
||||
});
|
||||
expect(getFolderName('nc-a')).toBe('Alpha');
|
||||
expect(getFolderName('nc-b')).toBe('Beta');
|
||||
|
||||
@@ -13,10 +13,9 @@ const NO_CACHE: RequestInit = {
|
||||
export interface FolderListing {
|
||||
folders: FolderItem[];
|
||||
files: FileItem[];
|
||||
/** Ids in this listing the caller has favorited (server-computed badge set). */
|
||||
favoriteIds: string[];
|
||||
/** Ids in this listing the caller has an outgoing share/grant on. */
|
||||
sharedIds: string[];
|
||||
// Legacy `favoriteIds` / `sharedIds` fields are gone — the same
|
||||
// signals now live inline on every `FileItem` / `FolderItem`
|
||||
// (`is_favorite`, `is_shared`). Consumers read those directly.
|
||||
}
|
||||
|
||||
/** Result of a (possibly conditional) listing fetch. */
|
||||
@@ -213,13 +212,13 @@ export async function fetchFolderListing(
|
||||
files.push(...page.files);
|
||||
cursor = page.nextCursor;
|
||||
} while (cursor);
|
||||
return { status: 200, listing: { folders, files, favoriteIds: [], sharedIds: [] } };
|
||||
return { status: 200, listing: { folders, files } };
|
||||
}
|
||||
|
||||
/** Non-conditional listing fetch (e.g. the move-dialog folder tree). */
|
||||
export async function listFolder(folderId: string, forceRefresh = false): Promise<FolderListing> {
|
||||
const res = await fetchFolderListing(folderId, { forceRefresh });
|
||||
return res.listing ?? { folders: [], files: [], favoriteIds: [], sharedIds: [] };
|
||||
return res.listing ?? { folders: [], files: [] };
|
||||
}
|
||||
|
||||
export async function createFolder(name: string, parentId: string | null): Promise<FolderItem> {
|
||||
|
||||
@@ -89,6 +89,21 @@ export function expiryToIso(date: string | null | undefined): string | null {
|
||||
return date ? new Date(`${date}T00:00:00Z`).toISOString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Today's date in YYYY-MM-DD form (local time zone). Used as the `min`
|
||||
* attribute on grant / share expiry date inputs so the native picker
|
||||
* refuses to select a past date. Callers should also validate the
|
||||
* changed value in their `onchange` handler as a belt-and-braces guard
|
||||
* (some browsers still let scripted / paste input bypass `min`).
|
||||
*/
|
||||
export function todayIso(): string {
|
||||
const now = new Date();
|
||||
const y = now.getFullYear();
|
||||
const m = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(now.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
export function fetchGrantsForResource(type: GrantResourceType, id: string): Promise<Grant[]> {
|
||||
const params = new URLSearchParams({ resource_type: type, resource_id: id });
|
||||
return apiJson<Grant[]>(`/api/grants?${params}`, { credentials: 'same-origin' });
|
||||
|
||||
@@ -48,6 +48,21 @@ export interface FolderItem {
|
||||
* leading segment being a drive-root folder id.
|
||||
*/
|
||||
drive_id: string;
|
||||
/**
|
||||
* Caller-scoped: `true` when the requesting user has favorited
|
||||
* this folder. Always present on the wire — never null, never
|
||||
* absent — per the backend enrichment contract. `ResourceList`
|
||||
* renders the fav-star chip natively from this field.
|
||||
*/
|
||||
is_favorite: boolean;
|
||||
/**
|
||||
* Resource-scoped: `true` when the folder has any
|
||||
* `storage.role_grants` entry (link share via `subject_type =
|
||||
* 'token'`, user grant, group grant, any role). "Someone was
|
||||
* given access to this beyond drive membership." Always present
|
||||
* on the wire.
|
||||
*/
|
||||
is_shared: boolean;
|
||||
}
|
||||
|
||||
export interface FileItem {
|
||||
@@ -70,6 +85,10 @@ export interface FileItem {
|
||||
sort_date: number;
|
||||
etag: string;
|
||||
content_hash: string;
|
||||
/** See `FolderItem.is_favorite` — same wire contract. */
|
||||
is_favorite: boolean;
|
||||
/** See `FolderItem.is_shared` — same wire contract. */
|
||||
is_shared: boolean;
|
||||
/** Search-only: plain-text fragment around a content match. */
|
||||
snippet?: string;
|
||||
/** Search-only: "name" or "content". */
|
||||
|
||||
Reference in New Issue
Block a user