contacts-pane 2.4.8 → 2.4.12-beta4
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 +3 -1
- package/.github/workflows/ci.yml +74 -0
- package/.nvmrc +1 -1
- package/LICENSE.md +0 -0
- package/Makefile +0 -0
- package/README.md +0 -0
- package/__tests__/unit/data-reformat-test.js +166 -0
- package/__tests__/unit/data-reformat-test.ts +185 -0
- package/__tests__/unit/setup.js +74 -0
- package/__tests__/unit/setup.ts +77 -0
- package/babel.config.js +13 -0
- package/card.ai +0 -0
- package/card.png +0 -0
- package/contactLogic.js +84 -7
- package/contactsPane.js +64 -20
- 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 +56 -13
- package/individual.js +3 -2
- package/individualForm.js +0 -0
- package/jest.config.js +11 -0
- package/jest.setup.ts +10 -0
- package/lib/autocompleteBar.js +99 -0
- package/lib/autocompleteBar.js.map +1 -0
- package/lib/autocompleteField.js +157 -0
- package/lib/autocompleteField.js.map +1 -0
- package/lib/autocompletePicker.js +240 -0
- package/lib/autocompletePicker.js.map +1 -0
- package/lib/forms.js +315 -0
- package/lib/instituteDetailsQuery.js +38 -0
- package/lib/publicData.js +387 -0
- package/lib/publicData.js.map +1 -0
- package/lib/vcard.js +916 -0
- package/mintNewAddressBook.js +5 -4
- package/mugshotGallery.js +16 -4
- package/organizationForm.js +0 -0
- package/organizationForm.ttl +0 -0
- package/package.json +26 -13
- package/shapes/contacts-shapes.ttl +0 -0
- package/src/autocompleteBar.ts +92 -0
- package/src/autocompleteField.ts +180 -0
- package/src/autocompletePicker.ts +253 -0
- package/src/forms.ttl +1 -1
- package/src/instituteDetailsQuery.sparql +0 -0
- package/src/publicData.ts +385 -0
- package/src/vcard.ttl +0 -0
- package/toolsPane.js +70 -19
- package/tsconfig.json +16 -0
- package/webidControl.js +49 -4
package/contactLogic.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// Logic for solid contacts
|
|
2
2
|
|
|
3
3
|
import * as UI from 'solid-ui'
|
|
4
|
+
import { store } from 'solid-logic'
|
|
5
|
+
import { getPersonas } from './webidControl'
|
|
4
6
|
|
|
5
7
|
const ns = UI.ns
|
|
6
8
|
const $rdf = UI.rdf
|
|
7
9
|
const utils = UI.utils
|
|
8
|
-
const kb =
|
|
10
|
+
const kb = store
|
|
9
11
|
const updater = kb.updater
|
|
10
12
|
|
|
11
13
|
/** Perform updates on more than one document @@ Move to rdflib!
|
|
@@ -28,6 +30,7 @@ export async function updateMany (deletions, insertions = []) {
|
|
|
28
30
|
* @returns {NamedNode} the person
|
|
29
31
|
*/
|
|
30
32
|
export async function saveNewContact (book, name, selectedGroups, klass) {
|
|
33
|
+
await kb.fetcher.load(book.doc())
|
|
31
34
|
const nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex'))
|
|
32
35
|
|
|
33
36
|
const uuid = utils.genUuid()
|
|
@@ -59,9 +62,9 @@ export async function saveNewContact (book, name, selectedGroups, klass) {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
try {
|
|
62
|
-
await updateMany([], agenda) // @@ in future, updater.updateMany
|
|
65
|
+
await updater.updateMany([], agenda) // @@ in future, updater.updateMany
|
|
63
66
|
} catch (e) {
|
|
64
|
-
console.
|
|
67
|
+
console.error("Error: can't update " + person + ' as new contact:' + e)
|
|
65
68
|
throw new Error('Updating new contact: ' + e)
|
|
66
69
|
}
|
|
67
70
|
return person
|
|
@@ -77,12 +80,13 @@ export function sanitizeToAlpha (name) { // https://mathiasbynens.be/notes/es6-u
|
|
|
77
80
|
* @returns group
|
|
78
81
|
*/
|
|
79
82
|
export async function saveNewGroup (book, name) {
|
|
83
|
+
await kb.fetcher.load(book.doc())
|
|
80
84
|
const gix = kb.any(book, ns.vcard('groupIndex'))
|
|
81
85
|
|
|
82
86
|
const gname = sanitizeToAlpha(name)
|
|
83
87
|
const group = kb.sym(book.dir().uri + 'Group/' + gname + '.ttl#this')
|
|
84
88
|
const doc = group.doc()
|
|
85
|
-
console.log(' New group will be: ' + group + '\n')
|
|
89
|
+
// console.log(' New group will be: ' + group + '\n')
|
|
86
90
|
try {
|
|
87
91
|
await kb.fetcher.load(gix)
|
|
88
92
|
} catch (err) {
|
|
@@ -125,7 +129,7 @@ export async function addPersonToGroup (thing, group) {
|
|
|
125
129
|
|
|
126
130
|
const types = kb.findTypeURIs(thing)
|
|
127
131
|
for (const ty in types) {
|
|
128
|
-
console.log(' drop object type includes: ' + ty) // @@ Allow email addresses and phone numbers to be dropped?
|
|
132
|
+
// console.log(' drop object type includes: ' + ty) // @@ Allow email addresses and phone numbers to be dropped?
|
|
129
133
|
}
|
|
130
134
|
if (!(ns.vcard('Individual').uri in types ||
|
|
131
135
|
ns.vcard('Organization').uri in types)) {
|
|
@@ -134,7 +138,7 @@ export async function addPersonToGroup (thing, group) {
|
|
|
134
138
|
const pname = kb.any(thing, ns.vcard('fn'))
|
|
135
139
|
const gname = kb.any(group, ns.vcard('fn'))
|
|
136
140
|
if (!pname) { return alert('No vcard name known for ' + thing) }
|
|
137
|
-
const already = kb.holds(
|
|
141
|
+
const already = kb.holds(thing, ns.vcard('fn'), null, group.doc())
|
|
138
142
|
if (already) {
|
|
139
143
|
return alert(
|
|
140
144
|
'ALREADY added ' + pname + ' to group ' + gname
|
|
@@ -143,13 +147,86 @@ export async function addPersonToGroup (thing, group) {
|
|
|
143
147
|
const message = 'Add ' + pname + ' to group ' + gname + '?'
|
|
144
148
|
if (!confirm(message)) return
|
|
145
149
|
const ins = [
|
|
146
|
-
$rdf.st(group, ns.vcard('hasMember'), thing, group.doc()),
|
|
147
150
|
$rdf.st(thing, ns.vcard('fn'), pname, group.doc())
|
|
148
151
|
]
|
|
152
|
+
// find person webIDs and insert in vcard:hasMember
|
|
153
|
+
const webIDs = getPersonas(kb, thing).map(webid => webid.value)
|
|
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
|
+
}
|
|
149
162
|
try {
|
|
150
163
|
await updater.update([], ins)
|
|
164
|
+
// to allow refresh of card groupList
|
|
165
|
+
kb.fetcher.unload(group.doc())
|
|
166
|
+
await kb.fetcher.load(group.doc())
|
|
151
167
|
} catch (e) {
|
|
152
168
|
throw new Error(`Error adding ${pname} to group ${gname}:` + e)
|
|
153
169
|
}
|
|
154
170
|
return thing
|
|
155
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
|
@@ -14,11 +14,14 @@ to change its state according to an ontology, comment on it, etc.
|
|
|
14
14
|
*/
|
|
15
15
|
/* global alert, confirm */
|
|
16
16
|
|
|
17
|
+
import { authn } from 'solid-logic'
|
|
18
|
+
import { addPersonToGroup, saveNewContact, saveNewGroup, groupMembers } from './contactLogic'
|
|
17
19
|
import * as UI from 'solid-ui'
|
|
18
|
-
import { toolsPane } from './toolsPane'
|
|
19
20
|
import { mintNewAddressBook } from './mintNewAddressBook'
|
|
20
21
|
import { renderIndividual } from './individual'
|
|
21
|
-
import {
|
|
22
|
+
import { toolsPane } from './toolsPane'
|
|
23
|
+
import { groupMembership } from './groupMembershipControl'
|
|
24
|
+
import { getDataModelIssues } from './contactLogic'
|
|
22
25
|
|
|
23
26
|
// const $rdf = UI.rdf
|
|
24
27
|
const ns = UI.ns
|
|
@@ -63,7 +66,7 @@ export default {
|
|
|
63
66
|
|
|
64
67
|
// Reproduction: Spawn a new instance of this app
|
|
65
68
|
function newAddressBookButton (thisAddressBook) {
|
|
66
|
-
return UI.
|
|
69
|
+
return UI.login.newAppInstance(
|
|
67
70
|
dom,
|
|
68
71
|
{ noun: 'address book', appPathSegment: 'contactorator.timbl.com' },
|
|
69
72
|
function (ws, newBase) {
|
|
@@ -79,14 +82,14 @@ export default {
|
|
|
79
82
|
const dom = dataBrowserContext.dom
|
|
80
83
|
const kb = dataBrowserContext.session.store
|
|
81
84
|
const div = dom.createElement('div')
|
|
82
|
-
const me =
|
|
85
|
+
const me = authn.currentUser() // If already logged on
|
|
83
86
|
|
|
84
87
|
UI.aclControl.preventBrowserDropEvents(dom) // protect drag and drop
|
|
85
88
|
|
|
86
89
|
div.setAttribute('class', 'contactPane')
|
|
87
90
|
|
|
88
91
|
asyncRender().then(
|
|
89
|
-
() => console.log('
|
|
92
|
+
() => console.log('contactsPane Rendered ' + subject),
|
|
90
93
|
err => complain('' + err))
|
|
91
94
|
return div
|
|
92
95
|
|
|
@@ -96,7 +99,7 @@ export default {
|
|
|
96
99
|
|
|
97
100
|
const t = kb.findTypeURIs(subject)
|
|
98
101
|
|
|
99
|
-
let me =
|
|
102
|
+
let me = authn.currentUser()
|
|
100
103
|
|
|
101
104
|
const context = {
|
|
102
105
|
target: subject,
|
|
@@ -212,9 +215,30 @@ export default {
|
|
|
212
215
|
) {
|
|
213
216
|
console.log('Deleting a contact ' + pname)
|
|
214
217
|
await loadAllGroups() // need to wait for all groups to be loaded in case they have a link to this person
|
|
215
|
-
|
|
218
|
+
// load people.ttl
|
|
219
|
+
const nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex'))
|
|
220
|
+
await kb.fetcher.load(nameEmailIndex)
|
|
221
|
+
|
|
222
|
+
// - delete person's WebID's in each Group
|
|
216
223
|
// - delete the references to it in group files and save them back
|
|
217
|
-
//
|
|
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)
|
|
241
|
+
await deleteThingAndDoc(person)
|
|
218
242
|
await deleteRecursive(kb, container)
|
|
219
243
|
refreshNames() // "Doesn't work" -- maybe does now with waiting for async
|
|
220
244
|
cardMain.innerHTML = 'Contact Data Deleted.'
|
|
@@ -289,8 +313,10 @@ export default {
|
|
|
289
313
|
}
|
|
290
314
|
|
|
291
315
|
async function loadAllGroups () {
|
|
316
|
+
await kb.fetcher.load(groupIndex)
|
|
292
317
|
const gs = book ? kb.each(book, ns.vcard('includesGroup'), null, groupIndex) : []
|
|
293
318
|
await kb.fetcher.load(gs)
|
|
319
|
+
return gs
|
|
294
320
|
}
|
|
295
321
|
|
|
296
322
|
function groupsInOrder () {
|
|
@@ -409,12 +435,12 @@ export default {
|
|
|
409
435
|
}
|
|
410
436
|
|
|
411
437
|
let cards = []
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
cards = cards.concat(
|
|
438
|
+
const groups = Object.keys(selectedGroups).map(groupURI => kb.sym(groupURI))
|
|
439
|
+
groups.forEach(group => {
|
|
440
|
+
if (selectedGroups[group.value]) {
|
|
441
|
+
cards = cards.concat(groupMembers(kb, group))
|
|
416
442
|
}
|
|
417
|
-
}
|
|
443
|
+
})
|
|
418
444
|
cards.sort(compareForSort) // @@ sort by name not UID later
|
|
419
445
|
for (let k = 0; k < cards.length - 1;) {
|
|
420
446
|
if (cards[k].uri === cards[k + 1].uri) {
|
|
@@ -572,14 +598,28 @@ export default {
|
|
|
572
598
|
const groups = groupsInOrder()
|
|
573
599
|
utils.syncTableToArrayReOrdered(groupsMainTable, groups, renderGroupRow)
|
|
574
600
|
refreshGroupsSelected()
|
|
601
|
+
// await checkDataModel(groups)
|
|
575
602
|
} // syncGroupTable
|
|
576
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
|
+
|
|
577
617
|
// Click on New Group button
|
|
578
618
|
async function newGroupClickHandler (_event) {
|
|
579
619
|
cardMain.innerHTML = ''
|
|
580
620
|
const groupIndex = kb.any(book, ns.vcard('groupIndex'))
|
|
581
621
|
try {
|
|
582
|
-
await
|
|
622
|
+
await kb.fetcher.load(groupIndex)
|
|
583
623
|
} catch (e) {
|
|
584
624
|
console.log('Error: Group index NOT loaded:' + e + '\n')
|
|
585
625
|
}
|
|
@@ -623,7 +663,7 @@ export default {
|
|
|
623
663
|
const nameEmailIndex = kb.any(ourBook, ns.vcard('nameEmailIndex'))
|
|
624
664
|
if (!nameEmailIndex) throw new Error('Wot no nameEmailIndex?')
|
|
625
665
|
await kb.fetcher.load(nameEmailIndex)
|
|
626
|
-
console.log('Name index loaded async' + nameEmailIndex)
|
|
666
|
+
// console.log('Name index loaded async' + nameEmailIndex)
|
|
627
667
|
|
|
628
668
|
const name = await UI.widgets
|
|
629
669
|
.askName(dom, kb, cardMain, UI.ns.foaf('name'), klass) // @@ was, 'person'
|
|
@@ -772,7 +812,7 @@ export default {
|
|
|
772
812
|
const container = dom.createElement('div')
|
|
773
813
|
newContactButton.setAttribute('type', 'button')
|
|
774
814
|
if (!me) newContactButton.setAttribute('disabled', 'true')
|
|
775
|
-
|
|
815
|
+
authn.checkUser().then(webId => {
|
|
776
816
|
if (webId) {
|
|
777
817
|
me = webId
|
|
778
818
|
newContactButton.removeAttribute('disabled')
|
|
@@ -788,7 +828,7 @@ export default {
|
|
|
788
828
|
const container2 = dom.createElement('div')
|
|
789
829
|
newOrganizationButton.setAttribute('type', 'button')
|
|
790
830
|
if (!me) newOrganizationButton.setAttribute('disabled', 'true')
|
|
791
|
-
|
|
831
|
+
authn.checkUser().then(webId => {
|
|
792
832
|
if (webId) {
|
|
793
833
|
me = webId
|
|
794
834
|
newOrganizationButton.removeAttribute('disabled')
|
|
@@ -835,6 +875,10 @@ export default {
|
|
|
835
875
|
// })
|
|
836
876
|
|
|
837
877
|
div.appendChild(dom.createElement('hr'))
|
|
878
|
+
|
|
879
|
+
// const groups = await loadAllGroups() @@@
|
|
880
|
+
checkDataModel().then(()=> {console.log('async checkDataModel done.')})
|
|
881
|
+
|
|
838
882
|
// div.appendChild(newAddressBookButton(book)) // later
|
|
839
883
|
// end of AddressBook instance
|
|
840
884
|
} // renderThreeColumnBrowser
|
|
@@ -855,7 +899,7 @@ export default {
|
|
|
855
899
|
// Render a Group instance
|
|
856
900
|
} else if (t[ns.vcard('Group').uri]) {
|
|
857
901
|
// If we have a main address book, then render this group as a guest group within it
|
|
858
|
-
UI.
|
|
902
|
+
UI.login
|
|
859
903
|
.findAppInstances(context, ns.vcard('AddressBook'))
|
|
860
904
|
.then(function (context) {
|
|
861
905
|
const addressBooks = context.instances
|
|
@@ -883,12 +927,12 @@ export default {
|
|
|
883
927
|
)
|
|
884
928
|
}
|
|
885
929
|
|
|
886
|
-
me =
|
|
930
|
+
me = authn.currentUser()
|
|
887
931
|
if (!me) {
|
|
888
932
|
console.log(
|
|
889
933
|
'(You do not have your Web Id set. Sign in or sign up to make changes.)'
|
|
890
934
|
)
|
|
891
|
-
UI.
|
|
935
|
+
UI.login.ensureLoadedProfile(context).then(
|
|
892
936
|
context => {
|
|
893
937
|
console.log('Logged in as ' + context.me)
|
|
894
938
|
me = context.me
|
package/diff.txt
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,23 +1,41 @@
|
|
|
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
|
-
const $rdf = UI.rdf
|
|
6
|
+
// const $rdf = UI.rdf
|
|
6
7
|
const ns = UI.ns
|
|
7
8
|
// const buttons = UI.buttonsn no
|
|
8
9
|
// const widgets = UI.widgets
|
|
9
10
|
const utils = UI.utils
|
|
10
|
-
const kb = UI.store
|
|
11
11
|
// const style = UI.style
|
|
12
|
+
const kb = store
|
|
12
13
|
|
|
13
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
|
+
}
|
|
14
22
|
|
|
15
23
|
export async function renderGroupMemberships (person, context) {
|
|
16
24
|
// Remove a person from a group
|
|
17
|
-
function removeFromGroup (thing, group) {
|
|
25
|
+
async function removeFromGroup (thing, group) {
|
|
18
26
|
const pname = kb.any(thing, ns.vcard('fn'))
|
|
19
27
|
const gname = kb.any(group, ns.vcard('fn'))
|
|
20
|
-
|
|
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
|
|
21
39
|
if (groups.length < 2) {
|
|
22
40
|
alert(
|
|
23
41
|
'Must be a member of at least one group. Add to another group first.'
|
|
@@ -26,21 +44,28 @@ export async function renderGroupMemberships (person, context) {
|
|
|
26
44
|
}
|
|
27
45
|
const message = 'Remove ' + pname + ' from group ' + gname + '?'
|
|
28
46
|
if (confirm(message)) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
let del = kb
|
|
48
|
+
.statementsMatching(person, undefined, undefined, group.doc())
|
|
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
|
+
})
|
|
33
55
|
kb.updater.update(del, [], function (uri, ok, err) {
|
|
34
56
|
if (!ok) {
|
|
35
57
|
const message = 'Error removing member from group ' + group + ': ' + err
|
|
36
58
|
groupList.parentNode.appendChild(UI.widgets.errorMessageBlock(dom, message, 'pink'))
|
|
37
|
-
return
|
|
38
59
|
}
|
|
39
|
-
console.log('Removed ' + pname + ' from group ' + gname)
|
|
40
|
-
syncGroupList()
|
|
41
60
|
})
|
|
61
|
+
console.log('Removed ' + pname + ' from group ' + gname)
|
|
62
|
+
// to allow refresh of card groupList
|
|
63
|
+
kb.fetcher.unload(group.doc())
|
|
64
|
+
await kb.fetcher.load(group.doc())
|
|
65
|
+
syncGroupList()
|
|
42
66
|
}
|
|
43
67
|
}
|
|
68
|
+
|
|
44
69
|
function newRowForGroup (group) {
|
|
45
70
|
const options = {
|
|
46
71
|
deleteFunction: function () {
|
|
@@ -52,13 +77,31 @@ 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
|
-
utils.syncTableToArray(groupList,
|
|
82
|
+
// person and/or WebIDs to be changed
|
|
83
|
+
utils.syncTableToArray(groupList, groupMembership(person), newRowForGroup)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function loadGroupsFromBook (book = null) {
|
|
87
|
+
if (!book) {
|
|
88
|
+
book = kb.any(undefined, ns.vcard('includesGroup'))
|
|
89
|
+
if (!book) {
|
|
90
|
+
throw new Error('findBookFromGroups: Cant find address book which this group is part of')
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const groupIndex = kb.any(book, ns.vcard('groupIndex'))
|
|
94
|
+
const gs = book ? kb.each(book, ns.vcard('includesGroup'), null, groupIndex) : []
|
|
95
|
+
await kb.fetcher.load(gs)
|
|
58
96
|
}
|
|
59
97
|
|
|
60
98
|
const { dom } = context
|
|
99
|
+
const kb = context.session.store
|
|
61
100
|
const groupList = dom.createElement('table')
|
|
101
|
+
|
|
102
|
+
// find book any group and load all groups
|
|
103
|
+
await loadGroupsFromBook()
|
|
104
|
+
|
|
62
105
|
groupList.refresh = syncGroupList
|
|
63
106
|
syncGroupList()
|
|
64
107
|
return groupList
|
package/individual.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as UI from 'solid-ui'
|
|
2
|
+
import { authn, store } from 'solid-logic'
|
|
2
3
|
import { renderMugshotGallery } from './mugshotGallery'
|
|
3
4
|
import { renderWebIdControl, renderPublicIdControl } from './webidControl'
|
|
4
5
|
import { renderGroupMemberships } from './groupMembershipControl.js'
|
|
@@ -7,7 +8,7 @@ import VCARD_ONTOLOGY_TEXT from './lib/vcard.js'
|
|
|
7
8
|
|
|
8
9
|
const $rdf = UI.rdf
|
|
9
10
|
const ns = UI.ns
|
|
10
|
-
const kb =
|
|
11
|
+
const kb = store
|
|
11
12
|
const style = UI.style
|
|
12
13
|
|
|
13
14
|
export function loadTurtleText (kb, thing, text) {
|
|
@@ -68,7 +69,7 @@ export async function renderIndividual (dom, div, subject, dataBrowserContext) {
|
|
|
68
69
|
|
|
69
70
|
div.style = style.paneDivStyle || 'padding: 0.5em 1.5em 1em 1.5em;'
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
authn.checkUser() // kick off async operation @@@ use async version
|
|
72
73
|
|
|
73
74
|
div.appendChild(renderMugshotGallery(dom, subject))
|
|
74
75
|
|
package/individualForm.js
CHANGED
|
File without changes
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"verbose": true, // Turn on console.log
|
|
3
|
+
|
|
4
|
+
testEnvironment: 'jsdom',
|
|
5
|
+
setupFilesAfterEnv: ["./jest.setup.ts"],
|
|
6
|
+
transformIgnorePatterns: ["/node_modules/(?!lit-html).+\\.js"],
|
|
7
|
+
|
|
8
|
+
testEnvironmentOptions: {
|
|
9
|
+
customExportConditions: ['node']
|
|
10
|
+
}
|
|
11
|
+
};
|
package/jest.setup.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import fetchMock from "jest-fetch-mock";
|
|
3
|
+
|
|
4
|
+
fetchMock.enableMocks();
|
|
5
|
+
|
|
6
|
+
// Polyfill for encoding which isn't present globally in jsdom
|
|
7
|
+
import { TextEncoder, TextDecoder } from 'util';
|
|
8
|
+
|
|
9
|
+
global.TextEncoder = TextEncoder;
|
|
10
|
+
global.TextDecoder = TextDecoder;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The Control with decorations
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.renderAutocompleteControl = void 0;
|
|
14
|
+
const solid_logic_1 = require("solid-logic");
|
|
15
|
+
const solid_ui_1 = require("solid-ui");
|
|
16
|
+
const autocompletePicker_1 = require("./autocompletePicker"); // dbpediaParameters
|
|
17
|
+
const publicData_1 = require("./publicData");
|
|
18
|
+
const WEBID_NOUN = 'Solid ID';
|
|
19
|
+
const kb = solid_logic_1.store;
|
|
20
|
+
const AUTOCOMPLETE_THRESHOLD = 4; // don't check until this many characters typed
|
|
21
|
+
const AUTOCOMPLETE_ROWS = 12; // 20?
|
|
22
|
+
const GREEN_PLUS = solid_ui_1.icons.iconBase + 'noun_34653_green.svg';
|
|
23
|
+
const SEARCH_ICON = solid_ui_1.icons.iconBase + 'noun_Search_875351.svg';
|
|
24
|
+
function renderAutocompleteControl(dom, person, options, addOneIdAndRefresh) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
function autoCompleteDone(object, _name) {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
const webid = object.uri;
|
|
29
|
+
removeDecorated();
|
|
30
|
+
return addOneIdAndRefresh(person, webid);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function greenButtonHandler(_event) {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
const webid = yield solid_ui_1.widgets.askName(dom, solid_logic_1.store, creationArea, solid_ui_1.ns.vcard('url'), null, WEBID_NOUN);
|
|
36
|
+
if (!webid) {
|
|
37
|
+
return; // cancelled by user
|
|
38
|
+
}
|
|
39
|
+
return addOneIdAndRefresh(person, webid);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function removeDecorated() {
|
|
43
|
+
creationArea.removeChild(decoratedAutocomplete);
|
|
44
|
+
decoratedAutocomplete = null;
|
|
45
|
+
}
|
|
46
|
+
function searchButtonHandler(_event) {
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
if (decoratedAutocomplete) {
|
|
49
|
+
creationArea.removeChild(decoratedAutocomplete);
|
|
50
|
+
decoratedAutocomplete = null;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
decoratedAutocomplete = dom.createElement('div');
|
|
54
|
+
decoratedAutocomplete.setAttribute('style', 'display: flex; flex-flow: wrap;');
|
|
55
|
+
decoratedAutocomplete.appendChild(yield (0, autocompletePicker_1.renderAutoComplete)(dom, acOptions, autoCompleteDone));
|
|
56
|
+
decoratedAutocomplete.appendChild(acceptButton);
|
|
57
|
+
decoratedAutocomplete.appendChild(cancelButton);
|
|
58
|
+
creationArea.appendChild(decoratedAutocomplete);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function droppedURIHandler(uris) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
for (const webid of uris) { // normally one but can be more than one
|
|
65
|
+
yield addOneIdAndRefresh(person, webid);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const queryParams = options.queryParameters || publicData_1.wikidataParameters;
|
|
70
|
+
const acceptButton = solid_ui_1.widgets.continueButton(dom);
|
|
71
|
+
const cancelButton = solid_ui_1.widgets.cancelButton(dom, removeDecorated);
|
|
72
|
+
const klass = options.class;
|
|
73
|
+
const acOptions = {
|
|
74
|
+
queryParams,
|
|
75
|
+
class: klass,
|
|
76
|
+
acceptButton,
|
|
77
|
+
cancelButton
|
|
78
|
+
};
|
|
79
|
+
var decoratedAutocomplete = null;
|
|
80
|
+
// const { dom } = dataBrowserContext
|
|
81
|
+
options = options || {};
|
|
82
|
+
options.editable = kb.updater.editable(person.doc().uri, kb);
|
|
83
|
+
const creationArea = dom.createElement('div');
|
|
84
|
+
creationArea.setAttribute('style', 'display: flex; flex-flow: wrap;');
|
|
85
|
+
if (options.editable) {
|
|
86
|
+
// creationArea.appendChild(await renderAutoComplete(dom, options, autoCompleteDone)) wait for searchButton
|
|
87
|
+
creationArea.style.width = '100%';
|
|
88
|
+
const plus = creationArea.appendChild(solid_ui_1.widgets.button(dom, GREEN_PLUS, options.idNoun, greenButtonHandler));
|
|
89
|
+
solid_ui_1.widgets.makeDropTarget(plus, droppedURIHandler, null);
|
|
90
|
+
if (options.dbLookup) {
|
|
91
|
+
creationArea.appendChild(solid_ui_1.widgets.button(dom, SEARCH_ICON, options.idNoun, searchButtonHandler));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return creationArea;
|
|
95
|
+
});
|
|
96
|
+
} // renderAutocompleteControl
|
|
97
|
+
exports.renderAutocompleteControl = renderAutocompleteControl;
|
|
98
|
+
// ends
|
|
99
|
+
//# sourceMappingURL=autocompleteBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocompleteBar.js","sourceRoot":"","sources":["../src/autocompleteBar.ts"],"names":[],"mappings":"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"}
|