solid-panes
Version:
Solid-compatible Panes: applets and views for the mashlib and databrowser
430 lines (411 loc) • 16.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.socialPane = void 0;
var UI = _interopRequireWildcard(require("solid-ui"));
var _solidLogic = require("solid-logic");
var $rdf = _interopRequireWildcard(require("rdflib"));
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/* Social Pane
**
** This outline pane provides social network functions
** Using for example the FOAF ontology.
** Goal: A *distributed* version of facebook, advogato, etc etc
** - Similarly easy user interface, but data storage distributed
** - Read and write both user-private (address book) and public data clearly
** -- todo: use common code to get username and load profile and set 'me'
*/
const socialPane = exports.socialPane = {
icon: UI.icons.originalIconBase + 'foaf/foafTiny.gif',
name: 'social',
label: function (subject, context) {
const kb = context.session.store;
const types = kb.findTypeURIs(subject);
if (types[UI.ns.foaf('Person').uri] || types[UI.ns.vcard('Individual').uri]) {
return 'Friends';
}
return null;
},
render: function (s, context) {
const dom = context.dom;
const common = function (x, y) {
// Find common members of two lists
const both = [];
for (let i = 0; i < x.length; i++) {
for (let j = 0; j < y.length; j++) {
if (y[j].sameTerm(x[i])) {
both.push(y[j]);
break;
}
}
}
return both;
};
const people = function (n) {
let res = ' ';
res += n || 'no';
if (n === 1) return res + ' person';
return res + ' people';
};
const say = function (str) {
console.log(str);
const p = dom.createElement('p');
p.textContent = str;
tips.appendChild(p);
};
const link = function (contents, uri) {
if (!uri) return contents;
const a = dom.createElement('a');
a.setAttribute('href', uri);
a.appendChild(contents);
return a;
};
const text = function (str) {
return dom.createTextNode(str);
};
const buildCheckboxForm = function (lab, statement, state) {
const f = dom.createElement('form');
const input = dom.createElement('input');
f.appendChild(input);
const tx = dom.createTextNode(lab);
tx.className = 'question';
f.appendChild(tx);
input.setAttribute('type', 'checkbox');
const boxHandler = function (_e) {
tx.className = 'pendingedit';
// alert('Should be greyed out')
if (this.checked) {
// Add link
try {
outliner.UserInput.sparqler.insert_statement(statement, function (uri, success, errorBody) {
tx.className = 'question';
if (!success) {
UI.log.alert(null, 'Message', 'Error occurs while inserting ' + statement + '\n\n' + errorBody);
input.checked = false; // rollback UI
return;
}
kb.add(statement.subject, statement.predicate, statement.object, statement.why);
});
} catch (e) {
UI.log.error('Data write fails:' + e);
UI.log.alert('Data write fails:' + e);
input.checked = false; // rollback UI
tx.className = 'question';
}
} else {
// Remove link
try {
outliner.UserInput.sparqler.delete_statement(statement, function (uri, success, errorBody) {
tx.className = 'question';
if (!success) {
UI.log.alert('Error occurs while deleting ' + statement + '\n\n' + errorBody);
this.checked = true; // Rollback UI
} else {
kb.removeMany(statement.subject, statement.predicate, statement.object, statement.why);
}
});
} catch (e) {
UI.log.alert('Delete fails:' + e);
this.checked = true; // Rollback UI
// return
}
}
};
input.checked = state;
input.addEventListener('click', boxHandler, false);
return f;
};
const oneFriend = function (friend, _confirmed) {
return UI.widgets.personTR(dom, UI.ns.foaf('knows'), friend, {});
};
// ////////// Body of render():
const outliner = context.getOutliner(dom);
const kb = context.session.store;
const div = dom.createElement('div');
div.setAttribute('class', 'socialPane');
const foaf = UI.ns.foaf;
const vcard = UI.ns.vcard;
// extracted from tabbedtab.css 2017-03-21
const navBlockStyle = 'background-color: #eee; width: 25%; border: 0; padding: 0.5em; margin: 0;';
const mainBlockStyle = 'background-color: #fff; color: #000; width: 46%; margin: 0; border-left: 1px solid #ccc; border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; padding: 0;';
const foafPicStyle = ' width: 100% ; border: none; margin: 0; padding: 0;';
const structure = div.appendChild(dom.createElement('table'));
const tr = structure.appendChild(dom.createElement('tr'));
const left = tr.appendChild(dom.createElement('td'));
const middle = tr.appendChild(dom.createElement('td'));
const right = tr.appendChild(dom.createElement('td'));
const tools = left;
tools.style.cssText = navBlockStyle;
const mainTable = middle.appendChild(dom.createElement('table'));
mainTable.style.cssText = mainBlockStyle;
const tips = right;
tips.style.cssText = navBlockStyle;
// Image top left
const src = kb.any(s, foaf('img')) || kb.any(s, foaf('depiction'));
if (src) {
const img = dom.createElement('IMG');
img.setAttribute('src', src.uri); // w640 h480
// img.className = 'foafPic'
img.style.cssText = foafPicStyle;
tools.appendChild(img);
}
const name = kb.anyValue(s, foaf('name')) || '???';
let h3 = dom.createElement('H3');
h3.appendChild(dom.createTextNode(name));
let me = _solidLogic.authn.currentUser();
const meUri = me ? me.uri : null;
// @@ Add: event handler to redraw the stuff below when me changes.
const loginOutButton = UI.login.loginStatusBox(dom, webIdUri => {
me = kb.sym(webIdUri);
// @@ To be written: redraw as a function the new me
// @@ refresh the sidebars
UI.widgets.refreshTree(div); // this refreshes the middle at least
});
tips.appendChild(loginOutButton);
const thisIsYou = me && kb.sameThings(me, s);
const knows = foaf('knows');
// var givenName = kb.sym('http://www.w3.org/2000/10/swap/pim/contact#givenName')
const familiar = kb.anyValue(s, foaf('givenname')) || kb.anyValue(s, foaf('firstName')) || kb.anyValue(s, foaf('nick')) || kb.anyValue(s, foaf('name')) || kb.anyValue(s, vcard('fn'));
const friends = kb.each(s, knows);
// Do I have a public profile document?
let profile = null; // This could be SPARQL { ?me foaf:primaryTopic [ a foaf:PersonalProfileDocument ] }
let editable = false;
let incoming;
let outgoing;
if (me) {
// The definition of FAF personal profile document is ..
const works = kb.each(undefined, foaf('primaryTopic'), me); // having me as primary topic
let message = '';
for (let i = 0; i < works.length; i++) {
if (kb.whether(works[i], UI.ns.rdf('type'), foaf('PersonalProfileDocument'))) {
editable = outliner.UserInput.sparqler.editable(works[i].uri, kb);
if (!editable) {
message += 'Your profile <' + UI.utils.escapeForXML(works[i].uri) + '> is not remotely editable.';
} else {
profile = works[i];
break;
}
}
}
if (!profile) {
say(message + '\nI couldn\'t find your editable personal profile document.');
} else {
say('Editing your profile ' + profile + '.');
// Do I have an EDITABLE profile?
editable = outliner.UserInput.sparqler.editable(profile.uri, kb);
}
if (thisIsYou) {
// This is about me
// pass... @@
} else {
// This is about someone else
// My relationship with this person
h3 = dom.createElement('h3');
h3.appendChild(dom.createTextNode('You and ' + familiar));
tools.appendChild(h3);
const cme = kb.canon(me);
incoming = kb.whether(s, knows, cme);
outgoing = false;
const outgoingSt = kb.statementsMatching(cme, knows, s);
if (outgoingSt.length) {
outgoing = true;
if (!profile) profile = outgoingSt[0].why;
}
const tr = dom.createElement('tr');
tools.appendChild(tr);
const youAndThem = function () {
tr.appendChild(link(text('You'), meUri));
tr.appendChild(text(' and '));
tr.appendChild(link(text(familiar), s.uri));
};
if (!incoming) {
if (!outgoing) {
youAndThem();
tr.appendChild(text(' have not said you know each other.'));
} else {
tr.appendChild(link(text('You'), meUri));
tr.appendChild(text(' know '));
tr.appendChild(link(text(familiar), s.uri));
tr.appendChild(text(' (unconfirmed)'));
}
} else {
if (!outgoing) {
tr.appendChild(link(text(familiar), s.uri));
tr.appendChild(text(' knows '));
tr.appendChild(link(text('you'), meUri));
tr.appendChild(text(' (unconfirmed).')); // @@
tr.appendChild(text(' confirm you know '));
tr.appendChild(link(text(familiar), s.uri));
tr.appendChild(text('.'));
} else {
youAndThem();
tr.appendChild(text(' say you know each other.'));
}
}
if (editable) {
const f = buildCheckboxForm('You know ' + familiar, new $rdf.Statement(me, knows, s, profile), outgoing);
tools.appendChild(f);
} // editable
// //////////////// Mutual friends
if (friends) {
const myFriends = kb.each(me, foaf('knows'));
if (myFriends.length) {
const mutualFriends = common(friends, myFriends);
const tr = dom.createElement('tr');
tools.appendChild(tr);
tr.appendChild(dom.createTextNode('You' + (familiar ? ' and ' + familiar : '') + ' know' + people(mutualFriends.length) + ' found in common'));
if (mutualFriends) {
for (let i = 0; i < mutualFriends.length; i++) {
tr.appendChild(dom.createTextNode(', ' + UI.utils.label(mutualFriends[i])));
}
}
}
const tr = dom.createElement('tr');
tools.appendChild(tr);
} // friends
} // About someone else
} // me is defined
// End of you and s
// div.appendChild(dom.createTextNode(plural(friends.length, 'acquaintance') +'. '))
// ///////////////////////////////////////////// Main block
//
// Should: Find the intersection and difference sets
// List all x such that s knows x.
UI.widgets.attachmentList(dom, s, mainTable, {
doc: profile,
modify: !!editable,
predicate: foaf('knows'),
noun: 'friend'
});
// Figure out which are reciprocated:
// @@ Does not look up profiles
// Does distinguish reciprocated from unreciprocated friendships
//
function _triageFriends(s) {
outgoing = kb.each(s, foaf('knows'));
incoming = kb.each(undefined, foaf('knows'), s); // @@ have to load the friends
const confirmed = [];
const unconfirmed = [];
const requests = [];
for (let i = 0; i < outgoing.length; i++) {
const friend = outgoing[i];
let found = false;
for (let j = 0; j < incoming.length; j++) {
if (incoming[j].sameTerm(friend)) {
found = true;
break;
}
}
if (found) confirmed.push(friend);else unconfirmed.push(friend);
} // outgoing
for (let i = 0; i < incoming.length; i++) {
const friend = incoming[i];
// var lab = UI.utils.label(friend)
let found = false;
for (let j = 0; j < outgoing.length; j++) {
if (outgoing[j].sameTerm(friend)) {
found = true;
break;
}
}
if (!found) requests.push(friend);
} // incoming
const cases = [['Acquaintances', outgoing], ['Mentioned as acquaintances by: ', requests]];
for (let i = 0; i < cases.length; i++) {
const thisCase = cases[i];
const friends = thisCase[1];
if (friends.length === 0) continue; // Skip empty sections (sure?)
const h3 = dom.createElement('h3');
h3.textContent = thisCase[0];
const htr = dom.createElement('tr');
htr.appendChild(h3);
mainTable.appendChild(htr);
const items = [];
for (let j9 = 0; j9 < friends.length; j9++) {
items.push([UI.utils.label(friends[j9]), friends[j9]]);
}
items.sort();
let last = null;
let fr;
for (let j7 = 0; j7 < items.length; j7++) {
fr = items[j7][1];
if (fr.sameTerm(last)) continue; // unique
last = fr;
if (UI.utils.label(fr) !== '...') {
// This check is to avoid bnodes with no labels attached
// appearing in the friends list with "..." - Oshani
mainTable.appendChild(oneFriend(fr));
}
}
}
}
/* if ($rdf.keepThisCodeForLaterButDisableFerossConstantConditionPolice) {
triageFriends(s)
} */
// //////////////////////////////////// Basic info on left
h3 = dom.createElement('h3');
h3.appendChild(dom.createTextNode('Basic Information'));
tools.appendChild(h3);
// For each home page like thing make a label which will
// make sense and add the domain (like "w3.org blog") if there are more than one of the same type
//
const preds = [UI.ns.foaf('homepage'), UI.ns.foaf('weblog'), UI.ns.foaf('workplaceHomepage'), UI.ns.foaf('schoolHomepage')];
for (let i6 = 0; i6 < preds.length; i6++) {
const pred = preds[i6];
const sts = kb.statementsMatching(s, pred);
if (sts.length === 0) {
// if (editable) say("No home page set. Use the blue + icon at the bottom of the main view to add information.")
} else {
const uris = [];
for (let j5 = 0; j5 < sts.length; j5++) {
const st = sts[j5];
if (st.object.uri) uris.push(st.object.uri); // Ignore if not symbol
}
uris.sort();
let last2 = '';
let lab2;
for (let k = 0; k < uris.length; k++) {
const uri = uris[k];
if (uri === last2) continue; // uniques only
last2 = uri;
let hostlabel = '';
lab2 = UI.utils.label(pred);
if (uris.length > 1) {
const l = uri.indexOf('//');
if (l > 0) {
let r = uri.indexOf('/', l + 2);
const r2 = uri.lastIndexOf('.', r);
if (r2 > 0) r = r2;
hostlabel = uri.slice(l + 2, r);
}
}
if (hostlabel) lab2 = hostlabel + ' ' + lab2; // disambiguate
const t = dom.createTextNode(lab2);
const a = dom.createElement('a');
a.appendChild(t);
a.setAttribute('href', uri);
const d = dom.createElement('div');
// d.className = 'social_linkButton'
d.style.cssText = 'width: 80%; background-color: #fff; border: solid 0.05em #ccc; margin-top: 0.1em; margin-bottom: 0.1em; padding: 0.1em; text-align: center;';
d.appendChild(a);
tools.appendChild(d);
}
}
}
const preds2 = [UI.ns.foaf('openid'), UI.ns.foaf('nick')];
for (let i2 = 0; i2 < preds2.length; i2++) {
const pred = preds2[i2];
const sts2 = kb.statementsMatching(s, pred);
if (sts2.length === 0) {
// if (editable) say("No home page set. Use the blue + icon at the bottom of the main view to add information.")
} else {
outliner.appendPropertyTRs(tools, sts2, false, function (_pred) {
return true;
});
}
}
return div;
} // render()
}; //
// ends