UNPKG

contacts-pane

Version:

Contacts Pane: Contacts manager for Address Book, Groups, and Individuals.

963 lines (864 loc) 36.2 kB
/* Contact AddressBook Pane ** ** This outline pane allows a user to interact with an contact, to change its state according to an ontology, comment on it, etc. ** ** See also things like ** http://www.w3.org/TR/vcard-rdf/ ** http://tools.ietf.org/html/rfc6350 ** http://www.iana.org/assignments/vcard-elements/vcard-elements.xhtml ** ** Feross "Standard" style note: Callback functions should not be called "callback" ** or the "standard" linter will complain if the first param is not a node.js error code. (2018-01) ** Hence "callbackFunction" */ /* global alert, confirm */ import { authn } from 'solid-logic' import { addPersonToGroup, saveNewContact, saveNewGroup, groupMembers } from './contactLogic' import * as UI from 'solid-ui' import { mintNewAddressBook } from './mintNewAddressBook' import { renderIndividual } from './individual' import { toolsPane } from './toolsPane' import { groupMembership } from './groupMembershipControl' import { getDataModelIssues } from './contactLogic' // const $rdf = UI.rdf const ns = UI.ns const utils = UI.utils const style = UI.style export default { icon: UI.icons.iconBase + 'noun_99101.svg', // changed from embedded icon 2016-05-01 name: 'contact', // Does the subject deserve an contact pane? label: function (subject, context) { const t = context.session.store.findTypeURIs(subject) if (t[ns.vcard('Individual').uri]) return 'Contact' if (t[ns.vcard('Organization').uri]) return 'contact' if (t[ns.foaf('Person').uri]) return 'Person' if (t[ns.schema('Person').uri]) return 'Person' if (t[ns.vcard('Group').uri]) return 'Group' if (t[ns.vcard('AddressBook').uri]) return 'Address book' return null // No under other circumstances }, mintClass: UI.ns.vcard('AddressBook'), mintNew: mintNewAddressBook, // Make a new address book // Render the pane render: function (subject, dataBrowserContext, paneOptions = {}) { const thisPane = this // stick functions here function complain (message) { console.log('contactsPane: ' + message) div.appendChild(UI.widgets.errorMessageBlock(dom, message, 'pink')) } function complainIfBad (ok, body) { if (!ok) { complain('Error: ' + body) } } // Reproduction: Spawn a new instance of this app function newAddressBookButton (thisAddressBook) { return UI.login.newAppInstance( dom, { noun: 'address book', appPathSegment: 'contactorator.timbl.com' }, function (ws, newBase) { thisPane.clone(thisAddressBook, newBase, { // @@ clone is not a thing - use mintNew me: me, div: div, dom: dom }) } ) } // newAddressBookButton const dom = dataBrowserContext.dom const kb = dataBrowserContext.session.store const div = dom.createElement('div') const me = authn.currentUser() // If already logged on UI.aclControl.preventBrowserDropEvents(dom) // protect drag and drop div.setAttribute('class', 'contactPane') asyncRender().then( () => console.log('contactsPane Rendered ' + subject), err => complain('' + err)) return div // Async part of render. Maybe API will later allow render to be async async function asyncRender () { UI.aclControl.preventBrowserDropEvents(dom) const t = kb.findTypeURIs(subject) let me = authn.currentUser() const context = { target: subject, me: me, noun: 'address book', div: div, dom: dom } // missing: statusRegion // Render a 3-column browser for an address book or a group function renderThreeColumnBrowser (books, context, options) { kb.fetcher .load(books) .then(function (_xhr) { renderThreeColumnBrowser2(books, context, options) }) .catch(function (err) { complain(err) }) } function renderThreeColumnBrowser2 (books, context, options) { const classLabel = utils.label(ns.vcard('AddressBook')) // const IndividualClassLabel = utils.label(ns.vcard('Individual')) let book = books[0] // for now const groupIndex = kb.any(book, ns.vcard('groupIndex')) let selectedGroups = {} let selectedPeople = {} // Actually prob max 1 const target = options.foreignGroup || book let title = kb.any(target, ns.dc('title')) || kb.any(target, ns.vcard('fn')) if (paneOptions.solo && title && typeof document !== 'undefined') { document.title = title.value // @@ only when the outermmost pane } title = title ? title.value : classLabel // The book could be the main subject, or linked from a group we are dealing with function findBookFromGroups (book) { if (book) { return book } let g for (const gu in selectedGroups) { g = kb.sym(gu) const b = kb.any(undefined, ns.vcard('includesGroup'), g) if (b) return b } throw new Error( 'findBookFromGroups: Cant find address book which this group is part of' ) } // Write a new contact to the web // organization-name is a hack for Mac records with no FN which is mandatory. function nameFor (x) { const name = kb.any(x, ns.vcard('fn')) || kb.any(x, ns.foaf('name')) || kb.any(x, ns.vcard('organization-name')) return name ? name.value : '???' } function filterName (name) { const filter = searchInput.value.trim().toLowerCase() if (filter.length === 0) return true const parts = filter.split(' ') // Each name part must be somewhere for (let j = 0; j < parts.length; j++) { const word = parts[j] if (name.toLowerCase().indexOf(word) < 0) return false } return true } function selectPerson (person) { cardMain.innerHTML = 'loading...' selectedPeople = {} selectedPeople[person.uri] = true refreshFilteredPeople() // Color to remember which one you picked const local = book ? localNode(person) : person kb.fetcher.nowOrWhenFetched(local.doc(), undefined, function ( ok, message ) { cardMain.innerHTML = '' if (!ok) { return complainIfBad( ok, "Can't load card: " + local + ': ' + message ) } // console.log("Loaded card " + local + '\n') cardMain.appendChild(renderPane(dom, local, 'contact')) cardMain.appendChild(dom.createElement('br')) cardMain.appendChild(UI.widgets.linkIcon(dom, local)) // hoverHide // Add in a delete button to delete from AB const deleteButton = UI.widgets.deleteButtonWithCheck( dom, cardMain, 'contact', async function () { const container = person.dir() // ASSUMPTION THAT CARD IS IN ITS OWN DIRECTORY // alert('Container to delete is ' + container) const pname = kb.any(person, ns.vcard('fn')) if ( confirm( 'Delete contact ' + pname + ' completely?? ' + container ) ) { console.log('Deleting a contact ' + pname) await loadAllGroups() // need to wait for all groups to be loaded in case they have a link to this person // load people.ttl const nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex')) await kb.fetcher.load(nameEmailIndex) // - delete person's WebID's in each Group // - delete the references to it in group files and save them back // - delete the reference in people.ttl and save it back // find all Groups const groups = groupMembership(person) let removeFromGroups = [] // find person WebID's groups.map( group => { const webids = getSameAs(kb, person, group.doc()) // for each check in each Group that it is not used by an other person then delete webids.map( webid => { if (getSameAs(kb, webid, group.doc()).length = 1) { removeFromGroups = removeFromGroups.concat(kb.statementsMatching(group, ns.vcard('hasMember'), webid, group.doc())) } }) }) // console.log(removeFromGroups) await kb.updater.updateMany(removeFromGroups) await deleteThingAndDoc(person) await deleteRecursive(kb, container) refreshNames() // "Doesn't work" -- maybe does now with waiting for async cardMain.innerHTML = 'Contact Data Deleted.' } } ) deleteButton.style = 'height: 2em;' }) } function refreshFilteredPeople (active) { let count = 0 let lastRow = null for (let i = 0; i < peopleMainTable.children.length; i++) { const row = peopleMainTable.children[i] const matches = filterName(nameFor(row.subject)) if (matches) { count++ lastRow = row } row.setAttribute( 'style', matches ? selectedPeople[row.subject.uri] ? 'background-color: #cce;' : '' : 'display: none;' ) } if (count === 1 && active) { const unique = lastRow.subject // selectedPeople = { } // selectedPeople[unique.uri] = true // lastRow.setAttribute('style', 'background-color: #cce;') selectPerson(unique) } } function selectAllGroups ( selectedGroups, groupsMainTable, callbackFunction ) { function fetchGroupAndSelct (group, groupRow) { groupRow.setAttribute('style', 'background-color: #ffe;') kb.fetcher.nowOrWhenFetched(group.doc(), undefined, function ( ok, message ) { if (!ok) { const msg = "Can't load group file: " + group + ': ' + message badness.push(msg) return complainIfBad(ok, msg) } groupRow.setAttribute('style', 'background-color: #cce;') selectedGroups[group.uri] = true refreshGroupsSelected() refreshNames() // @@ every time?? todo -= 1 if (!todo) { if (callbackFunction) { callbackFunction(badness.length === 0, badness) } } }) } let todo = groupsMainTable.children.length var badness = [] /* eslint-disable-line no-var */ for (let k = 0; k < groupsMainTable.children.length; k++) { const groupRow = groupsMainTable.children[k] const group = groupRow.subject fetchGroupAndSelct(group, groupRow) } // for each row } async function loadAllGroups () { await kb.fetcher.load(groupIndex) const gs = book ? kb.each(book, ns.vcard('includesGroup'), null, groupIndex) : [] await kb.fetcher.load(gs) return gs } function groupsInOrder () { let sortMe = [] if (options.foreignGroup) { sortMe.push([ '', kb.any(options.foreignGroup, ns.vcard('fn')), options.foreignGroup ]) } if (book) { books.forEach(function (book) { const gs = book ? kb.each(book, ns.vcard('includesGroup'), null, groupIndex) : [] const gs2 = gs.map(function (g) { return [book, kb.any(g, ns.vcard('fn')), g] }) sortMe = sortMe.concat(gs2) }) sortMe.sort() } return sortMe.map(tuple => tuple[2]) } function renderPane (dom, subject, paneName) { const p = dataBrowserContext.session.paneRegistry.byName(paneName) const d = p.render(subject, dataBrowserContext) d.setAttribute( 'style', 'border: 0.1em solid #444; border-radius: 0.5em' ) return d } function compareForSort (self, other) { let s = nameFor(self) let o = nameFor(other) if (s && o) { s = s.toLowerCase() o = o.toLowerCase() if (s > o) return 1 if (s < o) return -1 } if (self.uri > other.uri) return 1 if (self.uri < other.uri) return -1 return 0 } // In a LDP work, deletes the whole document describing a thing // plus patch out ALL mentiosn of it! Use with care! // beware of other data picked up from other places being smushed // together and then deleted. async function deleteThingAndDoc (x) { console.log('deleteThingAndDoc: ' + x) const ds = kb .statementsMatching(x) .concat(kb.statementsMatching(undefined, undefined, x)) try { await kb.updater.updateMany(ds) console.log('Deleting resoure ' + x.doc()) await kb.fetcher.delete(x.doc()) console.log('Delete thing ' + x + ': complete.') } catch (err) { complain('Error deleting thing ' + x + ': ' + err) } } // For deleting an addressbook sub-folder eg person - use with care! // @@ move to solid-logic function deleteRecursive (kb, folder) { return new Promise(function (resolve) { kb.fetcher.load(folder).then(function () { const promises = kb.each(folder, ns.ldp('contains')).map(file => { if (kb.holds(file, ns.rdf('type'), ns.ldp('BasicContainer'))) { return deleteRecursive(kb, file) } else { console.log('deleteRecirsive file: ' + file) if (!confirm(' Really DELETE File ' + file)) { throw new Error('User aborted delete file') } return kb.fetcher.webOperation('DELETE', file.uri) } }) console.log('deleteRecirsive folder: ' + folder) if (!confirm(' Really DELETE folder ' + folder)) { throw new Error('User aborted delete file') } promises.push(kb.fetcher.webOperation('DELETE', folder.uri)) Promise.all(promises).then(_res => { resolve() }) }) }) } function localNode (person, _div) { const aliases = kb.allAliases(person) const prefix = book.dir().uri for (let i = 0; i < aliases.length; i++) { if (aliases[i].uri.slice(0, prefix.length) === prefix) { return aliases[i] } } throw new Error('No local URI for ' + person) } /** Refresh the list of names */ function refreshNames () { function setPersonListener (personRow, person) { personRow.addEventListener('click', function (event) { event.preventDefault() selectPerson(person) }) } let cards = [] const groups = Object.keys(selectedGroups).map(groupURI => kb.sym(groupURI)) groups.forEach(group => { if (selectedGroups[group.value]) { cards = cards.concat(groupMembers(kb, group)) } }) cards.sort(compareForSort) // @@ sort by name not UID later for (let k = 0; k < cards.length - 1;) { if (cards[k].uri === cards[k + 1].uri) { cards.splice(k, 1) // Eliminate duplicates from more than one group } else { k++ } } peopleHeader.textContent = cards.length > 5 ? '' + cards.length + ' contacts' : 'contact' function renderNameInGroupList (person) { const personRow = dom.createElement('tr') const personLeft = personRow.appendChild(dom.createElement('td')) // const personRight = personRow.appendChild(dom.createElement('td')) personLeft.setAttribute('style', dataCellStyle) personRow.subject = person const name = nameFor(person) personLeft.textContent = name personRow.subject = person UI.widgets.makeDraggable(personRow, person) setPersonListener(personRow, person) return personRow } utils.syncTableToArrayReOrdered(peopleMainTable, cards, renderNameInGroupList) refreshFilteredPeople() } // refreshNames function refreshThingsSelected (table, selectionArray) { for (let i = 0; i < table.children.length; i++) { const row = table.children[i] if (row.subject) { row.setAttribute( 'style', selectionArray[row.subject.uri] ? 'background-color: #cce;' : '' ) } } } function refreshGroupsSelected () { return refreshThingsSelected(groupsMainTable, selectedGroups) } // Check every group is in the list and add it if not. function syncGroupTable () { function renderGroupRow (group) { // Is something is dropped on a group, add people to group async function handleURIsDroppedOnGroup (uris) { uris.forEach(function (u) { console.log('Dropped on group: ' + u) const thing = kb.sym(u) try { addPersonToGroup(thing, group) } catch (e) { complain(e) } refreshNames() }) } function groupRowClickListener (event) { event.preventDefault() const groupList = kb.sym(group.uri.split('#')[0]) if (!event.metaKey) { selectedGroups = {} // If Command key pressed, accumulate multiple } selectedGroups[group.uri] = !selectedGroups[group.uri] refreshGroupsSelected() peopleMainTable.innerHTML = '' // clear in case refreshNames doesn't work for unknown reason kb.fetcher.nowOrWhenFetched( groupList.uri, undefined, function (ok, message) { if (!ok) { return complainIfBad( ok, "Can't load group file: " + groupList + ': ' + message ) } refreshNames() if (!event.metaKey) { // If only one group has been selected, show ACL cardMain.innerHTML = '' let visible = false const aclControl = UI.aclControl.ACLControlBox5( group, dataBrowserContext, 'group', kb, function (ok, body, response) { if (!ok) { if (response && response.status && response.status === 403) { cardMain.innerHTML = 'No control access.' } else { cardMain.innerHTML = 'Failed to load access control: ' + body } } } ) const sharingButton = cardMain.appendChild( dom.createElement('button') ) sharingButton.style.cssText = 'padding: 1em; margin: 1em' const img = sharingButton.appendChild( dom.createElement('img') ) img.style.cssText = 'width: 1.5em; height: 1.5em' img.setAttribute( 'src', UI.icons.iconBase + 'noun_123691.svg' ) sharingButton.addEventListener('click', function () { visible = !visible if (visible) { cardMain.appendChild(aclControl) } else { cardMain.removeChild(aclControl) } }) } } ) } // Body of renderGroupRow const name = kb.any(group, ns.vcard('fn')) const groupRow = dom.createElement('tr') groupRow.subject = group UI.widgets.makeDraggable(groupRow, group) groupRow.setAttribute('style', dataCellStyle) groupRow.textContent = name UI.widgets.makeDropTarget(groupRow, handleURIsDroppedOnGroup) UI.widgets.deleteButtonWithCheck( dom, groupRow, 'group ' + name, async function () { await deleteThingAndDoc(group) syncGroupTable() } ) groupRow.addEventListener('click', groupRowClickListener, true) return groupRow } // renderGroupRow const groups = groupsInOrder() utils.syncTableToArrayReOrdered(groupsMainTable, groups, renderGroupRow) refreshGroupsSelected() // await checkDataModel(groups) } // syncGroupTable async function checkDataModel () { // await kb.fetcher.load(groups) // asssume loaded already const groups = await loadAllGroups() const { del, ins } = await getDataModelIssues(groups) if (del.length && confirm(`Groups data model need to be updated? (${del.length})`)) { await kb.updater.updateMany(del, ins) alert('Update done') } } // checkDataModel // Click on New Group button async function newGroupClickHandler (_event) { cardMain.innerHTML = '' const groupIndex = kb.any(book, ns.vcard('groupIndex')) try { await kb.fetcher.load(groupIndex) } catch (e) { console.log('Error: Group index NOT loaded:' + e + '\n') } console.log(' Group index has been loaded\n') const name = await UI.widgets.askName( dom, kb, cardMain, UI.ns.foaf('name'), ns.vcard('Group'), 'group') if (!name) return // cancelled by user let group try { group = await saveNewGroup(book, name) } catch (err) { console.log("Error: can't save new group:" + err) cardMain.innerHTML = 'Failed to save group' + err return } selectedGroups = {} selectedGroups[group.uri] = true syncGroupTable() // Refresh list of groups cardMain.innerHTML = '' cardMain.appendChild(UI.aclControl.ACLControlBox5( group.doc(), dataBrowserContext, 'group', kb, function (ok, body) { if (!ok) { cardMain.innerHTML = 'Group sharing setup failed: ' + body } })) } // newGroupClickHandler async function craeteNewCard (klass) { cardMain.innerHTML = '' const ourBook = findBookFromGroups(book) try { await kb.fetcher.load(ourBook) } catch (err) { throw new Error("Book won't load:" + ourBook) } const nameEmailIndex = kb.any(ourBook, ns.vcard('nameEmailIndex')) if (!nameEmailIndex) throw new Error('Wot no nameEmailIndex?') await kb.fetcher.load(nameEmailIndex) // console.log('Name index loaded async' + nameEmailIndex) const name = await UI.widgets .askName(dom, kb, cardMain, UI.ns.foaf('name'), klass) // @@ was, 'person' if (!name) return // cancelled by user cardMain.innerHTML = 'indexing...' book = findBookFromGroups(book) let person try { person = await saveNewContact(book, name, selectedGroups, klass) } catch (err) { const msg = "Error: can't save new contact: " + err console.log(msg) alert(msg) } selectedPeople = {} selectedPeople[person.uri] = true refreshNames() // Add name to list of group cardMain.innerHTML = '' // Clear 'indexing' cardMain.appendChild(renderPane(dom, person, 'contact')) } // //////////////////////////// Three-column Contact Browser - Body // ////////////////// Body of 3-column browser const bookTable = dom.createElement('table') bookTable.setAttribute( 'style', 'border-collapse: collapse; margin-right: 0; max-height: 9in;' ) div.appendChild(bookTable) /* bookTable.innerHTML = ` <tr> <td id="groupsHeader"></td> <td id="peopleHeader"></td> <td id="cardHeader"></td> </tr> <tr id="bookMain"></tr> <tr> <td id="groupsFooter"> </td><td id="peopleFooter"> </td><td id="cardFooter"> </td> </tr>` */ const bookHeader = bookTable.appendChild(dom.createElement('tr')) const bookMain = bookTable.appendChild(dom.createElement('tr')) const bookFooter = bookTable.appendChild(dom.createElement('tr')) const groupsHeader = bookHeader.appendChild(dom.createElement('td')) const peopleHeader = bookHeader.appendChild(dom.createElement('td')) const cardHeader = bookHeader.appendChild(dom.createElement('td')) const groupsMain = bookMain.appendChild(dom.createElement('td')) const groupsMainTable = groupsMain.appendChild(dom.createElement('table')) const peopleMain = bookMain.appendChild(dom.createElement('td')) const peopleMainTable = peopleMain.appendChild(dom.createElement('table')) const groupsFooter = bookFooter.appendChild(dom.createElement('td')) const peopleFooter = bookFooter.appendChild(dom.createElement('td')) const cardFooter = bookFooter.appendChild(dom.createElement('td')) cardHeader.appendChild(dom.createElement('div')) // searchDiv // searchDiv.setAttribute('style', 'border: 0.1em solid #888; border-radius: 0.5em') const searchInput = cardHeader.appendChild(dom.createElement('input')) searchInput.setAttribute('type', 'text') searchInput.style = style.searchInputStyle || 'border: 0.1em solid #444; border-radius: 0.5em; width: 100%; font-size: 100%; padding: 0.1em 0.6em' searchInput.addEventListener('input', function (_event) { refreshFilteredPeople(true) // Active: select person if just one left }) const cardMain = bookMain.appendChild(dom.createElement('td')) cardMain.setAttribute('style', 'margin: 0;') // fill space available const dataCellStyle = 'padding: 0.1em;' groupsHeader.textContent = 'groups' groupsHeader.setAttribute( 'style', 'min-width: 10em; padding-bottom 0.2em;' ) function setGroupListVisibility (visible) { const vis = visible ? '' : 'display: none;' groupsHeader.setAttribute( 'style', 'min-width: 10em; padding-bottom 0.2em;' + vis ) const hfstyle = 'padding: 0.1em;' groupsMain.setAttribute('style', hfstyle + vis) groupsFooter.setAttribute('style', hfstyle + vis) } setGroupListVisibility(true) if (options.foreignGroup) { selectedGroups[options.foreignGroup.uri] = true } if (book) { const allGroups = groupsHeader.appendChild(dom.createElement('button')) allGroups.textContent = 'All' const style = 'margin-left: 1em; font-size: 100%;' allGroups.setAttribute('style', style) allGroups.addEventListener('click', function (_event) { allGroups.state = allGroups.state ? 0 : 1 peopleMainTable.innerHTML = '' // clear in case refreshNames doesn't work for unknown reason if (allGroups.state) { allGroups.setAttribute('style', style + 'background-color: #ff8;') selectAllGroups(selectedGroups, groupsMainTable, function ( ok, message ) { if (!ok) return complain(message) allGroups.setAttribute( 'style', style + 'background-color: black; color: white' ) refreshGroupsSelected() }) } else { allGroups.setAttribute('style', style + 'background-color: #cfc;') // pale green hint groups loaded selectedGroups = {} refreshGroupsSelected() } }) // on button click kb.fetcher.nowOrWhenFetched(groupIndex.uri, book, function (ok, body) { if (!ok) return console.log('Cannot load group index: ' + body) syncGroupTable() refreshNames() }) } else { syncGroupTable() refreshNames() console.log('No book, only one group -> hide list of groups') setGroupListVisibility(false) // If no books involved, hide group list } // if not book peopleHeader.textContent = 'name' peopleHeader.setAttribute('style', 'min-width: 18em;') peopleMain.setAttribute('style', 'overflow:scroll;') // New Contact button const newContactButton = dom.createElement('button') const container = dom.createElement('div') newContactButton.setAttribute('type', 'button') if (!me) newContactButton.setAttribute('disabled', 'true') authn.checkUser().then(webId => { if (webId) { me = webId newContactButton.removeAttribute('disabled') } }) container.appendChild(newContactButton) newContactButton.innerHTML = 'New Contact' // + IndividualClassLabel peopleFooter.appendChild(container) newContactButton.addEventListener('click', async _event => craeteNewCard(ns.vcard('Individual')), false) // New Organization button const newOrganizationButton = dom.createElement('button') const container2 = dom.createElement('div') newOrganizationButton.setAttribute('type', 'button') if (!me) newOrganizationButton.setAttribute('disabled', 'true') authn.checkUser().then(webId => { if (webId) { me = webId newOrganizationButton.removeAttribute('disabled') } }) container2.appendChild(newOrganizationButton) newOrganizationButton.innerHTML = 'New Organization' // + IndividualClassLabel peopleFooter.appendChild(container2) newOrganizationButton.addEventListener('click', async _event => craeteNewCard(ns.vcard('Organization')), false) // New Group button if (book) { const newGroupButton = groupsFooter.appendChild( dom.createElement('button') ) newGroupButton.setAttribute('type', 'button') newGroupButton.innerHTML = 'New Group' // + IndividualClassLabel newGroupButton.addEventListener( 'click', newGroupClickHandler, false ) // Tools button const toolsButton = cardFooter.appendChild(dom.createElement('button')) toolsButton.setAttribute('type', 'button') toolsButton.innerHTML = 'Tools' toolsButton.addEventListener('click', function (_event) { cardMain.innerHTML = '' cardMain.appendChild( toolsPane( selectAllGroups, selectedGroups, groupsMainTable, book, dataBrowserContext, me ) ) }) } // if book cardFooter.appendChild(newAddressBookButton(book)) // }) div.appendChild(dom.createElement('hr')) // const groups = await loadAllGroups() @@@ checkDataModel().then(()=> {console.log('async checkDataModel done.')}) // div.appendChild(newAddressBookButton(book)) // later // end of AddressBook instance } // renderThreeColumnBrowser // /////////////////////////////////////////////////////////////////////////////////// // Render a single contact Individual if ( t[ns.vcard('Individual').uri] || t[ns.foaf('Person').uri] || t[ns.schema('Person').uri] || t[ns.vcard('Organization').uri] || t[ns.schema('Organization').uri] ) { renderIndividual(dom, div, subject, dataBrowserContext).then(() => console.log('(individual rendered)')) // Render a Group instance } else if (t[ns.vcard('Group').uri]) { // If we have a main address book, then render this group as a guest group within it UI.login .findAppInstances(context, ns.vcard('AddressBook')) .then(function (context) { const addressBooks = context.instances const options = { foreignGroup: subject } if (addressBooks.length > 0) { // const book = addressBooks[0] renderThreeColumnBrowser(addressBooks, context, options) } else { renderThreeColumnBrowser([], context, options) // @@ button to Make a new addressBook } }) .catch(function (e) { UI.widgets.complain(context, e) }) // Render a AddressBook instance } else if (t[ns.vcard('AddressBook').uri]) { renderThreeColumnBrowser([subject], context, {}) } else { console.log( 'Error: Contact pane: No evidence that ' + subject + ' is anything to do with contacts.' ) } me = authn.currentUser() if (!me) { console.log( '(You do not have your Web Id set. Sign in or sign up to make changes.)' ) UI.login.ensureLoadedProfile(context).then( context => { console.log('Logged in as ' + context.me) me = context.me }, err => { div.appendChild(UI.widgets.errorMessageBlock(err)) } ) } else { // console.log("(Your webid is "+ me +")") } // /////////////// Fix user when testing on a plane if ( typeof document !== 'undefined' && document.location && ('' + document.location).slice(0, 16) === 'http://localhost' ) { me = kb.any(subject, UI.ns.acl('owner')) // when testing on plane with no webid console.log('Assuming user is ' + me) } return div } // asyncRender } // render function } // pane object // ends