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.
- package/.eslintrc +0 -0
- package/.github/workflows/ci.yml +0 -0
- package/.nvmrc +0 -0
- package/LICENSE.md +0 -0
- package/Makefile +0 -0
- package/README.md +0 -0
- package/__tests__/unit/data-reformat-test.mjs +192 -0
- package/__tests__/unit/setup.ts +27 -0
- package/babel.config.js +13 -0
- package/card.ai +0 -0
- package/card.png +0 -0
- package/contactLogic.js +71 -6
- package/contactsPane.js +42 -4
- package/diff.txt +0 -0
- package/exampleOfOpenData/mit-wikidata-details.ttl +0 -0
- package/exampleOfOpenData/mit-wikidata-query.sparql +0 -0
- package/exampleOfOpenData/wikidata-get.json +0 -0
- package/groupMembershipControl.js +30 -5
- package/individual.js +0 -0
- package/individualForm.js +0 -0
- package/jest.config.js +5 -0
- package/jest.setup.js +4 -0
- package/lib/autocompleteBar.js +0 -0
- package/lib/autocompleteField.js +0 -0
- package/lib/autocompletePicker.js +0 -0
- package/lib/forms.js +0 -0
- package/lib/instituteDetailsQuery.js +0 -0
- package/lib/publicData.js +0 -0
- package/lib/vcard.js +0 -0
- package/mintNewAddressBook.js +0 -0
- package/mugshotGallery.js +0 -0
- package/organizationForm.js +0 -0
- package/organizationForm.ttl +0 -0
- package/package.json +17 -11
- package/shapes/contacts-shapes.ttl +0 -0
- package/src/autocompleteBar.ts +0 -0
- package/src/autocompleteField.ts +0 -0
- package/src/autocompletePicker.ts +0 -0
- package/src/forms.ttl +0 -0
- package/src/instituteDetailsQuery.sparql +0 -0
- package/src/publicData.ts +0 -0
- package/src/vcard.ttl +0 -0
- package/toolsPane.js +66 -16
- package/webidControl.js +19 -8
package/.eslintrc
CHANGED
|
File without changes
|
package/.github/workflows/ci.yml
CHANGED
|
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;
|
package/babel.config.js
ADDED
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(
|
|
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.
|
|
156
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/jest.setup.js
ADDED
package/lib/autocompleteBar.js
CHANGED
|
File without changes
|
package/lib/autocompleteField.js
CHANGED
|
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
|
package/mintNewAddressBook.js
CHANGED
|
File without changes
|
package/mugshotGallery.js
CHANGED
|
File without changes
|
package/organizationForm.js
CHANGED
|
File without changes
|
package/organizationForm.ttl
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contacts-pane",
|
|
3
|
-
"version": "2.6.
|
|
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
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
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
|
-
"@
|
|
47
|
-
"@typescript-eslint/
|
|
48
|
-
"eslint": "^
|
|
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.
|
|
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
|
package/src/autocompleteBar.ts
CHANGED
|
File without changes
|
package/src/autocompleteField.ts
CHANGED
|
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
|
-
|
|
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
|
|
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 =
|
|
436
|
-
|
|
437
|
-
.map(
|
|
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, {
|
|
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((
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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'),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|