solid-panes 4.4.0 → 4.4.1-test.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +4 -2
  2. package/dist/RDFXMLPane.css +70 -0
  3. package/dist/RDFXMLPane.d.ts +13 -0
  4. package/dist/RDFXMLPane.d.ts.map +1 -0
  5. package/dist/RDFXMLPane.js +46 -5
  6. package/dist/dataContentPane.css +271 -0
  7. package/dist/dataContentPane.d.ts +14 -0
  8. package/dist/dataContentPane.d.ts.map +1 -0
  9. package/dist/dataContentPane.js +68 -101
  10. package/dist/defaultPane.css +97 -0
  11. package/dist/defaultPane.d.ts +14 -0
  12. package/dist/defaultPane.d.ts.map +1 -0
  13. package/dist/defaultPane.js +9 -2
  14. package/dist/form/formPane.css +120 -0
  15. package/dist/form/pane.d.ts +4 -0
  16. package/dist/form/pane.d.ts.map +1 -0
  17. package/dist/form/pane.js +120 -54
  18. package/dist/humanReadablePane.css +129 -0
  19. package/dist/humanReadablePane.d.ts +13 -0
  20. package/dist/humanReadablePane.d.ts.map +1 -0
  21. package/dist/humanReadablePane.js +29 -18
  22. package/dist/icons/signUp.svg +9 -0
  23. package/dist/imagePane.css +4 -0
  24. package/dist/imagePane.d.ts +12 -0
  25. package/dist/imagePane.d.ts.map +1 -0
  26. package/dist/imagePane.js +19 -21
  27. package/dist/internal/internalPane.css +14 -0
  28. package/dist/internal/internalPane.d.ts +1 -0
  29. package/dist/internal/internalPane.d.ts.map +1 -1
  30. package/dist/internal/internalPane.js +5 -6
  31. package/dist/mainPage/header.js +2 -2
  32. package/dist/mainPage/index.d.ts +2 -1
  33. package/dist/mainPage/index.d.ts.map +1 -1
  34. package/dist/mainPage/index.js +23 -0
  35. package/dist/mainPage/menu.d.ts.map +1 -1
  36. package/dist/mainPage/menu.js +29 -2
  37. package/dist/n3Pane.css +49 -0
  38. package/dist/n3Pane.d.ts +13 -0
  39. package/dist/n3Pane.d.ts.map +1 -0
  40. package/dist/n3Pane.js +36 -5
  41. package/dist/outline/manager.js +20 -1
  42. package/dist/pad/padPane.css +6 -2
  43. package/dist/pad/padPane.js +1 -1
  44. package/dist/registerPanes.js +8 -8
  45. package/dist/schedule/schedulePane.css +294 -0
  46. package/dist/schedule/schedulePane.d.ts +23 -0
  47. package/dist/schedule/schedulePane.d.ts.map +1 -0
  48. package/dist/schedule/schedulePane.js +161 -61
  49. package/dist/social/editProfileDetails.d.ts +3 -3
  50. package/dist/social/editProfileDetails.d.ts.map +1 -1
  51. package/dist/social/editProfileDetails.js +222 -127
  52. package/dist/social/icons.d.ts +2 -0
  53. package/dist/social/icons.d.ts.map +1 -1
  54. package/dist/social/icons.js +39 -4
  55. package/dist/social/socialPane.css +838 -178
  56. package/dist/social/socialPane.d.ts.map +1 -1
  57. package/dist/social/socialPane.js +136 -43
  58. package/dist/social/socialSections.d.ts +11 -0
  59. package/dist/social/socialSections.d.ts.map +1 -1
  60. package/dist/social/socialSections.js +138 -62
  61. package/dist/social/spinner.d.ts +3 -0
  62. package/dist/social/spinner.d.ts.map +1 -0
  63. package/dist/social/spinner.js +13 -0
  64. package/dist/social/triage.d.ts +17 -0
  65. package/dist/social/triage.d.ts.map +1 -0
  66. package/dist/social/triage.js +79 -0
  67. package/dist/solid-panes.js +25772 -9576
  68. package/dist/solid-panes.js.map +1 -1
  69. package/dist/solid-panes.min.js +2583 -927
  70. package/dist/solid-panes.min.js.map +1 -1
  71. package/dist/{style → styles}/tabbedtab.css +0 -157
  72. package/dist/styles/utilities.css +5 -0
  73. package/dist/versionInfo.js +13 -13
  74. package/package.json +27 -26
  75. package/dist/icons/signup.png +0 -0
@@ -6,14 +6,16 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.createAllFriendsSection = createAllFriendsSection;
7
7
  exports.createHeaderSection = createHeaderSection;
8
8
  exports.createMutualSection = createMutualSection;
9
+ exports.createRequestsSection = createRequestsSection;
9
10
  var _profilePane = require("profile-pane");
11
+ var _rdflib = require("rdflib");
10
12
  var _solidUi = require("solid-ui");
11
13
  var _editProfileDetails = require("./editProfileDetails");
12
14
  var _icons = require("./icons");
13
15
  function createHeaderSection(context, subject, controls, stats, getProfileData) {
14
16
  const dom = context.dom;
15
17
  const kb = context.session.store;
16
- const header = document.createElement('header');
18
+ const header = dom.createElement('header');
17
19
  header.className = 'social-pane__header';
18
20
  let headerControls = controls;
19
21
  const renderHeader = function () {
@@ -21,34 +23,34 @@ function createHeaderSection(context, subject, controls, stats, getProfileData)
21
23
  const profileData = getProfileData();
22
24
  const headerContent = dom.createElement('div');
23
25
  headerContent.className = 'social-pane__header-content';
26
+ const headerActions = dom.createElement('div');
27
+ headerActions.className = 'social-pane__header-actions profile__actions profile__heading-actions';
24
28
  if (headerControls.canEdit) {
25
- header.appendChild((0, _editProfileDetails.createEditProfileDetailsButton)({
26
- dom,
27
- store: kb,
28
- subject,
29
- header,
30
- onSaved: renderHeader
31
- }));
32
- } else if (headerControls.viewerMode === 'authenticated') {
29
+ // Hidden for now because the social pane header edit control does not match the new design.
30
+ // Revisit later if we decide to restore profile-link editing here.
31
+ } else if (headerControls.viewerMode === 'authenticated' && headerControls.showAddFriendAction) {
32
+ headerActions.classList.add('social-pane__header-actions--friend');
33
33
  const addToFriendsButton = (0, _profilePane.createAddMeToYourFriendsButton)(subject, context);
34
- addToFriendsButton.classList.add('social-pane__friend-action', 'profile__action-button', 'profile__btn-friends', 'flex-center');
35
- // header.appendChild(addToFriendsButton)
36
- headerContent.appendChild(addToFriendsButton);
34
+ addToFriendsButton.classList.add('flex-center');
35
+ headerActions.appendChild(addToFriendsButton);
37
36
  }
38
37
  header.appendChild(headerContent);
39
38
  const headerMedia = dom.createElement('div');
40
39
  headerMedia.className = 'social-pane__header-media';
41
40
  headerContent.appendChild(headerMedia);
42
41
  if (profileData) {
43
- headerMedia.appendChild(createImage(profileData.imageUrl, profileData.name));
42
+ headerMedia.appendChild(createImage(dom, profileData.imageUrl, profileData.name));
44
43
  }
45
44
  const headerDetails = dom.createElement('div');
46
45
  headerDetails.className = 'social-pane__header-details';
47
46
  headerContent.appendChild(headerDetails);
47
+ if (headerActions.childNodes.length > 0) {
48
+ headerContent.appendChild(headerActions);
49
+ }
48
50
  const headerSummary = dom.createElement('div');
49
51
  headerSummary.classList.add('social-pane__header-summary', 'flex-column');
50
52
  headerDetails.appendChild(headerSummary);
51
- const name = profileData?.name || '???';
53
+ const name = profileData?.name || _solidUi.utils.label(subject);
52
54
  const h1 = dom.createElement('h1');
53
55
  h1.classList.add('social-pane__header-name');
54
56
  h1.appendChild(dom.createTextNode(name));
@@ -106,9 +108,9 @@ function createHeaderSection(context, subject, controls, stats, getProfileData)
106
108
  renderHeader();
107
109
  return header;
108
110
  }
109
- function createImage(src, alt = '') {
111
+ function createImage(dom, src, alt = '') {
110
112
  if (src) {
111
- const img = document.createElement('img');
113
+ const img = dom.createElement('img');
112
114
  img.className = 'social-pane__header-hero';
113
115
  img.src = src;
114
116
  img.alt = alt;
@@ -117,12 +119,12 @@ function createImage(src, alt = '') {
117
119
  img.loading = 'eager';
118
120
  return img;
119
121
  }
120
- const fallback = document.createElement('div');
122
+ const fallback = dom.createElement('div');
121
123
  fallback.className = 'social-pane__header-hero-alt flex-center';
122
124
  fallback.setAttribute('role', 'img');
123
125
  fallback.setAttribute('aria-label', alt);
124
126
  fallback.tabIndex = 0;
125
- const icon = document.createElement('span');
127
+ const icon = dom.createElement('span');
126
128
  icon.classList.add('social-pane__header-hero-icon', 'inline-flex-row');
127
129
  icon.innerHTML = _icons.personInCircleIcon;
128
130
  fallback.appendChild(icon);
@@ -133,20 +135,20 @@ function createMutualSection(options) {
133
135
  dom,
134
136
  subject,
135
137
  familiar,
138
+ me,
136
139
  meUri,
137
140
  incoming,
138
141
  outgoing,
142
+ editable,
143
+ profile,
144
+ knows,
139
145
  mutualConnections,
140
146
  link,
141
147
  text,
148
+ buildCheckboxForm,
142
149
  renderSupportingInfo,
143
150
  renderNameSuffix
144
151
  } = options;
145
-
146
- // Mutual confirm UI is intentionally hidden for now.
147
- // The related options remain in the function contract so that block can be
148
- // restored later without reshaping callers.
149
-
150
152
  let refreshMutualFriends = function () {};
151
153
  const mutualSection = dom.createElement('section');
152
154
  mutualSection.className = 'social-pane__mutual-friends social-primary__panel';
@@ -174,49 +176,38 @@ function createMutualSection(options) {
174
176
  if (!outgoing) {
175
177
  const line = youAndThem();
176
178
  line.appendChild(text(' have not said you know each other.'));
177
- /* NOTE: hiding the outgoing-only unconfirmed message for now.
178
- } else {
179
- relationshipSummary.appendChild(link(text('You'), meUri))
180
- relationshipSummary.appendChild(text(' know '))
181
- relationshipSummary.appendChild(link(text(familiar), subject.uri))
182
- relationshipSummary.appendChild(text(' (unconfirmed)'))
183
- */
179
+ } else {
180
+ const line = createRelationshipLine();
181
+ line.appendChild(link(text('You'), meUri));
182
+ line.appendChild(text(' know '));
183
+ line.appendChild(link(text(familiar), subject.uri));
184
+ line.appendChild(text(' (unconfirmed).'));
184
185
  }
185
- /* NOTE: hiding the incoming-only unconfirmed message for now.
186
- } else if (!outgoing) {
187
- relationshipSummary.classList.add('social-mutual-summary--confirm')
188
- const incomingLine = relationshipSummary.appendChild(dom.createElement('div'))
189
- incomingLine.classList.add('social-mutual-summary-line')
190
- incomingLine.appendChild(link(text(familiar), subject.uri))
191
- incomingLine.appendChild(text(' knows '))
192
- incomingLine.appendChild(link(text('you'), meUri))
193
- incomingLine.appendChild(text(' (unconfirmed).'))
194
- */
186
+ } else if (!outgoing) {
187
+ relationshipSummary.classList.add('social-mutual-summary--confirm');
188
+ const incomingLine = relationshipSummary.appendChild(dom.createElement('div'));
189
+ incomingLine.classList.add('social-mutual-summary-line');
190
+ incomingLine.appendChild(link(text(familiar), subject.uri));
191
+ incomingLine.appendChild(text(' knows '));
192
+ incomingLine.appendChild(link(text('you'), meUri));
193
+ incomingLine.appendChild(text(' (unconfirmed).'));
195
194
  } else {
196
195
  const line = youAndThem();
197
196
  line.appendChild(text(' say you know each other.'));
198
197
  }
199
-
200
- /* NOTE: hiding the confirm-friend checkbox for now.
201
- const shouldShowCheckboxPreview = editable || (Boolean(incoming) && !outgoing)
198
+ const shouldShowCheckboxPreview = Boolean(incoming) && !outgoing;
202
199
  if (shouldShowCheckboxPreview) {
203
- const confirmLabel = dom.createElement('span')
204
- confirmLabel.appendChild(text('Confirm you know '))
205
- confirmLabel.appendChild(link(text(familiar), subject.uri))
206
- const relationshipForm = buildCheckboxForm(
207
- confirmLabel,
208
- new Statement(me, knows, subject, profile ?? undefined),
209
- Boolean(outgoing),
210
- {
211
- disabled: !editable,
212
- disabledTitle: !editable ? 'Your profile is not editable' : undefined
213
- }
214
- )
215
- relationshipForm.classList.add('social-mutual-checkbox-form')
216
- mutualContent.appendChild(relationshipForm)
200
+ const confirmLabel = dom.createElement('span');
201
+ confirmLabel.className = 'social-mutual-confirm-prompt';
202
+ confirmLabel.appendChild(text('Confirm you know '));
203
+ confirmLabel.appendChild(link(text(familiar), subject.uri));
204
+ const relationshipForm = buildCheckboxForm(confirmLabel, new _rdflib.Statement(me, knows, subject, profile ?? undefined), Boolean(outgoing), {
205
+ disabled: !editable,
206
+ disabledTitle: !editable ? 'Your profile is not editable' : undefined
207
+ });
208
+ relationshipForm.classList.add('social-mutual-checkbox-form');
209
+ mutualContent.appendChild(relationshipForm);
217
210
  }
218
- */
219
-
220
211
  if (mutualConnections.length) {
221
212
  const mutualFriendsTable = mutualContent.appendChild(dom.createElement('table'));
222
213
  mutualFriendsTable.className = 'social-main social-friends-list social-friends-grid';
@@ -266,6 +257,11 @@ function createAllFriendsSection(options) {
266
257
  modify: !!editable,
267
258
  predicate: _solidUi.ns.foaf('knows'),
268
259
  noun: 'friend',
260
+ // Social pane already owns the async refresh cycle for friend data in
261
+ // socialPane.ts. Leave attachmentList's generic follow-up rerender disabled
262
+ // here or each fetched friend profile will trigger an extra whole-table refresh
263
+ // on top of the pane's own batched updates.
264
+ refreshOnDocumentLoad: false,
269
265
  renderSupportingInfo,
270
266
  renderNameSuffix
271
267
  });
@@ -276,14 +272,23 @@ function createAllFriendsSection(options) {
276
272
  const friendsListRightCell = friendsListRow?.children?.[1];
277
273
  const friendsHeaderActions = dom.createElement('div');
278
274
  friendsHeaderActions.classList.add('social-friends-header-actions');
279
- if (friendsListPromptCell instanceof HTMLElement) {
275
+ if (friendsListPromptCell instanceof HTMLTableCellElement) {
276
+ friendsListPromptCell.classList.add('social-friends-header-dropzone-cell');
277
+ const friendsHeaderDropzone = dom.createElement('table');
278
+ friendsHeaderDropzone.className = 'social-friends-header-dropzone';
279
+ friendsHeaderDropzone.setAttribute('role', 'presentation');
280
+ const friendsHeaderDropzoneBody = friendsHeaderDropzone.appendChild(dom.createElement('tbody'));
281
+ const friendsHeaderDropzoneRow = friendsHeaderDropzoneBody.appendChild(dom.createElement('tr'));
282
+ friendsHeaderDropzoneRow.appendChild(friendsListPromptCell);
283
+ friendsHeaderActions.appendChild(friendsHeaderDropzone);
284
+ } else if (friendsListPromptCell instanceof HTMLElement) {
280
285
  while (friendsListPromptCell.firstChild) {
281
286
  friendsHeaderActions.appendChild(friendsListPromptCell.firstChild);
282
287
  }
283
288
  friendsListPromptCell.remove();
284
289
  }
285
- if (friendsHeaderActions.childNodes.length > 0) {
286
- const friendDropButtons = friendsHeaderActions.querySelectorAll('button');
290
+ const friendDropButtons = friendsHeaderActions.querySelectorAll('button');
291
+ if (editable && friendDropButtons.length > 0) {
287
292
  friendDropButtons.forEach(button => {
288
293
  button.setAttribute('title', 'Drop friend here');
289
294
  button.setAttribute('aria-label', 'Drop friend here');
@@ -314,4 +319,75 @@ function createAllFriendsSection(options) {
314
319
  mainTable,
315
320
  friendsList
316
321
  };
322
+ }
323
+ function createRequestsSection(options) {
324
+ const {
325
+ dom,
326
+ triage,
327
+ renderSupportingInfo,
328
+ renderNameSuffix
329
+ } = options;
330
+ const requestsSection = dom.createElement('section');
331
+ requestsSection.className = 'social-pane__requests social-primary__panel';
332
+ requestsSection.id = 'social-panel-requests';
333
+ requestsSection.setAttribute('role', 'tabpanel');
334
+ requestsSection.setAttribute('aria-labelledby', 'social-tab-requests');
335
+ const requestsContent = requestsSection.appendChild(dom.createElement('div'));
336
+ requestsContent.classList.add('social-main', 'social-main--requests', 'flex-column');
337
+ const note = requestsContent.appendChild(dom.createElement('p'));
338
+ note.className = 'social-requests__note';
339
+ note.textContent = 'Best effort preview: this view only reflects profile documents that are currently loaded. A dedicated inbox would still be needed for complete request discovery.';
340
+ const createRequestGroup = function (title, description, emptyText) {
341
+ const group = requestsContent.appendChild(dom.createElement('section'));
342
+ group.classList.add('social-requests__group', 'flex-column');
343
+ const header = group.appendChild(dom.createElement('div'));
344
+ header.className = 'social-requests__group-header';
345
+ const heading = header.appendChild(dom.createElement('h2'));
346
+ heading.className = 'social-requests__group-title';
347
+ heading.textContent = title;
348
+ const blurb = header.appendChild(dom.createElement('p'));
349
+ blurb.className = 'social-requests__group-description';
350
+ blurb.textContent = description;
351
+ const empty = group.appendChild(dom.createElement('p'));
352
+ empty.className = 'social-requests__empty';
353
+ empty.textContent = emptyText;
354
+ const table = group.appendChild(dom.createElement('table'));
355
+ table.className = 'social-main social-friends-list social-friends-grid social-requests__table';
356
+ return {
357
+ empty,
358
+ table
359
+ };
360
+ };
361
+ const incomingGroup = createRequestGroup('Incoming requests', 'People who say they know this profile, but have not been confirmed yet.', 'No incoming requests are visible from the currently loaded data.');
362
+ const pendingGroup = createRequestGroup('Awaiting confirmation', 'People this profile knows who have not linked back yet.', 'Nothing is waiting for confirmation right now.');
363
+ const createPersonRow = function (target) {
364
+ return _solidUi.widgets.personTR(dom, _solidUi.ns.foaf('knows'), target, {
365
+ renderSupportingInfo,
366
+ renderNameSuffix
367
+ });
368
+ };
369
+ const sortNodes = function (nodes) {
370
+ return [...nodes].sort((left, right) => {
371
+ const leftLabel = _solidUi.utils.label(left) || left.value;
372
+ const rightLabel = _solidUi.utils.label(right) || right.value;
373
+ return leftLabel.localeCompare(rightLabel);
374
+ });
375
+ };
376
+ const syncRequestGroup = function (group, nodes) {
377
+ const sortedNodes = sortNodes(nodes);
378
+ group.empty.hidden = sortedNodes.length > 0;
379
+ group.table.hidden = sortedNodes.length === 0;
380
+ _solidUi.utils.syncTableToArray(group.table, sortedNodes, createPersonRow, function (_row, thing) {
381
+ return createPersonRow(thing);
382
+ });
383
+ };
384
+ const refreshRequests = function (nextTriage) {
385
+ syncRequestGroup(incomingGroup, nextTriage.requests);
386
+ syncRequestGroup(pendingGroup, nextTriage.unconfirmed);
387
+ };
388
+ refreshRequests(triage);
389
+ return {
390
+ section: requestsSection,
391
+ refreshRequests
392
+ };
317
393
  }
@@ -0,0 +1,3 @@
1
+ import { TemplateResult } from 'lit-html';
2
+ export declare function createSpinner(): TemplateResult;
3
+ //# sourceMappingURL=spinner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../src/social/spinner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,cAAc,EAAE,MAAM,UAAU,CAAA;AAE/C,wBAAgB,aAAa,IAAK,cAAc,CAK/C"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createSpinner = createSpinner;
7
+ var _litHtml = require("lit-html");
8
+ function createSpinner() {
9
+ return (0, _litHtml.html)`
10
+ <span class="loading-spinner" aria-hidden="true"></span>
11
+ <span class="sr-only">Saving...</span>
12
+ `;
13
+ }
@@ -0,0 +1,17 @@
1
+ import { DataBrowserContext } from 'pane-registry';
2
+ import { IndexedFormula, NamedNode } from 'rdflib';
3
+ type FriendshipStore = IndexedFormula & {
4
+ fetcher?: {
5
+ load?: (target: NamedNode | string) => Promise<unknown>;
6
+ };
7
+ };
8
+ export interface FriendshipTriage {
9
+ acquaintances: NamedNode[];
10
+ confirmed: NamedNode[];
11
+ unconfirmed: NamedNode[];
12
+ requests: NamedNode[];
13
+ }
14
+ export declare function triageFriends(store: FriendshipStore, subject: NamedNode): FriendshipTriage;
15
+ export declare function loadFriendshipTriage(context: DataBrowserContext, subject: NamedNode): Promise<FriendshipTriage>;
16
+ export {};
17
+ //# sourceMappingURL=triage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"triage.d.ts","sourceRoot":"","sources":["../../src/social/triage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAGlD,KAAK,eAAe,GAAG,cAAc,GAAG;IACtC,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KACxD,CAAA;CACF,CAAA;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,SAAS,EAAE,CAAA;IAC1B,SAAS,EAAE,SAAS,EAAE,CAAA;IACtB,WAAW,EAAE,SAAS,EAAE,CAAA;IACxB,QAAQ,EAAE,SAAS,EAAE,CAAA;CACtB;AAgBD,wBAAgB,aAAa,CAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,GAAG,gBAAgB,CA+B3F;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,gBAAgB,CAAC,CAwC3B"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.loadFriendshipTriage = loadFriendshipTriage;
7
+ exports.triageFriends = triageFriends;
8
+ var _solidUi = require("solid-ui");
9
+ function uniqueNamedNodes(nodes) {
10
+ const seen = new Set();
11
+ const unique = [];
12
+ for (const node of nodes) {
13
+ const key = node?.value;
14
+ if (!key || seen.has(key)) continue;
15
+ seen.add(key);
16
+ unique.push(node);
17
+ }
18
+ return unique;
19
+ }
20
+ function triageFriends(store, subject) {
21
+ const outgoingFriends = uniqueNamedNodes(store.each(subject, _solidUi.ns.foaf('knows')));
22
+ const incomingFriends = uniqueNamedNodes(store.each(undefined, _solidUi.ns.foaf('knows'), subject));
23
+ const confirmed = [];
24
+ const unconfirmed = [];
25
+ const requests = [];
26
+ for (const friend of outgoingFriends) {
27
+ const isConfirmed = incomingFriends.some(incomingFriend => incomingFriend.sameTerm(friend));
28
+ if (isConfirmed) {
29
+ confirmed.push(friend);
30
+ } else {
31
+ unconfirmed.push(friend);
32
+ }
33
+ }
34
+ for (const friend of incomingFriends) {
35
+ const isAlreadyOutgoing = outgoingFriends.some(outgoingFriend => outgoingFriend.sameTerm(friend));
36
+ if (!isAlreadyOutgoing) {
37
+ requests.push(friend);
38
+ }
39
+ }
40
+ return {
41
+ acquaintances: outgoingFriends,
42
+ confirmed,
43
+ unconfirmed,
44
+ requests
45
+ };
46
+ }
47
+ async function loadFriendshipTriage(context, subject) {
48
+ const store = context.session.store;
49
+ const fetcher = store.fetcher;
50
+ if (!fetcher || typeof fetcher.load !== 'function') {
51
+ return triageFriends(store, subject);
52
+ }
53
+ try {
54
+ await fetcher.load(subject.doc());
55
+ } catch {
56
+ // Continue with the store snapshot we already have.
57
+ }
58
+ const initialTriage = triageFriends(store, subject);
59
+ const documentsToLoad = new Map();
60
+ documentsToLoad.set(subject.doc().value, subject.doc());
61
+ for (const friend of [...initialTriage.acquaintances, ...initialTriage.requests]) {
62
+ documentsToLoad.set(friend.doc().value, friend.doc());
63
+ }
64
+ const incomingStatements = store.statementsMatching(undefined, _solidUi.ns.foaf('knows'), subject);
65
+ for (const statement of incomingStatements) {
66
+ const why = statement.why;
67
+ if (why?.value) {
68
+ documentsToLoad.set(why.value, why);
69
+ }
70
+ }
71
+ for (const target of documentsToLoad.values()) {
72
+ try {
73
+ await fetcher.load(target);
74
+ } catch {
75
+ // Keep partial results when one profile document fails to load.
76
+ }
77
+ }
78
+ return triageFriends(store, subject);
79
+ }