contacts-pane 2.4.9 → 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 +80 -12
- package/contactsPane.js +55 -16
- 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 +33 -8
- 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 +24 -12
- 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 +28 -10
package/contactLogic.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// Logic for solid contacts
|
|
2
2
|
|
|
3
3
|
import * as UI from 'solid-ui'
|
|
4
|
+
import { store } from 'solid-logic'
|
|
4
5
|
import { getPersonas } from './webidControl'
|
|
5
6
|
|
|
6
7
|
const ns = UI.ns
|
|
7
8
|
const $rdf = UI.rdf
|
|
8
9
|
const utils = UI.utils
|
|
9
|
-
const kb =
|
|
10
|
+
const kb = store
|
|
10
11
|
const updater = kb.updater
|
|
11
12
|
|
|
12
13
|
/** Perform updates on more than one document @@ Move to rdflib!
|
|
@@ -29,6 +30,7 @@ export async function updateMany (deletions, insertions = []) {
|
|
|
29
30
|
* @returns {NamedNode} the person
|
|
30
31
|
*/
|
|
31
32
|
export async function saveNewContact (book, name, selectedGroups, klass) {
|
|
33
|
+
await kb.fetcher.load(book.doc())
|
|
32
34
|
const nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex'))
|
|
33
35
|
|
|
34
36
|
const uuid = utils.genUuid()
|
|
@@ -60,9 +62,9 @@ export async function saveNewContact (book, name, selectedGroups, klass) {
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
try {
|
|
63
|
-
await updateMany([], agenda) // @@ in future, updater.updateMany
|
|
65
|
+
await updater.updateMany([], agenda) // @@ in future, updater.updateMany
|
|
64
66
|
} catch (e) {
|
|
65
|
-
console.
|
|
67
|
+
console.error("Error: can't update " + person + ' as new contact:' + e)
|
|
66
68
|
throw new Error('Updating new contact: ' + e)
|
|
67
69
|
}
|
|
68
70
|
return person
|
|
@@ -78,12 +80,13 @@ export function sanitizeToAlpha (name) { // https://mathiasbynens.be/notes/es6-u
|
|
|
78
80
|
* @returns group
|
|
79
81
|
*/
|
|
80
82
|
export async function saveNewGroup (book, name) {
|
|
83
|
+
await kb.fetcher.load(book.doc())
|
|
81
84
|
const gix = kb.any(book, ns.vcard('groupIndex'))
|
|
82
85
|
|
|
83
86
|
const gname = sanitizeToAlpha(name)
|
|
84
87
|
const group = kb.sym(book.dir().uri + 'Group/' + gname + '.ttl#this')
|
|
85
88
|
const doc = group.doc()
|
|
86
|
-
console.log(' New group will be: ' + group + '\n')
|
|
89
|
+
// console.log(' New group will be: ' + group + '\n')
|
|
87
90
|
try {
|
|
88
91
|
await kb.fetcher.load(gix)
|
|
89
92
|
} catch (err) {
|
|
@@ -126,7 +129,7 @@ export async function addPersonToGroup (thing, group) {
|
|
|
126
129
|
|
|
127
130
|
const types = kb.findTypeURIs(thing)
|
|
128
131
|
for (const ty in types) {
|
|
129
|
-
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?
|
|
130
133
|
}
|
|
131
134
|
if (!(ns.vcard('Individual').uri in types ||
|
|
132
135
|
ns.vcard('Organization').uri in types)) {
|
|
@@ -135,7 +138,7 @@ export async function addPersonToGroup (thing, group) {
|
|
|
135
138
|
const pname = kb.any(thing, ns.vcard('fn'))
|
|
136
139
|
const gname = kb.any(group, ns.vcard('fn'))
|
|
137
140
|
if (!pname) { return alert('No vcard name known for ' + thing) }
|
|
138
|
-
const already = kb.holds(
|
|
141
|
+
const already = kb.holds(thing, ns.vcard('fn'), null, group.doc())
|
|
139
142
|
if (already) {
|
|
140
143
|
return alert(
|
|
141
144
|
'ALREADY added ' + pname + ' to group ' + gname
|
|
@@ -144,21 +147,86 @@ export async function addPersonToGroup (thing, group) {
|
|
|
144
147
|
const message = 'Add ' + pname + ' to group ' + gname + '?'
|
|
145
148
|
if (!confirm(message)) return
|
|
146
149
|
const ins = [
|
|
147
|
-
$rdf.st(group, ns.vcard('hasMember'), thing, group.doc()),
|
|
148
150
|
$rdf.st(thing, ns.vcard('fn'), pname, group.doc())
|
|
149
151
|
]
|
|
150
|
-
// find person webIDs
|
|
152
|
+
// find person webIDs and insert in vcard:hasMember
|
|
151
153
|
const webIDs = getPersonas(kb, thing).map(webid => webid.value)
|
|
152
|
-
webIDs.
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
}
|
|
155
162
|
try {
|
|
156
163
|
await updater.update([], ins)
|
|
157
164
|
// to allow refresh of card groupList
|
|
158
165
|
kb.fetcher.unload(group.doc())
|
|
159
|
-
kb.fetcher.load(group.doc())
|
|
166
|
+
await kb.fetcher.load(group.doc())
|
|
160
167
|
} catch (e) {
|
|
161
168
|
throw new Error(`Error adding ${pname} to group ${gname}:` + e)
|
|
162
169
|
}
|
|
163
170
|
return thing
|
|
164
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,
|
|
@@ -216,8 +219,25 @@ export default {
|
|
|
216
219
|
const nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex'))
|
|
217
220
|
await kb.fetcher.load(nameEmailIndex)
|
|
218
221
|
|
|
222
|
+
// - delete person's WebID's in each Group
|
|
219
223
|
// - delete the references to it in group files and save them back
|
|
220
|
-
//
|
|
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)
|
|
221
241
|
await deleteThingAndDoc(person)
|
|
222
242
|
await deleteRecursive(kb, container)
|
|
223
243
|
refreshNames() // "Doesn't work" -- maybe does now with waiting for async
|
|
@@ -293,8 +313,10 @@ export default {
|
|
|
293
313
|
}
|
|
294
314
|
|
|
295
315
|
async function loadAllGroups () {
|
|
316
|
+
await kb.fetcher.load(groupIndex)
|
|
296
317
|
const gs = book ? kb.each(book, ns.vcard('includesGroup'), null, groupIndex) : []
|
|
297
318
|
await kb.fetcher.load(gs)
|
|
319
|
+
return gs
|
|
298
320
|
}
|
|
299
321
|
|
|
300
322
|
function groupsInOrder () {
|
|
@@ -416,8 +438,7 @@ export default {
|
|
|
416
438
|
const groups = Object.keys(selectedGroups).map(groupURI => kb.sym(groupURI))
|
|
417
439
|
groups.forEach(group => {
|
|
418
440
|
if (selectedGroups[group.value]) {
|
|
419
|
-
|
|
420
|
-
cards = cards.concat(a)
|
|
441
|
+
cards = cards.concat(groupMembers(kb, group))
|
|
421
442
|
}
|
|
422
443
|
})
|
|
423
444
|
cards.sort(compareForSort) // @@ sort by name not UID later
|
|
@@ -577,14 +598,28 @@ export default {
|
|
|
577
598
|
const groups = groupsInOrder()
|
|
578
599
|
utils.syncTableToArrayReOrdered(groupsMainTable, groups, renderGroupRow)
|
|
579
600
|
refreshGroupsSelected()
|
|
601
|
+
// await checkDataModel(groups)
|
|
580
602
|
} // syncGroupTable
|
|
581
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
|
+
|
|
582
617
|
// Click on New Group button
|
|
583
618
|
async function newGroupClickHandler (_event) {
|
|
584
619
|
cardMain.innerHTML = ''
|
|
585
620
|
const groupIndex = kb.any(book, ns.vcard('groupIndex'))
|
|
586
621
|
try {
|
|
587
|
-
await
|
|
622
|
+
await kb.fetcher.load(groupIndex)
|
|
588
623
|
} catch (e) {
|
|
589
624
|
console.log('Error: Group index NOT loaded:' + e + '\n')
|
|
590
625
|
}
|
|
@@ -628,7 +663,7 @@ export default {
|
|
|
628
663
|
const nameEmailIndex = kb.any(ourBook, ns.vcard('nameEmailIndex'))
|
|
629
664
|
if (!nameEmailIndex) throw new Error('Wot no nameEmailIndex?')
|
|
630
665
|
await kb.fetcher.load(nameEmailIndex)
|
|
631
|
-
console.log('Name index loaded async' + nameEmailIndex)
|
|
666
|
+
// console.log('Name index loaded async' + nameEmailIndex)
|
|
632
667
|
|
|
633
668
|
const name = await UI.widgets
|
|
634
669
|
.askName(dom, kb, cardMain, UI.ns.foaf('name'), klass) // @@ was, 'person'
|
|
@@ -777,7 +812,7 @@ export default {
|
|
|
777
812
|
const container = dom.createElement('div')
|
|
778
813
|
newContactButton.setAttribute('type', 'button')
|
|
779
814
|
if (!me) newContactButton.setAttribute('disabled', 'true')
|
|
780
|
-
|
|
815
|
+
authn.checkUser().then(webId => {
|
|
781
816
|
if (webId) {
|
|
782
817
|
me = webId
|
|
783
818
|
newContactButton.removeAttribute('disabled')
|
|
@@ -793,7 +828,7 @@ export default {
|
|
|
793
828
|
const container2 = dom.createElement('div')
|
|
794
829
|
newOrganizationButton.setAttribute('type', 'button')
|
|
795
830
|
if (!me) newOrganizationButton.setAttribute('disabled', 'true')
|
|
796
|
-
|
|
831
|
+
authn.checkUser().then(webId => {
|
|
797
832
|
if (webId) {
|
|
798
833
|
me = webId
|
|
799
834
|
newOrganizationButton.removeAttribute('disabled')
|
|
@@ -840,6 +875,10 @@ export default {
|
|
|
840
875
|
// })
|
|
841
876
|
|
|
842
877
|
div.appendChild(dom.createElement('hr'))
|
|
878
|
+
|
|
879
|
+
// const groups = await loadAllGroups() @@@
|
|
880
|
+
checkDataModel().then(()=> {console.log('async checkDataModel done.')})
|
|
881
|
+
|
|
843
882
|
// div.appendChild(newAddressBookButton(book)) // later
|
|
844
883
|
// end of AddressBook instance
|
|
845
884
|
} // renderThreeColumnBrowser
|
|
@@ -860,7 +899,7 @@ export default {
|
|
|
860
899
|
// Render a Group instance
|
|
861
900
|
} else if (t[ns.vcard('Group').uri]) {
|
|
862
901
|
// If we have a main address book, then render this group as a guest group within it
|
|
863
|
-
UI.
|
|
902
|
+
UI.login
|
|
864
903
|
.findAppInstances(context, ns.vcard('AddressBook'))
|
|
865
904
|
.then(function (context) {
|
|
866
905
|
const addressBooks = context.instances
|
|
@@ -888,12 +927,12 @@ export default {
|
|
|
888
927
|
)
|
|
889
928
|
}
|
|
890
929
|
|
|
891
|
-
me =
|
|
930
|
+
me = authn.currentUser()
|
|
892
931
|
if (!me) {
|
|
893
932
|
console.log(
|
|
894
933
|
'(You do not have your Web Id set. Sign in or sign up to make changes.)'
|
|
895
934
|
)
|
|
896
|
-
UI.
|
|
935
|
+
UI.login.ensureLoadedProfile(context).then(
|
|
897
936
|
context => {
|
|
898
937
|
console.log('Logged in as ' + context.me)
|
|
899
938
|
me = context.me
|
package/diff.txt
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,22 +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
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
|
+
}
|
|
22
|
+
|
|
14
23
|
export async function renderGroupMemberships (person, context) {
|
|
15
24
|
// Remove a person from a group
|
|
16
25
|
async function removeFromGroup (thing, group) {
|
|
17
26
|
const pname = kb.any(thing, ns.vcard('fn'))
|
|
18
27
|
const gname = kb.any(group, ns.vcard('fn'))
|
|
19
|
-
|
|
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
|
|
20
39
|
if (groups.length < 2) {
|
|
21
40
|
alert(
|
|
22
41
|
'Must be a member of at least one group. Add to another group first.'
|
|
@@ -25,9 +44,14 @@ export async function renderGroupMemberships (person, context) {
|
|
|
25
44
|
}
|
|
26
45
|
const message = 'Remove ' + pname + ' from group ' + gname + '?'
|
|
27
46
|
if (confirm(message)) {
|
|
28
|
-
|
|
47
|
+
let del = kb
|
|
29
48
|
.statementsMatching(person, undefined, undefined, group.doc())
|
|
30
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
|
+
})
|
|
31
55
|
kb.updater.update(del, [], function (uri, ok, err) {
|
|
32
56
|
if (!ok) {
|
|
33
57
|
const message = 'Error removing member from group ' + group + ': ' + err
|
|
@@ -37,7 +61,7 @@ export async function renderGroupMemberships (person, context) {
|
|
|
37
61
|
console.log('Removed ' + pname + ' from group ' + gname)
|
|
38
62
|
// to allow refresh of card groupList
|
|
39
63
|
kb.fetcher.unload(group.doc())
|
|
40
|
-
kb.fetcher.load(group.doc())
|
|
64
|
+
await kb.fetcher.load(group.doc())
|
|
41
65
|
syncGroupList()
|
|
42
66
|
}
|
|
43
67
|
}
|
|
@@ -53,13 +77,13 @@ export async function renderGroupMemberships (person, context) {
|
|
|
53
77
|
return tr
|
|
54
78
|
}
|
|
55
79
|
|
|
80
|
+
// find all groups where person has membership
|
|
56
81
|
function syncGroupList () {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
utils.syncTableToArray(groupList, groups, newRowForGroup)
|
|
82
|
+
// person and/or WebIDs to be changed
|
|
83
|
+
utils.syncTableToArray(groupList, groupMembership(person), newRowForGroup)
|
|
60
84
|
}
|
|
61
85
|
|
|
62
|
-
async function loadGroupsFromBook (book =
|
|
86
|
+
async function loadGroupsFromBook (book = null) {
|
|
63
87
|
if (!book) {
|
|
64
88
|
book = kb.any(undefined, ns.vcard('includesGroup'))
|
|
65
89
|
if (!book) {
|
|
@@ -72,6 +96,7 @@ export async function renderGroupMemberships (person, context) {
|
|
|
72
96
|
}
|
|
73
97
|
|
|
74
98
|
const { dom } = context
|
|
99
|
+
const kb = context.session.store
|
|
75
100
|
const groupList = dom.createElement('table')
|
|
76
101
|
|
|
77
102
|
// find book any group and load all groups
|
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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"}
|