UNPKG

solid-panes

Version:

Solid-compatible Panes: applets and views for the mashlib and databrowser

1,330 lines (1,204 loc) 2.31 MB
(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 = '&laquo;'; spanNext.innerHTML = '&raquo;'; // 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: