Skip to content

Commit 8683c25

Browse files
Merge pull request #13 from solid/feat/my-storages
fix: fixed baseIRI parser issue
2 parents 7297a8b + 16d11d0 commit 8683c25

6 files changed

Lines changed: 215 additions & 258 deletions

File tree

app/components/FileManager.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,20 @@ export default function FileManager() {
216216
setShowPreviewModal(true);
217217
};
218218

219-
const storageFiles: FileItemData[] = filterProfileItems(storages).map((storage) => ({
219+
console.log("storages:", storages);
220+
221+
222+
// const storageFiles: FileItemData[] = filterProfileItems(storages).map((storage) => ({
223+
const storageFiles: FileItemData[] = storages.map((storage) => ({
220224
id: storage.id,
221225
name: storage.name,
222226
type: "folder" as const,
223227
url: storage.url,
224228
}));
229+
console.log("storageFiles:", storageFiles);
225230

226-
const filteredFiles = filterProfileItems(browsedFiles);
227-
const displayFiles = selectedStorageId ? filteredFiles : storageFiles;
231+
// const filteredFiles = filterProfileItems(browsedFiles);
232+
const displayFiles = selectedStorageId ? browsedFiles : storageFiles;
228233

229234
const selectedStorage = storages.find((s) => s.id === selectedStorageId);
230235
const breadcrumbItems = buildBreadcrumbItems(

app/components/NewFolderDialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useState, useEffect, useRef } from "react";
44
import Modal from "./shared/Modal";
55
import Button from "./shared/Button";
6+
import Input from "./shared/Input";
67
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
78
import { createContainerAt, getSolidDataset, UrlString } from "@inrupt/solid-client";
89
import toast from "react-hot-toast";
@@ -129,14 +130,13 @@ export default function NewFolderDialog({
129130
}
130131
>
131132
<div className="py-2">
132-
<input
133+
<Input
133134
ref={inputRef}
134135
type="text"
135136
value={folderName}
136-
onChange={(e) => setFolderName(e.target.value)}
137+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setFolderName(e.target.value)}
137138
onKeyDown={handleKeyDown}
138139
placeholder="Untitled folder"
139-
className="w-full h-10 rounded-md border border-gray-300 bg-white px-3 text-sm text-black placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-[#7B42F6] focus:border-[#7B42F6] transition-colors disabled:bg-gray-50 disabled:text-gray-500"
140140
disabled={isCreating}
141141
/>
142142
</div>

app/lib/helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export * from "./fileFilters";
55
export * from "./urlUtils";
66
export * from "./breadcrumbUtils";
77
export * from "./fileTypeUtils";
8+
export * from "./profileUtils";
89

app/lib/helpers/profileUtils.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
2+
import { Parser, Store, NamedNode } from "n3";
3+
4+
// Cache for parsed profile documents
5+
const profileCache = new Map<string, { store: Store; baseUrl: string; mainSubject: NamedNode }>();
6+
7+
/**
8+
* Fetches and parses the WebID profile document, with caching to avoid duplicate fetches
9+
* @param webId - The WebID to fetch
10+
* @returns The parsed RDF store, base URL, and main subject
11+
*/
12+
export async function fetchAndParseProfile(
13+
webId: string
14+
): Promise<{ store: Store; baseUrl: string; mainSubject: NamedNode }> {
15+
// Check cache first
16+
if (profileCache.has(webId)) {
17+
return profileCache.get(webId)!;
18+
}
19+
20+
const session = getDefaultSession();
21+
const fetchFn = session.fetch || fetch;
22+
23+
// Try different Accept headers to get the profile
24+
const acceptHeaders = [
25+
'text/turtle, application/turtle, text/n3, application/n3',
26+
'text/turtle',
27+
'application/ld+json',
28+
];
29+
30+
let content: string | null = null;
31+
let contentType: string = '';
32+
33+
for (const acceptHeader of acceptHeaders) {
34+
try {
35+
const response = await fetchFn(webId, {
36+
method: 'GET',
37+
headers: {
38+
'Accept': acceptHeader,
39+
},
40+
});
41+
42+
if (response.ok) {
43+
contentType = response.headers.get('content-type') || '';
44+
content = await response.text();
45+
break;
46+
}
47+
} catch (err) {
48+
continue;
49+
}
50+
}
51+
52+
if (!content) {
53+
throw new Error("Failed to fetch profile document with any Accept header");
54+
}
55+
56+
// Parse the RDF content
57+
const store = new Store();
58+
const baseUrl = webId.split('#')[0];
59+
60+
if (contentType.includes('text/turtle') || contentType.includes('application/turtle') ||
61+
contentType.includes('text/n3') || contentType.includes('application/n3')) {
62+
const parser = new Parser({ baseIRI: baseUrl });
63+
const quads = parser.parse(content);
64+
store.addQuads(quads);
65+
} else if (contentType.includes('application/ld+json')) {
66+
// Try parsing as Turtle anyway (most servers return Turtle even if JSON-LD is requested)
67+
try {
68+
const parser = new Parser({ baseIRI: baseUrl });
69+
const quads = parser.parse(content);
70+
store.addQuads(quads);
71+
} catch (e) {
72+
// Silent error handling
73+
}
74+
}
75+
76+
// Find the main subject - try different variants
77+
const FOAF_NAME = "http://xmlns.com/foaf/0.1/name";
78+
const subjectVariants = [
79+
new NamedNode(webId),
80+
new NamedNode(baseUrl + '#me'),
81+
new NamedNode('#me'),
82+
new NamedNode(baseUrl + '#card'),
83+
];
84+
85+
let mainSubject: NamedNode | null = null;
86+
87+
for (const subject of subjectVariants) {
88+
const nameQuads = store.getQuads(subject, new NamedNode(FOAF_NAME), null, null);
89+
if (nameQuads.length > 0) {
90+
mainSubject = subject;
91+
break;
92+
}
93+
}
94+
95+
// If still not found, try to find Person type
96+
if (!mainSubject) {
97+
const personType = new NamedNode('http://xmlns.com/foaf/0.1/Person');
98+
const personQuads = store.getQuads(null, new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), personType, null);
99+
if (personQuads.length > 0 && personQuads[0].subject.termType === 'NamedNode') {
100+
mainSubject = personQuads[0].subject as NamedNode;
101+
}
102+
}
103+
104+
// Fallback to WebID itself
105+
if (!mainSubject) {
106+
mainSubject = new NamedNode(webId);
107+
}
108+
109+
// Cache the result
110+
const result = { store, baseUrl, mainSubject };
111+
profileCache.set(webId, result);
112+
113+
return result;
114+
}
115+
116+
/**
117+
* Clears the profile cache (useful for testing or when profile might have changed)
118+
*/
119+
export function clearProfileCache(webId?: string): void {
120+
if (webId) {
121+
profileCache.delete(webId);
122+
} else {
123+
profileCache.clear();
124+
}
125+
}
126+

0 commit comments

Comments
 (0)