contacts-pane 2.6.4 → 2.6.5-7d32a039

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 (44) hide show
  1. package/.eslintrc +0 -0
  2. package/.github/workflows/ci.yml +0 -0
  3. package/.nvmrc +0 -0
  4. package/LICENSE.md +0 -0
  5. package/Makefile +0 -0
  6. package/README.md +0 -0
  7. package/__tests__/unit/data-reformat-test.mjs +192 -0
  8. package/__tests__/unit/setup.ts +27 -0
  9. package/babel.config.js +13 -0
  10. package/card.ai +0 -0
  11. package/card.png +0 -0
  12. package/contactLogic.js +71 -6
  13. package/contactsPane.js +42 -4
  14. package/diff.txt +0 -0
  15. package/exampleOfOpenData/mit-wikidata-details.ttl +0 -0
  16. package/exampleOfOpenData/mit-wikidata-query.sparql +0 -0
  17. package/exampleOfOpenData/wikidata-get.json +0 -0
  18. package/groupMembershipControl.js +30 -5
  19. package/individual.js +0 -0
  20. package/individualForm.js +0 -0
  21. package/jest.config.js +5 -0
  22. package/jest.setup.js +4 -0
  23. package/lib/autocompleteBar.js +0 -0
  24. package/lib/autocompleteField.js +0 -0
  25. package/lib/autocompletePicker.js +0 -0
  26. package/lib/forms.js +0 -0
  27. package/lib/instituteDetailsQuery.js +0 -0
  28. package/lib/publicData.js +0 -0
  29. package/lib/vcard.js +0 -0
  30. package/mintNewAddressBook.js +0 -0
  31. package/mugshotGallery.js +0 -0
  32. package/organizationForm.js +0 -0
  33. package/organizationForm.ttl +0 -0
  34. package/package.json +17 -11
  35. package/shapes/contacts-shapes.ttl +0 -0
  36. package/src/autocompleteBar.ts +0 -0
  37. package/src/autocompleteField.ts +0 -0
  38. package/src/autocompletePicker.ts +0 -0
  39. package/src/forms.ttl +0 -0
  40. package/src/instituteDetailsQuery.sparql +0 -0
  41. package/src/publicData.ts +0 -0
  42. package/src/vcard.ttl +0 -0
  43. package/toolsPane.js +66 -16
  44. package/webidControl.js +19 -8
package/.eslintrc CHANGED
File without changes
File without changes
package/.nvmrc CHANGED
File without changes
package/LICENSE.md CHANGED
File without changes
package/Makefile CHANGED
File without changes
package/README.md CHANGED
File without changes
@@ -0,0 +1,192 @@
1
+ import { getDataModelIssues } from '../../src/contacsPane'
2
+
3
+ comsole.log('data reformat test')
4
+
5
+
6
+ import pane from "../index";
7
+ import { parse } from "rdflib";
8
+ import { store } from "solid-logic";
9
+ import { context, doc, subject } from "./setup";
10
+
11
+ // This was at testingsolidos.solidcommunity.net
12
+ const exampleProfile = `@prefix : <#>.
13
+ @prefix foaf: <http://xmlns.com/foaf/0.1/>.
14
+ @prefix ldp: <http://www.w3.org/ns/ldp#>.
15
+ @prefix schema: <http://schema.org/>.
16
+ @prefix solid: <http://www.w3.org/ns/solid/terms#>.
17
+ @prefix space: <http://www.w3.org/ns/pim/space#>.
18
+ @prefix vcard: <http://www.w3.org/2006/vcard/ns#>.
19
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
20
+ @prefix pro: <./>.
21
+ @prefix inbox: </inbox/>.
22
+ @prefix tes: </>.
23
+ @prefix l: <https://www.w3.org/ns/iana/language-code/>.
24
+ @prefix org: <http://www.w3.org/ns/org#>.
25
+ @prefix ent: <http://www.wikidata.org/entity/>.
26
+ @prefix not: <https://notthereal.apple.com/>.
27
+ @prefix www: <https://www.thebeatles.com/>.
28
+ @prefix ww: <https://www.nasa.gov/>.
29
+ @prefix occup: <http://data.europa.eu/esco/occupation/>.
30
+ @prefix c: <https://solidos.solidcommunity.net/profile/card#>.
31
+ @prefix n0: <http://www.w3.org/ns/auth/acl#>.
32
+ @prefix skill: <http://data.europa.eu/esco/skill/>.
33
+
34
+ occup:50af07f0-7a75-424e-a66d-5a9deea10f4c
35
+ schema:name
36
+ "testeur d\u2019accessibilit\u00e9/testeuse d\u2019accessibilit\u00e9".
37
+ occup:6b0ad7c0-a37f-45c6-a486-ee943a11429e schema:name "astrologue".
38
+
39
+ occup:807a1ac3-4f56-41f3-a68c-d653d558eb0a schema:name "copilote".
40
+
41
+ occup:88990bdf-4f6b-4411-82c9-9eecad1db8fb
42
+ schema:name "professeur de musique/professeure de musique".
43
+ skill:aec4c9bf-9c44-4f9d-99f1-70011cebe1a8
44
+ schema:name "tester du mat\u00e9riel d\u2019instrumentation".
45
+ skill:b805f989-14d5-46ad-80b0-755634b66dba
46
+ schema:name "travailler dans de mauvaises conditions m\u00e9t\u00e9orologiques".
47
+ ent:Q23548 schema:name "NASA"@yo.
48
+
49
+ ent:Q312 schema:name "Apple"@fr.
50
+
51
+ pro:card a foaf:PersonalProfileDocument; foaf:maker :me; foaf:primaryTopic :me.
52
+
53
+ :id1621179872094 solid:publicId l:fr.
54
+
55
+ :id1621182189397 solid:publicId l:de.
56
+
57
+ :id1621184812427
58
+ a solid:FutureRole;
59
+ schema:description "Second future role";
60
+ schema:startDate "2023-12-25"^^xsd:date;
61
+ vcard:role "Mission a Mars";
62
+ org:member :me;
63
+ org:organization :id1621184844320;
64
+ org:role occup:6b0ad7c0-a37f-45c6-a486-ee943a11429e.
65
+
66
+ :id1621182208190
67
+ a solid:CurrentRole;
68
+ schema:startDate "2021-04-01"^^xsd:date;
69
+ schema:endDate "2022-04-01"^^xsd:date;
70
+
71
+ vcard:role "Testeuse des Apps Solid";
72
+ org:member :me;
73
+ org:organization :id1621182234226;
74
+ org:role occup:50af07f0-7a75-424e-a66d-5a9deea10f4c.
75
+ :id1621182234226
76
+ a schema:Corporation;
77
+ schema:name "Apple";
78
+ schema:uri not:;
79
+ solid:publicId ent:Q312.
80
+
81
+ :id1621182452881
82
+ a solid:PastRole;
83
+ schema:description "This was an imaginary but fun gig.";
84
+ schema:endDate "1963-04-01"^^xsd:date;
85
+ schema:startDate "1960-04-01"^^xsd:date;
86
+ vcard:role "Directed the white album";
87
+ org:member :me;
88
+ org:organization :id1621182460879;
89
+ org:role occup:88990bdf-4f6b-4411-82c9-9eecad1db8fb.
90
+ :id1621182460879 a schema:MusicGroup; schema:name "The Beatles"; schema:uri www:.
91
+
92
+ :id1621183757035
93
+ a solid:FutureRole;
94
+ schema:description "Imaginary future roles are sometimes the best";
95
+ schema:endDate "1993-04-01"^^xsd:date;
96
+ schema:startDate "1990-05-01"^^xsd:date;
97
+ vcard:role "Dream: Fly a couple of missions";
98
+ org:member :me;
99
+ org:organization :id1621183860447;
100
+ org:role occup:807a1ac3-4f56-41f3-a68c-d653d558eb0a.
101
+
102
+
103
+
104
+
105
+ :id1621183860447
106
+ a schema:GovernmentOrganization;
107
+ schema:name "National Aeronautical and Space Administration";
108
+ schema:uri ww:;
109
+ solid:publicId ent:Q23548.
110
+
111
+
112
+ :id1621184844320
113
+ a schema:GovernmentOrganization;
114
+ schema:name "NASA";
115
+ schema:uri ww:;
116
+ solid:publicId ent:Q23548.
117
+ :id1622021411833
118
+ vcard:country-name "USA";
119
+ vcard:locality "Testingville";
120
+ vcard:region "Texas";
121
+ vcard:street-address "The testing tree house".
122
+ :id1622021761923 solid:publicId skill:aec4c9bf-9c44-4f9d-99f1-70011cebe1a8 .
123
+
124
+ :id1622021775187 solid:publicId skill:b805f989-14d5-46ad-80b0-755634b66dba.
125
+
126
+ :skill2 vcard:role "sitting and chatting" .
127
+
128
+
129
+ :me
130
+ a schema:Person, foaf:Person;
131
+ schema:knowsLanguage ( :id1621179872094 :id1621182189397 );
132
+ schema:skills :id1622021761923, :id1622021775187, :skill2, :NonExistentSkill;
133
+ vcard:bday "2021-05-14"^^xsd:date;
134
+ vcard:fn "Testing SolidOS Test";
135
+ vcard:hasAddress :id1622021411833;
136
+ vcard:hasPhoto <noun_test_2974484.svg>;
137
+ vcard:note
138
+ "This is a test account for testing versions of the SolidOS operating system for solid.";
139
+ vcard:organization-name "Solid";
140
+ vcard:role "foobar";
141
+ n0:trustedApp
142
+ [
143
+ n0:mode n0:Append, n0:Control, n0:Read, n0:Write;
144
+ n0:origin <https://timbl.com>
145
+ ];
146
+ ldp:inbox inbox:;
147
+ space:preferencesFile </settings/prefs.ttl>;
148
+ space:storage tes:;
149
+ solid:account tes:;
150
+ solid:preferredObjectPronoun "them";
151
+ solid:preferredRelativePronoun "foobar";
152
+ solid:preferredSubjectPronoun "they";
153
+ solid:privateTypeIndex </settings/privateTypeIndex.ttl>;
154
+ solid:profileBackgroundColor "#f4f5c2"^^xsd:color;
155
+ solid:profileHighlightColor "#06b74a"^^xsd:color;
156
+ solid:publicTypeIndex </settings/publicTypeIndex.ttl>;
157
+ foaf:knows c:me;
158
+ foaf:name "Testing SolidOS";
159
+ foaf:nick "tester1".
160
+ l:de schema:name "germano"@ia.
161
+
162
+ l:fr schema:name "French"@en.
163
+
164
+ `
165
+ describe("contacts-pane", () => {
166
+
167
+ describe("returns right label", () => {
168
+ beforeAll(async () => {
169
+ store.removeDocument(doc);
170
+ parse(exampleProfile, store, doc.uri);
171
+ // const label = pane.label(subject, context);
172
+ });
173
+ /*
174
+ if (t[ns.vcard('Individual').uri]) return 'Contact'
175
+ if (t[ns.vcard('Organization').uri]) return 'contact'
176
+ if (t[ns.foaf('Person').uri]) return 'Person'
177
+ if (t[ns.schema('Person').uri]) return 'Person'
178
+ if (t[ns.vcard('Group').uri]) return 'Group'
179
+ if (t[ns.vcard('AddressBook').uri]) return 'Address book'
180
+
181
+ */
182
+ it("returns a good label Profile if appropriate", () => {
183
+ store.add(subject, ns.rdf('type'), ns.vcard('Individual'), doc)
184
+ expect(pane.label(subject, context)).toEqual('Contact');
185
+ });
186
+ it("returns a null label if not a person", () => {
187
+ expect(pane.label(store.sym('https://random.example.com/'), context)).toEqual(null);
188
+ });
189
+
190
+ });
191
+
192
+ });
@@ -0,0 +1,27 @@
1
+ import { DataBrowserContext, PaneRegistry } from "pane-registry";
2
+ import { sym } from "rdflib";
3
+ import { SolidLogic, store } from "solid-logic";
4
+
5
+ export const subject = sym("https://janedoe.example/profile/card#me");
6
+ export const doc = subject.doc();
7
+
8
+ export const context = {
9
+ dom: document,
10
+ getOutliner: () => null,
11
+ session: {
12
+ paneRegistry: {
13
+ byName: (name: string) => {
14
+ return {
15
+ render: () => {
16
+ return document.createElement('div')
17
+ .appendChild(
18
+ document.createTextNode(`mock ${name} pane`)
19
+ );
20
+ }
21
+ }
22
+ }
23
+ } as PaneRegistry,
24
+ store,
25
+ logic: {} as SolidLogic,
26
+ },
27
+ } as unknown as DataBrowserContext;
@@ -0,0 +1,13 @@
1
+ module.exports = {
2
+ presets: [
3
+ [
4
+ "@babel/preset-env",
5
+ {
6
+ targets: {
7
+ node: "current",
8
+ },
9
+ },
10
+ ],
11
+ "@babel/preset-typescript",
12
+ ],
13
+ };
package/card.ai CHANGED
File without changes
package/card.png CHANGED
File without changes
package/contactLogic.js CHANGED
@@ -138,7 +138,7 @@ export async function addPersonToGroup (thing, group) {
138
138
  const pname = kb.any(thing, ns.vcard('fn'))
139
139
  const gname = kb.any(group, ns.vcard('fn'))
140
140
  if (!pname) { return alert('No vcard name known for ' + thing) }
141
- const already = kb.holds(group, ns.vcard('hasMember'), thing, group.doc())
141
+ const already = kb.holds(thing, ns.vcard('fn'), null, group.doc())
142
142
  if (already) {
143
143
  return alert(
144
144
  'ALREADY added ' + pname + ' to group ' + gname
@@ -147,14 +147,18 @@ export async function addPersonToGroup (thing, group) {
147
147
  const message = 'Add ' + pname + ' to group ' + gname + '?'
148
148
  if (!confirm(message)) return
149
149
  const ins = [
150
- $rdf.st(group, ns.vcard('hasMember'), thing, group.doc()),
151
150
  $rdf.st(thing, ns.vcard('fn'), pname, group.doc())
152
151
  ]
153
- // find person webIDs
152
+ // find person webIDs and insert in vcard:hasMember
154
153
  const webIDs = getPersonas(kb, thing).map(webid => webid.value)
155
- webIDs.forEach(webid => {
156
- ins.push($rdf.st(thing, ns.owl('sameAs'), kb.sym(webid), group.doc()))
157
- })
154
+ if (webIDs.length) {
155
+ webIDs.forEach(webid => {
156
+ ins.push($rdf.st(kb.sym(webid), ns.owl('sameAs'), thing, group.doc()))
157
+ ins.push($rdf.st(group, ns.vcard('hasMember'), kb.sym(webid), group.doc()))
158
+ })
159
+ } else {
160
+ ins.push($rdf.st(group, ns.vcard('hasMember'), thing, group.doc()))
161
+ }
158
162
  try {
159
163
  await updater.update([], ins)
160
164
  // to allow refresh of card groupList
@@ -165,3 +169,64 @@ export async function addPersonToGroup (thing, group) {
165
169
  }
166
170
  return thing
167
171
  }
172
+
173
+ /**
174
+ * Find persons member of a group
175
+ */
176
+
177
+ export function groupMembers (kb, group) {
178
+ const a = kb.each(group, ns.vcard('hasMember'), null, group.doc())
179
+ let b = []
180
+ a.forEach(item => {
181
+ /* const contacts = kb.each(item, ns.owl('sameAs'), null, group.doc())
182
+ if (contacts.length) {
183
+ if (!kb.any(contacts[0], ns.vard('fn'))) b = b.concat(item) // this is the old data model
184
+ else b = b.concat(contacts)
185
+ } else { b = b.concat(item) }
186
+ b = b.concat(item) */
187
+
188
+ // to keep compatibility with old data model
189
+ // check if item is a contact, else it is a WebID and parse 'sameAs' for contacts
190
+ b = kb.any(item, ns.vcard('fn'), null, group.doc()) ? b.concat(item) : b.concat(kb.each(item, ns.owl('sameAs'), null, group.doc()))
191
+ })
192
+ const strings = new Set(b.map(contact => contact.uri)) // remove dups
193
+ b = [...strings].map(uri => kb.sym(uri))
194
+ return b
195
+ }
196
+
197
+ export function isLocal(group, item) {
198
+ const tree = group.dir().dir().dir()
199
+ const local = item.uri && item.uri.startsWith(tree.uri)
200
+ // console.log(` isLocal ${local} for ${item.uri} in group ${group} tree ${tree.uri}`)
201
+ return local
202
+ }
203
+
204
+ export function getSameAs(kb, item, doc) {
205
+ return kb.each(item, ns.owl('sameAs'), null, doc).concat(
206
+ kb.each(null, ns.owl('sameAs'), item, doc))
207
+ }
208
+
209
+ export async function getDataModelIssues(groups) {
210
+ let del = []
211
+ let ins = []
212
+ groups.forEach(group => {
213
+ const members = kb.each(group, ns.vcard('hasMember'), null, group.doc())
214
+ members.forEach((member) => {
215
+ const others = getSameAs(kb, member, group.doc())
216
+ if (others.length && isLocal(group, member)) { // Problem: local ID used instead of webID
217
+ for (const other of others) {
218
+ if (!isLocal(group, other)) { // Let's use this one as the immediate member for CSS ACLs'
219
+ console.warn(`getDataModelIssues: Need to swap ${member} to ${other}`)
220
+ del.push($rdf.st(group, ns.vcard('hasMember'), member, group.doc()))
221
+ ins.push($rdf.st(group, ns.vcard('hasMember'), other, group.doc()))
222
+ break
223
+ }
224
+ console.log('getDataModelIssues: ??? expected id not to be local ' + other)
225
+ } // other
226
+ } // if
227
+ }) // member
228
+ }) // next group
229
+ return {del, ins }
230
+ } // getDataModelIssues
231
+
232
+ // Ends
package/contactsPane.js CHANGED
@@ -15,11 +15,13 @@ to change its state according to an ontology, comment on it, etc.
15
15
  /* global alert, confirm */
16
16
 
17
17
  import { authn } from 'solid-logic'
18
- import { addPersonToGroup, saveNewContact, saveNewGroup } from './contactLogic'
18
+ import { addPersonToGroup, saveNewContact, saveNewGroup, groupMembers } from './contactLogic'
19
19
  import * as UI from 'solid-ui'
20
20
  import { mintNewAddressBook } from './mintNewAddressBook'
21
21
  import { renderIndividual } from './individual'
22
22
  import { toolsPane } from './toolsPane'
23
+ import { groupMembership } from './groupMembershipControl'
24
+ import { getDataModelIssues } from './contactLogic'
23
25
 
24
26
  // const $rdf = UI.rdf
25
27
  const ns = UI.ns
@@ -217,8 +219,25 @@ export default {
217
219
  const nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex'))
218
220
  await kb.fetcher.load(nameEmailIndex)
219
221
 
222
+ // - delete person's WebID's in each Group
220
223
  // - delete the references to it in group files and save them back
221
- // - delete the reference in people.ttl and save it back
224
+ // - delete the reference in people.ttl and save it back
225
+
226
+ // find all Groups
227
+ const groups = groupMembership(person)
228
+ let removeFromGroups = []
229
+ // find person WebID's
230
+ groups.map( group => {
231
+ const webids = getSameAs(kb, person, group.doc())
232
+ // for each check in each Group that it is not used by an other person then delete
233
+ webids.map( webid => {
234
+ if (getSameAs(kb, webid, group.doc()).length = 1) {
235
+ removeFromGroups = removeFromGroups.concat(kb.statementsMatching(group, ns.vcard('hasMember'), webid, group.doc()))
236
+ }
237
+ })
238
+ })
239
+ // console.log(removeFromGroups)
240
+ await kb.updater.updateMany(removeFromGroups)
222
241
  await deleteThingAndDoc(person)
223
242
  await deleteRecursive(kb, container)
224
243
  refreshNames() // "Doesn't work" -- maybe does now with waiting for async
@@ -294,8 +313,10 @@ export default {
294
313
  }
295
314
 
296
315
  async function loadAllGroups () {
316
+ await kb.fetcher.load(groupIndex)
297
317
  const gs = book ? kb.each(book, ns.vcard('includesGroup'), null, groupIndex) : []
298
318
  await kb.fetcher.load(gs)
319
+ return gs
299
320
  }
300
321
 
301
322
  function groupsInOrder () {
@@ -417,8 +438,7 @@ export default {
417
438
  const groups = Object.keys(selectedGroups).map(groupURI => kb.sym(groupURI))
418
439
  groups.forEach(group => {
419
440
  if (selectedGroups[group.value]) {
420
- const a = kb.each(group, ns.vcard('hasMember'), null, group.doc())
421
- cards = cards.concat(a)
441
+ cards = cards.concat(groupMembers(kb, group))
422
442
  }
423
443
  })
424
444
  cards.sort(compareForSort) // @@ sort by name not UID later
@@ -578,8 +598,22 @@ export default {
578
598
  const groups = groupsInOrder()
579
599
  utils.syncTableToArrayReOrdered(groupsMainTable, groups, renderGroupRow)
580
600
  refreshGroupsSelected()
601
+ // await checkDataModel(groups)
581
602
  } // syncGroupTable
582
603
 
604
+
605
+ async function checkDataModel () {
606
+ // await kb.fetcher.load(groups) // asssume loaded already
607
+ const groups = await loadAllGroups()
608
+
609
+ const { del, ins } = await getDataModelIssues(groups)
610
+
611
+ if (del.length && confirm(`Groups data model need to be updated? (${del.length})`)) {
612
+ await kb.updater.updateMany(del, ins)
613
+ alert('Update done')
614
+ }
615
+ } // checkDataModel
616
+
583
617
  // Click on New Group button
584
618
  async function newGroupClickHandler (_event) {
585
619
  cardMain.innerHTML = ''
@@ -841,6 +875,10 @@ export default {
841
875
  // })
842
876
 
843
877
  div.appendChild(dom.createElement('hr'))
878
+
879
+ // const groups = await loadAllGroups() @@@
880
+ checkDataModel().then(()=> {console.log('async checkDataModel done.')})
881
+
844
882
  // div.appendChild(newAddressBookButton(book)) // later
845
883
  // end of AddressBook instance
846
884
  } // renderThreeColumnBrowser
package/diff.txt CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -1,6 +1,7 @@
1
1
 
2
2
  // Render a control to record the group memberships we have for this agent
3
3
  import * as UI from 'solid-ui'
4
+ import { store } from 'solid-logic'
4
5
 
5
6
  // const $rdf = UI.rdf
6
7
  const ns = UI.ns
@@ -8,14 +9,33 @@ const ns = UI.ns
8
9
  // const widgets = UI.widgets
9
10
  const utils = UI.utils
10
11
  // const style = UI.style
12
+ const kb = store
11
13
 
12
14
  // Groups the person is a member of
15
+ export function groupMembership (person) {
16
+ let groups = kb.statementsMatching(null, ns.owl('sameAs'), person).map(st => st.why)
17
+ .concat(kb.each(null, ns.vcard('hasMember'), person))
18
+ const strings = new Set(groups.map(group => group.uri)) // remove dups
19
+ groups = [...strings].map(uri => kb.sym(uri))
20
+ return groups
21
+ }
22
+
13
23
  export async function renderGroupMemberships (person, context) {
14
24
  // Remove a person from a group
15
25
  async function removeFromGroup (thing, group) {
16
26
  const pname = kb.any(thing, ns.vcard('fn'))
17
27
  const gname = kb.any(group, ns.vcard('fn'))
18
- const groups = kb.each(null, ns.vcard('hasMember'), thing)
28
+ // find all WebIDs of thing
29
+ const thingwebids = kb.each(null, ns.owl('sameAs'), thing, group.doc())
30
+ // WebID can be deleted only if not used in another thing
31
+ let webids = []
32
+ thingwebids.map(webid => {
33
+ if (kb.statementsMatching(webid, ns.owl('sameAs'), thing, group.doc())) webids = webids.concat(webid)
34
+ }
35
+ )
36
+ let thingOrWebid = thing
37
+ if (webids.length > 0) thingOrWebid = webids[0]
38
+ const groups = kb.each(null, ns.vcard('hasMember'), thingOrWebid) // in all groups a person has same structure
19
39
  if (groups.length < 2) {
20
40
  alert(
21
41
  'Must be a member of at least one group. Add to another group first.'
@@ -24,9 +44,14 @@ export async function renderGroupMemberships (person, context) {
24
44
  }
25
45
  const message = 'Remove ' + pname + ' from group ' + gname + '?'
26
46
  if (confirm(message)) {
27
- const del = kb
47
+ let del = kb
28
48
  .statementsMatching(person, undefined, undefined, group.doc())
29
49
  .concat(kb.statementsMatching(undefined, undefined, person, group.doc()))
50
+ webids.map(webid => {
51
+ if (kb.statementsMatching(webid, ns.owl('sameAs'), undefined, group.doc()).length < 2) {
52
+ del = del.concat(kb.statementsMatching(undefined, undefined, webid, group.doc()))
53
+ }
54
+ })
30
55
  kb.updater.update(del, [], function (uri, ok, err) {
31
56
  if (!ok) {
32
57
  const message = 'Error removing member from group ' + group + ': ' + err
@@ -52,10 +77,10 @@ export async function renderGroupMemberships (person, context) {
52
77
  return tr
53
78
  }
54
79
 
80
+ // find all groups where person has membership
55
81
  function syncGroupList () {
56
- const groups = kb.each(null, ns.vcard('hasMember'), person)
57
-
58
- utils.syncTableToArray(groupList, groups, newRowForGroup)
82
+ // person and/or WebIDs to be changed
83
+ utils.syncTableToArray(groupList, groupMembership(person), newRowForGroup)
59
84
  }
60
85
 
61
86
  async function loadGroupsFromBook (book = null) {
package/individual.js CHANGED
File without changes
package/individualForm.js CHANGED
File without changes
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ testEnvironment: 'jsdom',
3
+ setupFilesAfterEnv: ["./jest.setup.js"],
4
+ transformIgnorePatterns: ["/node_modules/(?!lit-html).+\\.js"],
5
+ };
package/jest.setup.js ADDED
@@ -0,0 +1,4 @@
1
+ // import "@testing-library/jest-dom";
2
+ // import fetchMock from "jest-fetch-mock";
3
+
4
+ // fetchMock.enableMocks();
File without changes
File without changes
File without changes
package/lib/forms.js CHANGED
File without changes
File without changes
package/lib/publicData.js CHANGED
File without changes
package/lib/vcard.js CHANGED
File without changes
File without changes
package/mugshotGallery.js CHANGED
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contacts-pane",
3
- "version": "2.6.4",
3
+ "version": "2.6.5-7d32a039",
4
4
  "description": "Contacts Pane: Contacts manager for Address Book, Groups, and Individuals.",
5
5
  "main": "./contactsPane.js",
6
6
  "scripts": {
@@ -10,8 +10,8 @@
10
10
  "lint": "eslint '*.js'",
11
11
  "lint-fix": "eslint '*.js' --fix",
12
12
  "test": "npm run lint",
13
- "prepublishOnly": "npm test && npm run build",
14
- "postpublish": "git push origin main --follow-tags"
13
+ "ignore:prepublishOnly": "npm test && npm run build",
14
+ "ignore:postpublish": "git push origin main --follow-tags"
15
15
  },
16
16
  "repository": {
17
17
  "type": "git",
@@ -37,17 +37,23 @@
37
37
  },
38
38
  "homepage": "https://github.com/solid/contacts-pane",
39
39
  "dependencies": {
40
- "pane-registry": "2.4.12",
41
- "rdflib": "^2.2.20",
42
- "solid-logic": "2.0.0",
43
- "solid-ui": "2.4.23"
40
+ "@babel/preset-env": "^7.20.2",
41
+ "babel": "^6.23.0",
42
+ "babel-cli": "^6.26.0",
43
+ "jest": "^29.3.1",
44
+ "jest-environment-jsdom": "^29.3.1",
45
+ "pane-registry": "2.4.13",
46
+ "rdflib": "^2.2.21",
47
+ "solid-logic": "2.0.1",
48
+ "solid-ui": "2.4.24"
44
49
  },
45
50
  "devDependencies": {
46
- "@typescript-eslint/eslint-plugin": "^5.36.1",
47
- "@typescript-eslint/parser": "^5.36.1",
48
- "eslint": "^8.23.0",
51
+ "@babel/preset-typescript": "^7.18.6",
52
+ "@typescript-eslint/eslint-plugin": "^5.43.0",
53
+ "@typescript-eslint/parser": "^5.43.0",
54
+ "eslint": "^8.27.0",
49
55
  "eslint-plugin-import": "^2.26.0",
50
- "husky": "^8.0.1",
56
+ "husky": "^8.0.2",
51
57
  "lint-staged": "^13.0.3",
52
58
  "typescript": "^4.8.2",
53
59
  "typescript-transpile-only": "0.0.4"
File without changes
File without changes
File without changes
File without changes
package/src/forms.ttl CHANGED
File without changes
File without changes
package/src/publicData.ts CHANGED
File without changes
package/src/vcard.ttl CHANGED
File without changes
package/toolsPane.js CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import * as UI from 'solid-ui'
6
6
  import { store } from 'solid-logic'
7
- import { saveNewGroup, addPersonToGroup } from './contactLogic'
7
+ import { saveNewGroup, addPersonToGroup, groupMembers } from './contactLogic'
8
8
  export function toolsPane (
9
9
  selectAllGroups,
10
10
  selectedGroups,
@@ -85,7 +85,9 @@ export function toolsPane (
85
85
  function stats () {
86
86
  const totalCards = kb.each(undefined, VCARD('inAddressBook'), book).length
87
87
  log('' + totalCards + ' cards loaded. ')
88
- const groups = kb.each(book, VCARD('includesGroup'))
88
+ let groups = kb.each(book, VCARD('includesGroup'))
89
+ const strings = new Set(groups.map(group => group.uri)) // remove dups
90
+ groups = [...strings].map(uri => kb.sym(uri))
89
91
  log('' + groups.length + ' total groups. ')
90
92
  const gg = []
91
93
  for (const g in selectedGroups) {
@@ -137,7 +139,7 @@ export function toolsPane (
137
139
 
138
140
  for (let i = 0; i < gg.length; i++) {
139
141
  const g = kb.sym(gg[i])
140
- const a = kb.each(g, ns.vcard('hasMember'))
142
+ const a = groupMembers(kb, g)
141
143
  log(UI.utils.label(g) + ': ' + a.length + ' members')
142
144
  for (let j = 0; j < a.length; j++) {
143
145
  const card = a[j]
@@ -357,6 +359,7 @@ export function toolsPane (
357
359
  const other = stats.nameLessIndex[cardText]
358
360
  if (other) {
359
361
  log(' Matches with ' + other)
362
+ // alain not sure it works we may need to concat with 'sameAs' group.doc (.map(st => st.why))
360
363
  const cardGroups = kb.each(null, ns.vcard('hasMember'), card)
361
364
  const otherGroups = kb.each(null, ns.vcard('hasMember'), other)
362
365
  for (let j = 0; j < cardGroups.length; j++) {
@@ -432,9 +435,9 @@ export function toolsPane (
432
435
  for (let i = 0; i < stats.uniques.length; i++) {
433
436
  stats.uniquesSet[stats.uniques[i].uri] = true
434
437
  }
435
- stats.groupMembers = kb
436
- .statementsMatching(null, ns.vcard('hasMember'))
437
- .map(st => st.object)
438
+ stats.groupMembers = []
439
+ kb.each(null, ns.vcard('hasMember'))
440
+ .map(group => { stats.groupMembers = stats.groupMembers.concat(groupMembers(kb, group)) })
438
441
  log(' Naive group members ' + stats.groupMembers.length)
439
442
  stats.groupMemberSet = []
440
443
  for (let j = 0; j < stats.groupMembers.length; j++) {
@@ -575,7 +578,10 @@ export function toolsPane (
575
578
  log(' Regenerating group of uniques...' + cleanGroup)
576
579
  const data = sz.statementsToN3(sts)
577
580
 
578
- return kb.fetcher.webOperation('PUT', cleanGroup, { data })
581
+ return kb.fetcher.webOperation('PUT', cleanGroup, {
582
+ data: data,
583
+ contentType: 'text/turtle'
584
+ })
579
585
  })
580
586
  .then(() => {
581
587
  log(' Done uniques group ' + cleanGroup)
@@ -615,12 +621,14 @@ export function toolsPane (
615
621
  .then(scanForDuplicates)
616
622
  .then(checkGroupMembers)
617
623
  .then(checkAllNameless)
618
- .then((resolve, reject) => {
619
- if (confirm('Write new clean versions?')) {
620
- resolve(true)
621
- } else {
622
- reject()
623
- }
624
+ .then(() => {
625
+ return new Promise(function (resolve, reject) {
626
+ if (confirm('Write new clean versions?')) {
627
+ resolve(true)
628
+ } else {
629
+ reject()
630
+ }
631
+ })
624
632
  })
625
633
  .then(saveCleanPeople)
626
634
  .then(saveAllGroups)
@@ -660,13 +668,15 @@ export function toolsPane (
660
668
 
661
669
  const reverseIndex = {}
662
670
  const groupless = []
663
- const groups = kb.each(book, VCARD('includesGroup'))
664
-
671
+ let groups = kb.each(book, VCARD('includesGroup'))
672
+ const strings = new Set(groups.map(group => group.uri)) // remove dups
673
+ groups = [...strings].map(uri => kb.sym(uri))
665
674
  log('' + groups.length + ' total groups. ')
666
675
 
667
676
  for (let i = 0; i < groups.length; i++) {
668
677
  const g = groups[i]
669
- const a = kb.each(g, ns.vcard('hasMember'))
678
+ const a = groupMembers(kb, g)
679
+
670
680
  log(UI.utils.label(g) + ': ' + a.length + ' members')
671
681
  for (let j = 0; j < a.length; j++) {
672
682
  kb.allAliases(a[j]).forEach(function (y) {
@@ -714,6 +724,46 @@ export function toolsPane (
714
724
  fixGrouplessButton.style.cssText = buttonStyle
715
725
  fixGrouplessButton.textContent = 'Put all individuals with no group in a new group'
716
726
  fixGrouplessButton.addEventListener('click', _event => fixGroupless(book))
727
+
728
+ async function fixToOldDataModel (book) {
729
+ async function updateToOldDataModel(groups) {
730
+ let ds = []
731
+ let ins = []
732
+ groups.forEach(group => {
733
+ let vcardOrWebids = kb.statementsMatching(null, ns.owl('sameAs'), null, group.doc()).map(st => st.subject)
734
+ const strings = new Set(vcardOrWebids.map(contact => contact.uri)) // remove dups
735
+ vcardOrWebids = [...strings].map(uri => kb.sym(uri))
736
+ vcardOrWebids.forEach(item => {
737
+ if (!kb.each(item, ns.vcard('fn'), null, group.doc()).length) {
738
+ // delete item this is a new data model, item is a webid not a card.
739
+ ds = ds.concat(kb
740
+ .statementsMatching(item, ns.owl('sameAs'), null, group.doc())
741
+ .concat(kb.statementsMatching(undefined, undefined, item, group.doc())))
742
+ // add webid card to group
743
+ const cards = kb.each(item, ns.owl('sameAs'), null, group.doc())
744
+ cards.forEach(card => {
745
+ ins = ins.concat($rdf.st(card, ns.owl('sameAs'), item, group.doc()))
746
+ .concat($rdf.st(group, ns.vcard('hasMember'), card, group.doc()))
747
+ })
748
+ }
749
+ })
750
+ })
751
+ if (ds.length && confirm('Groups can be updated to old data model ?')) {
752
+ await kb.updater.updateMany(ds, ins)
753
+ alert('Update done')
754
+ } else { if (!ds.length) alert('Nothing to update.\nAll Groups already use the old data model.')}
755
+ }
756
+ let groups = kb.each(book, VCARD('includesGroup'))
757
+ const strings = new Set(groups.map(group => group.uri)) // remove dups
758
+ groups = [...strings].map(uri => kb.sym(uri))
759
+ updateToOldDataModel(groups)
760
+ }
761
+
762
+ const fixToOldDataModelButton = pane.appendChild(dom.createElement('button'))
763
+ fixToOldDataModelButton.style.cssText = buttonStyle
764
+ fixToOldDataModelButton.textContent = 'Revert groups to old data model'
765
+ fixToOldDataModelButton.addEventListener('click', _event => fixToOldDataModel(book))
766
+
717
767
  } // main
718
768
  main()
719
769
  return pane
package/webidControl.js CHANGED
@@ -38,7 +38,7 @@ export async function addWebIDToContacts (person, webid, urlType, kb) {
38
38
 
39
39
  // check this is a url
40
40
  try {
41
- const url = new URL(webid)
41
+ const _url = new URL(webid)
42
42
  } catch (error) {
43
43
  throw new Error(`${WEBID_NOUN}: ${webid} is not a valid url.`)
44
44
  }
@@ -51,12 +51,18 @@ export async function addWebIDToContacts (person, webid, urlType, kb) {
51
51
  $rdf.st(vcardURLThing, ns.rdf('type'), urlType, person.doc()),
52
52
  $rdf.st(vcardURLThing, ns.vcard('value'), webid, person.doc())
53
53
  ]
54
+ // insert WebID in groups
55
+ // replace person with WebID in vcard:hasMember (WebID may already exist)
56
+ // insert owl:sameAs
54
57
  const groups = kb.each(null, ns.vcard('hasMember'), person)
58
+ let deletables = []
55
59
  groups.forEach(group => {
56
- insertables.push($rdf.st(person, ns.owl('sameAs'), kb.sym(webid), group.doc()))
60
+ deletables = deletables.concat(kb.statementsMatching(group, ns.vcard('hasMember'), person, group.doc()))
61
+ insertables.push($rdf.st(group, ns.vcard('hasMember'), kb.sym(webid), group.doc())) // May exist; do we need to check?
62
+ insertables.push($rdf.st(kb.sym(webid), ns.owl('sameAs'), person, group.doc()))
57
63
  })
58
64
  try {
59
- await updateMany([], insertables)
65
+ await updateMany(deletables, insertables)
60
66
  } catch (err) { throw new Error(`Could not create webId ${WEBID_NOUN}: ${webid}.`) }
61
67
  }
62
68
 
@@ -79,12 +85,17 @@ export async function removeWebIDFromContacts (person, webid, urlType, kb) {
79
85
  await kb.updater.update(deletables, [])
80
86
 
81
87
  // remove webIDs from groups
82
- const groups = kb.each(null, ns.vcard('hasMember'), person)
83
- const removeFromGroups = []
84
- groups.forEach(group => {
85
- removeFromGroups.push($rdf.st(person, ns.owl('sameAs'), kb.sym(webid), group.doc()))
88
+ const groups = kb.each(null, ns.vcard('hasMember'), kb.sym(webid))
89
+ let removeFromGroups = []
90
+ const insertInGroups = []
91
+ groups.forEach(async group => {
92
+ removeFromGroups = removeFromGroups.concat(kb.statementsMatching(kb.sym(webid), ns.owl('sameAs'), person, group.doc()))
93
+ insertInGroups.push($rdf.st(group, ns.vcard('hasMember'), person, group.doc()))
94
+ if (kb.statementsMatching(kb.sym(webid), ns.owl('sameAs'), null, group.doc()).length < 2) {
95
+ removeFromGroups = removeFromGroups.concat(kb.statementsMatching(group, ns.vcard('hasMember'), kb.sym(webid), group.doc()))
96
+ }
86
97
  })
87
- await updateMany(removeFromGroups)
98
+ await updateMany(removeFromGroups, insertInGroups)
88
99
  }
89
100
 
90
101
  // Trace things the same as this - other IDs for same thing