solid-panes
Version:
Solid-compatible Panes: applets and views for the mashlib and databrowser
1,330 lines (1,204 loc) • 2.31 MB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("UI"), require("SolidLogic"), require("rdflib"));
else if(typeof define === 'function' && define.amd)
define(["UI", "SolidLogic", "rdflib"], factory);
else if(typeof exports === 'object')
exports["SolidPanes"] = factory(require("UI"), require("SolidLogic"), require("rdflib"));
else
root["SolidPanes"] = factory(root["UI"], root["SolidLogic"], root["rdflib"]);
})(this, (__WEBPACK_EXTERNAL_MODULE__9426__, __WEBPACK_EXTERNAL_MODULE__5663__, __WEBPACK_EXTERNAL_MODULE__5491__) => {
return /******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ 6305
(module) {
var makeBSS = function (el, options) {
if (typeof document === 'undefined') {
var document = options.dom;
}
var $slideshows = document.querySelectorAll(el), // a collection of all of the slideshow
$slideshow = {},
Slideshow = {
init: function (el, options) {
this.counter = 0; // to keep track of current slide
this.el = el; // current slideshow container
this.$items = el.querySelectorAll('figure'); // a collection of all of the slides, caching for performance
this.numItems = this.$items.length; // total number of slides
options = options || {}; // if options object not passed in, then set to empty object
options.auto = options.auto || false; // if options.auto object not passed in, then set to false
this.opts = {
auto: (typeof options.auto === "undefined") ? false : options.auto,
speed: (typeof options.auto.speed === "undefined") ? 1500 : options.auto.speed,
pauseOnHover: (typeof options.auto.pauseOnHover === "undefined") ? false : options.auto.pauseOnHover,
fullScreen: (typeof options.fullScreen === "undefined") ? false : options.fullScreen,
swipe: (typeof options.swipe === "undefined") ? false : options.swipe
};
this.$items[0].classList.add('bss-show'); // add show class to first figure
this.injectControls(el);
this.addEventListeners(el);
if (this.opts.auto) {
this.autoCycle(this.el, this.opts.speed, this.opts.pauseOnHover);
}
if (this.opts.fullScreen) {
this.addFullScreen(this.el);
}
if (this.opts.swipe) {
this.addSwipe(this.el);
}
},
showCurrent: function (i) {
// increment or decrement this.counter depending on whether i === 1 or i === -1
if (i > 0) {
this.counter = (this.counter + 1 === this.numItems) ? 0 : this.counter + 1;
} else {
this.counter = (this.counter - 1 < 0) ? this.numItems - 1 : this.counter - 1;
}
// remove .show from whichever element currently has it
// http://stackoverflow.com/a/16053538/2006057
[].forEach.call(this.$items, function (el) {
el.classList.remove('bss-show');
});
// add .show to the one item that's supposed to have it
this.$items[this.counter].classList.add('bss-show');
},
injectControls: function (el) {
// build and inject prev/next controls
// first create all the new elements
var spanPrev = document.createElement("span"),
spanNext = document.createElement("span"),
docFrag = document.createDocumentFragment();
// add classes
spanPrev.classList.add('bss-prev');
spanNext.classList.add('bss-next');
// add contents
spanPrev.innerHTML = '«';
spanNext.innerHTML = '»';
// append elements to fragment, then append fragment to DOM
docFrag.appendChild(spanPrev);
docFrag.appendChild(spanNext);
el.appendChild(docFrag);
},
addEventListeners: function (el) {
var that = this;
el.querySelector('.bss-next').addEventListener('click', function () {
that.showCurrent(1); // increment & show
}, false);
el.querySelector('.bss-prev').addEventListener('click', function () {
that.showCurrent(-1); // decrement & show
}, false);
el.onkeydown = function (e) {
e = e || window.event;
if (e.keyCode === 37) {
that.showCurrent(-1); // decrement & show
} else if (e.keyCode === 39) {
that.showCurrent(1); // increment & show
}
};
},
autoCycle: function (el, speed, pauseOnHover) {
var that = this,
interval = window.setInterval(function () {
that.showCurrent(1); // increment & show
}, speed);
if (pauseOnHover) {
el.addEventListener('mouseover', function () {
interval = clearInterval(interval);
}, false);
el.addEventListener('mouseout', function () {
interval = window.setInterval(function () {
that.showCurrent(1); // increment & show
}, speed);
}, false);
} // end pauseonhover
},
addFullScreen: function(el){
var that = this,
fsControl = document.createElement("span");
fsControl.classList.add('bss-fullscreen');
el.appendChild(fsControl);
el.querySelector('.bss-fullscreen').addEventListener('click', function () {
that.toggleFullScreen(el);
}, false);
},
addSwipe: function(el){
var that = this,
ht = new Hammer(el);
ht.on('swiperight', function(e) {
that.showCurrent(-1); // decrement & show
});
ht.on('swipeleft', function(e) {
that.showCurrent(1); // increment & show
});
},
toggleFullScreen: function(el){
// https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode
if (!document.fullscreenElement && // alternative standard method
!document.mozFullScreenElement && !document.webkitFullscreenElement &&
!document.msFullscreenElement ) { // current working methods
if (document.documentElement.requestFullscreen) {
el.requestFullscreen();
} else if (document.documentElement.msRequestFullscreen) {
el.msRequestFullscreen();
} else if (document.documentElement.mozRequestFullScreen) {
el.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullscreen) {
el.webkitRequestFullscreen(el.ALLOW_KEYBOARD_INPUT);
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
} // end toggleFullScreen
}; // end Slideshow object .....
// make instances of Slideshow as needed
[].forEach.call($slideshows, function (el) {
$slideshow = Object.create(Slideshow);
$slideshow.init(el, options);
});
};
if ( true && module.exports) {
module.exports = makeBSS
}
/***/ },
/***/ 701
(__unused_webpack_module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.findChat = findChat;
exports.getChat = getChat;
var _solidUi = __webpack_require__(9426);
var _solidLogic = __webpack_require__(5663);
var _rdflib = __webpack_require__(5491);
var _longChatPane = __webpack_require__(8055);
async function getMe() {
const me = _solidLogic.authn.currentUser();
if (me === null) {
throw new Error('Current user not found! Not logged in?');
}
await _solidLogic.store.fetcher.load(me.doc());
return me;
}
async function getPodRoot(me) {
const podRoot = _solidLogic.store.any(me, _solidUi.ns.space('storage'), undefined, me.doc());
if (!podRoot) {
throw new Error('Current user pod root not found!');
}
return podRoot;
}
async function sendInvite(invitee, chatThing) {
await _solidLogic.store.fetcher.load(invitee.doc());
const inviteeInbox = _solidLogic.store.any(invitee, _solidUi.ns.ldp('inbox'), undefined, invitee.doc());
if (!inviteeInbox) {
throw new Error(`Invitee inbox not found! ${invitee.value}`);
}
const inviteBody = `
<> a <http://www.w3.org/ns/pim/meeting#LongChatInvite> ;
${_solidUi.ns.rdf('seeAlso')} <${chatThing.value}> .
`;
const inviteResponse = await _solidLogic.store.fetcher.webOperation('POST', inviteeInbox.value, {
data: inviteBody,
contentType: 'text/turtle'
});
const locationStr = inviteResponse.headers.get('location');
if (!locationStr) {
throw new Error(`Invite sending returned a ${inviteResponse.status}`);
}
}
function determineChatContainer(invitee, podRoot) {
// Create chat
// See https://gitter.im/solid/chat-app?at=5f3c800f855be416a23ae74a
const chatContainerStr = new URL(`IndividualChats/${new URL(invitee.value).host}/`, podRoot.value).toString();
return new _rdflib.NamedNode(chatContainerStr);
}
async function createChatThing(chatContainer, me) {
const created = await _longChatPane.longChatPane.mintNew({
session: {
store: _solidLogic.store
}
}, {
me,
newBase: chatContainer.value
});
return created.newInstance;
}
async function setAcl(chatContainer, me, invitee) {
// Some servers don't present a Link http response header
// if the container doesn't exist yet, so refetch the container
// now that it has been created:
await _solidLogic.store.fetcher.load(chatContainer);
// FIXME: check the Why value on this quad:
const chatAclDoc = _solidLogic.store.any(chatContainer, new _rdflib.NamedNode('http://www.iana.org/assignments/link-relations/acl'));
if (!chatAclDoc) {
throw new Error('Chat ACL doc not found!');
}
const aclBody = `
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
<#owner>
a acl:Authorization;
acl:agent <${me.value}>;
acl:accessTo <.>;
acl:default <.>;
acl:mode
acl:Read, acl:Write, acl:Control.
<#invitee>
a acl:Authorization;
acl:agent <${invitee.value}>;
acl:accessTo <.>;
acl:default <.>;
acl:mode
acl:Read, acl:Append.
`;
const aclResponse = await _solidLogic.store.fetcher.webOperation('PUT', chatAclDoc.value, {
data: aclBody,
contentType: 'text/turtle'
});
}
async function addToPrivateTypeIndex(chatThing, me) {
// Add to private type index
const privateTypeIndex = _solidLogic.store.any(me, _solidUi.ns.solid('privateTypeIndex'));
if (!privateTypeIndex) {
throw new Error('Private type index not found!');
}
await _solidLogic.store.fetcher.load(privateTypeIndex);
const reg = _solidUi.widgets.newThing(privateTypeIndex);
const ins = [(0, _rdflib.st)(reg, _solidUi.ns.rdf('type'), _solidUi.ns.solid('TypeRegistration'), privateTypeIndex.doc()), (0, _rdflib.st)(reg, _solidUi.ns.solid('forClass'), _solidUi.ns.meeting('LongChat'), privateTypeIndex.doc()), (0, _rdflib.st)(reg, _solidUi.ns.solid('instance'), chatThing, privateTypeIndex.doc())];
await new Promise((resolve, reject) => {
_solidLogic.store.updater.update([], ins, function (_uri, ok, errm) {
if (!ok) {
reject(new Error(errm));
} else {
resolve();
}
});
});
}
async function findChat(invitee) {
const me = await getMe();
const podRoot = await getPodRoot(me);
const chatContainer = determineChatContainer(invitee, podRoot);
let exists = true;
try {
await _solidLogic.store.fetcher.load(new _rdflib.NamedNode(chatContainer.value + _longChatPane.longChatPane.CHAT_LOCATION_IN_CONTAINER));
} catch (e) {
exists = false;
}
return {
me,
chatContainer,
exists
};
}
async function getChat(invitee, createIfMissing = true) {
const {
me,
chatContainer,
exists
} = await findChat(invitee);
if (exists) {
return new _rdflib.NamedNode(chatContainer.value + _longChatPane.longChatPane.CHAT_LOCATION_IN_CONTAINER);
}
if (createIfMissing) {
const chatThing = await createChatThing(chatContainer, me);
await sendInvite(invitee, chatThing);
await setAcl(chatContainer, me, invitee);
await addToPrivateTypeIndex(chatThing, me);
return chatThing;
}
throw new Error('Chat does not exist and createIfMissing is false');
}
//# sourceMappingURL=create.js.map
/***/ },
/***/ 8055
(__unused_webpack_module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.longChatPane = void 0;
var _solidLogic = __webpack_require__(5663);
var UI = _interopRequireWildcard(__webpack_require__(9426));
var $rdf = _interopRequireWildcard(__webpack_require__(5491));
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); }
/* Long Chat Pane
**
** A long chat consists a of a series of chat files saved by date.
*/
const ns = UI.ns;
const mainClass = ns.meeting('LongChat'); // @@ something from SIOC?
const CHAT_LOCATION_IN_CONTAINER = 'index.ttl#this';
// const menuIcon = 'noun_897914.svg'
const SPANNER_ICON = 'noun_344563.svg';
// resize: horizontal; min-width: 20em;
const SIDEBAR_COMPONENT_STYLE = UI.style.sidebarComponentStyle || ' padding: 0.5em; width: 100%;';
const SIDEBAR_STYLE = UI.style.sidebarStyle || 'overflow-x: auto; overflow-y: auto; border-radius: 1em; border: 0.1em solid white;';
// was purple border
const longChatPane = exports.longChatPane = {
CHAT_LOCATION_IN_CONTAINER,
// noun_704.svg Canoe noun_346319.svg = 1 Chat noun_1689339.svg = three chat
icon: UI.icons.iconBase + 'noun_1689339.svg',
name: 'long chat',
label: function (subject, context) {
const kb = context.session.store;
if (kb.holds(subject, ns.rdf('type'), ns.meeting('LongChat'))) {
// subject is the object
return 'Chat channnel';
}
if (kb.holds(subject, ns.rdf('type'), ns.sioc('Thread'))) {
// subject is the object
return 'Thread';
}
// Looks like a message -- might not havre any class declared
if (kb.any(subject, ns.sioc('content')) && kb.any(subject, ns.dct('created'))) {
return 'message';
}
return null; // Suppress pane otherwise
},
mintClass: mainClass,
mintNew: function (context, newPaneOptions) {
const kb = context.session.store;
const updater = kb.updater;
if (newPaneOptions.me && !newPaneOptions.me.uri) {
throw new Error('chat mintNew: Invalid userid ' + newPaneOptions.me);
}
const newInstance = newPaneOptions.newInstance = newPaneOptions.newInstance || kb.sym(newPaneOptions.newBase + CHAT_LOCATION_IN_CONTAINER);
const newChatDoc = newInstance.doc();
kb.add(newInstance, ns.rdf('type'), ns.meeting('LongChat'), newChatDoc);
kb.add(newInstance, ns.dc('title'), 'Chat channel', newChatDoc);
kb.add(newInstance, ns.dc('created'), new Date(), newChatDoc);
if (newPaneOptions.me) {
kb.add(newInstance, ns.dc('author'), newPaneOptions.me, newChatDoc);
}
const aclBody = (me, resource, AppendWrite) => `
@prefix : <#>.
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
@prefix lon: <./${resource}>.
:ControlReadWrite
a acl:Authorization;
acl:accessTo lon:;
acl:agent <${me.uri}>;
acl:default lon:;
acl:mode acl:Control, acl:Read, acl:Write.
:Read
a acl:Authorization;
acl:accessTo lon:;
acl:agentClass foaf:Agent;
acl:default lon:;
acl:mode acl:Read.
:Read${AppendWrite}
a acl:Authorization;
acl:accessTo lon:;
acl:agentClass acl:AuthenticatedAgent;
acl:default lon:;
acl:mode acl:Read, acl:${AppendWrite}.`;
return new Promise(function (resolve, reject) {
updater.put(newChatDoc, kb.statementsMatching(undefined, undefined, undefined, newChatDoc), 'text/turtle', function (uri2, ok, message) {
if (ok) {
resolve(newPaneOptions);
} else {
reject(new Error('FAILED to save new chat channel at: ' + uri2 + ' : ' + message));
}
})
// newChat container authenticated users Append only
.then(result => {
return new Promise((resolve, reject) => {
if (newPaneOptions.me) {
kb.fetcher.webOperation('PUT', newPaneOptions.newBase + '.acl', {
data: aclBody(newPaneOptions.me, '', 'Append'),
contentType: 'text/turtle'
});
kb.fetcher.webOperation('PUT', newPaneOptions.newBase + 'index.ttl.acl', {
data: aclBody(newPaneOptions.me, 'index.ttl', 'Write'),
contentType: 'text/turtle'
});
}
resolve(newPaneOptions);
});
});
});
},
render: function (subject, context, paneOptions) {
const dom = context.dom;
const kb = context.session.store;
/* Preferences
**
** Things like whether to color text by author webid, to expand image URLs inline,
** expanded inline image height. ...
** In general, preferences can be set per user, per user/app combo, per instance,
** and per instance/user combo. Per instance? not sure about unless it is valuable
** for everyone to be seeing the same thing.
*/
// const DCT = $rdf.Namespace('http://purl.org/dc/terms/')
const preferencesFormText = `
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ui: <http://www.w3.org/ns/ui#>.
@prefix : <#>.
:this
<http://purl.org/dc/elements/1.1/title> "Chat preferences" ;
a ui:Form ;
ui:parts ( :colorizeByAuthor :expandImagesInline :newestFirst :inlineImageHeightEms
:shiftEnterSendsMessage :authorDateOnLeft :showDeletedMessages).
:colorizeByAuthor a ui:TristateField; ui:property solid:colorizeByAuthor;
ui:label "Color user input by user".
:expandImagesInline a ui:TristateField; ui:property solid:expandImagesInline;
ui:label "Expand image URLs inline".
:newestFirst a ui:TristateField; ui:property solid:newestFirst;
ui:label "Newest messages at the top".
:inlineImageHeightEms a ui:IntegerField; ui:property solid:inlineImageHeightEms;
ui:label "Inline image height (lines)".
:shiftEnterSendsMessage a ui:TristateField; ui:property solid:shiftEnterSendsMessage;
ui:label "Shift-Enter sends message".
:authorDateOnLeft a ui:TristateField; ui:property solid:authorDateOnLeft;
ui:label "Author & date of message on left".
:showDeletedMessages a ui:TristateField; ui:property solid:showDeletedMessages;
ui:label "Show placeholders for deleted messages".
`;
const preferencesForm = kb.sym('https://solid.github.io/solid-panes/longCharPane/preferencesForm.ttl#this');
const preferencesFormDoc = preferencesForm.doc();
if (!kb.holds(undefined, undefined, undefined, preferencesFormDoc)) {
// If not loaded already
$rdf.parse(preferencesFormText, kb, preferencesFormDoc.uri, 'text/turtle'); // Load form directly
}
const preferenceProperties = kb.statementsMatching(null, ns.ui.property, null, preferencesFormDoc).map(st => st.object);
// Preferences Menu
//
// Build a menu a the side (@@ reactive: on top?)
async function renderPreferencesSidebar(context) {
// const noun = 'chat room'
const {
dom,
noun
} = context;
const preferencesArea = dom.createElement('div');
preferencesArea.appendChild(panelCloseButton(preferencesArea));
// @@ style below fix .. just make it onviious while testing
preferencesArea.style = SIDEBAR_COMPONENT_STYLE;
preferencesArea.style.minWidth = '25em'; // bit bigger
preferencesArea.style.maxHeight = triptychHeight;
const menuTable = preferencesArea.appendChild(dom.createElement('table'));
const registrationArea = menuTable.appendChild(dom.createElement('tr'));
const statusArea = menuTable.appendChild(dom.createElement('tr'));
const me = _solidLogic.authn.currentUser();
if (me) {
await UI.login.registrationControl({
noun,
me,
statusArea,
dom,
div: registrationArea
}, chatChannel, mainClass);
// eslint-disable-next-line no-console
console.log('Registration control finsished.');
preferencesArea.appendChild(UI.preferences.renderPreferencesForm(chatChannel, mainClass, preferencesForm, {
noun,
me,
statusArea,
div: preferencesArea,
dom,
kb
}));
}
return preferencesArea;
}
// @@ Split out into solid-ui
function panelCloseButton(panel) {
function removePanel() {
panel.parentNode.removeChild(panel);
}
const button = UI.widgets.button(context.dom, UI.icons.iconBase + 'noun_1180156.svg', 'close', removePanel);
button.style.float = 'right';
button.style.margin = '0.7em';
delete button.style.backgroundColor; // do not want white
return button;
}
async function preferencesButtonPressed(_event) {
if (!preferencesArea) {
// Expand
preferencesArea = await renderPreferencesSidebar({
dom,
noun: 'chat room'
});
}
if (paneRight.contains(preferencesArea)) {
// Close menu (hide or delete??)
preferencesArea.parentNode.removeChild(preferencesArea);
preferencesArea = null;
} else {
paneRight.appendChild(preferencesArea);
}
} // preferencesButtonPressed
// All my chats
//
/* Build a other chats list drawer the side
*/
function renderCreationControl(refreshTarget, noun) {
const creationDiv = dom.createElement('div');
const me = _solidLogic.authn.currentUser();
const creationContext = {
// folder: subject,
div: creationDiv,
dom,
noun,
statusArea: creationDiv,
me,
refreshTarget
};
const chatPane = context.session.paneRegistry.byName('chat');
const relevantPanes = [chatPane];
UI.create.newThingUI(creationContext, context, relevantPanes); // Have to pass panes down newUI
return creationDiv;
}
async function renderInstances(theClass, noun) {
const instancesDiv = dom.createElement('div');
const context = {
dom,
div: instancesDiv,
noun
};
await UI.login.registrationList(context, {
public: true,
private: true,
type: theClass
});
instancesDiv.appendChild(renderCreationControl(instancesDiv, noun));
return instancesDiv;
}
let otherChatsArea = null;
async function otherChatsHandler(_event) {
if (!otherChatsArea) {
// Lazy build when needed
// Expand
otherChatsArea = dom.createElement('div');
otherChatsArea.style = SIDEBAR_COMPONENT_STYLE;
otherChatsArea.style.maxHeight = triptychHeight;
otherChatsArea.appendChild(panelCloseButton(otherChatsArea));
otherChatsArea.appendChild(await renderInstances(ns.meeting('LongChat'), 'chat'));
}
// Toggle visibility with button clicks
if (paneLeft.contains(otherChatsArea)) {
otherChatsArea.parentNode.removeChild(otherChatsArea);
} else {
paneLeft.appendChild(otherChatsArea);
}
} // otherChatsHandler
// People in the chat
//
/* Build a participants list drawer the side
*/
let participantsArea;
function participantsHandler(_event) {
if (!participantsArea) {
// Expand
participantsArea = dom.createElement('div');
participantsArea.style = SIDEBAR_COMPONENT_STYLE;
participantsArea.style.maxHeight = triptychHeight;
participantsArea.appendChild(panelCloseButton(participantsArea));
// Record my participation and display participants
const me = _solidLogic.authn.currentUser();
if (!me) alert('Should be logeed in for partipants panel');
UI.pad.manageParticipation(dom, participantsArea, chatChannel.doc(), chatChannel, me, {});
}
// Toggle appearance in sidebar with clicks
// Note also it can remove itself using the X button
if (paneLeft.contains(participantsArea)) {
// Close participants (hide or delete??)
participantsArea.parentNode.removeChild(participantsArea);
participantsArea = null;
} else {
paneLeft.appendChild(participantsArea);
}
} // participantsHandler
let chatChannel = subject;
let selectedMessage = null;
let thread = null;
if (kb.holds(subject, ns.rdf('type'), ns.meeting('LongChat'))) {
// subject is the chatChannel
// eslint-disable-next-line no-console
console.log('@@@ Chat channnel');
// Looks like a message -- might not havre any class declared
} else if (kb.holds(subject, ns.rdf('type'), ns.sioc('Thread'))) {
// subject is the chatChannel
// eslint-disable-next-line no-console
console.log('Thread is subject ' + subject.uri);
thread = subject;
const rootMessage = kb.the(null, ns.sioc('has_reply'), thread, thread.doc());
if (!rootMessage) throw new Error('Thread has no root message ' + thread);
chatChannel = kb.any(null, ns.wf('message'), rootMessage);
if (!chatChannel) throw new Error('Thread root has no link to chatChannel');
} else if (
// Looks like a message -- might not havre any class declared
kb.any(subject, ns.sioc('content')) && kb.any(subject, ns.dct('created'))) {
// eslint-disable-next-line no-console
console.log('message is subject ' + subject.uri);
selectedMessage = subject;
chatChannel = kb.any(null, ns.wf('message'), selectedMessage);
if (!chatChannel) throw new Error('Message has no link to chatChannel');
}
const div = dom.createElement('div');
// Three large columns for particpant, chat, Preferences. formula below just as a note
// const windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const triptychHeight = '20cm'; // @@ need to be able to set to window!
const triptych = div.appendChild(dom.createElement('table'));
triptych.style.maxHeight = '12"'; // Screen max
const paneRow = triptych.appendChild(dom.createElement('tr'));
const paneLeft = paneRow.appendChild(dom.createElement('td'));
const paneMiddle = paneRow.appendChild(dom.createElement('td'));
const paneThread = paneRow.appendChild(dom.createElement('td'));
const paneRight = paneRow.appendChild(dom.createElement('td'));
const paneBottom = triptych.appendChild(dom.createElement('tr'));
paneLeft.style = SIDEBAR_STYLE;
paneLeft.style.paddingRight = '1em';
paneThread.style = SIDEBAR_STYLE;
paneThread.style.paddingLeft = '1em';
paneRight.style = SIDEBAR_STYLE;
paneRight.style.paddingLeft = '1em';
paneBottom.appendChild(dom.createElement('td'));
const buttonCell = paneBottom.appendChild(dom.createElement('td'));
paneBottom.appendChild(dom.createElement('td'));
// Button to bring up participants drawer on left
const participantsIcon = 'noun_339237.svg';
const participantsButton = UI.widgets.button(dom, UI.icons.iconBase + participantsIcon, 'participants ...'); // wider var
buttonCell.appendChild(participantsButton);
participantsButton.addEventListener('click', participantsHandler);
// Button to bring up otherChats drawer on left
const otherChatsIcon = 'noun_1689339.svg'; // long chat icon -- not ideal for a set of chats @@
const otherChatsButton = UI.widgets.button(dom, UI.icons.iconBase + otherChatsIcon, 'List of other chats ...'); // wider var
buttonCell.appendChild(otherChatsButton);
otherChatsButton.addEventListener('click', otherChatsHandler);
let preferencesArea = null;
const menuButton = UI.widgets.button(dom, UI.icons.iconBase + SPANNER_ICON, 'Setting ...'); // wider var
buttonCell.appendChild(menuButton);
menuButton.style.float = 'right';
menuButton.addEventListener('click', preferencesButtonPressed);
div.setAttribute('class', 'chatPane');
const options = {
infinite: true
};
const participantsHandlerContext = {
noun: 'chat room',
div,
dom
};
participantsHandlerContext.me = _solidLogic.authn.currentUser(); // If already logged on
async function showThread(thread, options) {
// eslint-disable-next-line no-console
console.log('@@@@ showThread thread ' + thread);
const newOptions = {}; // @@@ inherit
newOptions.thread = thread;
newOptions.includeRemoveButton = true;
newOptions.authorDateOnLeft = options.authorDateOnLeft;
newOptions.newestFirst = options.newestFirst;
paneThread.innerHTML = '';
// eslint-disable-next-line no-console
console.log('Options for showThread message Area', newOptions);
const chatControl = await UI.infiniteMessageArea(dom, kb, chatChannel, newOptions);
chatControl.style.resize = 'both';
chatControl.style.overflow = 'auto';
chatControl.style.maxHeight = triptychHeight;
paneThread.appendChild(chatControl);
}
async function buildPane() {
let prefMap;
try {
prefMap = await UI.preferences.getPreferencesForClass(chatChannel, mainClass, preferenceProperties, participantsHandlerContext);
} catch (err) {
UI.widgets.complain(participantsHandlerContext, err);
}
for (const propuri in prefMap) {
options[propuri.split('#')[1]] = prefMap[propuri];
}
if (selectedMessage) {
options.selectedMessage = selectedMessage;
}
if (paneOptions.solo) {
// This is the top pane, title, scrollbar etc are ours
options.solo = true;
}
if (thread) {
// Rendereing a thread as first class object
options.thread = thread;
} else {
// either show thread *or* allow new threads. Threads don't nest but they could
options.showThread = showThread;
}
const chatControl = await UI.infiniteMessageArea(dom, kb, chatChannel, options);
chatControl.style.resize = 'both';
chatControl.style.overflow = 'auto';
chatControl.style.maxHeight = triptychHeight;
paneMiddle.appendChild(chatControl);
}
// eslint-disable-next-line no-console
buildPane().then(console.log('async - chat pane built'));
return div;
}
};
//# sourceMappingURL=longChatPane.js.map
/***/ },
/***/ 7052
(__unused_webpack_module, exports, __webpack_require__) {
"use strict";
var __webpack_unused_export__;
__webpack_unused_export__ = ({
value: true
});
__webpack_unused_export__ = ({
enumerable: true,
get: function () {
return _create.getChat;
}
});
Object.defineProperty(exports, "Pq", ({
enumerable: true,
get: function () {
return _longChatPane.longChatPane;
}
}));
Object.defineProperty(exports, "bz", ({
enumerable: true,
get: function () {
return _shortChatPane.shortChatPane;
}
}));
var _shortChatPane = __webpack_require__(6303);
var _longChatPane = __webpack_require__(8055);
var _create = __webpack_require__(701);
//# sourceMappingURL=main.js.map
/***/ },
/***/ 6303
(__unused_webpack_module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.shortChatPane = void 0;
var _solidLogic = __webpack_require__(5663);
var UI = _interopRequireWildcard(__webpack_require__(9426));
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); }
/* Chat Pane
**
** Plan is to support a finte number of chat graph shapes
** and investigate the interop between them.
*/
/* global $rdf */
const ns = UI.ns;
const shortChatPane = exports.shortChatPane = {
icon: UI.icons.iconBase + 'noun_346319.svg',
name: 'chat',
/*
* As part of the work on merging the existing chat views (aka panes) into one view
* https://github.com/solid/chat-pane/issues/17
* we want to dis-incentivize the use of Small Chat until we've gotten the work done
* by making it difficult to create new data resources that uses the Small Chat view
* but we still want existing resources to be viewed by the Small Chat view
*/
audience: [ns.solid('PowerUser')],
/* AN RRSAgent IRC log:
<irc://localhost:6667/&mit>
a foaf:ChatChannel
foaf:chatEventList
[ rdf:_100
<#T19-10-58>
rdf:_101
<#T19-10-58-1>
rdf:_102
..
<#T19-28-47-1>
dc:creator
[ a wn:Person; foaf:nick "timbl" ]
dc:date
"2016-03-15T19:28:47Z"
dc:description
"timbl has joined &mit"
a foaf:chatEvent.
*/
label: function (subject, context) {
const kb = context.session.store;
const n = kb.each(subject, ns.wf('message')).length;
if (n > 0) return 'Chat (' + n + ')'; // Show how many in hover text
if (kb.holds(subject, ns.rdf('type'), ns.meeting('Chat'))) {
// subject is the file
return 'Meeting chat';
}
if (kb.holds(undefined, ns.rdf('type'), ns.foaf('ChatChannel'), subject)) {
// subject is the file
return 'IRC log'; // contains anything of this type
}
return null; // Suppress pane otherwise
},
mintClass: ns.meeting('Chat'),
mintNew: function (context, newPaneOptions) {
// This deprecates the creation of short Chats after 2023-04-03.
// The mintNew function will be removed/commented out in a few months.
if (!confirm('short Chat is deprecated in favor of long Chat.' + '\nEmbedded chat for comments and existing short Chats will work.' + '\nYou can report any issues at https://github.com/SolidOS/chat-pane ?' + '\n\nDo you really want to create a new deprecated short Chat?')) return;
const kb = context.session.store;
const updater = kb.updater;
if (newPaneOptions.me && !newPaneOptions.me.uri) {
throw new Error('chat mintNew: Invalid userid ' + newPaneOptions.me);
}
const newInstance = newPaneOptions.newInstance = newPaneOptions.newInstance || kb.sym(newPaneOptions.newBase + 'index.ttl#this');
const newChatDoc = newInstance.doc();
kb.add(newInstance, ns.rdf('type'), ns.meeting('Chat'), newChatDoc);
kb.add(newInstance, ns.dc('title'), 'Chat', newChatDoc);
kb.add(newInstance, ns.dc('created'), new Date(), newChatDoc);
if (newPaneOptions.me) {
kb.add(newInstance, ns.dc('author'), newPaneOptions.me, newChatDoc);
}
return new Promise(function (resolve, reject) {
updater.put(newChatDoc, kb.statementsMatching(undefined, undefined, undefined, newChatDoc), 'text/turtle', function (uri2, ok, message) {
if (ok) {
resolve(newPaneOptions);
} else {
reject(new Error('FAILED to save new tool at: ' + uri2 + ' : ' + message));
}
});
});
},
render: function (subject, context) {
const kb = context.session.store;
const dom = context.dom;
const complain = function complain(message, color) {
const pre = dom.createElement('pre');
pre.setAttribute('style', 'background-color: ' + color || 0);
div.appendChild(pre);
pre.appendChild(dom.createTextNode(message));
};
const div = dom.createElement('div');
div.setAttribute('class', 'chatPane');
const options = {}; // Like newestFirst
let messageStore;
if (kb.holds(subject, ns.rdf('type'), ns.meeting('Chat'))) {
// subject may be the file
messageStore = subject.doc();
} else if (kb.any(subject, UI.ns.wf('message'))) {
messageStore = _solidLogic.store.any(subject, UI.ns.wf('message')).doc();
} else if (kb.holds(undefined, ns.rdf('type'), ns.foaf('ChatChannel'), subject) || kb.holds(subject, ns.rdf('type'), ns.foaf('ChatChannel'))) {
// subject is the file
const ircLogQuery = function () {
const query = new $rdf.Query('IRC log entries');
const v = [];
const vv = ['chan', 'msg', 'date', 'list', 'pred', 'creator', 'content'];
vv.forEach(function (x) {
query.vars.push(v[x] = $rdf.variable(x));
});
query.pat.add(v.chan, ns.foaf('chatEventList'), v.list); // chatEventList
query.pat.add(v.list, v.pred, v.msg); //
query.pat.add(v.msg, ns.dc('date'), v.date);
query.pat.add(v.msg, ns.dc('creator'), v.creator);
query.pat.add(v.msg, ns.dc('description'), v.content);
return query;
};
messageStore = subject.doc();
options.query = ircLogQuery();
} else {
complain('Unknown chat type');
}
div.appendChild(UI.messageArea(dom, kb, subject, messageStore, options));
kb.updater.addDownstreamChangeListener(messageStore, function () {
UI.widgets.refreshTree(div);
}); // Live update
// })
return div;
}
};
//# sourceMappingURL=shortChatPane.js.map
/***/ },
/***/ 3955
(module, __unused_webpack_exports, __webpack_require__) {
(function webpackUniversalModuleDefinition(root, factory) {
if(true)
module.exports = factory(__webpack_require__(5663), __webpack_require__(9426), __webpack_require__(5491));
else // removed by dead control flow
{}
})(globalThis, (__WEBPACK_EXTERNAL_MODULE__941__, __WEBPACK_EXTERNAL_MODULE__104__, __WEBPACK_EXTERNAL_MODULE__53__) => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 903
(module, __nested_webpack_exports__, __nested_webpack_require_822__) {
/* harmony export */ __nested_webpack_require_822__.d(__nested_webpack_exports__, {
/* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_822__(354);
/* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_822__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_822__(314);
/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_822__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__ = __nested_webpack_require_822__(417);
/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__nested_webpack_require_822__.n(_node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__);
// Imports
var ___CSS_LOADER_URL_IMPORT_0___ = new URL(/* asset import */ __nested_webpack_require_822__(102), __nested_webpack_require_822__.b);
var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
var ___CSS_LOADER_URL_REPLACEMENT_0___ = _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default()(___CSS_LOADER_URL_IMPORT_0___);
// Module
___CSS_LOADER_EXPORT___.push([module.id, `/* Focus indicator for keyboard navigation */
.contactPane table tr[tabindex="0"]:focus {
outline: var(--focus-ring-width) solid var(--color-primary);
outline-offset: 2px;
background: var(--color-info-bg);
}
/* contactsPane styles — extracted from inline styles in contactsPane.js */
/* Uses CSS custom properties from the global stylesheet (dev-global.css / mashlib) */
/* ── Layout: Three-column browser ────────────────────────────── */
.contactPane .peopleSection .selected {
background-color: var(--color-info-bg) !important;
}
.contactPane .detailSection,
.contactPane .addressBookSection {
display: flex;
flex-direction: column;
align-items: stretch;
flex: 1 1 0; /* allow it to grow but not force wrap */
min-width: 300px;
box-sizing: border-box;
background: var(--color-section-bg);
}
.contactPane .detailsSectionContent {
flex: 1 1 auto;
min-height: 200px;
padding: var(--spacing-lg);
max-width: 900px;
width: 100%;
box-sizing: border-box;
}
.contactPane .detailsSectionContent--wide {
max-width: 900px;
}
.contactPane .cardFooter {
display: flex;
flex-wrap: nowrap; /* keep buttons inline */
align-items: center; /* vertical centering if varied heights */
gap: var(--spacing-xs);
padding-top: var(--spacing-md);
margin-top: var(--spacing-md);
}
.contactPane .detailsSectionContent {
margin: 0;
}
/* ── Contact type chooser ───────────────────────────────────── */
.contactPane .contactTypeChooser {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
max-width: 360px;
}
.contactPane .contactTypeChooser h3 {
margin: 0 0 var(--spacing-xs) 0;
font-size: var(--font-size-lg);
}
.contactPane .contactTypeSelect {
height: var(--min-touch-target);
border: 1px solid var(--color-border-pale);
border-radius: var(--border-radius-base);
padding: 0 var(--spacing-sm);
font-size: var(--font-size-sm);
background: var(--color-section-bg);
}
/* ── Search ──────────────────────────────────────────────────── */
.contactPane .allGroupsButton {
border-radius: var(--border-radius-full) !important;
/* existing styles */
}
/* wrapper to position clear icon/button */
.contactPane .searchDiv {
position: relative;
}
.contactPane .searchInput {
height: var(--min-touch-target);
border: 1px solid var(--color-border-pale);
background-color: var(--color-section-bg);
background-image: url(${___CSS_LOADER_URL_REPLACEMENT_0___});
background-repeat: no-repeat;
background-position: 8px center;
background-size: 20px 20px;
border-radius: var(--border-radius-base);
padding: 0 var(--spacing-sm) 0 34px;
font-size: var(--font-size-base);
width: 100%;
box-sizing: border-box;
}
/* clear button inside search input */
.contactPane .searchClearButton {
position: absolute;
right: var(--spacing-sm);
top: 50%;
transform: translateY(-50%);
border: none;
background: transparent;
font-size: var(--font-size-base);
line-height: 1;
padding: 0;
cursor: pointer;
color: var(--color-text-muted);
/* visibility is controlled via the generic \`.hidden\` utility class */
display: block;
}
.contactPane .searchClearButton.hidden {
display: none;
}
.contactPane .searchClearButton:hover {
color: var(--color-text);
}
/* ── Contact toolbar (top-right link + delete) ──────────────── */
.contactPane .contact-toolbar {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-xs) 0;
}
.contactPane .contact-toolbar a {
margin: 0.3em;
}
.contactPane .contact-toolbar a img {
width: 1.3em;
height: 1em;
margin: 0;
}
.contact-toolbar .deleteButton {
margin-left: auto; /* keeps delete icon on the right */
margin-right: 0.2em;
width: 1em;
height: 1em;
float: none; /* important: prevents overlap behavior */
}
/* ── "All" groups button ─────────────────────────────────────── */
.contactPane .allGroupsButton {
margin-left: var(--spacing-md);
font-size: var(--font-size-base);
}
.contactPane .allGroupsButton--loading {
background-color: var(--color-primary);
}
.contactPane .allGroupsButton--active {
background-color: var(--color-primary);
color: var(--color-background);
}
.contactPane .allGroupsButton--loaded {
background-color: var(--color-primary);
}
/* ── Selection & visibility states ───────────────────────────── */
.contactPane .group-loading {
}
/* ── Mint new address book ───────────────────────────────────── */
.contactPane .claimSuccess {
font-size: var(--font-size-xl);
}
.contactPane {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.contactPane .addressBook-grid {
display: flex;
flex-wrap: nowrap; /* keep sections side-by-side */
flex: 1;
min-width: 50%;
align-items: stretch;
width: 100%;
box-sizing: border-box;
overflow-x: auto; /* allow horizontal scroll if needed */
}
@media ((min-width: 500px) and (max-width: 900px)) {
.contactPane .addressBookSection {
max-width: 900px;
}
.contactPane .addressBookSection section {
max-width: 485px;
}
}
.contactPane .contactPane--narrow .addressBook-grid {
flex-direction: column !important;
flex-wrap: wrap !important;
}
.contactPane .contactPane--narrow .addressBookSection,
.contactPane .contactPane--narrow .detailSection {
flex: 1 1 100% !important;
max-width: 100% !important;
min-width: 0 !important;
width: 100% !important;
}
@media (max-width: 1000px) {
/* Stack sidebar + details vertically on narrow screens */
.contactPane {
min-height: auto !important;
}
.contactPane .addressBook-grid {
flex-direction: column !important;
flex-wrap: nowrap !important;
min-height: auto !important;
height: auto !important;
}
.contactPane .addressBookSection,
.contactPane .detailSection {
order: initial !important;
flex: none !important;
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
}
.contactPane .addressBookSection {
max-height: 60vh !important;
min-height: auto !important;
overflow-y: auto !important;
padding-bottom: var(--spacing-lg) !important;
}
.contactPane .detailSection {
max-height: none !important;
min-height: auto !important;
overflow-y: visible !important;
}
.contactPane .detailsSectionContent {
display: flex !important;
flex-direction: column !important;
justify-content: flex-start !important;
align-items: stretch !important;
min-height: auto !important;
height: auto !important;
overflow-y: visible !important;
}
.contactPane .detailSection > .detailsSectionContent {
padding-top: var(--spacing-sm) !important;
box-sizing: border-box !important;
}
/* Small-screen larger text and spacing */
.contactPane,
.contactPane * {
font-size: 2rem !important;
}
.contactPane .actionButton,
.contactPane .searchInput,
.contactPane .flatButton,
.contactPane .buttonSection button,
.contactPane .groupButtonsList button {
min-height: calc(var(--min-touch-target) + 0.5em) !important;
font-size: 2rem !important;
padding: 1em 1em !important;
}
.contactPane .group-membership-item .group-membership-toolbar > img.hoverControlHide, .contactPane .group-membership-item .group-membership-toolbar > [data-testid="deleteButtonWithCheck"],
.individualPane .hoverControl img.hoverControlHide,
.individualPane .hoverControl [data-testid="deleteButtonWithCheck"] {
display: inline-flex !important;
visibility: visible !important;
opacity: 1 !important;
}
}
/* Card Section Background */
.contactPane .section-bg {
background: var(--color-section-bg);
padding: var(--spacing-md);
box-sizing: border-box;
border: none !important;
border-radius: 0 !important;
}
/* Keep detail section content anchored at top */
.contactPane .detailSection {
display: flex;
flex-direction: