diff --git a/js/panes/contact/contactPane.js b/js/panes/contact/contactPane.js index 00eadfa..e408c2a 100644 --- a/js/panes/contact/contactPane.js +++ b/js/panes/contact/contactPane.js @@ -22,203 +22,6 @@ if (typeof console == 'undefined') { // e.g. firefox extension. Node and browser -////////////////////////////////////////////////////// SUBCRIPTIONS - -$rdf.subscription = function(options, doc, onChange) { - - - // for all Link: uuu; rel=rrr ---> { rrr: uuu } - var linkRels = function(doc) { - var links = {}; // map relationship to uri - var linkHeaders = tabulator.fetcher.getHeader(doc, 'link'); - if (!linkHeaders) return null; - linkHeaders.map(function(headerValue){ - var arg = headerValue.trim().split(';'); - var uri = arg[0]; - arg.slice(1).map(function(a){ - var key = a.split('=')[0].trim(); - var val = a.split('=')[1].trim(); - if (key ==='rel') { - links[val] = uri.trim(); - } - }); - }); - return links; - }; - - - var getChangesURI = function(doc, rel) { - var links = linkRels(doc); - if (!links[rel]) { - console.log("No link header rel=" + rel + " on " + doc.uri) - return null; - } - var changesURI = $rdf.uri.join(links[rel], doc.uri); - // console.log("Found rel=" + rel + " URI: " + changesURI); - return changesURI; - }; - - - -/////////////// Subscribe to changes by SSE - - - var getChanges_SSE = function(doc, onChange) { - var streamURI = getChangesURI(doc, 'events'); - if (!streamURI) return; - var source = new EventSource(streamURI); // @@@ just path?? - console.log("Server Side Source"); - - source.addEventListener('message', function(e) { - console.log("Server Side Event: " + e); - alert("SSE: " + e) - // $('ul').append('
  • ' + e.data + ' (message id: ' + e.lastEventId + ')
  • '); - }, false); - }; - - - - - //////////////// Subscribe to changes websocket - - // This implementation uses web sockets using update-via - - var getChanges_WS2 = function(doc, onChange) { - var router = new $rdf.UpdatesVia(tabulator.fetcher); // Pass fetcher do it can subscribe to headers - var wsuri = getChangesURI(doc, 'changes').replace(/^http:/, 'ws:').replace(/^https:/, 'wss:'); - router.register(wsuri, doc.uri); - }; - - - var getChanges_WS = function(doc, onChange) { - var SQNS = $rdf.Namespace('http://www.w3.org/ns/pim/patch#'); - var changesURI = getChangesURI(doc, 'updates'); // @@@@ use single - var socket; - try { - socket = new WebSocket(changesURI); - } catch(e) { - socket = new MozWebSocket(changesURI); - }; - - socket.onopen = function(event){ - console.log("socket opened"); - }; - - socket.onmessage = function (event) { - console.log("socket received: " +event.data); - var patchText = event.data; - console.log("Success: patch received:" + patchText); - - // @@ check integrity of entire patch - var patchKB = $rdf.graph(); - var sts; - try { - $rdf.parse(patchText, patchKB, doc.uri, 'text/n3'); - } catch(e) { - console.log("Parse error in patch: "+e); - }; - clauses = {}; - ['where', 'insert', 'delete'].map(function(pred){ - sts = patchKB.statementsMatching(undefined, SQNS(pred), undefined); - if (sts) clauses[pred] = sts[0].object; - }); - console.log("Performing patch!"); - kb.applyPatch(clauses, doc, function(err){ - if (err) { - console.log("Incoming patch failed!!!\n" + err) - alert("Incoming patch failed!!!\n" + err) - socket.close(); - } else { - console.log("Incoming patch worked!!!!!!\n" + err) - onChange(); // callback user - }; - }); - }; - - }; // end getChanges - - - ////////////////////////// Subscribe to changes using Long Poll - - // This implementation uses link headers and a diff returned by plain HTTP - - var getChanges_LongPoll = function(doc, onChange) { - var changesURI = getChangesURI(doc, 'changes'); - if (!changesURI) return "No advertized long poll URI"; - console.log(tabulator.panes.utils.shortTime() + " Starting new long poll.") - var xhr = $rdf.Util.XMLHTTPFactory(); - xhr.alreadyProcessed = 0; - - xhr.onreadystatechange = function(){ - switch (xhr.readyState) { - case 0: - case 1: - return; - case 3: - console.log("Mid delta stream (" + xhr.responseText.length + ") "+ changesURI); - handlePartial(); - break; - case 4: - handlePartial(); - console.log(tabulator.panes.utils.shortTime() + " End of delta stream " + changesURI); - break; - } - }; - - try { - xhr.open('GET', changesURI); - } catch (er) { - console.log("XHR open for GET changes failed <"+changesURI+">:\n\t" + er); - } - try { - xhr.send(); - } catch (er) { - console.log("XHR send failed <"+changesURI+">:\n\t" + er); - } - - var handlePartial = function() { - // @@ check content type is text/n3 - - if (xhr.status >= 400) { - console.log("HTTP (" + xhr.readyState + ") error " + xhr.status + "on change stream:" + xhr.statusText); - console.log(" error body: " + xhr.responseText); - xhr.abort(); - return; - } - if (xhr.responseText.length > xhr.alreadyProcessed) { - var patchText = xhr.responseText.slice(xhr.alreadyProcessed); - xhr.alreadyProcessed = xhr.responseText.length; - - console.log(tabulator.panes.utils.shortTime() + " Long poll returns, processing...") - xhr.headers = $rdf.Util.getHTTPHeaders(xhr); - try { - onChange(patchText); - } catch (e) { - console.log("Exception in patch update handler: " + e) - // @@ Where to report error e? - } - getChanges_LongPoll(doc, onChange); // Queue another one - - } - }; - return null; // No error - - }; // end getChanges_LongPoll - - if (options.longPoll ) { - getChanges_LongPoll(doc, onChange); - } - if (options.SSE) { - getChanges_SSE(doc, onChange); - } - if (options.websockets) { - getChanges_WS(doc, onChange); - } - -}; // subscription - -/////////////////////////////////////////// End of subscription stufff - // Sets the best name we have and looks up a better one tabulator.panes.utils.setName = function(element, subject) { var kb = tabulator.kb, ns = tabulator.ns; @@ -271,15 +74,15 @@ tabulator.panes.utils.deleteButtonWithCheck = function(dom, container, noun, del tabulator.panes.utils.adoptACLDefault = function(doc, aclDoc, defaultResource, defaultACLdoc) { var kb = tabulator.kb; - var auth = tabulator.ns.auth; + var ACL = tabulator.ns.acl; var ns = tabulator.ns; - var defaults = kb.each(undefined, auth('defaultForNew'), defaultResource, defaultACLdoc); + var defaults = kb.each(undefined, ACL('defaultForNew'), defaultResource, defaultACLdoc); var proposed = []; defaults.map(function(da) { - proposed = proposed.concat(kb.statementsMatching(da, auth('agent'), undefined, defaultACLdoc)) - .concat(kb.statementsMatching(da, auth('agentClass'), undefined, defaultACLdoc)) - .concat(kb.statementsMatching(da, auth('mode'), undefined, defaultACLdoc)); - proposed.push($rdf.st(da, auth('accessTo'), doc, defaultACLdoc)); // Suppose + proposed = proposed.concat(kb.statementsMatching(da, ACL('agent'), undefined, defaultACLdoc)) + .concat(kb.statementsMatching(da, ACL('agentClass'), undefined, defaultACLdoc)) + .concat(kb.statementsMatching(da, ACL('mode'), undefined, defaultACLdoc)); + proposed.push($rdf.st(da, ACL('accessTo'), doc, defaultACLdoc)); // Suppose }); var kb2 = $rdf.graph(); // Potential - derived is kept apart proposed.map(function(st){ @@ -292,29 +95,177 @@ tabulator.panes.utils.adoptACLDefault = function(doc, aclDoc, defaultResource, d }); // @@@@@ ADD TRIPLES TO ACCES CONTROL ACL FILE -- until servers fixed @@@@@ - var ccc = kb2.each(undefined, auth('accessTo'), doc) - .filter(function(au){ return kb2.holds(au, auth('mode'), auth('Control'))}); + var ccc = kb2.each(undefined, ACL('accessTo'), doc) + .filter(function(au){ return kb2.holds(au, ACL('mode'), ACL('Control'))}); ccc.map(function(au){ var au2 = kb2.sym(au.uri + "__ACLACL"); - kb2.add(au2, ns.rdf('type'), auth('Authorization'), aclDoc); - kb2.add(au2, auth('accessTo'), aclDoc, aclDoc); - kb2.add(au2, auth('mode'), auth('Read'), aclDoc); - kb2.add(au2, auth('mode'), auth('Write'), aclDoc); - kb2.each(au, auth('agent')).map(function(who){ - kb2.add(au2, auth('agent'), who, aclDoc); + kb2.add(au2, ns.rdf('type'), ACL('Authorization'), aclDoc); + kb2.add(au2, ACL('accessTo'), aclDoc, aclDoc); + kb2.add(au2, ACL('mode'), ACL('Read'), aclDoc); + kb2.add(au2, ACL('mode'), ACL('Write'), aclDoc); + kb2.each(au, ACL('agent')).map(function(who){ + kb2.add(au2, ACL('agent'), who, aclDoc); }); - kb2.each(au, auth('agentClass')).map(function(who){ - kb2.add(au2, auth('agentClass'), who, aclDoc); + kb2.each(au, ACL('agentClass')).map(function(who){ + kb2.add(au2, ACL('agentClass'), who, aclDoc); }); }); return kb2; } + +// Read and conaonicalize the ACL for x in aclDoc +// +// Accumulate the access rights which each agent or class has +// +tabulator.panes.utils.readACL = function(x, aclDoc) { + var ac = {'agent': [], 'agentClass': []}; + var auths = kb.each(undefined, ACL('acccessTo'), x); + for (var pred in ['agent', 'agentClass']) { + auths.map(function(a){ + kb.each(a, ACL('mode')).map(function(mode){ + kb.each(a, ACL('agent')).map(function(agent){ + + if (!ac[pred][agent]) ac[pred][agent] = []; + ac[pred][agent][mode] = true; + agents[mode][agent] = a; + }); + }); + }); + } + return ac; +} + +// Compare two ACLs +tabulator.panes.utils.sameACL = function(a, b) { + var contains = function(a, b) { + ['agent', 'agentClass'].map(function(pred){ + if (a[pred]) { + for (var ag in a[pred]) { + for (var mode in a[pred][agent]) { + if (!b[pred][agent] || !b[pred][agent][mode]) { + return false; + } + } + } + }; + }); + return true; + } + return contains(a, b) && contains(b,a); +} + +// Union N ACLs +tabulator.panes.utils.ACLunion = function(list) { + var b = list[0]; + for (var k=1; k < list.length; k++) { + ['agent', 'agentClass'].map(function(pred){ + if (a[pred]) { + for (var ag in a[pred]) { + for (var mode in a[pred][ag]) { + if (!b[pred][ag]) b[pred][ag] = []; + b[pred][ag][mode] = true; + } + } + }; + }); + } + return b; +} + + +// Merge ACLs lists from things to form union + +tabulator.panes.utils.loadUnionACL = function(subjectList, callback) { + var aclList = []; + var doList = function(list) { + if (list.length) { + doc = list.shift(); + tabulator.panes.utils.getACLorDefault(list[0], function(ok, defa, p3, p4, defaultHolder, defaultACLDoc){ + if (!ok) return callback(ok, p4); + acList.append((defa) ? tabulator.panes.utils.readACL(defaultHolder, defaultACLDoc) : + tabulator.panes.utils.readACL(p3, p4)); + doList(list.slice(1)); + }); + } else { // all gone + callback(true, tabulator.panes.utils.ACLunion(aclList)) + } + } + doList(subjectList); +} + +// Represents these as a RDF graph by combination of modes +// +tabulator.panes.utils.writeACL = function(kb, x, ac, aclDoc) { + var byCombo = []; + for (var pred in ['agent', 'agentClass']) { + for (var agent in ac[pred]) { + if (ac[pred][agent]) for (var agent in ac[pred][agent]) { + var modes = ac[pred][agent]; + var combo = []; + for (var mode in ac[pred][agent]) { + combo.append(mode.fromNT.uri) // + } + combo.sort() + combo = combo.join('\n'); + if (!byCombo[combo]) byCombo[combo] = []; + byCombo[combo].append([pred, agent]) + } + } + } + for (combo in byCombo) { + var a = kb.sym(aclDoc.uri + '#' + combo); + kb.add(a, tabulator.ns.rdf('type'), ACL('Authorization'), aclDoc); + var m = combo.split('\n'); + for (var i=0; i < m.length; i++) { + kb.add(a, ACL('mode'), kb.sym(m[i]), aclDoc); + } + var pairs = byCombo[combo]; + for (i=0; i< pairs.length; i++) { + var p = pairs[0], ag = pairs[1]; + kb.add(a, ACL(p), kb.sym(ag), aclDoc); + } + + } +} + +// Fix the ACl for an individual card as a function of the groups it is in +// +// All group files must be loaded first +// + +tabulator.panes.utils.fixIndividualCardACL = function(person, callback) { + var groups = tabulator.kb.each(undefined, tabulator.ns.vcard('hasMember'), person); + if (groups) { + tabulator.panes.utils.fixIndividualACL(person.doc(), groups, callback); + } + // @@ if no groups, then use default for People container or the book top container. +} + +tabulator.panes.utils.fixIndividualACL = function(doc, subjects, callback) { + tabulator.panes.utils.getACLorDefault(doc, function(ok, defa, p3, p4, defaultHolder, defaultACLDoc){ + if (!ok) return callback(ok, p4); + var ac = (defa) ? tabulator.panes.utils.readACL(defaultHolder, defaultACLDoc) : + tabulator.panes.utils.readACL(p3, p4); + tabulator.panes.utils.loadUnionACL(subjects, function(ok, union){ + if (tabulator.panes.utils.sameACL(union, ac)) { + console.log("Nice - same ACL. no change " + doc); + } else { + console.log("Group ACLs differ for " + doc); + console.log("Group ACLs " + union + "\n"); + console.log((defa ? "Default" : "Previous set") + " ACLs " + ac + "\n"); + // @@@ TBD + // serialize and store ACL. + } + }); + }) +} + tabulator.panes.utils.ACLControlBox = function(subject, dom, callback) { var kb = tabulator.kb; var updater = new tabulator.rdf.sparqlUpdate(kb); - var auth = tabulator.ns.auth; + var ACL = tabulator.ns.acl; var doc = $rdf.sym(subject.uri.split('#')[0]); // The ACL is actually to the doc describing the thing var table = dom.createElement('table'); @@ -331,15 +282,15 @@ tabulator.panes.utils.ACLControlBox = function(subject, dom, callback) { var bottomRow = table.appendChild(dom.createElement('tr')); var ACLControl = function(box, doc, aclDoc, kb) { - var authorizations = kb.each(undefined, auth('accessTo'), doc, aclDoc); // ONLY LOOK IN ACL DOC + var authorizations = kb.each(undefined, ACL('accessTo'), doc, aclDoc); // ONLY LOOK IN ACL DOC if (authorizations.length === 0) { statusBlock.textContent += "Access control file exists but contains no authorizations! " + aclDoc + ")"; } for (i=0; i < authorizations.length; i++) { var row = box.appendChild(dom.createElement('tr')); var rowdiv1 = row.appendChild(dom.createElement('div')); - row.setAttribute('style', 'margin: 1em; border: 0.1em solid black; border-radius: 0.5em; padding: 1em;') // doesn't work - rowdiv1.setAttribute('style', 'margin: 1em; border: 0.1em solid black; border-radius: 1em; padding: 1em;'); + + rowdiv1.setAttribute('style', 'margin: 1em; border: 0.1em solid #444; border-radius: 0.5em; padding: 1em;'); rowtable1 = rowdiv1.appendChild(dom.createElement('table')); rowrow = rowtable1.appendChild(dom.createElement('tr')); var left = rowrow.appendChild(dom.createElement('td')); @@ -351,44 +302,105 @@ tabulator.panes.utils.ACLControlBox = function(subject, dom, callback) { var rightTable = right.appendChild(dom.createElement('table')); var a = authorizations[i]; - kb.each(a, auth('agent')).map(function(x){ + kb.each(a, ACL('agent')).map(function(x){ var tr = leftTable.appendChild(dom.createElement('tr')); tabulator.panes.utils.setName(tr, x); tr.setAttribute('style', 'min-width: 12em'); }); - kb.each(a, auth('agentClass')).map(function(x){ + kb.each(a, ACL('agentClass')).map(function(x){ var tr = leftTable.appendChild(dom.createElement('tr')); tr.textContent = tabulator.Util.label(x) + ' *'; // for now // later add # or members }); - kb.each(a, auth('mode')).map(function(x){ + kb.each(a, ACL('mode')).map(function(x){ var tr = rightTable.appendChild(dom.createElement('tr')); tr.textContent = tabulator.Util.label(x); // for now // later add # or members }); } } - tabulator.panes.utils.getACL(doc, function(ok, status, aclDoc, message) { - var i, row, left, right, a; - var auth = tabulator.ns.auth; - var useDefault; - var addDefaultButton = function() { - useDefault = bottomRow.appendChild(dom.createElement('button')); - useDefault.textContent = "Stop specific sharing for this group -- just use default."; - useDefault.addEventListener('click', function(event) { - updater.delete(doc, function(uri, ok, message){ - if (!ok) { - statusBlock.textContent += " (Error deleting access control file: "+message+")"; - } else { - statusBlock.textContent = " The sharing for this group is now the default."; - bottomRow.removeChild(useDefault); - } - }); - }); + tabulator.panes.utils.getACLorDefault(doc, function(ok, defa, p3, p4, defaultHolder, defaultACLDoc){ + if (!ok) { + statusBlock.textContent += "Error reading " + (defa? " default " : "") + "ACL." + + " status " + p3 + ": " + p4; + } else { + if (defa) { + var defaults = kb.each(undefined, ACL('defaultForNew'), defaultHolder, defaultACLDoc); + if (!defaults.length) { + statusBlock.textContent += " (No defaults given.)"; + } else { + statusBlock.textContent = "The sharing for this group is the default."; + var kb2 = tabulator.panes.utils.adoptACLDefault(doc, aclDoc, defaultHolder, defaultACLDoc) + ACLControl(box, doc, aclDoc, kb2); // Add btton to save them as actual + + var editPlease = bottomRow.appendChild(dom.createElement('button')); + editPlease.textContent = "Set specific sharing\nfor this group"; + editPlease.addEventListener('click', function(event) { + updater.put(aclDoc, kb2.statements, + 'text/turtle', function(uri, ok, message){ + if (!ok) { + statusBlock.textContent += " (Error writing back access control file: "+message+")"; + } else { + statusBlock.textContent = " (Now editing specific access for this group)"; + bottomRow.removeChild(editPlease); + } + }); + + }); + } // defaults.length + } else { // Not using defaults + + ACLControl(box, p3, p4, kb); + addDefaultButton(); + + } // Not using defaults } + + }); + + return table + +}; // ACLControl + + +tabulator.panes.utils.setACL = function(docURI, aclText, callback) { + var aclDoc = kb.any(kb.sym(docURI), + kb.sym('http://www.iana.org/assignments/link-relations/acl')); // @@ check that this get set by web.js + if (aclDoc) { // Great we already know where it is + webOperation('PUT', aclDoc.uri, { data: aclText, contentType: 'text/turtle'}, callback); + } else { + + fetcher.nowOrWhenFetched(docURI, undefined, function(ok, body){ + if (!ok) return callback(ok, "Gettting headers for ACL: " + body); + var aclDoc = kb.any(kb.sym(docURI), + kb.sym('http://www.iana.org/assignments/link-relations/acl')); // @@ check that this get set by web.js + if (!aclDoc) { + // complainIfBad(false, "No Link rel=ACL header for " + docURI); + callback(false, "No Link rel=ACL header for " + docURI); + } else { + webOperation('PUT', aclDoc.uri, { data: aclText, contentType: 'text/turtle'}, callback); + } + }) + } +}; + +// Get ACL file or default if necessary +// +// callback(true, true, doc, aclDoc) The ACL did exist +// callback(true, false, doc, aclDoc, defaultHolder, defaultACLDoc) ACL file did not exist but a default did +// callback(false, false, status, message) error getting original +// callback(false, true, status, message) error getting defualt + +tabulator.panes.utils.getACLorDefault = function(doc, callback) { + + tabulator.panes.utils.getACL(doc, function(ok, status, aclDoc, message) { + var i, row, left, right, a; + var kb = tabulator.kb; + var ACL = tabulator.ns.acl; + if (!ok) return callback(false, false, status, message); // Recursively search for the ACL file which gives default access var tryParent = function(uri) { @@ -399,91 +411,48 @@ tabulator.panes.utils.ACLControlBox = function(subject, dom, callback) { var left = uri.indexOf('/', uri.indexOf('//') + 2); uri = uri.slice(0, right + 1); var doc2 = $rdf.sym(uri); - tabulator.panes.utils.getACL(doc2, function(ok, status, aclDoc2) { + tabulator.panes.utils.getACL(doc2, function(ok, status, defaultACLDoc) { if (!ok) { - statusBlock.textContent += ("( No ACL pointer " + uri + ' ' + status + ")"); + return callback(false, true, status, "( No ACL pointer " + uri + ' ' + status + ")") } else if (status === 403) { - statusBlock.textContent += ("( ACL file FORBIDDEN. Stop." + uri + ")"); + return callback(false, true, status,"( default ACL file FORBIDDEN. Stop." + uri + ")"); } else if (status === 404) { - statusBlock.textContent += ("( No ACL file for set " + uri + ")"); if (left >= right) { - statusBlock.textContent += ("( Thats all folks.)"); + return callback(false, true, 499, "Nothing to hold a default"); } else { tryParent(uri); } + } else if (status !== 200) { + return callback(false, true, status, "Error searching for default"); } else { // 200 - statusBlock.textContent += (" ACCESS set at " + uri + ". End search."); - var defaults = kb.each(undefined, auth('defaultForNew'), kb.sym(uri), aclDoc2); + //statusBlock.textContent += (" ACCESS set at " + uri + ". End search."); + var defaults = kb.each(undefined, ACL('defaultForNew'), kb.sym(uri), defaultACLDoc); if (!defaults.length) { - statusBlock.textContent += " (No defaults given.)"; + tryParent(uri); // Keep searching } else { - statusBlock.textContent = "The sharing for this group is the default."; - var kb2 = tabulator.panes.utils.adoptACLDefault(doc, aclDoc, kb.sym(uri), aclDoc2) - ACLControl(box, doc, aclDoc, kb2); // Add btton to save them as actual - - var editPlease = bottomRow.appendChild(dom.createElement('button')); - editPlease.textContent = "Set specific sharing\nfor this group"; - editPlease.addEventListener('click', function(event) { - updater.put(aclDoc, kb2.statements, - 'text/turtle', function(uri, ok, message){ - if (!ok) { - statusBlock.textContent += " (Error writing back access control file: "+message+")"; - } else { - statusBlock.textContent = " (Now editing specific access for this group)"; - bottomRow.removeChild(editPlease); - } - }); - - }); + var defaultHolder = kb.sym(uri); + callback(true, false, doc, aclDoc, defaultHolder, defaultACLDoc) } } }); - }; + }; // tryParent if (!ok) { - statusBlock.textContent += ("( Access control information not provided " + uri +")"); + return callback(false, false, status , "Error accessing Access Control information for " + uri +")"); } else if (status === 404) { - statusBlock.textContent = '(No specific access control has been set.)\n'; // error message - statusBlock.setAttribute('style', 'background-color: #ffe; padding:2em;'); - tryParent(doc.uri); - // @@ construct default one - the server should do that + tryParent(doc.uri); // @@ construct default one - the server should do that } else if (status === 403) { - statusBlock.textContent = '(Sharing not available to you)'; // error message + return callback(false, false, status, "(Sharing not available to you)"); } else if (status !== 200) { - statusBlock.textContent = message; // error message - statusBlock.setAttribute('style', 'background-color: #f99; padding:2em;') + return callback(false, false, status, "Error " + status + " accessing Access Control information for " + uri); } else { // 200 - ACLControl(box, doc, aclDoc, kb); - addDefaultButton(); - } - }); - - return table -}; // ACLControl + return callback(true, true, doc, aclDoc); + } + }); // Call to getACL +} // getACLorDefault -tabulator.panes.utils.setACL = function(docURI, aclText, callback) { - var aclDoc = kb.any(kb.sym(docURI), - kb.sym('http://www.iana.org/assignments/link-relations/acl')); // @@ check that this get set by web.js - if (aclDoc) { // Great we already know where it is - webOperation('PUT', aclDoc.uri, { data: aclText, contentType: 'text/turtle'}, callback); - } else { - - fetcher.nowOrWhenFetched(docURI, undefined, function(ok, body){ - if (!ok) return callback(ok, "Gettting headers for ACL: " + body); - var aclDoc = kb.any(kb.sym(docURI), - kb.sym('http://www.iana.org/assignments/link-relations/acl')); // @@ check that this get set by web.js - if (!aclDoc) { - // complainIfBad(false, "No Link rel=ACL header for " + docURI); - callback(false, "No Link rel=ACL header for " + docURI); - } else { - webOperation('PUT', aclDoc.uri, { data: aclText, contentType: 'text/turtle'}, callback); - } - }) - } -}; - // Calls back (ok, status, acldoc, message) // // (false, errormessage) no link header @@ -574,9 +543,9 @@ tabulator.panes.register( { } var complainIfBad = function(ok,body){ - if (ok) { + if (!ok) { + console.log("Error: " + body); } - else console.log("Sorry, failed to save your change:\n"+body, 'background-color: pink;'); } var getOption = function (tracker, option){ // eg 'allowSubContacts' @@ -966,7 +935,7 @@ tabulator.panes.register( { // Write new group to web // Creates an empty new group file and adds it to the index // - var createNewGroup = function(book, name, callback) { + var saveNewGroup = function(book, name, callback) { var gix = kb.any(book, ns.vcard('groupIndex')); var x = subject.uri.split('#')[0] @@ -1060,6 +1029,25 @@ tabulator.panes.register( { return name ? name.value : '???'; } + var filterName = function(name) { + var filter = searchInput.value.trim().toLowerCase(); + if (filter.length === 0) return true; + var parts = filter.split(' '); // Each name part must be somewhere + for (var j=0; j< parts.length; j++) { + word = parts[j]; + if (name.toLowerCase().indexOf(word) <0 ) return false; + } + return true; + } + + var searchFilterNames = function() { + for (var i=0; i < peopleMainTable.children.length; i++) { + row = peopleMainTable.children[i] + row.setAttribute('style', + filterName(nameFor(row.subject)) ? '' : 'display: none;'); + } + } + var bookTable = dom.createElement('table'); bookTable.setAttribute('style', 'border-collapse: collapse; margin-right: 0;') div.appendChild(bookTable); @@ -1078,17 +1066,53 @@ tabulator.panes.register( { var peopleFooter = bookFooter.appendChild(dom.createElement('td')); var cardFooter = bookFooter.appendChild(dom.createElement('td')); + var searchDiv = cardHeader.appendChild(dom.createElement('div')); + // searchDiv.setAttribute('style', 'border: 0.1em solid #888; border-radius: 0.5em'); + searchInput = cardHeader.appendChild(dom.createElement('input')); + searchInput.setAttribute('type', 'text'); + searchInput.setAttribute('style', 'border: 0.1em solid #444; border-radius: 0.5em; width: 100%;'); + // searchInput.addEventListener('input', searchFilterNames); + searchInput.addEventListener('input', function(e){ + searchFilterNames(); + }); + var cardMain = bookMain.appendChild(dom.createElement('td')); cardMain.setAttribute('style', 'margin: 0;'); // fill space available var dataCellStyle = 'padding: 0.1em;' groupsHeader.textContent = "groups"; groupsHeader.setAttribute('style', 'min-width: 10em; padding-bottom 0.2em;'); + var allGroups = groupsHeader.appendChild(dom.createElement('button')); + allGroups.textContent = "All"; + allGroups.setAttribute('style', 'margin-left: 1em;'); + 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) { + for (var k=0; k < groupsMainTable.children.length; k++) { + var groupRow = groupsMainTable.children[k]; + var group = groupRow.subject; + + var groupList = kb.sym(group.uri.split('#')[0]); + selected[group.uri] = true; + + kb.fetcher.nowOrWhenFetched(groupList.uri, undefined, function(ok, message){ + if (!ok) return complainIfBad(ok, "Can't load group file: " + groupList + ": " + message); + groupRow.setAttribute('style', 'background-color: #cce;'); + refreshNames(); // @@ every time?? + }); + //refreshGroups(); + } // for each row + } else { + selected = {}; + refreshGroups(); + } + }); // on button click + peopleHeader.textContent = "name"; peopleHeader.setAttribute('style', 'min-width: 18em;'); peopleMain.setAttribute('style','overflow:scroll;'); - // cardHeader.textContent = "contact details"; // clutter var groups = kb.each(subject, ns.vcard('includesGroup')); @@ -1099,7 +1123,7 @@ tabulator.panes.register( { var cardPane = function(dom, subject, paneName) { var p = tabulator.panes.byName(paneName); var d = p.render(subject, dom); - d.setAttribute('style', 'border: 0.1em solid #888; border-radius: 0.5em') + d.setAttribute('style', 'border: 0.1em solid #444; border-radius: 0.5em') return d; }; @@ -1156,19 +1180,29 @@ tabulator.panes.register( { } } cards.sort(compareForSort); // @@ sort by name not UID later + for (var k=0; k < cards.length - 1;) { + if (cards[k].uri === cards[k+1].uri) { + cards.splice(k,1); + } else { + k++; + } + } + peopleMainTable.innerHTML = ''; // clear peopleHeader.textContent = (cards.length > 5 ? '' + cards.length + " contacts" : "contact"); for (var j =0; j < cards.length; j++) { var personRow = peopleMainTable.appendChild(dom.createElement('tr')); - personRow.setAttribute('style', dataCellStyle); + var personLeft = personRow.appendChild(dom.createElement('td')); + var personRight = personRow.appendChild(dom.createElement('td')); + personLeft.setAttribute('style', dataCellStyle); var person = cards[j]; var name = nameFor(person); - personRow.textContent = name; + personLeft.textContent = name; personRow.subject = person; - var setPersonListener = function toggle(personRow, person) { - tabulator.panes.utils.deleteButtonWithCheck(dom, personRow, 'contact', function(){ + var setPersonListener = function toggle(personLeft, person) { + tabulator.panes.utils.deleteButtonWithCheck(dom, personRight, 'contact', function(){ deleteThing(person); refreshNames(); cardMain.innerHTML = ''; @@ -1181,12 +1215,17 @@ tabulator.panes.register( { cardMain.innerHTML = ''; if (!ok) return complainIfBad(ok, "Can't load card: " + group.uri.split('#')[0] + ": " + message) // dump("Loaded card " + cardURI + '\n') - cardMain.appendChild(cardPane(dom, person, 'contact')); + cardMain.appendChild(cardPane(dom, person, 'contact')); + cardMain.appendChild(dom.createElement('br')); + var anchor = cardMain.appendChild(dom.createElement('a')); + anchor.setAttribute('href', person.uri); + anchor.textContent = '->'; }) }); }; setPersonListener(personRow, person); }; + searchFilterNames(); } @@ -1209,10 +1248,8 @@ tabulator.panes.register( { // var groupLeft = groupRow.appendChild(dom.createElement('td')); // var groupRight = groupRow.appendChild(dom.createElement('td')); groupRow.textContent = name; - // var checkBox = groupLeft.appendChild(dom.createElement('input')) - // checkBox.setAttribute('type', 'checkbox'); // @@ set from personal last settings - var foo = function toggle(groupRow, group) { - tabulator.panes.utils.deleteButtonWithCheck(dom, groupRow, "group", function(){ + var foo = function toggle(groupRow, group, name) { + tabulator.panes.utils.deleteButtonWithCheck(dom, groupRow, "group " + name, function(){ deleteThing(group); }); groupRow.addEventListener('click', function(event){ @@ -1220,12 +1257,6 @@ tabulator.panes.register( { var groupList = kb.sym(group.uri.split('#')[0]); if (!event.altKey) { selected = {}; // If alt key pressed, accumulate multiple - /* - cardMain.innerHTML = ''; - cardMain.appendChild(tabulator.panes.utils.ACLControlBox(group, dom, function(ok, body){ - if (!ok) cardMain.innerHTML = "Failed: " + body; - })); - */ } selected[group.uri] = selected[group.uri] ? false : true; refreshGroups(); @@ -1244,7 +1275,7 @@ tabulator.panes.register( { }) }, true); }; - foo(groupRow, group); + foo(groupRow, group, name); } @@ -1311,7 +1342,7 @@ tabulator.panes.register( { // cardMain.appendChild(newContactForm(dom, kb, selected, createdNewContactCallback1)); cardMain.appendChild(getNameForm(dom, kb, "Group", selected, function(subject, name, selectedGroups) { - createNewGroup(subject, name, function(success, body) { + saveNewGroup(subject, name, function(success, body) { if (!success) { console.log("Error: can\'t save new group:" + body); cardMain.innerHTML = "Failed to save group" + body