Skip to content

Commit e669fb8

Browse files
feat: add webid to contact after sharing
fix: enable browser back button navigation through folders
1 parent ea6e6d4 commit e669fb8

3 files changed

Lines changed: 140 additions & 13 deletions

File tree

app/components/FileManager.tsx

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,36 @@ export default function FileManager() {
119119
});
120120
};
121121

122+
// Handle URL changes from browser back/forward buttons
123+
useEffect(() => {
124+
if (isLoadingStorages || storages.length === 0 || !isInitialized) {
125+
return;
126+
}
127+
128+
const urlParam = getUrlFromSearchParams();
129+
130+
if (!urlParam) {
131+
// No URL in params - reset to root if we have a storage selected
132+
if (selectedStorageId) {
133+
const storage = storages.find((s) => s.id === selectedStorageId);
134+
if (storage) {
135+
setCurrentPath("/");
136+
removeUrlFromStorage();
137+
}
138+
}
139+
return;
140+
}
141+
142+
// URL changed - update state to match
143+
const matchingStorage = storages.find((s) => urlParam === s.url || urlParam.startsWith(s.url));
144+
145+
if (matchingStorage) {
146+
setSelectedStorageId(matchingStorage.id);
147+
setCurrentPath(urlParam === matchingStorage.url ? "/" : urlParam);
148+
saveUrlToStorage(urlParam);
149+
}
150+
}, [searchParams, storages, isLoadingStorages, isInitialized, selectedStorageId]);
151+
122152
useEffect(() => {
123153
if (isLoadingStorages || storages.length === 0 || isInitialized) {
124154
return;
@@ -187,19 +217,28 @@ export default function FileManager() {
187217
};
188218
}, [contextMenuState]);
189219

190-
const updateUrl = (url: string | null) => {
220+
const updateUrl = (url: string | null, addToHistory: boolean = true) => {
191221
if (!url || url === "/") {
192222
removeUrlFromStorage();
193223
if (typeof window !== "undefined" && window.location.search) {
194-
router.replace("/", { scroll: false });
224+
if (addToHistory) {
225+
router.push("/", { scroll: false });
226+
} else {
227+
router.replace("/", { scroll: false });
228+
}
195229
}
196230
return;
197231
}
198232

199233
const params = new URLSearchParams();
200234
params.set("url", safeEncodeUrl(url));
201235
saveUrlToStorage(url);
202-
router.replace(`/?${params.toString()}`, { scroll: false });
236+
237+
if (addToHistory) {
238+
router.push(`/?${params.toString()}`, { scroll: false });
239+
} else {
240+
router.replace(`/?${params.toString()}`, { scroll: false });
241+
}
203242
};
204243

205244
const containerUrlToBrowse = selectedStorageId
@@ -273,7 +312,7 @@ export default function FileManager() {
273312

274313
if (fileToRename && currentPath === fileToRename.url) {
275314
setCurrentPath(newUrl);
276-
updateUrl(newUrl);
315+
updateUrl(newUrl, false);
277316
}
278317
// Trigger refresh to update file list immediately
279318
setRefreshKey((prev) => prev + 1);
@@ -600,11 +639,11 @@ export default function FileManager() {
600639
setSelectedStorageId(file.id);
601640
setCurrentPath("/");
602641
setSelectedFileIds([]);
603-
updateUrl(file.url);
642+
updateUrl(file.url, true);
604643
} else if (selectedStorageId) {
605644
setCurrentPath(file.url);
606645
setSelectedFileIds([]);
607-
updateUrl(file.url);
646+
updateUrl(file.url, true);
608647
}
609648
} else {
610649
window.open(file.url, "_blank");
@@ -725,15 +764,15 @@ export default function FileManager() {
725764
setSelectedStorageId(null);
726765
setCurrentPath("/");
727766
setSelectedFileIds([]);
728-
updateUrl(null);
767+
updateUrl(null, true);
729768
} else {
730769
const selectedStorage = storages.find((s) => s.id === selectedStorageId);
731770
if (selectedStorage && path === selectedStorage.url) {
732771
setCurrentPath("/");
733-
updateUrl(selectedStorage.url);
772+
updateUrl(selectedStorage.url, true);
734773
} else {
735774
setCurrentPath(path);
736-
updateUrl(path);
775+
updateUrl(path, true);
737776
}
738777
setSelectedFileIds([]);
739778
}
@@ -907,8 +946,7 @@ export default function FileManager() {
907946
resourceUrl={sharedResourceUrl}
908947
resourceName={sharedResourceName}
909948
onOpenInApp={(url) => {
910-
// Navigate to the resource URL in the file manager
911-
updateUrl(url);
949+
updateUrl(url, true);
912950
}}
913951
/>
914952
{isDragActive && (

app/components/ShareDialog.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Modal from "./shared/Modal";
55
import Button from "./shared/Button";
66
import UrlCombobox, { ComboboxOption } from "./shared/UrlCombobox";
77
import { FileItemData } from "./FileItem";
8-
import { fetchUserContacts, Contact } from "../lib/helpers/contactUtils";
8+
import { fetchUserContacts, Contact, addContactToProfile } from "../lib/helpers/contactUtils";
99
import { fetchAndParseProfile, extractNameAndEmail } from "../lib/helpers/profileUtils";
1010
import { getResourceAccessList } from "../lib/helpers/acpUtils";
1111
import { UserIcon, MagnifyingGlassIcon, LockClosedIcon, XMarkIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
@@ -165,6 +165,28 @@ export default function ShareDialog({
165165
const webIds = peopleChips.map((chip) => chip.webId);
166166
await onShare(webIds, selectedAccessLevel);
167167

168+
// After successful sharing, add new WebIDs to contacts if they're not already there
169+
const existingContactWebIds = new Set(contacts.map(c => c.webId));
170+
const newWebIds = webIds.filter(webId => !existingContactWebIds.has(webId));
171+
172+
for (const webId of newWebIds) {
173+
try {
174+
await addContactToProfile(webId);
175+
} catch (error) {
176+
console.warn(`Failed to add ${webId} to contacts:`, error);
177+
}
178+
}
179+
180+
// Refresh contacts list if we added any new ones
181+
if (newWebIds.length > 0) {
182+
try {
183+
const updatedContacts = await fetchUserContacts();
184+
setContacts(updatedContacts);
185+
} catch (error) {
186+
console.warn("Failed to refresh contacts list:", error);
187+
}
188+
}
189+
168190
// Refresh access list after sharing
169191
if (file) {
170192
const resourceUrl = file.type === "folder" && !file.url.endsWith("/") ? file.url + "/" : file.url;

app/lib/helpers/contactUtils.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { NamedNode } from "n3";
22
import { fetchAndParseProfile, extractNameAndEmail, getCachedProfile } from "./profileUtils";
3-
import { getSession } from "./sessionUtils";
3+
import { getSession, getAuthenticatedSession } from "./sessionUtils";
4+
import {
5+
getSolidDataset,
6+
getThing,
7+
createThing,
8+
addUrl,
9+
getUrlAll,
10+
setThing,
11+
saveSolidDatasetAt,
12+
UrlString
13+
} from "@inrupt/solid-client";
414

515
export interface Contact {
616
webId: string;
@@ -75,3 +85,60 @@ export async function fetchUserContacts(): Promise<Contact[]> {
7585
return [];
7686
}
7787
}
88+
89+
/**
90+
* Adds a contact (WebID) to the user's profile using foaf:knows relationship
91+
* @param contactWebId - The WebID of the contact to add
92+
* @returns Promise that resolves when the contact is added
93+
*/
94+
export async function addContactToProfile(contactWebId: string): Promise<void> {
95+
const session = getSession();
96+
97+
if (!session.info.isLoggedIn || !session.info.webId) {
98+
throw new Error("User is not logged in");
99+
}
100+
101+
const userWebId = session.info.webId;
102+
const { fetch } = getAuthenticatedSession();
103+
104+
const profileUrl = userWebId.split('#')[0] as UrlString;
105+
106+
try {
107+
// Fetch the user's profile dataset
108+
let dataset = await getSolidDataset(profileUrl, { fetch });
109+
110+
// Get the main subject (usually WebID or WebID#me)
111+
const mainSubject = userWebId as UrlString;
112+
let thing = getThing(dataset, mainSubject);
113+
114+
// If thing doesn't exist, try with #me fragment
115+
if (!thing) {
116+
const meSubject = `${profileUrl}#me` as UrlString;
117+
thing = getThing(dataset, meSubject);
118+
}
119+
120+
// If still doesn't exist, create a new thing
121+
if (!thing) {
122+
thing = createThing({ url: mainSubject });
123+
}
124+
125+
// Check if the contact is already in the knows list
126+
const existingKnows = getUrlAll(thing, FOAF_KNOWS);
127+
if (existingKnows.includes(contactWebId)) {
128+
// Contact already exists, no need to add
129+
return;
130+
}
131+
132+
// Add the foaf:knows relationship
133+
thing = addUrl(thing, FOAF_KNOWS, contactWebId as UrlString);
134+
135+
// Update the dataset
136+
const updatedDataset = setThing(dataset, thing);
137+
138+
// Save the updated dataset
139+
await saveSolidDatasetAt(profileUrl, updatedDataset, { fetch });
140+
} catch (error) {
141+
console.error("Failed to add contact to profile:", error);
142+
throw error;
143+
}
144+
}

0 commit comments

Comments
 (0)