From cfa9a15a7daf22a8e5216f5a60fab8fa03e78cdb Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Wed, 27 Mar 2019 10:13:08 +0000 Subject: [PATCH 01/35] Make chat code more moular --- src/chat/bookmarks.js | 190 ++++++++++ src/chat/infinte.js | 723 +++++++++++++++++++++++++++++++++++++++ src/chat/message.js | 194 +++++++++++ src/chat/messageTools.js | 220 ++++++++++++ src/create.js | 12 +- src/index.js | 1 + src/signin.js | 6 +- 7 files changed, 1342 insertions(+), 4 deletions(-) create mode 100644 src/chat/bookmarks.js create mode 100644 src/chat/infinte.js create mode 100644 src/chat/message.js create mode 100644 src/chat/messageTools.js diff --git a/src/chat/bookmarks.js b/src/chat/bookmarks.js new file mode 100644 index 000000000..8e1338556 --- /dev/null +++ b/src/chat/bookmarks.js @@ -0,0 +1,190 @@ + +/* global alert confirm */ + +const UI = { + authn: require('./signin'), + icons: require('./iconBase'), + log: require('./log'), + ns: require('./ns'), + media: require('./media-capture'), + pad: require('./pad'), + rdf: require('rdflib'), + store: require('./store'), + style: require('./style'), + utils: require('./utils'), + widgets: require('./widgets') +} + +const BOOK = $rdf.Namespace('http://www.w3.org/2002/01/bookmark#') +const BOOKMARK_ICON = 'noun_45961.svg' + +const kb = UI.store +const ns = UI.ns +const label = UI.utils.label +const dom = UI.dom || window.document + +/** Create a resource if it really does not exist + * Be absolutely sure something does not exist before creating a new empty file + * as otherwise existing could be deleted. + * @param doc {NamedNode} - The resource +*/ +function createIfNotExists (doc) { + return new Promise(function (resolve, reject) { + kb.fetcher.load(doc).then(response => { + console.log('createIfNotExists doc exists, all good ' + doc) + // kb.fetcher.webOperation('HEAD', doc.uri).then(response => { + resolve(response) + }, err => { + if (err.response.status === 404) { + console.log('createIfNotExists doc does NOT exist, will create... ' + doc) + + kb.fetcher.webOperation('PUT', doc.uri, {data: '', contentType: 'text/turtle'}).then(response => { + // fetcher.requested[doc.uri] = 'done' // do not need to read ?? but no headers + delete kb.fetcher.requested[doc.uri] // delete cached 404 error + console.log('createIfNotExists doc created ok ' + doc) + resolve(response) + }, err => { + console.log('createIfNotExists doc FAILED: ' + doc + ': ' + err) + reject(err) + }) + } else { + console.log('createIfNotExists doc load error NOT 404: ' + doc + ': ' + err) + reject(err) + } + }) + }) +} + +// @@@@ use the one in rdflib.js when it is avaiable and delete this +function updatePromise (del, ins) { + return new Promise(function (resolve, reject) { + kb.updater.update(del, ins, function (uri, ok, errorBody) { + if (!ok) { + reject(new Error(errorBody)) + } else { + resolve() + } + }) // callback + }) // promise +} + +// export findBookmarkDocument, + +/* Bookmarking +*/ +/** Find a user's bookmarks +*/ +export async function findBookmarkDocument (userContext) { + const klass = BOOK('Bookmark') + const fileTail = 'bookmarks.ttl' + const isPublic = true + + await UI.authn.findAppInstances(userContext, klass, isPublic) // public -- only look for public links + if (userContext.instances && userContext.instances.length > 0) { + userContext.bookmarkDocument = userContext.instances[0] + if (userContext.instances.length > 1) { + alert('More than one bookmark file! ' + userContext.instances) + } + } else { + if (userContext.publicProfile) { // publicProfile or preferencesFile + var newBookmarkFile = $rdf.sym(userContext.publicProfile.dir().uri + fileTail) + try { + console.log('Creating new bookmark file ' + newBookmarkFile) + await createIfNotExists(newBookmarkFile) + } catch (e) { + alert.error('Can\'t make fresh bookmark file:' + e) + return userContext + } + await UI.authn.registerInTypeIndex(userContext, newBookmarkFile, klass, true) // public + userContext.bookmarkDocument = newBookmarkFile + } else { + alert('You seem to have no bookmark file and not even a profile file.') + } + } + return userContext +} + + /** Add a bookmark + */ + +async function addBookmark (context, target) { + /* like + @prefix terms: . + @prefix bookm: . + @prefix n0: . + <> terms:references <#0.5534145389246576>. + <#0.5534145389246576> + a bookm:Bookmark; + terms:created "2019-01-26T20:26:44.374Z"^^XML:dateTime; + terms:title "Herons"; + bookm:recalls wiki:Heron; + n0:maker c:me. + */ + var title = '' + var me = UI.authn.currentUser() // If already logged on + if (!me) throw new Error('Must be logged on to add Bookmark') + + var author = kb.any(target, ns.foaf('maker')) + title = label(author) + ': ' + + kb.anyValue(target, ns.sioc('content')).slice(0, 80) // @@ add chat title too? + const bookmarkDoc = context.bookmarkDocument + const bookmark = UI.widgets.newThing(bookmarkDoc, title) + const ins = [ + $rdf.st(bookmarkDoc, UI.ns.dct('references'), bookmark, bookmarkDoc), + $rdf.st(bookmark, UI.ns.rdf('type'), BOOK('Bookmark'), bookmarkDoc), + $rdf.st(bookmark, UI.ns.dct('created'), new Date(), bookmarkDoc), + $rdf.st(bookmark, BOOK('recalls'), target, bookmarkDoc), + $rdf.st(bookmark, UI.ns.foaf('maker'), me, bookmarkDoc), + $rdf.st(bookmark, UI.ns.dct('title'), title, bookmarkDoc) + ] + try { + await updatePromise([], ins) // 20190118A + } catch (e) { + let msg = 'Making bookmark: ' + e + alert.error(msg) + return null + } + return bookmark +} + +export async function toggleBookmark (userContext, target, bookmarkButton) { + await kb.fetcher.load(userContext.bookmarkDocument) + let bookmarks = kb.each(null, BOOK('recalls'), target, userContext.bookmarkDocument) + if (bookmarks.length) { // delete + if (!confirm('Delete bookmark on this?' + bookmarks.length)) return + for (let i = 0; i < bookmarks.length; i++) { + try { + await updatePromise(kb.connectedStatements(bookmarks[i]), []) + bookmarkButton.style.backgroundColor = 'white' + console.log('Bookmark deleted: ' + bookmarks[i]) + } catch (e) { + console.error('Cant delete bookmark:' + e) + alert('Cant delete bookmark:' + e) + } + } + } else { + let bookmark = await addBookmark(userContext, target) + bookmarkButton.style.backgroundColor = 'yellow' + console.log('Bookmark added: ' + bookmark) + } +} + +export async function renderBookmarksButton (userContext, target) { + async function setBookmarkButtonColor (bookmarkButton) { + await kb.fetcher.load(userContext.bookmarkDocument) + let bookmarked = kb.any(null, BOOK('recalls'), bookmarkButton.target, userContext.bookmarkDocument) + bookmarkButton.style = UI.style.buttonStyle + if (bookmarked) bookmarkButton.style.backgroundColor = 'yellow' + } + + var bookmarkButton + if (userContext.bookmarkDocument) { + bookmarkButton = UI.widgets.button(dom, UI.icons.iconBase + BOOKMARK_ICON, + label(BOOK('Bookmark')), () => { + toggleBookmark(userContext, target, bookmarkButton) + }) + bookmarkButton.target = target + await setBookmarkButtonColor(bookmarkButton) + return bookmarkButton + } +} diff --git a/src/chat/infinte.js b/src/chat/infinte.js new file mode 100644 index 000000000..ceaa6f8eb --- /dev/null +++ b/src/chat/infinte.js @@ -0,0 +1,723 @@ +// Common code for a discussion are a of messages about something +// This version runs over a series of files for different time periods +// +// Parameters for the whole chat like its title are stred on +// index.ttl#this and the chats messages are stored in YYYY/MM/DD/chat.ttl +// +/* global alert */ +const UI = { + authn: require('./signin'), + icons: require('./iconBase'), + log: require('./log'), + ns: require('./ns'), + media: require('./media-capture'), + pad: require('./pad'), + rdf: require('rdflib'), + store: require('./store'), + style: require('./style'), + utils: require('./utils'), + widgets: require('./widgets') +} + +// const utils = require('./utils') + +const { renderMessage, creatorAndDate } = require('./renderMessage') +const bookmarks = require('./bookmarks') + +module.exports = { infiniteMessageArea } + +function infiniteMessageArea (dom, kb, chatChannel, options) { + kb = kb || UI.store + const ns = UI.ns + const WF = $rdf.Namespace('http://www.w3.org/2005/01/wf/flow#') + const DCT = $rdf.Namespace('http://purl.org/dc/terms/') + // const POSIX = $rdf.Namespace('http://www.w3.org/ns/posix/stat#') + + options = options || {} + + var newestFirst = options.newestFirst === '1' || options.newestFirst === true // hack for now + + options.authorAboveContent = true + + // var participation // An object tracking users use and prefs + const messageBodyStyle = UI.style.messageBodyStyle + + // var messageBodyStyle = 'white-space: pre-wrap; width: 90%; font-size:100%; border: 0.07em solid #eee; padding: .2em 0.5em; margin: 0.1em 1em 0.1em 1em;' + // 'font-size: 100%; margin: 0.1em 1em 0.1em 1em; background-color: white; white-space: pre-wrap; padding: 0.1em;' + + var div = dom.createElement('div') + var menuButton + const statusArea = div.appendChild(dom.createElement('div')) + var userContext = {dom, statusArea, div: statusArea} // logged on state, pointers to user's stuff + var me + + var updater = UI.store.updater + + var mention = function mention (message, style) { + console.log(message) + var pre = dom.createElement('pre') + pre.setAttribute('style', style || 'color: grey;') + pre.appendChild(dom.createTextNode(message)) + statusArea.appendChild(pre) + } + + var announce = { + log: function (message) { mention(message, 'color: #111;') }, + warn: function (message) { mention(message, 'color: #880;') }, + error: function (message) { mention(message, 'color: #800;') } + } + + /** Create a resource if it really does not exist + * Be absolutely sure something does not exist before creating a new empty file + * as otherwise existing could be deleted. + * @param doc {NamedNode} - The resource + */ + function createIfNotExists (doc) { + return new Promise(function (resolve, reject) { + kb.fetcher.load(doc).then(response => { + console.log('createIfNotExists doc exists, all good ' + doc) + // kb.fetcher.webOperation('HEAD', doc.uri).then(response => { + resolve(response) + }, err => { + if (err.response.status === 404) { + console.log('createIfNotExists doc does NOT exist, will create... ' + doc) + + kb.fetcher.webOperation('PUT', doc.uri, {data: '', contentType: 'text/turtle'}).then(response => { + // fetcher.requested[doc.uri] = 'done' // do not need to read ?? but no headers + delete kb.fetcher.requested[doc.uri] // delete cached 404 error + console.log('createIfNotExists doc created ok ' + doc) + resolve(response) + }, err => { + console.log('createIfNotExists doc FAILED: ' + doc + ': ' + err) + reject(err) + }) + } else { + console.log('createIfNotExists doc load error NOT 404: ' + doc + ': ' + err) + reject(err) + } + }) + }) + } + + /* Form for a new message + */ + function newMessageForm (messageTable) { + var form = dom.createElement('tr') + var lhs = dom.createElement('td') + var middle = dom.createElement('td') + var rhs = dom.createElement('td') + form.appendChild(lhs) + form.appendChild(middle) + form.appendChild(rhs) + form.AJAR_date = '9999-01-01T00:00:00Z' // ISO format for field sort + var field, sendButton + + function sendMessage (text) { + var now = new Date() + addNewTableIfNewDay(now).then(() => { + if (!text) { + field.setAttribute('style', messageBodyStyle + 'color: #bbb;') // pendingedit + field.disabled = true + } + var sts = [] + var timestamp = '' + now.getTime() + var dateStamp = $rdf.term(now) + let chatDocument = chatDocumentFromDate(now) + + var message = kb.sym(chatDocument.uri + '#' + 'Msg' + timestamp) + var content = kb.literal(text || field.value) + // if (text) field.value = text No - don't destroy half-finsihed user input + + sts.push(new $rdf.Statement(chatChannel, ns.wf('message'), message, chatDocument)) + sts.push(new $rdf.Statement(message, ns.sioc('content'), content, chatDocument)) + sts.push(new $rdf.Statement(message, DCT('created'), dateStamp, chatDocument)) + if (me) sts.push(new $rdf.Statement(message, ns.foaf('maker'), me, chatDocument)) + + var sendComplete = function (uri, success, body) { + if (!success) { + form.appendChild(UI.widgets.errorMessageBlock( + dom, 'Error writing message: ' + body)) + } else { + var bindings = { '?msg': message, + '?content': content, + '?date': dateStamp, + '?creator': me} + renderMessage(dom, kb, liveMessageTable, bindings, false) // not green + + if (!text) { + field.value = '' // clear from out for reuse + field.setAttribute('style', messageBodyStyle) + field.disabled = false + field.scrollIntoView(newestFirst) // allign bottom (top) + field.focus() // Start typing next line immediately + field.select() + } + } + } + updater.update([], sts, sendComplete) + }) // then + } // sendMessage + + form.appendChild(dom.createElement('br')) + + // DRAG AND DROP + function droppedFileHandler (files) { + let base = messageTable.chatDocument.dir().uri + UI.widgets.uploadFiles(kb.fetcher, files, base + 'Files', base + 'Pictures', + function (theFile, destURI) { // @@@@@@ Wait for eachif several + sendMessage(destURI) + }) + } + + // When a set of URIs are dropped on the field + var droppedURIHandler = function (uris) { + sendMessage(uris[0]) // @@@@@ wait + } + + // When we are actually logged on + function turnOnInput () { + if (options.menuHandler && menuButton) { + let menuOptions = { me, dom, div, newBase: messageTable.chatDocument.dir().uri } + menuButton.addEventListener('click', + event => { options.menuHandler(event, chatChannel, menuOptions) } + , false) + } + + // Turn on message input + creatorAndDate(lhs, me, '', null) + + field = dom.createElement('textarea') + middle.innerHTML = '' + middle.appendChild(field) + field.rows = 3 + // field.cols = 40 + field.setAttribute('style', messageBodyStyle + 'background-color: #eef;') + + // Trap the Enter BEFORE it is used ti make a newline + field.addEventListener('keydown', function (e) { // User preference? + if (e.keyCode === 13) { + if (!e.altKey) { // Alt-Enter just adds a new line + sendMessage() + } + } + }, false) + UI.widgets.makeDropTarget(field, droppedURIHandler, droppedFileHandler) + + rhs.innerHTML = '' + sendButton = UI.widgets.button(dom, UI.icons.iconBase + 'noun_383448.svg', 'Send') + sendButton.setAttribute('style', UI.style.buttonStyle + 'float: right;') + sendButton.addEventListener('click', ev => sendMessage(), false) + rhs.appendChild(sendButton) + + const chatDocument = chatDocumentFromDate(new Date()) + var imageDoc + function getImageDoc () { + imageDoc = kb.sym(chatDocument.dir().uri + 'Image_' + Date.now() + '.png') + return imageDoc + } + function tookPicture (imageDoc) { + if (imageDoc) { + sendMessage(imageDoc.uri) + } + } + middle.appendChild(UI.media.cameraButton(dom, kb, getImageDoc, tookPicture)) + + UI.pad.recordParticipation(chatChannel, chatChannel.doc()) // participation = + } // turn on inpuut + + let context = {div: middle, dom: dom} + UI.authn.logIn(context).then(context => { + me = context.me + turnOnInput() + // userContext = context + bookmarks.findBookmarkDocument(context).then(context => { + console.log('Bookmark file: ' + context.bookmarkDocument) + }) + }) + + return form + } + + // /////////////////////////////////////////////////////////////////////// + + function syncMessages (about, messageTable) { + var displayed = {} + var ele, ele2 + for (ele = messageTable.firstChild; ele; ele = ele.nextSibling) { + if (ele.AJAR_subject) { + displayed[ele.AJAR_subject.uri] = true + } + } + + var messages = kb.statementsMatching( + about, ns.wf('message'), null, messageTable.chatDocument).map(st => { return st.object }) + var stored = {} + messages.map(function (m) { + stored[m.uri] = true + if (!displayed[m.uri]) { + addMessage(m, messageTable) + } + }) + + for (ele = messageTable.firstChild; ele;) { + ele2 = ele.nextSibling + if (ele.AJAR_subject && !stored[ele.AJAR_subject.uri]) { + messageTable.removeChild(ele) + } + ele = ele2 + } + for (ele = messageTable.firstChild; ele; ele = ele.nextSibling) { + if (ele.AJAR_subject) { // Refresh thumbs up etc + UI.widgets.refreshTree(ele) // Things inside may have changed too + } + } + } // syncMessages + + var addMessage = function (message, messageTable) { + var bindings = { + '?msg': message, + '?creator': kb.any(message, ns.foaf('maker')), + '?date': kb.any(message, DCT('created')), + '?content': kb.any(message, ns.sioc('content')) + } + renderMessage(dom, kb, messageTable, bindings, messageTable.fresh) // fresh from elsewhere + } + +// //////// + + /* Add a new messageTable at the top/bottom + */ + async function insertPreviousMessages (backwards) { + let extremity = backwards ? earliest : latest + let date = extremity.messageTable.date// day in mssecs + + date = await loadPrevious(date, backwards) // backwards + console.log(`insertPreviousMessages: from ${backwards ? 'backwards' : 'forwards'} loadPrevious: ${date}`) + if (!date && !backwards && !liveMessageTable) { + await appendCurrentMessages() // If necessary skip to today and add that + } + if (!date) return true // done + var live = false + if (!backwards) { + let todayDoc = chatDocumentFromDate(new Date()) + let doc = chatDocumentFromDate(date) + live = doc.sameTerm(todayDoc) // Is this todays? + } + let newMessageTable = await createMessageTable(date, live) + extremity.messageTable = newMessageTable // move pointer to earliest + if (backwards ? newestFirst : !newestFirst) { // put on bottom or top + div.appendChild(newMessageTable) + } else { // put on top as we scroll back + div.insertBefore(newMessageTable, div.firstChild) + } + return live // not done + } + /* Remove message tables earlier than this one + */ + function removePreviousMessages (backwards, messageTable) { + if (backwards ? newestFirst : !newestFirst) { // it was put on bottom + while (messageTable.nextSibling) { + div.removeChild(messageTable.nextSibling) + } + } else { // it was put on top as we scroll back + while (messageTable.previousSibling) { + div.removeChild(messageTable.previousSibling) + } + } + let extr = backwards ? earliest : latest + extr.messageTable = messageTable + } + + /* Generate the chat document (rdf object) from date + * @returns: - document + */ + function chatDocumentFromDate (date) { + let isoDate = date.toISOString() // Like "2018-05-07T17:42:46.576Z" + var path = isoDate.split('T')[0].replace(/-/g, '/') // Like "2018/05/07" + path = chatChannel.dir().uri + path + '/chat.ttl' + return $rdf.sym(path) + } + + /* Generate a date object from the chat file name + */ + function dateFromChatDocument (doc) { + const head = chatChannel.dir().uri.length + const str = doc.uri.slice(head, head + 10).replace(/\//g, '-') + // let date = new Date(str + 'Z') // GMT - but fails in FF - invalid format :-( + let date = new Date(str) // not explicitly UTC but is assumed so in spec + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse + console.log('Date for ' + doc + ':' + date.toISOString()) + return date + } + + /* LOad and render message table + ** @returns DOM element generates + */ + async function createMessageTable (date, live) { + console.log(' createMessageTable for ' + date) + const chatDocument = chatDocumentFromDate(date) + try { + await kb.fetcher.load(chatDocument) + } catch (err) { + let messageTable = (dom.createElement('table')) + let statusTR = messageTable.appendChild(dom.createElement('tr')) // ### find status in exception + if (err.response && err.response.status && err.response.status === 404) { + console.log('Error 404 for chat file ' + chatDocument) + statusTR.appendChild(UI.widgets.errorMessageBlock(dom, 'no messages', 'white')) + } else { + console.log('*** Error NON 404 for chat file ' + chatDocument) + statusTR.appendChild(UI.widgets.errorMessageBlock(dom, err, 'pink')) + } + return statusTR + } + return renderMessageTable(date, live) + } + + function renderMessageTable (date, live) { + var scrollBackButton + var scrollForwardButton + +/// ///////////////// Scrooll down adding more above + + async function extendBackwards () { + let done = await insertPreviousMessages(true) + if (done) { + scrollBackButton.firstChild.setAttribute('src', UI.icons.iconBase + 'noun_T-Block_1114655_000000.svg') // T + scrollBackButton.disabled = true + messageTable.initial = true + } else { + messageTable.extendedBack = true + } + setScrollBackButtonIcon() + return done + } + function setScrollBackButtonIcon () { + let sense = messageTable.extendedBack ? !newestFirst : newestFirst + let scrollBackIcon = messageTable.initial ? 'noun_T-Block_1114655_000000.svg' + : (sense ? 'noun_1369241.svg' : 'noun_1369237.svg') + scrollBackButton.firstChild.setAttribute('src', UI.icons.iconBase + scrollBackIcon) + } + async function scrollBackButtonHandler (event) { + if (messageTable.extendedBack) { + removePreviousMessages(true, messageTable) + messageTable.extendedBack = false + setScrollBackButtonIcon() + } else { + await extendBackwards() + } + } + + /// ////////////// Scroll up adding more below + + async function extendForwards () { + let done = await insertPreviousMessages(false) + if (done) { + scrollForwardButton.firstChild.setAttribute('src', UI.icons.iconBase + 'noun_T-Block_1114655_000000.svg') + scrollForwardButton.disabled = true + messageTable.final = true + } else { + messageTable.extendedForwards = true + } + setScrollForwardButtonIcon() + return done + } + function setScrollForwardButtonIcon () { + let sense = messageTable.extendedForwards ? !newestFirst : newestFirst // noun_T-Block_1114657_000000.svg + let scrollForwardIcon = messageTable.final ? 'noun_T-Block_1114657_000000.svg' + : (!sense ? 'noun_1369241.svg' : 'noun_1369237.svg') + scrollForwardButton.firstChild.setAttribute('src', UI.icons.iconBase + scrollForwardIcon) + } + async function scrollForwardButtonHandler (event) { + if (messageTable.extendedForwards) { + removePreviousMessages(false, messageTable) + messageTable.extendedForwards = false + setScrollForwardButtonIcon() + } else { + await extendForwards() // async + latest.messageTable.scrollIntoView(newestFirst) + } + } + + /// /////////////////////// + + var messageTable = dom.createElement('table') + + messageTable.extendBackwards = extendBackwards // Make function available to scroll stuff + messageTable.extendForwards = extendForwards // Make function available to scroll stuff + // var messageButton + messageTable.date = date + var chatDocument = chatDocumentFromDate(date) + messageTable.chatDocument = chatDocument + + messageTable.fresh = false + messageTable.setAttribute('style', 'width: 100%;') // fill that div! + + if (live) { + messageTable.final = true + liveMessageTable = messageTable + latest.messageTable = messageTable + var tr = newMessageForm(messageTable) + if (newestFirst) { + messageTable.insertBefore(tr, messageTable.firstChild) // If newestFirst + } else { + messageTable.appendChild(tr) // not newestFirst + } + messageTable.inputRow = tr + } + + /// ///// Infinite scroll + // + // @@ listen for swipe past end event not just button + if (options.infinite) { + let scrollBackButtonTR = dom.createElement('tr') + let scrollBackButtonCell = scrollBackButtonTR.appendChild(dom.createElement('td')) + // up traingles: noun_1369237.svg + // down triangles: noun_1369241.svg + let scrollBackIcon = newestFirst ? 'noun_1369241.svg' : 'noun_1369237.svg' // down and up arrows respoctively + scrollBackButton = UI.widgets.button(dom, UI.icons.iconBase + scrollBackIcon, 'Previous messages ...') + scrollBackButtonCell.style = 'width:3em; height:3em;' + scrollBackButton.addEventListener('click', scrollBackButtonHandler, false) + messageTable.extendedBack = false + scrollBackButtonCell.appendChild(scrollBackButton) + setScrollBackButtonIcon() + + let dateCell = scrollBackButtonTR.appendChild(dom.createElement('td')) + dateCell.style = 'text-align: center; vertical-align: middle; color: #888; font-style: italic;' + dateCell.textContent = UI.widgets.shortDate(date.toISOString(), true) // no time, only date + + // @@@@@@@@@@@ todo move this button to other end of message cell, o + let scrollForwardButtonCell = scrollBackButtonTR.appendChild(dom.createElement('td')) + let scrollForwardIcon = newestFirst ? 'noun_1369241.svg' : 'noun_1369237.svg' // down and up arrows respoctively + scrollForwardButton = UI.widgets.button(dom, UI.icons.iconBase + scrollForwardIcon, 'Later messages ...') + scrollForwardButtonCell.appendChild(scrollForwardButton) + scrollForwardButtonCell.style = 'width:3em; height:3em;' + scrollForwardButton.addEventListener('click', scrollForwardButtonHandler, false) + messageTable.extendedForward = false + setScrollForwardButtonIcon() + + messageTable.extendedForwards = false + + if (!newestFirst) { // opposite end from the entry field + messageTable.insertBefore(scrollBackButtonTR, messageTable.firstChild) // If not newestFirst + } else { + messageTable.appendChild(scrollBackButtonTR) // newestFirst + } + } + + let sts = kb.statementsMatching(null, WF('message'), null, chatDocument) + if (!live && sts.length === 0) { // not todays + // no need buttomns at the moment + // messageTable.style.visibility = 'collapse' // Hide files with no messages + } + sts.forEach(st => { + addMessage(st.object, messageTable) + }) + messageTable.fresh = true + + // loadMessageTable(messageTable, chatDocument) + messageTable.fresh = false + return messageTable + } // renderMessageTable + +/* Track back through the YYYY/MM/DD tree to find the previous/next day +** +*/ + async function loadPrevious (date, backwards) { + async function previousPeriod (file, level) { + function younger (x) { + if (backwards ? x.uri >= file.uri : x.uri <= file.uri) return false // later than we want or same -- looking for different + return true + } + function suitable (x) { + let tail = x.uri.slice(0, -1).split('/').slice(-1)[0] + if (!'0123456789'.includes(tail[0])) return false // not numeric + return true + // return kb.anyValue(chatDocument, POSIX('size')) !== 0 // empty file? + } + async function lastNonEmpty (siblings) { + siblings = siblings.filter(suitable) + siblings.sort() // chronological order + if (!backwards) siblings.reverse() + if (level !== 3) return siblings.pop() // only length chck final leverl + while (siblings.length) { + let folder = siblings.pop() + let chatDocument = kb.sym(folder.uri + 'chat.ttl') + await kb.fetcher.load(chatDocument) + // files can have seealso links. skip ones with no messages with a date + if (kb.statementsMatching(null, DCT('created'), null, chatDocument).length > 0) { + return folder + } + } + return null + } + // console.log(' previousPeriod level' + level + ' file ' + file) + const parent = file.dir() + await kb.fetcher.load(parent) + var siblings = kb.each(parent, ns.ldp('contains')) + siblings = siblings.filter(younger) + let folder = await lastNonEmpty(siblings) + if (folder) return folder + + if (level === 0) return null // 3:day, 2:month, 1: year 0: no + + const uncle = await previousPeriod(parent, level - 1) + if (!uncle) return null // reached first ever + await kb.fetcher.load(uncle) + var cousins = kb.each(uncle, ns.ldp('contains')) + let result = await lastNonEmpty(cousins) + return result + } // previousPeriod + + let folder = chatDocumentFromDate(date).dir() + let found = await previousPeriod(folder, 3) + if (found) { + let doc = kb.sym(found.uri + 'chat.ttl') + return dateFromChatDocument(doc) + } + return null + } + + async function addNewTableIfNewDay (now) { + // let now = new Date() + // @@ Remove listener from previous table as it is now static + let newChatDocument = chatDocumentFromDate(now) + if (!newChatDocument.sameTerm(latest.messageTable.chatDocument)) { // It is a new day + if (liveMessageTable.inputRow) { + liveMessageTable.removeChild(liveMessageTable.inputRow) + delete liveMessageTable.inputRow + } + var oldChatDocument = latest.messageTable.chatDocument + await appendCurrentMessages() + // Adding a link in the document will ping listeners to add the new block too + if (!kb.holds(oldChatDocument, ns.rdfs('seeAlso'), newChatDocument, oldChatDocument)) { + let sts = [$rdf.st(oldChatDocument, ns.rdfs('seeAlso'), newChatDocument, oldChatDocument)] + updater.update([], sts, function (ok, body) { + if (!ok) { + alert('Unable to link old message block to new one.' + body) + } + }) + } + } + } +/* + function messageCount () { + var n = 0 + const tables = div.children + for (let i = 0; i < tables.length; i++) { + n += tables[i].children.length - 1 + // console.log(' table length:' + tables[i].children.length) + } + return n + } +*/ +/* Add the live message block with entry field for today +*/ + async function appendCurrentMessages () { + var now = new Date() + var chatDocument = chatDocumentFromDate(now) + try { + await createIfNotExists(chatDocument) + } catch (e) { + div.appendChild(UI.widgets.errorMessageBlock( + dom, 'Problem accessing chat file: ' + e)) + return + } + const messageTable = await createMessageTable(now, true) + div.appendChild(messageTable) + div.refresh = function () { // only the last messageTable is live + addNewTableIfNewDay(new Date()).then(() => { syncMessages(chatChannel, messageTable) }) + } // The short chat version fors live update in the pane but we do it in the widget + kb.updater.addDownstreamChangeListener(chatDocument, div.refresh) // Live update + liveMessageTable = messageTable + latest.messageTable = liveMessageTable + return messageTable + } + + var liveMessageTable + var earliest = {messageTable: null} // Stuff about each end of the loaded days + var latest = {messageTable: null} + + var lock = false + async function loadMoreWhereNeeded (event, fixScroll) { + if (lock) return + lock = true + const freeze = !fixScroll + const magicZone = 150 + // const top = div.scrollTop + // const bottom = div.scrollHeight - top - div.clientHeight + var done + + while (div.scrollTop < magicZone && + earliest.messageTable && + !earliest.messageTable.initial && + earliest.messageTable.extendBackwards) { + let scrollBottom = div.scrollHeight - div.scrollTop + console.log('infinite scroll: adding above: top ' + div.scrollTop) + done = await earliest.messageTable.extendBackwards() + if (freeze) { + div.scrollTop = div.scrollHeight - scrollBottom + } + if (fixScroll) fixScroll() + if (done) break + } + while (options.selectedMessage && // we started in the middle not at the bottom + div.scrollHeight - div.scrollTop - div.clientHeight < magicZone && // we are scrolled right to the bottom + latest.messageTable && + !latest.messageTable.final && // there is more data to come + latest.messageTable.extendForwards) { + let scrollTop = div.scrollTop + console.log('infinite scroll: adding below: bottom: ' + (div.scrollHeight - div.scrollTop - div.clientHeight)) + done = await latest.messageTable.extendForwards() // then add more data on the bottom + if (freeze) { + div.scrollTop = scrollTop // while adding below keep same things in view + } + if (fixScroll) fixScroll() + if (done) break + } + lock = false + } + + async function go () { + function yank () { + selectedMessageTable.selectedElement.scrollIntoView({block: 'center'}) + } + + // During initial load ONLY keep scroll to selected thing or bottom + function fixScroll () { + if (options.selectedElement) { + options.selectedElement.scrollIntoView({block: 'center'}) // allign tops or bopttoms + } else { + liveMessageTable.inputRow.scrollIntoView(newestFirst) // allign tops or bopttoms + } + } + + var live + if (options.selectedMessage) { + var selectedDocument = options.selectedMessage.doc() + var now = new Date() + var todayDocument = chatDocumentFromDate(now) + live = todayDocument.sameTerm(selectedDocument) + } + if (options.selectedMessage && !live) { + var selectedDate = dateFromChatDocument(selectedDocument) + var selectedMessageTable = await createMessageTable(selectedDate, live) + div.appendChild(selectedMessageTable) + earliest.messageTable = selectedMessageTable + latest.messageTable = selectedMessageTable + yank() + setTimeout(yank, 1000) // @@ kludge - restore position distubed by other cHANGES + } else { // Live end + await appendCurrentMessages() + earliest.messageTable = liveMessageTable + latest.messageTable = liveMessageTable + } + + await loadMoreWhereNeeded(null, fixScroll) + div.addEventListener('scroll', loadMoreWhereNeeded) + if (options.solo) { + document.body.addEventListener('scroll', loadMoreWhereNeeded) + } + } + go() + return div +} diff --git a/src/chat/message.js b/src/chat/message.js new file mode 100644 index 000000000..a930f4107 --- /dev/null +++ b/src/chat/message.js @@ -0,0 +1,194 @@ + +const UI = { + authn: require('./signin'), + icons: require('./iconBase'), + log: require('./log'), + ns: require('./ns'), + media: require('./media-capture'), + pad: require('./pad'), + rdf: require('rdflib'), + store: require('./store'), + style: require('./style'), + utils: require('./utils'), + widgets: require('./widgets') +} +const dom = UI.dom || window.document +const kb = UI.store + +module.exports = { renderMessage, creatorAndDate, creatorAndDateHorizontal} + +const { messageTools, sentimentStripLinked } = require('./messageTools').messageTools +const label = UI.utils.label + +function elementForImageURI (imageUri, options) { + let img = dom.createElement('img') + let height = '10' + if (options.inlineImageHeightEms) { + height = ('' + options.inlineImageHeightEms).trim() + } + img.setAttribute('style', 'max-height: ' + height + 'em; border-radius: 1em; margin: 0.7em;') + // UI.widgets.makeDropTarget(img, handleURIsDroppedOnMugshot, droppedFileHandler) + if (imageUri) img.setAttribute('src', imageUri) + let anchor = dom.createElement('a') + anchor.setAttribute('href', imageUri) + anchor.setAttribute('target', 'images') + anchor.appendChild(img) + UI.widgets.makeDraggable(img, $rdf.sym(imageUri)) + return anchor +} + +var anchor = function (text, term) { // If there is no link return an element anyway + var a = dom.createElement('a') + if (term && term.uri) { + a.setAttribute('href', term.uri) + a.addEventListener('click', UI.widgets.openHrefInOutlineMode, true) + a.setAttribute('style', 'color: #3B5998; text-decoration: none; ') // font-weight: bold + } + a.textContent = text + return a +} + +function nick (person) { + var s = UI.store.any(person, UI.ns.foaf('nick')) + if (s) return '' + s.value + return '' + label(person) +} + +function creatorAndDate (td1, creator, date, message) { + var nickAnchor = td1.appendChild(anchor(nick(creator), creator)) + if (creator.uri) { + UI.store.fetcher.nowOrWhenFetched(creator.doc(), undefined, function (ok, body) { + nickAnchor.textContent = nick(creator) + }) + } + td1.appendChild(dom.createElement('br')) + td1.appendChild(anchor(date, message)) +} + +function creatorAndDateHorizontal (td1, creator, date, message) { + var nickAnchor = td1.appendChild(anchor(label(creator), creator)) + if (creator.uri) { + UI.store.fetcher.nowOrWhenFetched(creator.doc(), undefined, function (ok, body) { + nickAnchor.textContent = nick(creator) + }) + } + const dateBit = td1.appendChild(anchor(date, message)) + dateBit.style.fontSize = '80%' + dateBit.style.marginLeft = '1em' + td1.appendChild(dom.createElement('br')) +} + +// BODY of renderMessage + +function renderMessage (dom, kb, messageTable, bindings, fresh, options) { + var colorizeByAuthor = options.colorizeByAuthor === '1' || options.colorizeByAuthor === true + + var creator = bindings['?creator'] + var message = bindings['?msg'] + var date = bindings['?date'] + var content = bindings['?content'] + + var dateString = date.value + var messageRow = dom.createElement('tr') + messageRow.AJAR_date = dateString + messageRow.AJAR_subject = message + + if (options.selectedMessage && options.selectedMessage.sameTerm(message)) { + messageRow.style.backgroundColor = 'yellow' + options.selectedElement = messageRow + messageTable.selectedElement = messageRow + } + + var done = false + for (var ele = messageTable.firstChild; ; ele = ele.nextSibling) { + if (!ele) { // empty + break + } + newestfirst = options.newestfirst === true + if (((dateString > ele.AJAR_date) && newestFirst) || + ((dateString < ele.AJAR_date) && !newestFirst)) { + messageTable.insertBefore(messageRow, ele) + done = true + break + } + } + if (!done) { + messageTable.appendChild(messageRow) + } + + var td1 = dom.createElement('td') + messageRow.appendChild(td1) + if (options.authorAboveContent) { + let img = dom.createElement('img') + img.setAttribute('style', 'max-height: 2.5em; max-width: 2.5em; border-radius: 0.5em; margin: auto;') + UI.widgets.setImage(img, creator) + td1.appendChild(img) + } else { + creatorAndDate(td1, creator, UI.widgets.shortDate(dateString), message) + } + + // Render the content ot the message itself + var td2 = messageRow.appendChild(dom.createElement('td')) + + if (options.authorAboveContent) { + creatorAndDateHorizontal(td2, creator, UI.widgets.shortDate(dateString), message) + } + let text = content.value.trim() + let isURI = (/^https?:\/[^ <>]*$/i).test(text) + let para = null + if (isURI) { + var isImage = (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(text) // @@ Should use content-type not URI + if (isImage && options.expandImagesInline) { + let img = elementForImageURI(text, options) + td2.appendChild(img) + } else { // Link but not Image + let anc = td2.appendChild(dom.createElement('a')) + para = anc.appendChild(dom.createElement('p')) + anc.href = text + para.textContent = text + td2.appendChild(anc) + } + } else { // text + para = dom.createElement('p') + td2.appendChild(para) + para.textContent = text + } + if (para) { + var bgcolor = colorizeByAuthor + ? UI.pad.lightColorHash(creator) + : (fresh ? '#e8ffe8' : 'white') + para.setAttribute('style', messageBodyStyle + 'background-color: ' + bgcolor + ';') + } + // Sentiment strip + const strip = sentimentStripLinked(message, message.doc()) + if (strip.children.length) { + td2.appendChild(dom.createElement('br')) + td2.appendChild(strip) + } + + // Message tool bar button + var td3 = dom.createElement('td') + messageRow.appendChild(td3) + var toolsButton = UI.widgets.button(dom, UI.icons.iconBase + 'noun_243787.svg', '...') + td3.appendChild(toolsButton) + toolsButton.addEventListener('click', function (e) { + if (messageRow.toolTR) { // already got a toolbar? Toogle + messageRow.parentNode.removeChild(messageRow.toolTR) + delete messageRow.toolTR + return + } + const toolsTR = dom.createElement('tr') + const tools = messageTools(message, messageRow, userContext) + tools.style = 'border: 0.05em solid #888; border-radius: 0 0 0.7em 0.7em; border-top: 0; height:3.5em; background-color: #fff;' // @@ fix + if (messageRow.nextSibling) { + messageRow.parentElement.insertBefore(toolsTR, messageRow.nextSibling) + } else { + messageRow.parentElement.appendChild(toolsTR) + } + messageRow.toolTR = toolsTR + toolsTR.appendChild(dom.createElement('td')) // left + const toolsTD = toolsTR.appendChild(dom.createElement('td')) + toolsTR.appendChild(dom.createElement('td')) // right + toolsTD.appendChild(tools) + }) +} diff --git a/src/chat/messageTools.js b/src/chat/messageTools.js new file mode 100644 index 000000000..e83e982c0 --- /dev/null +++ b/src/chat/messageTools.js @@ -0,0 +1,220 @@ +/* Tools for doing things with a message + * Let is be cretiev here. Allow all sorts of things to + * be done to a message - linking to new or old objects in an open way + * + * Ideas: Bookmark, Like, star, pin at top of chat, reply as new thread, + * If you made it originally: edit, delete, attach +*/ + +const UI = { + authn: require('./signin'), + icons: require('./iconBase'), + log: require('./log'), + ns: require('./ns'), + media: require('./media-capture'), + pad: require('./pad'), + rdf: require('rdflib'), + store: require('./store'), + style: require('./style'), + utils: require('./utils'), + widgets: require('./widgets') +} + +const bookmarks = require('./bookmarks') + +const dom = window.document + +const kb = UI.store +const ns = UI.ns +// const label = UI.utils.label + +// THE UNUSED ICONS are here as reminders for possible future functionality +// const BOOKMARK_ICON = 'noun_45961.svg' +// const HEART_ICON = 'noun_130259.svg' -> Add this to my (private) favorites +// const MENU_ICON = 'noun_897914.svg' +// const PAPERCLIP_ICON = 'noun_25830.svg' -> add attachments to this message +// const PIN_ICON = 'noun_562340.svg' -> pin this message permanently in the chat UI +// const PENCIL_ICON = 'noun_253504.svg' +// const SPANNER_ICON = 'noun_344563.svg' -> settings +const THUMBS_UP_ICON = 'noun_1384132.svg' +const THUMBS_DOWN_ICON = 'noun_1384135.svg' + +module.export = { messageTools, sentimentStripLinked, sentimentStrip } + +// @@@@ use the one in rdflib.js when it is avaiable and delete this +function updatePromise (del, ins) { + return new Promise(function (resolve, reject) { + kb.updater.update(del, ins, function (uri, ok, errorBody) { + if (!ok) { + reject(new Error(errorBody)) + } else { + resolve() + } + }) // callback + }) // promise +} + +/* Emoji in Unicode +*/ + +var emoji = {} +emoji[ns.schema('AgreeAction')] = '👍' +emoji[ns.schema('DisagreeAction')] = '👎' +emoji[ns.schema('EndorseAction')] = '⭐️' +emoji[ns.schema('LikeAction')] = '❤️' + +/* Strip of sentiments expressed +*/ +function sentimentStrip (target, doc) { + const actions = kb.each(null, ns.schema('target'), target, doc) + const sentiments = actions.map(a => kb.any(a, ns.rdf('type'), null, doc)) + sentiments.sort() + const strings = sentiments.map(x => emoji[x] || '') + return dom.createTextNode(strings.join(' ')) +} +/** Strip of sentiments expressed + * + * @param target {NamedNode} - The thing about which they are expressed + * @param doc {NamedNode} - The document iun which they are expressed +*/ + +function sentimentStripLinked (target, doc) { + var strip = dom.createElement('span') + function refresh () { + strip.innerHTML = '' + const actions = kb.each(null, ns.schema('target'), target, doc) + const sentiments = actions.map(a => [ + kb.any(a, ns.rdf('type'), null, doc), + kb.any(a, ns.schema('agent'), null, doc)]) + sentiments.sort() + sentiments.forEach(ss => { + let [klass, agent] = ss + var res + if (agent) { + res = dom.createElement('a') + res.setAttribute('href', agent.uri) + } else { + res = dom.createTextNode('') + } + res.textContent = emoji[klass] || '*' + strip.appendChild(res) + }) + } + refresh() + strip.refresh = refresh + return strip +} + +function messageTools (message, messageRow, userContext) { + const div = dom.createElement('div') + function closeToolbar () { + div.parentElement.parentElement.removeChild(div.parentElement) // remive the TR + } + + async function deleteThingThen (x) { + await updatePromise(kb.connectedStatements(x), []) + } + + // Things only the original author can do + let me = UI.authn.currentUser() // If already logged on + if (me && kb.holds(message, ns.foaf('maker'), me)) { + // button to delete the message + const deleteButton = UI.widgets.deleteButtonWithCheck(dom, div, 'message', async function () { + await kb.updater.update(kb.connectedStatements[message], []) + closeToolbar() + }) + div.appendChild(deleteButton) + } // if mine + + // Things anyone can do if they have a bookmark list + var bookmarkButton = bookmarks.renderBookmarksButton(userContext) + if (bookmarkButton) { + div.appendChild(bookmarkButton) + } + + /** Button to allow user to express a sentiment (like, endorse, etc) about a target + * + * @param context {Object} - Provide dom and me + * @param target {NamedNode} - The thing the user expresses an opnion about + * @param icon {uristring} - The icon to be used for the button + * @param actionClass {NamedNode} - The RDF class - typically a subclass of schema:Action + * @param doc - {NamedNode} - the Solid document iunto which the data should be written + * @param mutuallyExclusive {Array} - Any RDF classes of sentimentswhich are mutiually exclusive + */ + function sentimentButton (context, target, icon, actionClass, doc, mutuallyExclusive) { + function setColor () { + button.style.backgroundColor = action ? 'yellow' : 'white' + } + var button = UI.widgets.button(dom, icon, UI.utils.label(actionClass), async function (event) { + if (action) { + await deleteThingThen(action) + action = null + setColor() + } else { // no action + action = UI.widgets.newThing(doc) + var insertMe = [ + $rdf.st(action, ns.schema('agent'), context.me, doc), + $rdf.st(action, ns.rdf('type'), actionClass, doc), + $rdf.st(action, ns.schema('target'), target, doc) + ] + await updatePromise([], insertMe) + setColor() + + if (mutuallyExclusive) { // Delete incompative sentiments + var dirty = false + for (let i = 0; i < mutuallyExclusive.length; i++) { + let a = existingAction(mutuallyExclusive[i]) + if (a) { + await deleteThingThen(a) // but how refresh? refreshTree the parent? + dirty = true + } + } + if (dirty) { + // UI.widgets.refreshTree(button.parentNode) // requires them all to be immediate siblings + UI.widgets.refreshTree(messageRow) // requires them all to be immediate siblings + } + } + } + }) + function existingAction (actionClass) { + var actions = kb.each(null, ns.schema('agent'), context.me, doc) + .filter(x => kb.holds(x, ns.rdf('type'), actionClass, doc)) + .filter(x => kb.holds(x, ns.schema('target'), target, doc)) + return actions.length ? actions[0] : null + } + function refresh () { + action = existingAction(actionClass) + setColor() + } + var action + button.refresh = refresh // If the file changes, refresh live + refresh() + return button + } + + // THUMBS_UP_ICON + // https://schema.org/AgreeAction + me = UI.authn.currentUser() // If already logged on + if (me) { // Things yo mnust be logged in fo + var context1 = {me, dom, div} + div.appendChild(sentimentButton(context1, message, // @@ use UI.widgets.sentimentButton + UI.icons.iconBase + THUMBS_UP_ICON, + ns.schema('AgreeAction'), + message.doc(), + [ns.schema('DisagreeAction')] + )) + // Thumbs down + div.appendChild(sentimentButton(context1, message, + UI.icons.iconBase + THUMBS_DOWN_ICON, + ns.schema('DisagreeAction'), + message.doc(), + [ns.schema('AgreeAction')] + )) + } + // X button to remove the tool UI itself + const cancelButton = div.appendChild(UI.widgets.cancelButton(dom)) + cancelButton.style.float = 'right' + cancelButton.firstChild.style.opacity = '0.3' + cancelButton.addEventListener('click', closeToolbar) + return div +} diff --git a/src/create.js b/src/create.js index 60291dc8c..baf30937e 100644 --- a/src/create.js +++ b/src/create.js @@ -78,8 +78,16 @@ function newThingUI (context, thePanes) { throw new Error('Cannot mint new - missing newInstance') } if (newPaneOptions.folder) { - kb.add(newPaneOptions.folder, UI.ns.ldp('contains'), kb.sym(newPaneOptions.newBase), - newPaneOptions.folder.doc()) // Ping the patch system? + var tail = newPaneOptions.newInstance.uri.slice(newPaneOptions.folder.uri.length) + const isPackage = tail.includes('/') + console.log(' new thing is packge? ' + isPackage) + if (isPackage) { + kb.add(newPaneOptions.folder, UI.ns.ldp('contains'), kb.sym(newPaneOptions.newBase), + newPaneOptions.folder.doc()) + } else { // single file + kb.add(newPaneOptions.folder, UI.ns.ldp('contains'), newPaneOptions.newInstance, + newPaneOptions.folder.doc()) // Ping the patch system? + } if (newPaneOptions.refreshTarget) { newPaneOptions.refreshTarget.refresh() // Refresh the cntaining display } diff --git a/src/index.js b/src/index.js index 133506b0a..f04e48333 100755 --- a/src/index.js +++ b/src/index.js @@ -46,6 +46,7 @@ const UI = { aclControl: require('./acl-control'), authn: require('./signin'), create: require('./create'), + dom: window.document, // Idea that UI.dom can be adapted in non-browser environments icons: require('./iconBase'), log: require('./log'), matrix: require('./matrix'), diff --git a/src/signin.js b/src/signin.js index 9743bdb9c..eb14ec20c 100644 --- a/src/signin.js +++ b/src/signin.js @@ -261,13 +261,15 @@ function logInLoadPreferences (context) { let m2 if (status === 401) { m2 = 'Strange - you are not authenticated (properly logged on) to read preferences file.' + alert(m2) } else if (status === 403) { if (differentOrigin()) { m2 = 'Unauthorized: Assuming prefs file blocked for origin ' + window.location.origin context.preferencesFileError = m2 return resolve(context) } - m2 = 'Strange - you are not authorized to read your preferences file.' + m2 = 'You are not authot=rized to read your prefernces file. This may be because you are using an trusted web app.' + console.warn(m2) } else if (status === 404) { if (confirm('You do not currently have a Preferences file. Ok for me to create an empty one? ' + preferencesFile)) { // @@@ code me ... weird to have a name o fthe file but no file @@ -278,8 +280,8 @@ function logInLoadPreferences (context) { } } else { m2 = 'Strange: Error ' + status + ' trying to read your preferences file.' + message + alert(m2) } - alert(m2) }) // load prefs file then }, err => { // Fail initial login load prefs reject(new Error('(via loadPrefs) ' + err)) From dbdf0a20e6eaf66b3b1677c1d7c5b08eada554ad Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Wed, 27 Mar 2019 10:40:33 +0000 Subject: [PATCH 02/35] standard --- src/chat/infinte.js | 10 +++++----- src/chat/message.js | 10 ++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/chat/infinte.js b/src/chat/infinte.js index ceaa6f8eb..3bf0d551f 100644 --- a/src/chat/infinte.js +++ b/src/chat/infinte.js @@ -52,7 +52,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { var me var updater = UI.store.updater - +/* var mention = function mention (message, style) { console.log(message) var pre = dom.createElement('pre') @@ -66,7 +66,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { warn: function (message) { mention(message, 'color: #880;') }, error: function (message) { mention(message, 'color: #800;') } } - +*/ /** Create a resource if it really does not exist * Be absolutely sure something does not exist before creating a new empty file * as otherwise existing could be deleted. @@ -142,7 +142,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { '?content': content, '?date': dateStamp, '?creator': me} - renderMessage(dom, kb, liveMessageTable, bindings, false) // not green + renderMessage(liveMessageTable, bindings, false, options, userContext) // not green if (!text) { field.value = '' // clear from out for reuse @@ -229,7 +229,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { UI.authn.logIn(context).then(context => { me = context.me turnOnInput() - // userContext = context + Object.assign(context, userContext) bookmarks.findBookmarkDocument(context).then(context => { console.log('Bookmark file: ' + context.bookmarkDocument) }) @@ -280,7 +280,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { '?date': kb.any(message, DCT('created')), '?content': kb.any(message, ns.sioc('content')) } - renderMessage(dom, kb, messageTable, bindings, messageTable.fresh) // fresh from elsewhere + renderMessage(dom, kb, messageTable, bindings, messageTable.fresh, options, userContext) // fresh from elsewhere } // //////// diff --git a/src/chat/message.js b/src/chat/message.js index a930f4107..0b7025a4a 100644 --- a/src/chat/message.js +++ b/src/chat/message.js @@ -13,9 +13,11 @@ const UI = { widgets: require('./widgets') } const dom = UI.dom || window.document -const kb = UI.store +// const kb = UI.store -module.exports = { renderMessage, creatorAndDate, creatorAndDateHorizontal} +module.exports = { renderMessage, creatorAndDate, creatorAndDateHorizontal } + +const messageBodyStyle = UI.style.messageBodyStyle const { messageTools, sentimentStripLinked } = require('./messageTools').messageTools const label = UI.utils.label @@ -80,7 +82,7 @@ function creatorAndDateHorizontal (td1, creator, date, message) { // BODY of renderMessage -function renderMessage (dom, kb, messageTable, bindings, fresh, options) { +function renderMessage (messageTable, bindings, fresh, options, userContext) { var colorizeByAuthor = options.colorizeByAuthor === '1' || options.colorizeByAuthor === true var creator = bindings['?creator'] @@ -104,7 +106,7 @@ function renderMessage (dom, kb, messageTable, bindings, fresh, options) { if (!ele) { // empty break } - newestfirst = options.newestfirst === true + var newestFirst = options.newestfirst === true if (((dateString > ele.AJAR_date) && newestFirst) || ((dateString < ele.AJAR_date) && !newestFirst)) { messageTable.insertBefore(messageRow, ele) From 99eb3493291c7a16a07bdcb1b21246593c05c807 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Sun, 31 Mar 2019 20:06:04 -0400 Subject: [PATCH 03/35] dateFolders extracts the common handling of tree of YYYY/MM/DD files --- src/chat/dateFolders.js | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/chat/dateFolders.js diff --git a/src/chat/dateFolders.js b/src/chat/dateFolders.js new file mode 100644 index 000000000..00078e6cf --- /dev/null +++ b/src/chat/dateFolders.js @@ -0,0 +1,92 @@ +/* Track back through the YYYY/MM/DD tree to find the previous/next day +** +*/ + +import { store as kb } from '../store' +import ns from '../ns' +// const kb = store + +export class DateFolder { + constructor (root, leafFileName) { + this.root = root + this.rootFolder = root.dir() + this.leafFileName = leafFileName + } + + /* Generate the leaf document (rdf object) from date + * @returns: - document + */ + leafDocumentFromDate (date) { + let isoDate = date.toISOString() // Like "2018-05-07T17:42:46.576Z" + var path = isoDate.split('T')[0].replace(/-/g, '/') // Like "2018/05/07" + path = root.dir().uri + path + '/' + this.leafFileName + return $rdf.sym(path) + } + + /* Generate a date object from the leaf file name + */ + dateFromLeafDocument (doc) { + const head = this.rootFolder.uri.length + const str = doc.uri.slice(head, head + 10).replace(/\//g, '-') + // let date = new Date(str + 'Z') // GMT - but fails in FF - invalid format :-( + let date = new Date(str) // not explicitly UTC but is assumed so in spec + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse + console.log('Date for ' + doc + ':' + date.toISOString()) + return date + } + + async loadPrevious (date, backwards) { + async function previousPeriod (file, level) { + function younger (x) { + if (backwards ? x.uri >= file.uri : x.uri <= file.uri) return false // later than we want or same -- looking for different + return true + } + function suitable (x) { + let tail = x.uri.slice(0, -1).split('/').slice(-1)[0] + if (!'0123456789'.includes(tail[0])) return false // not numeric + return true + // return kb.anyValue(leafDocument, POSIX('size')) !== 0 // empty file? + } + async function lastNonEmpty (siblings) { + siblings = siblings.filter(suitable) + siblings.sort() // chronological order + if (!backwards) siblings.reverse() + if (level !== 3) return siblings.pop() // only length chck final leverl + while (siblings.length) { + let folder = siblings.pop() + let leafDocument = kb.sym(folder.uri + 'this.leafFileName') + await kb.fetcher.load(leafDocument) + // files can have seealso links. skip ones with no messages with a date + if (kb.statementsMatching(null, ns.dct('created'), null, leafDocument).length > 0) { + return folder + } + } + return null + } + // console.log(' previousPeriod level' + level + ' file ' + file) + const parent = file.dir() + await kb.fetcher.load(parent) + var siblings = kb.each(parent, ns.ldp('contains')) + siblings = siblings.filter(younger) + let folder = await lastNonEmpty(siblings) + if (folder) return folder + + if (level === 0) return null // 3:day, 2:month, 1: year 0: no + + const uncle = await previousPeriod(parent, level - 1) + if (!uncle) return null // reached first ever + await kb.fetcher.load(uncle) + var cousins = kb.each(uncle, ns.ldp('contains')) + let result = await lastNonEmpty(cousins) + return result + } // previousPeriod + + let folder = this.leafDocumentFromDate(date).dir() + let found = await previousPeriod(folder, 3) + if (found) { + let doc = kb.sym(found.uri + 'this.leafFileName') + return this.dateFromleafDocument(doc) + } + return null + } // loadPrevious +} // class From 9240886ab071e8d7eeca6cd6cbb3dc7f712fb233 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Tue, 2 Apr 2019 06:50:30 -0700 Subject: [PATCH 04/35] dateFolders extracts the common handling of tree of YYYY/MM/DD files --- src/chat/dateFolders.js | 66 ++++++++++++++++++++++++++++++++++------- src/chat/infinte.js | 47 +++++++++++++++++++---------- 2 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src/chat/dateFolders.js b/src/chat/dateFolders.js index 00078e6cf..a638a68e1 100644 --- a/src/chat/dateFolders.js +++ b/src/chat/dateFolders.js @@ -2,25 +2,28 @@ ** */ -import { store as kb } from '../store' -import ns from '../ns' +const kb = require ('../store.js') +// import store from '../store' +// import ns from '../ns' // const kb = store - -export class DateFolder { - constructor (root, leafFileName) { - this.root = root - this.rootFolder = root.dir() +const ns = require('../ns.js') +module.exports = class DateFolder { + constructor (rootThing, leafFileName, membershipProperty) { + this.root = rootThing + this.rootFolder = rootThing.dir() this.leafFileName = leafFileName + this.membershipProperty = membershipProperty|| ns.wf('leafObject') } /* Generate the leaf document (rdf object) from date * @returns: - document */ leafDocumentFromDate (date) { + // console.log('incoming date: ' + date) let isoDate = date.toISOString() // Like "2018-05-07T17:42:46.576Z" var path = isoDate.split('T')[0].replace(/-/g, '/') // Like "2018/05/07" - path = root.dir().uri + path + '/' + this.leafFileName - return $rdf.sym(path) + path = this.root.dir().uri + path + '/' + this.leafFileName + return kb.sym(path) } /* Generate a date object from the leaf file name @@ -29,7 +32,7 @@ export class DateFolder { const head = this.rootFolder.uri.length const str = doc.uri.slice(head, head + 10).replace(/\//g, '-') // let date = new Date(str + 'Z') // GMT - but fails in FF - invalid format :-( - let date = new Date(str) // not explicitly UTC but is assumed so in spec + var date = new Date(str) // not explicitly UTC but is assumed so in spec // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse console.log('Date for ' + doc + ':' + date.toISOString()) return date @@ -56,7 +59,7 @@ export class DateFolder { let folder = siblings.pop() let leafDocument = kb.sym(folder.uri + 'this.leafFileName') await kb.fetcher.load(leafDocument) - // files can have seealso links. skip ones with no messages with a date + // files can have seealso links. skip ones with no leafObjects with a date if (kb.statementsMatching(null, ns.dct('created'), null, leafDocument).length > 0) { return folder } @@ -89,4 +92,45 @@ export class DateFolder { } return null } // loadPrevious + + + async firstLeaf (backwards) { // backwards -> last leafObject + var folderStore = $rdf.graph() + var folderFetcher = new $rdf.Fetcher(folderStore) + async function earliestSubfolder (parent) { + console.log(' parent ' + parent) + delete folderFetcher.requested[parent.uri] + var resp = await folderFetcher.load(parent, clone(forcingOptions)) // Force fetch as will have changed + + var kids = folderStore.each(parent, ns.ldp('contains')) + kids = kids.filter(suitable) + if (kids.length === 0) { + throw new Error(' @@@ No children to parent2 ' + parent) + } + + kids.sort() + if (backwards) kids.reverse() + return kids[0] + } + let y = await earliestSubfolder(this.root.dir()) + let month = await earliestSubfolder(y) + let d = await earliestSubfolder(month) + let leafDocument = $rdf.sym(d.uri + 'chat.ttl') + await folderFetcher.load(leafDocument) + let leafObjects = folderStore.each(this.root, this.membershipProperty), null, leafDocument) + if (leafObjects.length === 0) { + let msg = ' INCONSISTENCY -- no chat leafObject in file ' + leafDocument + console.trace(msg) + throw new Error(msg) + } + let sortMe = leafObjects.map(leafObject => [folderStore.any(leafObject, ns.dct('created')), leafObject]) + sortMe.sort() + if (backwards) sortMe.reverse() + console.log((backwards ? 'Latest' : 'Earliest') + ' leafObject is ' + sortMe[0][1]) + return sortMe[0][1] + } // firstleafObject + + + + } // class diff --git a/src/chat/infinte.js b/src/chat/infinte.js index 3bf0d551f..8757f7f40 100644 --- a/src/chat/infinte.js +++ b/src/chat/infinte.js @@ -5,6 +5,8 @@ // index.ttl#this and the chats messages are stored in YYYY/MM/DD/chat.ttl // /* global alert */ +import DateFolder from './dateFolder' + const UI = { authn: require('./signin'), icons: require('./iconBase'), @@ -20,7 +22,6 @@ const UI = { } // const utils = require('./utils') - const { renderMessage, creatorAndDate } = require('./renderMessage') const bookmarks = require('./bookmarks') @@ -37,6 +38,8 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { var newestFirst = options.newestFirst === '1' || options.newestFirst === true // hack for now + const dateFolder = new DateFolder(chatChannel, 'chat.ttl') + options.authorAboveContent = true // var participation // An object tracking users use and prefs @@ -122,7 +125,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { var sts = [] var timestamp = '' + now.getTime() var dateStamp = $rdf.term(now) - let chatDocument = chatDocumentFromDate(now) + let chatDocument = dateFolder.leafDocumentFromDate(now) var message = kb.sym(chatDocument.uri + '#' + 'Msg' + timestamp) var content = kb.literal(text || field.value) @@ -142,7 +145,12 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { '?content': content, '?date': dateStamp, '?creator': me} - renderMessage(liveMessageTable, bindings, false, options, userContext) // not green + var tr = renderMessage(liveMessageTable, bindings, false, options, userContext) // not green + if (options.newestFirst === true) { + messageTable.insertBefore(tr, messageTable.firstChild) // If newestFirst + } else { + messageTable.appendChild(tr) // not newestFirst + } if (!text) { field.value = '' // clear from out for reuse @@ -209,7 +217,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { sendButton.addEventListener('click', ev => sendMessage(), false) rhs.appendChild(sendButton) - const chatDocument = chatDocumentFromDate(new Date()) + const chatDocument = dateFolder.leafDocumentFromDate(new Date()) var imageDoc function getImageDoc () { imageDoc = kb.sym(chatDocument.dir().uri + 'Image_' + Date.now() + '.png') @@ -280,7 +288,12 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { '?date': kb.any(message, DCT('created')), '?content': kb.any(message, ns.sioc('content')) } - renderMessage(dom, kb, messageTable, bindings, messageTable.fresh, options, userContext) // fresh from elsewhere + var tr = renderMessage(dom, kb, messageTable, bindings, messageTable.fresh, options, userContext) // fresh from elsewhere + if (options.newestFirst === true) { + messageTable.insertBefore(tr, messageTable.firstChild) // If newestFirst + } else { + messageTable.appendChild(tr) // not newestFirst + } } // //////// @@ -291,7 +304,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { let extremity = backwards ? earliest : latest let date = extremity.messageTable.date// day in mssecs - date = await loadPrevious(date, backwards) // backwards + date = await dateFolder.loadPrevious(date, backwards) // backwards console.log(`insertPreviousMessages: from ${backwards ? 'backwards' : 'forwards'} loadPrevious: ${date}`) if (!date && !backwards && !liveMessageTable) { await appendCurrentMessages() // If necessary skip to today and add that @@ -299,8 +312,8 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { if (!date) return true // done var live = false if (!backwards) { - let todayDoc = chatDocumentFromDate(new Date()) - let doc = chatDocumentFromDate(date) + let todayDoc = dateFolder.leafDocumentFromDate(new Date()) + let doc = dateFolder.leafDocumentFromDate(date) live = doc.sameTerm(todayDoc) // Is this todays? } let newMessageTable = await createMessageTable(date, live) @@ -355,7 +368,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { */ async function createMessageTable (date, live) { console.log(' createMessageTable for ' + date) - const chatDocument = chatDocumentFromDate(date) + const chatDocument = dateFolder.leafDocumentFromDate(date) try { await kb.fetcher.load(chatDocument) } catch (err) { @@ -446,7 +459,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { messageTable.extendForwards = extendForwards // Make function available to scroll stuff // var messageButton messageTable.date = date - var chatDocument = chatDocumentFromDate(date) + var chatDocument = dateFolder.leafDocumentFromDate(date) messageTable.chatDocument = chatDocument messageTable.fresh = false @@ -568,11 +581,11 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { return result } // previousPeriod - let folder = chatDocumentFromDate(date).dir() + let folder = dateFolder.leafDocumentFromDate(date).dir() let found = await previousPeriod(folder, 3) if (found) { let doc = kb.sym(found.uri + 'chat.ttl') - return dateFromChatDocument(doc) + return dateFolder.dateFromLeafDocument(doc) } return null } @@ -580,7 +593,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { async function addNewTableIfNewDay (now) { // let now = new Date() // @@ Remove listener from previous table as it is now static - let newChatDocument = chatDocumentFromDate(now) + let newChatDocument = dateFolder.leafDocumentFromDate(now) if (!newChatDocument.sameTerm(latest.messageTable.chatDocument)) { // It is a new day if (liveMessageTable.inputRow) { liveMessageTable.removeChild(liveMessageTable.inputRow) @@ -614,7 +627,8 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { */ async function appendCurrentMessages () { var now = new Date() - var chatDocument = chatDocumentFromDate(now) + var chatDocument = dateFolder.leafDocumentFromDate(now) + /* Don't actually make the documemnt until a message is sent try { await createIfNotExists(chatDocument) } catch (e) { @@ -622,6 +636,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { dom, 'Problem accessing chat file: ' + e)) return } + */ const messageTable = await createMessageTable(now, true) div.appendChild(messageTable) div.refresh = function () { // only the last messageTable is live @@ -695,11 +710,11 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { if (options.selectedMessage) { var selectedDocument = options.selectedMessage.doc() var now = new Date() - var todayDocument = chatDocumentFromDate(now) + var todayDocument = dateFolder.leafDocumentFromDate(now) live = todayDocument.sameTerm(selectedDocument) } if (options.selectedMessage && !live) { - var selectedDate = dateFromChatDocument(selectedDocument) + var selectedDate = dateFolder.dateFromLeafDocument(selectedDocument) var selectedMessageTable = await createMessageTable(selectedDate, live) div.appendChild(selectedMessageTable) earliest.messageTable = selectedMessageTable From 2921077b7bc3738a7fe9c6416dfafd59ba96d2b4 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Fri, 5 Apr 2019 16:09:11 -0400 Subject: [PATCH 05/35] Getting there with chat refactor, not there yet --- package.json | 2 +- src/chat/bookmarks.js | 21 +- src/chat/{dateFolders.js => dateFolder.js} | 23 +- src/chat/{infinte.js => infinite.js} | 139 +++------ src/chat/message.js | 36 +-- src/chat/messageTools.js | 28 +- src/chat/thread.js | 324 +++++++++++++++++++++ src/index.js | 3 +- 8 files changed, 418 insertions(+), 158 deletions(-) rename src/chat/{dateFolders.js => dateFolder.js} (91%) rename src/chat/{infinte.js => infinite.js} (85%) create mode 100644 src/chat/thread.js diff --git a/package.json b/package.json index ae9e7785f..7e4152b01 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "escape-html": "^1.0.3", "mime-types": "^2.1.20", "node-uuid": "^1.4.7", - "rdflib": "^0.19.1", + "rdflib": ">=0.20.1", "solid-auth-client": "^2.2.13", "solid-auth-tls": "^0.1.2", "solid-namespace": "0.2.0" diff --git a/src/chat/bookmarks.js b/src/chat/bookmarks.js index 8e1338556..6773bb6f2 100644 --- a/src/chat/bookmarks.js +++ b/src/chat/bookmarks.js @@ -2,18 +2,19 @@ /* global alert confirm */ const UI = { - authn: require('./signin'), - icons: require('./iconBase'), - log: require('./log'), - ns: require('./ns'), - media: require('./media-capture'), - pad: require('./pad'), + authn: require('../signin'), + icons: require('../iconBase'), + log: require('../log'), + ns: require('../ns'), + media: require('../media-capture'), + pad: require('../pad'), rdf: require('rdflib'), - store: require('./store'), - style: require('./style'), - utils: require('./utils'), - widgets: require('./widgets') + store: require('../store'), + style: require('../style'), + utils: require('../utils'), + widgets: require('../widgets') } +const $rdf = UI.rdf const BOOK = $rdf.Namespace('http://www.w3.org/2002/01/bookmark#') const BOOKMARK_ICON = 'noun_45961.svg' diff --git a/src/chat/dateFolders.js b/src/chat/dateFolder.js similarity index 91% rename from src/chat/dateFolders.js rename to src/chat/dateFolder.js index a638a68e1..eff8e2722 100644 --- a/src/chat/dateFolders.js +++ b/src/chat/dateFolder.js @@ -2,7 +2,7 @@ ** */ -const kb = require ('../store.js') +const kb = require('../store.js') // import store from '../store' // import ns from '../ns' // const kb = store @@ -12,7 +12,7 @@ module.exports = class DateFolder { this.root = rootThing this.rootFolder = rootThing.dir() this.leafFileName = leafFileName - this.membershipProperty = membershipProperty|| ns.wf('leafObject') + this.membershipProperty = membershipProperty || ns.wf('leafObject') } /* Generate the leaf document (rdf object) from date @@ -48,8 +48,8 @@ module.exports = class DateFolder { let tail = x.uri.slice(0, -1).split('/').slice(-1)[0] if (!'0123456789'.includes(tail[0])) return false // not numeric return true - // return kb.anyValue(leafDocument, POSIX('size')) !== 0 // empty file? } + async function lastNonEmpty (siblings) { siblings = siblings.filter(suitable) siblings.sort() // chronological order @@ -93,14 +93,21 @@ module.exports = class DateFolder { return null } // loadPrevious - async firstLeaf (backwards) { // backwards -> last leafObject var folderStore = $rdf.graph() var folderFetcher = new $rdf.Fetcher(folderStore) async function earliestSubfolder (parent) { + function suitable (x) { + let tail = x.uri.slice(0, -1).split('/').slice(-1)[0] + if (!'0123456789'.includes(tail[0])) return false // not numeric + return true + } console.log(' parent ' + parent) delete folderFetcher.requested[parent.uri] - var resp = await folderFetcher.load(parent, clone(forcingOptions)) // Force fetch as will have changed + // try { + await folderFetcher.load(parent, {force: true}) // Force fetch as will have changed + // }catch (err) { + // } var kids = folderStore.each(parent, ns.ldp('contains')) kids = kids.filter(suitable) @@ -117,7 +124,7 @@ module.exports = class DateFolder { let d = await earliestSubfolder(month) let leafDocument = $rdf.sym(d.uri + 'chat.ttl') await folderFetcher.load(leafDocument) - let leafObjects = folderStore.each(this.root, this.membershipProperty), null, leafDocument) + let leafObjects = folderStore.each(this.root, this.membershipProperty, null, leafDocument) if (leafObjects.length === 0) { let msg = ' INCONSISTENCY -- no chat leafObject in file ' + leafDocument console.trace(msg) @@ -129,8 +136,4 @@ module.exports = class DateFolder { console.log((backwards ? 'Latest' : 'Earliest') + ' leafObject is ' + sortMe[0][1]) return sortMe[0][1] } // firstleafObject - - - - } // class diff --git a/src/chat/infinte.js b/src/chat/infinite.js similarity index 85% rename from src/chat/infinte.js rename to src/chat/infinite.js index 8757f7f40..32d6c47d1 100644 --- a/src/chat/infinte.js +++ b/src/chat/infinite.js @@ -7,27 +7,30 @@ /* global alert */ import DateFolder from './dateFolder' +const SERVER_MKDIRP_BUG = true + const UI = { - authn: require('./signin'), - icons: require('./iconBase'), - log: require('./log'), - ns: require('./ns'), - media: require('./media-capture'), - pad: require('./pad'), + authn: require('../signin'), + icons: require('../iconBase'), + log: require('../log'), + ns: require('../ns'), + media: require('../media-capture'), + pad: require('../pad'), rdf: require('rdflib'), - store: require('./store'), - style: require('./style'), - utils: require('./utils'), - widgets: require('./widgets') + store: require('../store'), + style: require('../style'), + utils: require('../utils'), + widgets: require('../widgets') } // const utils = require('./utils') -const { renderMessage, creatorAndDate } = require('./renderMessage') +const { renderMessage, creatorAndDate } = require('./message') const bookmarks = require('./bookmarks') -module.exports = { infiniteMessageArea } +// module.exports = module.exports || {} +// module.exports.infiniteMessageArea = -function infiniteMessageArea (dom, kb, chatChannel, options) { +export function infiniteMessageArea (dom, kb, chatChannel, options) { kb = kb || UI.store const ns = UI.ns const WF = $rdf.Namespace('http://www.w3.org/2005/01/wf/flow#') @@ -288,7 +291,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { '?date': kb.any(message, DCT('created')), '?content': kb.any(message, ns.sioc('content')) } - var tr = renderMessage(dom, kb, messageTable, bindings, messageTable.fresh, options, userContext) // fresh from elsewhere + var tr = renderMessage(messageTable, bindings, messageTable.fresh, options, userContext) // fresh from elsewhere if (options.newestFirst === true) { messageTable.insertBefore(tr, messageTable.firstChild) // If newestFirst } else { @@ -341,29 +344,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { extr.messageTable = messageTable } - /* Generate the chat document (rdf object) from date - * @returns: - document - */ - function chatDocumentFromDate (date) { - let isoDate = date.toISOString() // Like "2018-05-07T17:42:46.576Z" - var path = isoDate.split('T')[0].replace(/-/g, '/') // Like "2018/05/07" - path = chatChannel.dir().uri + path + '/chat.ttl' - return $rdf.sym(path) - } - - /* Generate a date object from the chat file name - */ - function dateFromChatDocument (doc) { - const head = chatChannel.dir().uri.length - const str = doc.uri.slice(head, head + 10).replace(/\//g, '-') - // let date = new Date(str + 'Z') // GMT - but fails in FF - invalid format :-( - let date = new Date(str) // not explicitly UTC but is assumed so in spec - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse - console.log('Date for ' + doc + ':' + date.toISOString()) - return date - } - - /* LOad and render message table + /* Load and render message table ** @returns DOM element generates */ async function createMessageTable (date, live) { @@ -376,7 +357,8 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { let statusTR = messageTable.appendChild(dom.createElement('tr')) // ### find status in exception if (err.response && err.response.status && err.response.status === 404) { console.log('Error 404 for chat file ' + chatDocument) - statusTR.appendChild(UI.widgets.errorMessageBlock(dom, 'no messages', 'white')) + return renderMessageTable(date, live) // no mssage file is fine.. will be craeted later + // statusTR.appendChild(UI.widgets.errorMessageBlock(dom, 'no message file', 'white')) } else { console.log('*** Error NON 404 for chat file ' + chatDocument) statusTR.appendChild(UI.widgets.errorMessageBlock(dom, err, 'pink')) @@ -390,7 +372,7 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { var scrollBackButton var scrollForwardButton -/// ///////////////// Scrooll down adding more above +/// ///////////////// Scroll down adding more above async function extendBackwards () { let done = await insertPreviousMessages(true) @@ -532,64 +514,6 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { return messageTable } // renderMessageTable -/* Track back through the YYYY/MM/DD tree to find the previous/next day -** -*/ - async function loadPrevious (date, backwards) { - async function previousPeriod (file, level) { - function younger (x) { - if (backwards ? x.uri >= file.uri : x.uri <= file.uri) return false // later than we want or same -- looking for different - return true - } - function suitable (x) { - let tail = x.uri.slice(0, -1).split('/').slice(-1)[0] - if (!'0123456789'.includes(tail[0])) return false // not numeric - return true - // return kb.anyValue(chatDocument, POSIX('size')) !== 0 // empty file? - } - async function lastNonEmpty (siblings) { - siblings = siblings.filter(suitable) - siblings.sort() // chronological order - if (!backwards) siblings.reverse() - if (level !== 3) return siblings.pop() // only length chck final leverl - while (siblings.length) { - let folder = siblings.pop() - let chatDocument = kb.sym(folder.uri + 'chat.ttl') - await kb.fetcher.load(chatDocument) - // files can have seealso links. skip ones with no messages with a date - if (kb.statementsMatching(null, DCT('created'), null, chatDocument).length > 0) { - return folder - } - } - return null - } - // console.log(' previousPeriod level' + level + ' file ' + file) - const parent = file.dir() - await kb.fetcher.load(parent) - var siblings = kb.each(parent, ns.ldp('contains')) - siblings = siblings.filter(younger) - let folder = await lastNonEmpty(siblings) - if (folder) return folder - - if (level === 0) return null // 3:day, 2:month, 1: year 0: no - - const uncle = await previousPeriod(parent, level - 1) - if (!uncle) return null // reached first ever - await kb.fetcher.load(uncle) - var cousins = kb.each(uncle, ns.ldp('contains')) - let result = await lastNonEmpty(cousins) - return result - } // previousPeriod - - let folder = dateFolder.leafDocumentFromDate(date).dir() - let found = await previousPeriod(folder, 3) - if (found) { - let doc = kb.sym(found.uri + 'chat.ttl') - return dateFolder.dateFromLeafDocument(doc) - } - return null - } - async function addNewTableIfNewDay (now) { // let now = new Date() // @@ Remove listener from previous table as it is now static @@ -628,15 +552,20 @@ function infiniteMessageArea (dom, kb, chatChannel, options) { async function appendCurrentMessages () { var now = new Date() var chatDocument = dateFolder.leafDocumentFromDate(now) - /* Don't actually make the documemnt until a message is sent - try { - await createIfNotExists(chatDocument) - } catch (e) { - div.appendChild(UI.widgets.errorMessageBlock( - dom, 'Problem accessing chat file: ' + e)) - return - } + + /* Don't actually make the documemnt until a message is sent @@@@@ WHEN SERVER FIXED + * currently server won't patch to a file ina non-existent directory */ + if (SERVER_MKDIRP_BUG) { + try { + await createIfNotExists(chatDocument) + } catch (e) { + div.appendChild(UI.widgets.errorMessageBlock( + dom, 'Problem accessing chat file: ' + e)) + return + } + } + /// /////////////////////////////////////////////////////////// const messageTable = await createMessageTable(now, true) div.appendChild(messageTable) div.refresh = function () { // only the last messageTable is live diff --git a/src/chat/message.js b/src/chat/message.js index 0b7025a4a..050504ceb 100644 --- a/src/chat/message.js +++ b/src/chat/message.js @@ -1,28 +1,29 @@ const UI = { - authn: require('./signin'), - icons: require('./iconBase'), - log: require('./log'), - ns: require('./ns'), - media: require('./media-capture'), - pad: require('./pad'), + authn: require('../signin'), + icons: require('../iconBase'), + log: require('../log'), + ns: require('../ns'), + media: require('../media-capture'), + pad: require('../pad'), rdf: require('rdflib'), - store: require('./store'), - style: require('./style'), - utils: require('./utils'), - widgets: require('./widgets') + store: require('../store'), + style: require('../style'), + utils: require('../utils'), + widgets: require('../widgets') } const dom = UI.dom || window.document // const kb = UI.store -module.exports = { renderMessage, creatorAndDate, creatorAndDateHorizontal } +// module.exports = { renderMessage, creatorAndDate, creatorAndDateHorizontal } const messageBodyStyle = UI.style.messageBodyStyle -const { messageTools, sentimentStripLinked } = require('./messageTools').messageTools +// const { messageToolbar, sentimentStripLinked } = require('./messageTools') +import { messageToolbar, sentimentStripLinked } from './messageTools' const label = UI.utils.label -function elementForImageURI (imageUri, options) { +export function elementForImageURI (imageUri, options) { let img = dom.createElement('img') let height = '10' if (options.inlineImageHeightEms) { @@ -56,7 +57,7 @@ function nick (person) { return '' + label(person) } -function creatorAndDate (td1, creator, date, message) { +export function creatorAndDate (td1, creator, date, message) { var nickAnchor = td1.appendChild(anchor(nick(creator), creator)) if (creator.uri) { UI.store.fetcher.nowOrWhenFetched(creator.doc(), undefined, function (ok, body) { @@ -67,7 +68,7 @@ function creatorAndDate (td1, creator, date, message) { td1.appendChild(anchor(date, message)) } -function creatorAndDateHorizontal (td1, creator, date, message) { +export function creatorAndDateHorizontal (td1, creator, date, message) { var nickAnchor = td1.appendChild(anchor(label(creator), creator)) if (creator.uri) { UI.store.fetcher.nowOrWhenFetched(creator.doc(), undefined, function (ok, body) { @@ -82,7 +83,7 @@ function creatorAndDateHorizontal (td1, creator, date, message) { // BODY of renderMessage -function renderMessage (messageTable, bindings, fresh, options, userContext) { +export function renderMessage (messageTable, bindings, fresh, options, userContext) { var colorizeByAuthor = options.colorizeByAuthor === '1' || options.colorizeByAuthor === true var creator = bindings['?creator'] @@ -180,7 +181,7 @@ function renderMessage (messageTable, bindings, fresh, options, userContext) { return } const toolsTR = dom.createElement('tr') - const tools = messageTools(message, messageRow, userContext) + const tools = messageToolbar(message, messageRow, userContext) tools.style = 'border: 0.05em solid #888; border-radius: 0 0 0.7em 0.7em; border-top: 0; height:3.5em; background-color: #fff;' // @@ fix if (messageRow.nextSibling) { messageRow.parentElement.insertBefore(toolsTR, messageRow.nextSibling) @@ -193,4 +194,5 @@ function renderMessage (messageTable, bindings, fresh, options, userContext) { toolsTR.appendChild(dom.createElement('td')) // right toolsTD.appendChild(tools) }) + return messageRow } diff --git a/src/chat/messageTools.js b/src/chat/messageTools.js index e83e982c0..db075b233 100644 --- a/src/chat/messageTools.js +++ b/src/chat/messageTools.js @@ -7,17 +7,17 @@ */ const UI = { - authn: require('./signin'), - icons: require('./iconBase'), - log: require('./log'), - ns: require('./ns'), - media: require('./media-capture'), - pad: require('./pad'), + authn: require('../signin'), + icons: require('../iconBase'), + log: require('../log'), + ns: require('../ns'), + media: require('../media-capture'), + pad: require('../pad'), rdf: require('rdflib'), - store: require('./store'), - style: require('./style'), - utils: require('./utils'), - widgets: require('./widgets') + store: require('../store'), + style: require('../style'), + utils: require('../utils'), + widgets: require('../widgets') } const bookmarks = require('./bookmarks') @@ -39,7 +39,7 @@ const ns = UI.ns const THUMBS_UP_ICON = 'noun_1384132.svg' const THUMBS_DOWN_ICON = 'noun_1384135.svg' -module.export = { messageTools, sentimentStripLinked, sentimentStrip } +// module.export = { messageTools, sentimentStripLinked, sentimentStrip } // @@@@ use the one in rdflib.js when it is avaiable and delete this function updatePromise (del, ins) { @@ -65,7 +65,7 @@ emoji[ns.schema('LikeAction')] = '❤️' /* Strip of sentiments expressed */ -function sentimentStrip (target, doc) { +export function sentimentStrip (target, doc) { const actions = kb.each(null, ns.schema('target'), target, doc) const sentiments = actions.map(a => kb.any(a, ns.rdf('type'), null, doc)) sentiments.sort() @@ -78,7 +78,7 @@ function sentimentStrip (target, doc) { * @param doc {NamedNode} - The document iun which they are expressed */ -function sentimentStripLinked (target, doc) { +export function sentimentStripLinked (target, doc) { var strip = dom.createElement('span') function refresh () { strip.innerHTML = '' @@ -105,7 +105,7 @@ function sentimentStripLinked (target, doc) { return strip } -function messageTools (message, messageRow, userContext) { +export function messageToolbar (message, messageRow, userContext) { const div = dom.createElement('div') function closeToolbar () { div.parentElement.parentElement.removeChild(div.parentElement) // remive the TR diff --git a/src/chat/thread.js b/src/chat/thread.js new file mode 100644 index 000000000..9ef44200b --- /dev/null +++ b/src/chat/thread.js @@ -0,0 +1,324 @@ +// Common code for a discussion are a of messages about something +// +var UI = { + authn: require('../signin'), + icons: require('../iconBase'), + log: require('../log'), + ns: require('../ns'), + pad: require('../'), + rdf: require('rdflib'), + store: require('../store'), + style: require('../style'), + widgets: require('../widgets') +} + +const utils = require('./utils') + +// var buttonStyle = 'font-size: 100%; margin: 0.8em; padding:0.5em; background-color: white;' + +module.exports = function (dom, kb, subject, messageStore, options) { + kb = kb || UI.store + messageStore = messageStore.doc() // No hash + var ns = UI.ns + var WF = $rdf.Namespace('http://www.w3.org/2005/01/wf/flow#') + var DCT = $rdf.Namespace('http://purl.org/dc/terms/') + + options = options || {} + + var newestFirst = !!options.newestFirst + + var messageBodyStyle = 'white-space: pre-wrap; width: 90%; font-size:100%; border: 0.07em solid #eee; padding: .2em 0.5em; margin: 0.1em 1em 0.1em 1em;' + // 'font-size: 100%; margin: 0.1em 1em 0.1em 1em; background-color: white; white-space: pre-wrap; padding: 0.1em;' + + var div = dom.createElement('div') + var messageTable // Shared by initial build and addMessageFromBindings + + var me + + var updater = UI.store.updater + + var anchor = function (text, term) { // If there is no link return an element anyway + var a = dom.createElement('a') + if (term && term.uri) { + a.setAttribute('href', term.uri) + a.addEventListener('click', UI.widgets.openHrefInOutlineMode, true) + a.setAttribute('style', 'color: #3B5998; text-decoration: none; ') // font-weight: bold + } + a.textContent = text + return a + } + + var mention = function mention (message, style) { + var pre = dom.createElement('pre') + pre.setAttribute('style', style || 'color: grey') + div.appendChild(pre) + pre.appendChild(dom.createTextNode(message)) + return pre + } + + var announce = { + log: function (message) { mention(message, 'color: #111;') }, + warn: function (message) { mention(message, 'color: #880;') }, + error: function (message) { mention(message, 'color: #800;') } + } + + // Form for a new message + // + var newMessageForm = function () { + var form = dom.createElement('tr') + var lhs = dom.createElement('td') + var middle = dom.createElement('td') + var rhs = dom.createElement('td') + form.appendChild(lhs) + form.appendChild(middle) + form.appendChild(rhs) + form.AJAR_date = '9999-01-01T00:00:00Z' // ISO format for field sort + + var sendMessage = function () { + // titlefield.setAttribute('class','pendingedit') + // titlefield.disabled = true + field.setAttribute('class', 'pendingedit') + field.disabled = true + var sts = [] + var now = new Date() + var timestamp = '' + now.getTime() + var dateStamp = $rdf.term(now) + // http://www.w3schools.com/jsref/jsref_obj_date.asp + var message = kb.sym(messageStore.uri + '#' + 'Msg' + timestamp) + + sts.push(new $rdf.Statement(subject, ns.wf('message'), message, messageStore)) + // sts.push(new $rdf.Statement(message, ns.dc('title'), kb.literal(titlefield.value), messageStore)) + sts.push(new $rdf.Statement(message, ns.sioc('content'), kb.literal(field.value), messageStore)) + sts.push(new $rdf.Statement(message, DCT('created'), dateStamp, messageStore)) + if (me) sts.push(new $rdf.Statement(message, ns.foaf('maker'), me, messageStore)) + + var sendComplete = function (uri, success, body) { + if (!success) { + form.appendChild(UI.widgets.errorMessageBlock( + dom, 'Error writing message: ' + body)) + } else { + var bindings = { '?msg': message, + '?content': kb.literal(field.value), + '?date': dateStamp, + '?creator': me} + renderMessage(bindings, false) // not green + + field.value = '' // clear from out for reuse + field.setAttribute('class', '') + field.disabled = false + } + } + updater.update([], sts, sendComplete) + } + form.appendChild(dom.createElement('br')) + + var field, sendButton + var turnOnInput = function () { + creatorAndDate(lhs, me, '', null) + + field = dom.createElement('textarea') + middle.innerHTML = '' + middle.appendChild(field) + field.rows = 3 + // field.cols = 40 + field.setAttribute('style', messageBodyStyle + 'background-color: #eef;') + + field.addEventListener('keyup', function (e) { // User preference? + if (e.keyCode === 13) { + if (!e.altKey) { // Alt-Enter just adds a new line + sendMessage() + } + } + }, false) + + rhs.innerHTML = '' + sendButton = UI.widgets.button(dom, UI.icons.iconBase + 'noun_383448.svg', 'Send') + sendButton.setAttribute('style', UI.style.buttonStyle + 'float: right;') + sendButton.addEventListener('click', sendMessage, false) + rhs.appendChild(sendButton) + } + + let context = {div: middle, dom: dom} + UI.authn.logIn(context).then(context => { + me = context.me + turnOnInput() + }) + + return form + } + + function nick (person) { + var s = UI.store.any(person, UI.ns.foaf('nick')) + if (s) return '' + s.value + return '' + utils.label(person) + } + + function creatorAndDate (td1, creator, date, message) { + var nickAnchor = td1.appendChild(anchor(nick(creator), creator)) + if (creator.uri) { + UI.store.fetcher.nowOrWhenFetched(creator.doc(), undefined, function (ok, body) { + nickAnchor.textContent = nick(creator) + }) + } + td1.appendChild(dom.createElement('br')) + td1.appendChild(anchor(date, message)) + } + + // /////////////////////////////////////////////////////////////////////// + + function syncMessages (about, messageTable) { + var displayed = {} + var ele, ele2 + for (ele = messageTable.firstChild; ele; ele = ele.nextSibling) { + if (ele.AJAR_subject) { + displayed[ele.AJAR_subject.uri] = true + } + } + var messages = kb.each(about, ns.wf('message')) + var stored = {} + messages.map(function (m) { + stored[m.uri] = true + if (!displayed[m.uri]) { + addMessage(m) + } + }) + + for (ele = messageTable.firstChild; ele;) { + ele2 = ele.nextSibling + if (ele.AJAR_subject && !stored[ele.AJAR_subject.uri]) { + messageTable.removeChild(ele) + } + ele = ele2 + } + } + + var deleteMessage = function (message) { + var deletions = kb.statementsMatching(message).concat( + kb.statementsMatching(undefined, undefined, message)) + updater.update(deletions, [], function (uri, ok, body) { + if (!ok) { + announce.error('Cant delete messages:' + body) + } else { + syncMessages(subject, messageTable) + } + }) + } + + var addMessage = function (message) { + var bindings = { + '?msg': message, + '?creator': kb.any(message, ns.foaf('maker')), + '?date': kb.any(message, DCT('created')), + '?content': kb.any(message, ns.sioc('content')) + } + renderMessage(bindings, true) // fresh from elsewhere + } + + var renderMessage = function (bindings, fresh) { + var creator = bindings['?creator'] + var message = bindings['?msg'] + var date = bindings['?date'] + var content = bindings['?content'] + + var dateString = date.value + var tr = dom.createElement('tr') + tr.AJAR_date = dateString + tr.AJAR_subject = message + + var done = false + for (var ele = messageTable.firstChild; ; ele = ele.nextSibling) { + if (!ele) { // empty + break + } + if (((dateString > ele.AJAR_date) && newestFirst) || + ((dateString < ele.AJAR_date) && !newestFirst)) { + messageTable.insertBefore(tr, ele) + done = true + break + } + } + if (!done) { + messageTable.appendChild(tr) + } + + var td1 = dom.createElement('td') + tr.appendChild(td1) + creatorAndDate(td1, creator, UI.widgets.shortDate(dateString), message) + + var td2 = dom.createElement('td') + tr.appendChild(td2) + var pre = dom.createElement('p') + pre.setAttribute('style', messageBodyStyle + + (fresh ? 'background-color: #e8ffe8;' : 'background-color: #white;')) + td2.appendChild(pre) + pre.textContent = content.value + + var td3 = dom.createElement('td') + tr.appendChild(td3) + + var delButton = dom.createElement('button') + td3.appendChild(delButton) + delButton.textContent = '-' + + tr.setAttribute('class', 'hoverControl') // See tabbedtab.css (sigh global CSS) + delButton.setAttribute('class', 'hoverControlHide') + delButton.setAttribute('style', 'color: red;') + delButton.addEventListener('click', function (e) { + td3.removeChild(delButton) // Ask -- are you sure? + var cancelButton = dom.createElement('button') + cancelButton.textContent = 'cancel' + td3.appendChild(cancelButton).addEventListener('click', function (e) { + td3.removeChild(sureButton) + td3.removeChild(cancelButton) + td3.appendChild(delButton) + }, false) + var sureButton = dom.createElement('button') + sureButton.textContent = 'Delete message' + td3.appendChild(sureButton).addEventListener('click', function (e) { + td3.removeChild(sureButton) + td3.removeChild(cancelButton) + deleteMessage(message) + }, false) + }, false) + } + + // Messages with date, author etc + + messageTable = dom.createElement('table') + messageTable.fresh = false + div.appendChild(messageTable) + messageTable.setAttribute('style', 'width: 100%;') // fill that div! + + var tr = newMessageForm() + if (newestFirst) { + messageTable.insertBefore(tr, messageTable.firstChild) // If newestFirst + } else { + messageTable.appendChild(tr) // not newestFirst + } + + var query + // Do this with a live query to pull in messages from web + if (options.query) { + query = options.query + } else { + query = new $rdf.Query('Messages') + var v = {} // semicolon needed + var vs = ['msg', 'date', 'creator', 'content'] + vs.map(function (x) { + query.vars.push(v[x] = $rdf.variable(x)) + }) + query.pat.add(subject, WF('message'), v['msg']) + query.pat.add(v['msg'], ns.dct('created'), v['date']) + query.pat.add(v['msg'], ns.foaf('maker'), v['creator']) + query.pat.add(v['msg'], ns.sioc('content'), v['content']) + } + function doneQuery () { + messageTable.fresh = true // any new are fresh and so will be greenish + } + kb.query(query, renderMessage, undefined, doneQuery) + div.refresh = function () { + syncMessages(subject, messageTable) + } + // syncMessages(subject, messageTable) // no the query will do this async + return div +} diff --git a/src/index.js b/src/index.js index f04e48333..7f5e0d247 100755 --- a/src/index.js +++ b/src/index.js @@ -52,7 +52,8 @@ const UI = { matrix: require('./matrix'), media: require('./media-capture'), messageArea: require('./messageArea'), - infiniteMessageArea: require('./infiniteMessageArea'), + infiniteMessageArea: require('./chat/infinite').infiniteMessageArea, + // infiniteMessageArea: require('./infiniteMessageArea'), pad: require('./pad'), preferences: require('./preferences'), store: require('./store'), From ba7f40fb8f6b4d34b17e6c1d3d0531349f929d99 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Wed, 10 Apr 2019 15:30:46 -0400 Subject: [PATCH 06/35] should fix https://github.com/solid/solid-ui/issues/61 --- src/preferences.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/preferences.js b/src/preferences.js index 9cdd72553..93d534e1e 100644 --- a/src/preferences.js +++ b/src/preferences.js @@ -38,7 +38,7 @@ function recordSharedPreferences (subject, context) { console.log('Creating shared preferences ' + sp) kb.updater.update([], ins, function (uri, ok, errorMessage) { if (!ok) { - reject(new Error('create shard prefs: ' + errorMessage)) + reject(new Error('Error creating shared prefs: ' + errorMessage)) } else { context.sharedPreferences = sp resolve(context) @@ -56,7 +56,11 @@ function recordSharedPreferences (subject, context) { function recordPersonalDefaults (klass, context) { return new Promise(function (resolve, reject) { authn.logInLoadPreferences(context).then(context => { - var regs = kb.each(null, ns.solid('forClass'), klass) + if (!context.preferencesFile) { + console.log('Not doing private class preferences as no access to preferences file. ' + context.preferencesFileError) + return + } + var regs = kb.each(null, ns.solid('forClass'), klass, context.preferencesFile) var ins = [] var prefs var reg From 0e2d386a56a66feff5b0f9221e5ce87417e9ee43 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Wed, 10 Apr 2019 15:34:27 -0400 Subject: [PATCH 07/35] aimed at fixing https://github.com/solid/solid-ui/issues/62 --- src/acl-control.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/acl-control.js b/src/acl-control.js index 8a4fec8a2..918faada1 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -399,6 +399,8 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { statusBlock.textContent += ' (Error writing back access control file: ' + message + ')' } else { kb.add(kb2.statements) + fetcher.requested[targetACLDoc.uri] === 'done' // cheat + // kb.fetcher.load(targetACLDoc, {force: true}) statusBlock.textContent = ' (Now editing specific access for this ' + noun + ')' // box.style.cssText = 'color: black;' bottomRow.removeChild(editPlease) @@ -473,7 +475,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { box.notice.textContent = 'Sharing for things within the folder currently tracks sharing for the folder.' box.notice.style.cssText = 'font-size: 80%; color: #888;' var splitButton = UI.widgets.clearElement(box.offer).appendChild(dom.createElement('button')) - splitButton.innerHTML = '

Set the sharing of folder contets
separately from the sharing for the folder

' + splitButton.innerHTML = '

the sharing of folder contets
separately from the sharing for the folder

' splitButton.style.cssText = bigButtonStyle splitButton.addEventListener('click', function (e) { box.addControlForDefaults() From 971890b9896bf25eb5b35145825f587741cf67fc Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Sun, 28 Apr 2019 22:02:40 +0100 Subject: [PATCH 08/35] Seeems to work with refactored code, not 100% yet --- package-lock.json | 484 ++++++++++++++++++++++++++++++++------- src/chat/infinite.js | 214 ++++++++--------- src/chat/messageTools.js | 17 +- 3 files changed, 529 insertions(+), 186 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc38ddaaf..0df250742 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,14 @@ "regenerator-runtime": "^0.12.0" } }, + "@solid/cli": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@solid/cli/-/cli-0.1.0.tgz", + "integrity": "sha512-+VYDgDxsAKa48MGnoaX2CUwh0gLrTdqYY6lrxxWGwCHiFChGsg95RRbHOYz9c97+QcCltcHpMZ2AelGZrU673A==", + "requires": { + "@trust/oidc-rp": "0.6.0" + } + }, "@solid/jose": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/@solid/jose/-/jose-0.1.8.tgz", @@ -38,6 +46,35 @@ "whatwg-url": "^6.4.1" } }, + "@trust/jose": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@trust/jose/-/jose-0.1.7.tgz", + "integrity": "sha512-JlWY97+Q1pU2CN08Ux5oN1/CXcvxLtQ5YkL4UhgVs4z9TR/+I4rKqhqoZQ0TDGPvCLP1QaT7F6bHbKswbDwgOQ==", + "requires": { + "@trust/json-document": "^0.1.4", + "@trust/webcrypto": "^0.0.2", + "base64url": "^2.0.0", + "text-encoding": "^0.6.1" + }, + "dependencies": { + "@trust/webcrypto": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.0.2.tgz", + "integrity": "sha1-53xpouYSudOSJRxZZscxaFN+Jmc=", + "requires": { + "base64url": "^2.0.0", + "node-rsa": "^0.4.0", + "pem-jwk": "^1.5.1", + "text-encoding": "^0.6.1" + } + }, + "base64url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" + } + } + }, "@trust/json-document": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@trust/json-document/-/json-document-0.1.4.tgz", @@ -53,6 +90,47 @@ "elliptic": "^6.4.0" } }, + "@trust/oidc-rp": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@trust/oidc-rp/-/oidc-rp-0.6.0.tgz", + "integrity": "sha512-6PgV0WI+gq6nGMjlg8oSxj7VgmS/m8Y61s6HPNBu3mX/NSVvnrXk+MqqR7KdxlBk84ti65O76HGSRNelJrRbeA==", + "requires": { + "@trust/jose": "^0.1.7", + "@trust/json-document": "^0.1.4", + "@trust/webcrypto": "0.4.0", + "base64url": "^2.0.0", + "node-fetch": "^1.7.3", + "text-encoding": "^0.6.4", + "whatwg-url": "^6.2.1" + }, + "dependencies": { + "@trust/webcrypto": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.4.0.tgz", + "integrity": "sha1-zIcSyomn5x01P877ZrJwemec9jU=", + "requires": { + "@trust/keyto": "^0.3.0", + "base64url": "^2.0.0", + "node-rsa": "^0.4.0", + "text-encoding": "^0.6.1" + } + }, + "base64url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + } + } + }, "@trust/webcrypto": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.9.2.tgz", @@ -89,9 +167,9 @@ } }, "ajv": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", - "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -157,13 +235,15 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "dev": true, + "optional": true }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "dev": true, + "optional": true }, "array-unique": { "version": "0.2.1", @@ -209,7 +289,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "dev": true, + "optional": true }, "async": { "version": "0.9.2", @@ -232,7 +313,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "dev": true, + "optional": true }, "auth-header": { "version": "1.0.0", @@ -923,6 +1005,7 @@ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, + "optional": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -938,6 +1021,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^1.0.0" } @@ -947,6 +1031,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -956,6 +1041,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -965,6 +1051,7 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -975,13 +1062,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true } } }, @@ -1050,8 +1139,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "builtin-modules": { "version": "1.1.1", @@ -1064,6 +1152,7 @@ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, + "optional": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -1080,7 +1169,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true } } }, @@ -1152,6 +1242,7 @@ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, + "optional": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -1164,6 +1255,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -1172,7 +1264,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true } } }, @@ -1208,6 +1301,7 @@ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, + "optional": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -1230,7 +1324,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", @@ -1269,7 +1364,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "dev": true, + "optional": true }, "core-js": { "version": "2.5.7", @@ -1318,7 +1414,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "dev": true, + "optional": true }, "deep-is": { "version": "0.1.3", @@ -1340,6 +1437,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, + "optional": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -1350,6 +1448,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -1359,6 +1458,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -1368,6 +1468,7 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -1378,13 +1479,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true } } }, @@ -1861,6 +1964,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, + "optional": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -1871,6 +1975,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, + "optional": true, "requires": { "is-plain-object": "^2.0.4" } @@ -1928,6 +2033,40 @@ "object-assign": "^4.0.1" } }, + "file-fetch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/file-fetch/-/file-fetch-1.2.0.tgz", + "integrity": "sha512-DzwKhcH/afS7thk5hao1kVJXIqMNB2pz0DFpjpA5IlIAA0nSqi/fqFQpX++NP9IK+Te7Z1ZxA5KTWKmdzio+tA==", + "requires": { + "concat-stream": "^2.0.0", + "mime-types": "^2.1.17", + "node-fetch": "^2.3.0", + "readable-error": "^1.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "readable-stream": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -1981,7 +2120,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "dev": true, + "optional": true }, "for-own": { "version": "0.1.5", @@ -2013,10 +2153,16 @@ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, + "optional": true, "requires": { "map-cache": "^0.2.2" } }, + "fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" + }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -2049,7 +2195,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2070,12 +2217,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2090,17 +2239,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2217,7 +2369,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2229,6 +2382,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2243,6 +2397,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2250,12 +2405,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -2274,6 +2431,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2354,7 +2512,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2366,6 +2525,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2451,7 +2611,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2487,6 +2648,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2506,6 +2668,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2549,12 +2712,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -2592,7 +2757,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "dev": true, + "optional": true }, "getpass": { "version": "0.1.7", @@ -2632,6 +2798,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, + "optional": true, "requires": { "is-glob": "^2.0.0" } @@ -2691,6 +2858,7 @@ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, + "optional": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -2701,7 +2869,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true } } }, @@ -2710,6 +2879,7 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, + "optional": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -2720,6 +2890,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -2729,6 +2900,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -2740,6 +2912,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -2861,6 +3034,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" } @@ -2885,7 +3059,8 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "is-callable": { "version": "1.1.4", @@ -2898,6 +3073,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" } @@ -2913,6 +3089,7 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -2923,7 +3100,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "dev": true, + "optional": true } } }, @@ -2948,13 +3126,15 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "dev": true, + "optional": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-finite": { "version": "1.0.2", @@ -2979,6 +3159,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3017,6 +3198,7 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, + "optional": true, "requires": { "isobject": "^3.0.1" }, @@ -3025,7 +3207,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3093,8 +3276,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isobject": { "version": "2.1.0", @@ -3246,6 +3428,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3293,8 +3476,7 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash.cond": { "version": "4.5.2", @@ -3320,13 +3502,15 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "dev": true, + "optional": true }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, + "optional": true, "requires": { "object-visit": "^1.0.0" } @@ -3403,6 +3587,7 @@ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, + "optional": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -3413,6 +3598,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, + "optional": true, "requires": { "is-plain-object": "^2.0.4" } @@ -3512,6 +3698,11 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" }, + "node-forge": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.2.tgz", + "integrity": "sha512-mXQ9GBq1N3uDCyV1pdSzgIguwgtVpM7f5/5J4ipz12PKWElmPpVWLDuWl8iXmhysr21+WmX/OJ5UKx82wjomgg==" + }, "node-rsa": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz", @@ -3537,6 +3728,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, + "optional": true, "requires": { "remove-trailing-separator": "^1.0.1" } @@ -3563,6 +3755,7 @@ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, + "optional": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -3574,6 +3767,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -3591,6 +3785,7 @@ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, + "optional": true, "requires": { "isobject": "^3.0.0" }, @@ -3599,7 +3794,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3631,6 +3827,7 @@ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, + "optional": true, "requires": { "isobject": "^3.0.1" }, @@ -3639,7 +3836,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3746,7 +3944,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "dev": true, + "optional": true }, "path-exists": { "version": "2.1.0", @@ -3775,6 +3974,32 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "pem-jwk": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pem-jwk/-/pem-jwk-1.5.1.tgz", + "integrity": "sha1-eoY3/S9nqCflfAxC4cI8P9Us+wE=", + "requires": { + "asn1.js": "1.0.3" + }, + "dependencies": { + "asn1.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-1.0.3.tgz", + "integrity": "sha1-KBuj7B8kSP52X5Kk7s+IP+E2S1Q=", + "requires": { + "bn.js": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "bn.js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-1.3.0.tgz", + "integrity": "sha1-DbTL+W+PI7dC9by50ap6mZSgXoM=", + "optional": true + } + } + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -3891,8 +4116,7 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "1.1.8", @@ -3901,9 +4125,9 @@ "dev": true }, "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" }, "punycode": { "version": "2.1.1", @@ -3943,23 +4167,54 @@ } } }, + "rdf-canonize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-1.0.3.tgz", + "integrity": "sha512-piLMOB5Q6LJSVx2XzmdpHktYVb8TmVTy8coXJBFtdkcMC96DknZOuzpAYqCWx2ERZX7xEW+mMi8/wDuMJS/95w==", + "requires": { + "node-forge": "^0.8.1", + "semver": "^5.6.0" + } + }, "rdflib": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/rdflib/-/rdflib-0.19.1.tgz", - "integrity": "sha512-O7osObhO5o++hsUPGOLTxdNwXRaI6O+uyL98E7yBmqWrQm0AfqUHFBiASb4gN9fYcLD2LLHBxfhJTIWl8VZHUg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/rdflib/-/rdflib-0.20.1.tgz", + "integrity": "sha512-5G1t7rURqPdsYE+mjpDW2e9/H19XFMuChTIjPFPtH2tI0fma+xlJYwrSgmd1aOm45SOYKpqwJfTSIvFpmP2n+g==", "requires": { "async": "^0.9.x", "jsonld": "^0.4.5", "n3": "^0.4.1", - "solid-auth-client": "^2.2.3", + "solid-auth-cli": "^0.1.12", + "solid-auth-client": "^2.3.0", "xmldom": "^0.1.22" + }, + "dependencies": { + "solid-auth-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/solid-auth-client/-/solid-auth-client-2.3.0.tgz", + "integrity": "sha512-+9RfqC64oWpH4afoWYhCtieuAziFyWiiW/isB9XEV6N+yb2jZ69UCJxpX7iBCpxK0j36bDeisdbp0zHRp/F46A==", + "requires": { + "@babel/runtime": "^7.0.0", + "@solid/oidc-rp": "^0.8.0", + "auth-header": "^1.0.0", + "commander": "^2.11.0", + "isomorphic-fetch": "^2.2.1" + } + } + } + }, + "readable-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readable-error/-/readable-error-1.0.0.tgz", + "integrity": "sha512-CLnInu5bUphmFiZ3pD/BC6+Cg4/BzK6ZMvWfd0b2QMzYo159Z/f/nVFQ9L5IeMrqUxy0EFsp3XJ+BRfLfY13IQ==", + "requires": { + "readable-stream": "^2.3.3" } }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3993,7 +4248,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "2.3.2", @@ -4256,7 +4512,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true }, "micromatch": { "version": "3.1.10", @@ -4339,6 +4596,7 @@ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, + "optional": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" @@ -4382,19 +4640,22 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true + "dev": true, + "optional": true }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true + "dev": true, + "optional": true }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "dev": true, + "optional": true }, "repeating": { "version": "2.0.1", @@ -4468,7 +4729,8 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "dev": true, + "optional": true }, "restore-cursor": { "version": "1.0.1", @@ -4484,7 +4746,8 @@ "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "dev": true, + "optional": true }, "rimraf": { "version": "2.6.2", @@ -4526,6 +4789,7 @@ "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, + "optional": true, "requires": { "ret": "~0.1.10" } @@ -4538,14 +4802,14 @@ "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -4558,6 +4822,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -4592,6 +4857,7 @@ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, + "optional": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -4608,6 +4874,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -4617,6 +4884,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -4688,7 +4956,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true } } }, @@ -4702,6 +4971,51 @@ "kind-of": "^3.2.0" } }, + "solid-auth-cli": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/solid-auth-cli/-/solid-auth-cli-0.1.13.tgz", + "integrity": "sha512-mRZ4eaWniGd9jLRzSWD3n9EwMfTcmwlfXK5MggIzSQOyXa1XJCWxHnJDL0DWTX5sMi1ovYa6RI4u6TNOzsEZiw==", + "requires": { + "@solid/cli": "^0.1.0", + "async": "^2.6.1", + "file-fetch": "^1.1.1", + "fs": "0.0.1-security", + "isomorphic-fetch": "^2.2.1", + "jsonld": "^1.4.0", + "n3": "^1.0.3" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } + }, + "jsonld": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.6.0.tgz", + "integrity": "sha512-gtbEplGXOgSrD7fP0vCmZoYX35MIQqFrpiMfJkEs9KwT7+bNzEd9y9gAUK8SGYXIYJISQCWNU5/eSDPKKxXeHw==", + "requires": { + "rdf-canonize": "^1.0.2", + "request": "^2.88.0", + "semver": "^5.6.0", + "xmldom": "0.1.19" + } + }, + "n3": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.1.1.tgz", + "integrity": "sha512-GEJXn+wc0f4l2noP1N/rMUH9Gei1DQ8IDN03eBsH+uQKkNQUOLgL7ZJVaDjY+pP3LmbLxL1LpUg/AvZ7Kc7KVw==" + }, + "xmldom": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.19.tgz", + "integrity": "sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=" + } + } + }, "solid-auth-client": { "version": "2.2.13", "resolved": "https://registry.npmjs.org/solid-auth-client/-/solid-auth-client-2.2.13.tgz", @@ -4735,6 +5049,7 @@ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, + "optional": true, "requires": { "atob": "^2.1.1", "decode-uri-component": "^0.2.0", @@ -4756,13 +5071,15 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true + "dev": true, + "optional": true }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, + "optional": true, "requires": { "extend-shallow": "^3.0.0" } @@ -4774,9 +5091,9 @@ "dev": true }, "sshpk": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", - "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -4844,6 +5161,7 @@ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, + "optional": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -4854,6 +5172,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -4875,7 +5194,6 @@ "version": "1.1.1", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -4992,6 +5310,7 @@ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" } @@ -5001,6 +5320,7 @@ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, + "optional": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -5086,14 +5406,14 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, + "optional": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -5106,6 +5426,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -5115,6 +5436,7 @@ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -5135,6 +5457,7 @@ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, + "optional": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -5145,6 +5468,7 @@ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, + "optional": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -5156,6 +5480,7 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, + "optional": true, "requires": { "isarray": "1.0.0" } @@ -5166,13 +5491,15 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "dev": true, + "optional": true }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true } } }, @@ -5188,13 +5515,15 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "dev": true, + "optional": true }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true + "dev": true, + "optional": true }, "user-home": { "version": "1.1.1", @@ -5205,8 +5534,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "v8flags": { "version": "2.1.1", diff --git a/src/chat/infinite.js b/src/chat/infinite.js index 32d6c47d1..e31106d6b 100644 --- a/src/chat/infinite.js +++ b/src/chat/infinite.js @@ -7,6 +7,8 @@ /* global alert */ import DateFolder from './dateFolder' +// @@ trace20190428T1745 + const SERVER_MKDIRP_BUG = true const UI = { @@ -30,6 +32,42 @@ const bookmarks = require('./bookmarks') // module.exports = module.exports || {} // module.exports.infiniteMessageArea = + + + +async function createIfNotExists (doc, contentType = 'text/turtle', data = '') { + const fetcher = UI.store.fetcher + try { + var response = await fetcher.load(doc) + } catch (err) { + if (err.response.status === 404) { + console.log('createIfNotExists: doc does NOT exist, will create... ' + doc) + try { + response = await fetcher.webOperation('PUT', doc.uri, {data, contentType}) + } catch (err) { + console.log('createIfNotExists doc FAILED: ' + doc + ': ' + err) + throw err + } + delete fetcher.requested[doc.uri] // delete cached 404 error + // console.log('createIfNotExists doc created ok ' + doc) + return response + } else { + console.log('createIfNotExists doc load error NOT 404: ' + doc + ': ' + err) + throw err + } + } + // console.log('createIfNotExists: doc exists, all good: ' + doc) + return response +} + + + + + + + + + export function infiniteMessageArea (dom, kb, chatChannel, options) { kb = kb || UI.store const ns = UI.ns @@ -58,51 +96,22 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { var me var updater = UI.store.updater -/* - var mention = function mention (message, style) { - console.log(message) - var pre = dom.createElement('pre') - pre.setAttribute('style', style || 'color: grey;') - pre.appendChild(dom.createTextNode(message)) - statusArea.appendChild(pre) - } - var announce = { - log: function (message) { mention(message, 'color: #111;') }, - warn: function (message) { mention(message, 'color: #880;') }, - error: function (message) { mention(message, 'color: #800;') } - } -*/ - /** Create a resource if it really does not exist - * Be absolutely sure something does not exist before creating a new empty file - * as otherwise existing could be deleted. - * @param doc {NamedNode} - The resource - */ - function createIfNotExists (doc) { - return new Promise(function (resolve, reject) { - kb.fetcher.load(doc).then(response => { - console.log('createIfNotExists doc exists, all good ' + doc) - // kb.fetcher.webOperation('HEAD', doc.uri).then(response => { - resolve(response) - }, err => { - if (err.response.status === 404) { - console.log('createIfNotExists doc does NOT exist, will create... ' + doc) - - kb.fetcher.webOperation('PUT', doc.uri, {data: '', contentType: 'text/turtle'}).then(response => { - // fetcher.requested[doc.uri] = 'done' // do not need to read ?? but no headers - delete kb.fetcher.requested[doc.uri] // delete cached 404 error - console.log('createIfNotExists doc created ok ' + doc) - resolve(response) - }, err => { - console.log('createIfNotExists doc FAILED: ' + doc + ': ' + err) - reject(err) - }) - } else { - console.log('createIfNotExists doc load error NOT 404: ' + doc + ': ' + err) - reject(err) - } - }) - }) + /** Does a file exist on the web? + * @returns {Boolean} + */ + async function documentExists (doc) { + try { + await kb.fetcher.load(doc) + } catch (err) { + if (err.response.status === 404) { + return false + } else { + console.log('documentExists: doc load error NOT 404: ' + doc + ': ' + err) + throw err + } + } + return true } /* Form for a new message @@ -118,55 +127,55 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { form.AJAR_date = '9999-01-01T00:00:00Z' // ISO format for field sort var field, sendButton - function sendMessage (text) { + async function sendMessage (text) { var now = new Date() - addNewTableIfNewDay(now).then(() => { + await addNewTableIfNewDay(now) + + if (!text) { + field.setAttribute('style', messageBodyStyle + 'color: #bbb;') // pendingedit + field.disabled = true + } + var sts = [] + var timestamp = '' + now.getTime() + var dateStamp = $rdf.term(now) + let chatDocument = dateFolder.leafDocumentFromDate(now) + + var message = kb.sym(chatDocument.uri + '#' + 'Msg' + timestamp) + var content = kb.literal(text || field.value) + // if (text) field.value = text No - don't destroy half-finsihed user input + + sts.push(new $rdf.Statement(chatChannel, ns.wf('message'), message, chatDocument)) + sts.push(new $rdf.Statement(message, ns.sioc('content'), content, chatDocument)) + sts.push(new $rdf.Statement(message, DCT('created'), dateStamp, chatDocument)) + if (me) sts.push(new $rdf.Statement(message, ns.foaf('maker'), me, chatDocument)) + + function sendComplete () { + var bindings = { '?msg': message, + '?content': content, + '?date': dateStamp, + '?creator': me} + var tr = renderMessage(liveMessageTable, bindings, false, options, userContext) // not green + if (!text) { - field.setAttribute('style', messageBodyStyle + 'color: #bbb;') // pendingedit - field.disabled = true - } - var sts = [] - var timestamp = '' + now.getTime() - var dateStamp = $rdf.term(now) - let chatDocument = dateFolder.leafDocumentFromDate(now) - - var message = kb.sym(chatDocument.uri + '#' + 'Msg' + timestamp) - var content = kb.literal(text || field.value) - // if (text) field.value = text No - don't destroy half-finsihed user input - - sts.push(new $rdf.Statement(chatChannel, ns.wf('message'), message, chatDocument)) - sts.push(new $rdf.Statement(message, ns.sioc('content'), content, chatDocument)) - sts.push(new $rdf.Statement(message, DCT('created'), dateStamp, chatDocument)) - if (me) sts.push(new $rdf.Statement(message, ns.foaf('maker'), me, chatDocument)) - - var sendComplete = function (uri, success, body) { - if (!success) { - form.appendChild(UI.widgets.errorMessageBlock( - dom, 'Error writing message: ' + body)) - } else { - var bindings = { '?msg': message, - '?content': content, - '?date': dateStamp, - '?creator': me} - var tr = renderMessage(liveMessageTable, bindings, false, options, userContext) // not green - if (options.newestFirst === true) { - messageTable.insertBefore(tr, messageTable.firstChild) // If newestFirst - } else { - messageTable.appendChild(tr) // not newestFirst - } - - if (!text) { - field.value = '' // clear from out for reuse - field.setAttribute('style', messageBodyStyle) - field.disabled = false - field.scrollIntoView(newestFirst) // allign bottom (top) - field.focus() // Start typing next line immediately - field.select() - } + field.value = '' // clear from out for reuse + field.setAttribute('style', messageBodyStyle) + field.disabled = false + field.scrollIntoView(newestFirst) // allign bottom (top) + field.focus() // Start typing next line immediately + field.select() } - } - updater.update([], sts, sendComplete) - }) // then + } + if (SERVER_MKDIRP_BUG && (kb.fetcher.requested[chatDocument.uri] === undefined || kb.fetcher.requested[chatDocument.uri] === 404)) { + console.log('@@@ SERVER_MKDIRP_BUG: Should only happen once: create chat file: ' + chatDocument) + await createIfNotExists(chatDocument) + } + try { + await updater.update([], sts) + } catch (err) { + form.appendChild(UI.widgets.errorMessageBlock(dom, 'Error writing message: ' + err)) + return + } + sendComplete() } // sendMessage form.appendChild(dom.createElement('br')) @@ -175,14 +184,16 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { function droppedFileHandler (files) { let base = messageTable.chatDocument.dir().uri UI.widgets.uploadFiles(kb.fetcher, files, base + 'Files', base + 'Pictures', - function (theFile, destURI) { // @@@@@@ Wait for eachif several - sendMessage(destURI) + async function (theFile, destURI) { // @@@@@@ Wait for eachif several + await sendMessage(destURI) }) } // When a set of URIs are dropped on the field - var droppedURIHandler = function (uris) { - sendMessage(uris[0]) // @@@@@ wait + var droppedURIHandler = async function (uris) { + for (var uri of uris) { + await sendMessage(uri) + } } // When we are actually logged on @@ -205,10 +216,10 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { field.setAttribute('style', messageBodyStyle + 'background-color: #eef;') // Trap the Enter BEFORE it is used ti make a newline - field.addEventListener('keydown', function (e) { // User preference? + field.addEventListener('keydown', async function (e) { // User preference? if (e.keyCode === 13) { if (!e.altKey) { // Alt-Enter just adds a new line - sendMessage() + await sendMessage() } } }, false) @@ -226,9 +237,9 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { imageDoc = kb.sym(chatDocument.dir().uri + 'Image_' + Date.now() + '.png') return imageDoc } - function tookPicture (imageDoc) { + async function tookPicture (imageDoc) { if (imageDoc) { - sendMessage(imageDoc.uri) + await sendMessage(imageDoc.uri) } } middle.appendChild(UI.media.cameraButton(dom, kb, getImageDoc, tookPicture)) @@ -292,11 +303,6 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { '?content': kb.any(message, ns.sioc('content')) } var tr = renderMessage(messageTable, bindings, messageTable.fresh, options, userContext) // fresh from elsewhere - if (options.newestFirst === true) { - messageTable.insertBefore(tr, messageTable.firstChild) // If newestFirst - } else { - messageTable.appendChild(tr) // not newestFirst - } } // //////// @@ -556,6 +562,7 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { /* Don't actually make the documemnt until a message is sent @@@@@ WHEN SERVER FIXED * currently server won't patch to a file ina non-existent directory */ + /* if (SERVER_MKDIRP_BUG) { try { await createIfNotExists(chatDocument) @@ -565,6 +572,7 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { return } } + */ /// /////////////////////////////////////////////////////////// const messageTable = await createMessageTable(now, true) div.appendChild(messageTable) diff --git a/src/chat/messageTools.js b/src/chat/messageTools.js index db075b233..93912bb6d 100644 --- a/src/chat/messageTools.js +++ b/src/chat/messageTools.js @@ -126,11 +126,18 @@ export function messageToolbar (message, messageRow, userContext) { div.appendChild(deleteButton) } // if mine - // Things anyone can do if they have a bookmark list - var bookmarkButton = bookmarks.renderBookmarksButton(userContext) - if (bookmarkButton) { - div.appendChild(bookmarkButton) - } + // Things anyone can do if they have a bookmark list async + /* + var bookmarkButton = await bookmarks.renderBookmarksButton(userContext) + if (bookmarkButton) { + div.appendChild(bookmarkButton) + } + */ + // Things anyone can do if they have a bookmark list + + bookmarks.renderBookmarksButton(userContext).then((bookmarkButton) => { + if (bookmarkButton) div.appendChild(bookmarkButton) + }) /** Button to allow user to express a sentiment (like, endorse, etc) about a target * From d0d508c8f22cbb7d8b8f0a7e4e8e58e5b8b6072f Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Thu, 23 May 2019 09:32:54 -0400 Subject: [PATCH 09/35] not tested, refactored --- src/chat/dateFolder.js | 15 +++-- src/widgets/index.js | 121 ++++++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 64 deletions(-) diff --git a/src/chat/dateFolder.js b/src/chat/dateFolder.js index eff8e2722..12e719fde 100644 --- a/src/chat/dateFolder.js +++ b/src/chat/dateFolder.js @@ -1,17 +1,15 @@ -/* Track back through the YYYY/MM/DD tree to find the previous/next day +/** Track back through the YYYY/MM/DD tree to find the previous/next day ** */ const kb = require('../store.js') -// import store from '../store' -// import ns from '../ns' -// const kb = store const ns = require('../ns.js') + module.exports = class DateFolder { constructor (rootThing, leafFileName, membershipProperty) { this.root = rootThing this.rootFolder = rootThing.dir() - this.leafFileName = leafFileName + this.leafFileName = leafFileName || 'index.ttl' // typically chat.ttl this.membershipProperty = membershipProperty || ns.wf('leafObject') } @@ -39,6 +37,7 @@ module.exports = class DateFolder { } async loadPrevious (date, backwards) { + const thisDateFolder = this async function previousPeriod (file, level) { function younger (x) { if (backwards ? x.uri >= file.uri : x.uri <= file.uri) return false // later than we want or same -- looking for different @@ -57,7 +56,7 @@ module.exports = class DateFolder { if (level !== 3) return siblings.pop() // only length chck final leverl while (siblings.length) { let folder = siblings.pop() - let leafDocument = kb.sym(folder.uri + 'this.leafFileName') + let leafDocument = kb.sym(folder.uri + thisDateFolder.leafFileName) await kb.fetcher.load(leafDocument) // files can have seealso links. skip ones with no leafObjects with a date if (kb.statementsMatching(null, ns.dct('created'), null, leafDocument).length > 0) { @@ -87,8 +86,8 @@ module.exports = class DateFolder { let folder = this.leafDocumentFromDate(date).dir() let found = await previousPeriod(folder, 3) if (found) { - let doc = kb.sym(found.uri + 'this.leafFileName') - return this.dateFromleafDocument(doc) + let doc = kb.sym(found.uri + this.leafFileNam) + return this.dateFromLeafDocument(doc) } return null } // loadPrevious diff --git a/src/widgets/index.js b/src/widgets/index.js index f6940b1b4..ccc55cd17 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -181,10 +181,68 @@ UI.widgets.imagesOf = function (x, kb) { // Best logo or avater or photo etc to represent someone or some group etc // +UI.widgets.iconForClass = { // Potentially extendable by other apps, panes, etc + // Relative URIs to the iconBase + 'solid:AppProviderClass': 'noun_144.svg', // @@ classs name should not contain 'Class' + 'solid:AppProvider': 'noun_15177.svg', // @@ + 'vcard:Group': 'noun_339237.svg', + 'rdfs:Class': 'noun_339237.svg', // @@ Make different from group! Icon??? + 'vcard:Organization': 'noun_143899.svg', + 'vcard:Individual': 'noun_15059.svg', + 'schema:Person': 'noun_15059.svg', + 'foaf:Person': 'noun_15059.svg', + 'vcard:AddressBook': 'noun_15695.svg', + 'trip:Trip': 'noun_581629.svg', + 'meeting:Meeting': 'noun_66617.svg', + 'meeting:LongChat': 'noun_1689339.svg', + 'ui:Form': 'noun_122196.svg' +} + +UI.widgets.findImageByClass = function findImageByClass (x) { + const kb = UI.store + const ns = UI.ns + const iconDir = UI.icons.iconBase + const types = kb.findTypeURIs(x) + if (ns.solid('AppProvider').uri in types) { + return iconDir + 'noun_15177.svg' // App + } + if (x.uri) { + if (x.uri.split('/').length === 4 && !(x.uri.split('/')[1]) && !(x.uri.split('/')[3])) { + return iconDir + 'noun_15177.svg' // App -- this is an origin + } + // Non-HTTP URI types imply types + if (x.uri.startsWith('message:') || x.uri.startsWith('mid:')) { // message: is aapple bug-- should be mid: + return iconDir + 'noun_480183.svg' // envelope noun_567486 + } + if (x.uri.startsWith('mailto:')) { + return iconDir + 'noun_567486.svg' // mailbox - an email desitination + } + // For HTTP(s) documents, we could look at the MIME type if we know it. + if (x.uri.startsWith('https:') && (x.uri.indexOf('#') < 0)) { + return x.site().uri + 'favicon.ico' + // Todo: make the docuent icon a fallback for if the favicon does not exist + // todo: pick up a possible favicon for the web page istelf from a link + // was: return iconDir + 'noun_681601.svg' // document - under solid assumptions + } + } + + for (var k in UI.widgets.iconForClass) { + let pref = k.split(':')[0] + let id = k.split(':')[1] + let klass = ns[pref](id) + if (klass.uri in types) { // Allow full URI in new additions + return $rdf.uri.join(UI.widgets.iconForClass[k], UI.icons.iconBase) + } + } + return iconDir + 'noun_10636_grey.svg' // Gret Circle - some thing +} + +// @@ Also add icons for *properties* like home, work, email, range, domain, comment, + UI.widgets.setImage = function (element, x) { - var kb = UI.store - var ns = UI.ns - var iconDir = UI.icons.iconBase + const kb = UI.store + const ns = UI.ns + const iconDir = UI.icons.iconBase var findImage = function (x) { if (x.sameTerm(ns.foaf('Agent')) || x.sameTerm(ns.rdf('Resource'))) { return iconDir + 'noun_98053.svg' // Globe @@ -198,61 +256,11 @@ UI.widgets.setImage = function (element, x) { return image ? image.uri : null } - var findImageByClass = function (x) { - var types = kb.findTypeURIs(x) - if (ns.solid('AppProvider').uri in types) { - return iconDir + 'noun_15177.svg' // App - } - if (x.uri) { - if (x.uri.split('/').length === 4 && !(x.uri.split('/')[1]) && !(x.uri.split('/')[3])) { - return iconDir + 'noun_15177.svg' // App -- this is an origin - } - // Non-HTTP URI types imply types - if (x.uri.startsWith('message:') || x.uri.startsWith('mid:')) { // message: is aapple bug-- should be mid: - return iconDir + 'noun_480183.svg' // envelope noun_567486 - } - if (x.uri.startsWith('mailto:')) { - return iconDir + 'noun_567486.svg' // mailbox - an email desitination - } - // For HTTP(s) documents, we could look at the MIME type if we know it. - if (x.uri.startsWith('https:') && (x.uri.indexOf('#') < 0)) { - return x.site().uri + 'favicon.ico' - // Todo: make the docuent icon a fallback for if the favicon does not exist - // todo: pick up a possible favicon for the web page istelf from a link - // was: return iconDir + 'noun_681601.svg' // document - under solid assumptions - } - } - if (ns.solid('AppProviderClass').uri in types) { - return iconDir + 'noun_144.svg' // App Whitelist @@@ ICON (three apps ?) - } - if (ns.vcard('Group').uri in types || ns.rdfs('Class').uri in types) { - return iconDir + 'noun_339237.svg' // Group of people - } - if (ns.vcard('Organization').uri in types || ns.rdfs('Class').uri in types) { - return iconDir + 'noun_143899.svg' // Org r or Com - } - if (ns.vcard('Individual').uri in types || - ns.foaf('Person').uri in types) { - return iconDir + 'noun_15059.svg' // Person - } - if (ns.vcard('AddressBook').uri in types) { - return iconDir + 'noun_15695.svg' - } - // http://www.w3.org/ns/pim/trip#Trip - if ('http://www.w3.org/ns/pim/trip#Trip' in types) { - return iconDir + 'noun_581629.svg' // Plane taking off - } - if (ns.meeting('Meeting').uri in types) { - return iconDir + 'noun_66617.svg' - } - return iconDir + 'noun_10636_grey.svg' // Circle - some thing - } - var uri = findImage(x) - element.setAttribute('src', uri || findImageByClass(x)) + element.setAttribute('src', uri || UI.widgets.findImageByClass(x)) if (!uri && x.uri) { kb.fetcher.nowOrWhenFetched(x.doc(), undefined, function (ok) { - element.setAttribute('src', findImage(x) || findImageByClass(x)) + element.setAttribute('src', findImage(x) || UI.widgets.findImageByClass(x)) }) } } @@ -460,6 +468,7 @@ UI.widgets.personTR = function (dom, pred, obj, options) { UI.widgets.makeDraggable(tr, obj) } } + tr.subject = obj return tr } @@ -918,7 +927,7 @@ UI.widgets.field[UI.ns.ui('PhoneField').uri] = } var is = ds.map(st => $rdf.st(st.subject, st.predicate, result, st.why)) // can include >1 doc if (is.length === 0) { // or none - is = [ $rdf.st(subject, property, result, store)] + is = [$rdf.st(subject, property, result, store)] } function updateMany (ds, is, callback) { From eecb36d41542b7e6ee19095fee66b47d1cd48203 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Sat, 1 Jun 2019 11:21:30 +0100 Subject: [PATCH 10/35] Robot and padlock icons --- src/icons/noun_Robot_849764.svg | 1 + src/icons/noun_locked_2160665_000000.svg | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/icons/noun_Robot_849764.svg create mode 100644 src/icons/noun_locked_2160665_000000.svg diff --git a/src/icons/noun_Robot_849764.svg b/src/icons/noun_Robot_849764.svg new file mode 100644 index 000000000..4e3b341eb --- /dev/null +++ b/src/icons/noun_Robot_849764.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/noun_locked_2160665_000000.svg b/src/icons/noun_locked_2160665_000000.svg new file mode 100644 index 000000000..17ed0e643 --- /dev/null +++ b/src/icons/noun_locked_2160665_000000.svg @@ -0,0 +1 @@ + \ No newline at end of file From a96a10d66b7e1ad9baf0356d7c40293a8e595dc4 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Sat, 1 Jun 2019 12:10:33 +0100 Subject: [PATCH 11/35] New larger simple padlock --- src/icons/padlock-timbl.svg | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/icons/padlock-timbl.svg diff --git a/src/icons/padlock-timbl.svg b/src/icons/padlock-timbl.svg new file mode 100644 index 000000000..41f53b752 --- /dev/null +++ b/src/icons/padlock-timbl.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 735e10a75f905fda027056c2f4aceaecc9ee17db Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Mon, 3 Jun 2019 16:30:20 -0400 Subject: [PATCH 12/35] ACL control plus basically working --- src/acl-control.js | 117 +++++++++++++++++++++++++++++++----- src/icons/padlock-timbl.svg | 10 +-- src/widgets/index.js | 27 +++++++-- 3 files changed, 129 insertions(+), 25 deletions(-) diff --git a/src/acl-control.js b/src/acl-control.js index 918faada1..4d9ebaccf 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -81,11 +81,14 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { // A world button can be dragged to gve public access. // later, allow it to be pressed to make pubicly viewable? - var publicAccessCell = bottomRow.appendChild(dom.createElement('td')) - var publicAccessButton = publicAccessCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_98053.svg', 'Public')) - UI.widgets.makeDraggable(publicAccessButton, UI.ns.foaf('Agent')) // Represent everyone + var bottomLeftCell = bottomRow.appendChild(dom.createElement('td')) + // var bottomMiddleCell = bottomRow.appendChild(dom.createElement('td')) + var bottomRightCell = bottomRow.appendChild(dom.createElement('td')) + + // var publicAccessButton = bottomLeftCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_98053.svg', 'Public')) + + const bigButtonStyle = 'border-radius: 0.3em; background-color: white; border: 0.1em solid #888;' - var bigButtonStyle = 'border-radius: 0.3em; background-color: white; border: 0.1em solid #888;' // This is the main function which produces an editable access control. // There are two of these in all iff the defaults are separate @@ -162,7 +165,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { if (ns.vcard('Group').uri in types) { return {pred: 'agentGroup', obj: obj} // @@ note vcard membership not RDFs } - if (obj.sameTerm(ns.foaf('Agent')) || + if (obj.sameTerm(ns.foaf('Agent')) || obj.sameTerm(ns.acl('AuthenticatedAgent')) || // AuthenticatedAgent obj.sameTerm(ns.rdf('Resource')) || obj.sameTerm(ns.owl('Thing'))) { return {pred: 'agentClass', obj: obj} } @@ -328,9 +331,14 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { } var res = agentTriage(u) // eg 'agent', 'origin', agentClass' + const thing = $rdf.sym(u) if (!res) { - console.log(' looking up dropped thing ' + u) - await kb.fetcher.load(u) + console.log(' Not obvious: looking up dropped thing ' + thing) + try { + await kb.fetcher.load(thing.doc()) + } catch (err) { + console.log('Ignore error looking up dropped thing: ' + err) + } res = agentTriage(u) if (!res) { console.log(' Error: Drop fails to drop appropriate thing! ' + u) @@ -342,10 +350,18 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { } }// handleOneDroppedURI + async function addNewUIRI (uri) { + await handleOneDroppedURI(uri) + saveAndRestoreUI() + } + if (options.modify) { + row.addNewURI = addNewUIRI UI.widgets.makeDropTarget(row, handleManyDroppedURIs) } - } + return row + } // renderCombo + var syncPanel = function () { var kids = box.children for (var i = 0; i < kids.length; i++) { @@ -355,13 +371,84 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { } // @@ later -- need to addd combos not in the box? } - var k, combo + + function renderAdditionTool (ele, lastRow) { + const ns = UI.ns + function removeBar () { + ele.removeChild(ele.bar) + ele.bar = null + } + if (ele.bar) { // toggle + return removeBar() + } + const bar = ele.appendChild(dom.createElement('div')) + ele.bar = bar + + var personButton = UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Individual'], 'Add Person') + bar.appendChild(personButton) + + var groupButton = UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Group'], 'Add Group') + bar.appendChild(groupButton) + + bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['foaf:Agent'], 'Add Everyone', async event => { + statusBlock.textContent = 'Adding the general public to those who can read. Drag the globe to a different level to give them more access.' + await lastRow.addNewURI(ns.foaf('Agent').uri) + })) + + // AuthenticatedAgent + bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_99101.svg', 'Anyone logged In', async event => { + statusBlock.textContent = 'Adding the anyone logged in to those who can read. Drag the ID icon to a different level to give them more access.' + await lastRow.addNewURI(ns.acl('AuthenticatedAgent').uri) + })) + + // Bots + bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_Robot_849764.svg', 'A Software Agent (bot)', async event => { + let name = await UI.widgets.askName(dom, kb, bar, null , ns.schema('WebApplication'), 'webapp') + if (!name) return removeBar() // user cancelled + const domainNameRegexp = /^https?:/i + if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element + return alert('Not a http URI') + } + // @@ check it actually is a bot and has an owner who agrees they own it + console.log('Adding to ACL bot: ' + name) + await lastRow.addNewURI(name) + })) + + // Web Apps + bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_15177.svg', 'A Web App (origin)', async event => { + console.log('@@ AppButton') + let name = await UI.widgets.askName(dom, kb, bar, null , ns.schema('WebApplication'), 'webapp') + if (!name) return removeBar() // user cancelled + const domainNameRegexp = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i + // https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html + if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element + return alert('Not a domain name') + } + const origin = 'https://' + name + console.log('Adding to ACL origin: ' + origin) + await lastRow.addNewURI(origin) + })) + } + + function renderAddToolBar (box, lastRow) { + const toolRow = box.appendChild(dom.createElement('tr')) + var addButton = bottomLeftCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_34653_green.svg', 'Add ...', event => { + renderAdditionTool(bottomLeftCell, lastRow) + })) + } + + var k, combo, lastRow for (k = 15; k > 0; k--) { combo = kToCombo(k) if ((options.modify && recommended[k]) || byCombo[combo]) { - renderCombo(byCombo, combo) + lastRow = renderCombo(byCombo, combo) } // if } // for + + if (options.modify) { + renderAddToolBar(box, lastRow) + } + return byCombo } // ACLControlEditable @@ -389,7 +476,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { ACLControlEditable(box, doc, targetACLDoc, kb2, {modify: false}) // Add btton to save them as actual box.style.cssText = 'color: #777;' - var editPlease = bottomRow.appendChild(dom.createElement('button')) + var editPlease = bottomRightCell.appendChild(dom.createElement('button')) editPlease.textContent = 'Set specific sharing\nfor this ' + noun editPlease.style.cssText = bigButtonStyle editPlease.addEventListener('click', function (event) { @@ -399,11 +486,11 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { statusBlock.textContent += ' (Error writing back access control file: ' + message + ')' } else { kb.add(kb2.statements) - fetcher.requested[targetACLDoc.uri] === 'done' // cheat + kb.fetcher.requested[targetACLDoc.uri] = 'done' // cheat // kb.fetcher.load(targetACLDoc, {force: true}) statusBlock.textContent = ' (Now editing specific access for this ' + noun + ')' // box.style.cssText = 'color: black;' - bottomRow.removeChild(editPlease) + bottomRightCell.removeChild(editPlease) renderBox() } }) @@ -412,7 +499,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { } else { // Not using defaults var useDefault var addDefaultButton = function (prospectiveDefaultHolder) { - useDefault = bottomRow.appendChild(dom.createElement('button')) + useDefault = bottomRightCell.appendChild(dom.createElement('button')) useDefault.textContent = 'Stop specific sharing for this ' + noun + ' -- just use default' // + UI.utils.label(thisDefaultHolder) if (prospectiveDefaultHolder) { @@ -475,7 +562,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { box.notice.textContent = 'Sharing for things within the folder currently tracks sharing for the folder.' box.notice.style.cssText = 'font-size: 80%; color: #888;' var splitButton = UI.widgets.clearElement(box.offer).appendChild(dom.createElement('button')) - splitButton.innerHTML = '

the sharing of folder contets
separately from the sharing for the folder

' + splitButton.innerHTML = '

Set the sharing of folder contets
separately from the sharing for the folder

' splitButton.style.cssText = bigButtonStyle splitButton.addEventListener('click', function (e) { box.addControlForDefaults() diff --git a/src/icons/padlock-timbl.svg b/src/icons/padlock-timbl.svg index 41f53b752..832a79ce5 100644 --- a/src/icons/padlock-timbl.svg +++ b/src/icons/padlock-timbl.svg @@ -1,8 +1,8 @@ - - - - - + + + + + \ No newline at end of file diff --git a/src/widgets/index.js b/src/widgets/index.js index ccc55cd17..38bc70877 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -191,6 +191,9 @@ UI.widgets.iconForClass = { // Potentially extendable by other apps, panes, etc 'vcard:Individual': 'noun_15059.svg', 'schema:Person': 'noun_15059.svg', 'foaf:Person': 'noun_15059.svg', + 'foaf:Agent': 'noun_98053.svg', + 'acl:AuthenticatedAgent': 'noun_99101.svg', + 'prov:SoftwareAgent': 'noun_Robot_849764.svg', // Bot 'vcard:AddressBook': 'noun_15695.svg', 'trip:Trip': 'noun_581629.svg', 'meeting:Meeting': 'noun_66617.svg', @@ -198,6 +201,19 @@ UI.widgets.iconForClass = { // Potentially extendable by other apps, panes, etc 'ui:Form': 'noun_122196.svg' } +var tempSite = function (x) { // use only while one in rdflib fails with origins 2019 + var str = x.uri.split('#')[0] + var p = str.indexOf('//') + if (p < 0) throw new Error('This URI does not have a web site part (origin)') + var q = str.indexOf('/', p+2) + if (q < 0) { // no third slash? + return str.slice(0) + '/' // Add slash to a bare origin + } else { + return str.slice(0, q + 1) + } + } + + UI.widgets.findImageByClass = function findImageByClass (x) { const kb = UI.store const ns = UI.ns @@ -219,22 +235,23 @@ UI.widgets.findImageByClass = function findImageByClass (x) { } // For HTTP(s) documents, we could look at the MIME type if we know it. if (x.uri.startsWith('https:') && (x.uri.indexOf('#') < 0)) { - return x.site().uri + 'favicon.ico' + return tempSite(x) + 'favicon.ico' // was x.site().uri + ... // Todo: make the docuent icon a fallback for if the favicon does not exist // todo: pick up a possible favicon for the web page istelf from a link // was: return iconDir + 'noun_681601.svg' // document - under solid assumptions } } + ns['prov'] = $rdf.Namespace('http://www.w3.org/ns/prov#') // In case not yet there for (var k in UI.widgets.iconForClass) { let pref = k.split(':')[0] let id = k.split(':')[1] let klass = ns[pref](id) - if (klass.uri in types) { // Allow full URI in new additions + if (klass.uri in types || klass.uri === x.uri) { // Allow full URI in new additions return $rdf.uri.join(UI.widgets.iconForClass[k], UI.icons.iconBase) } } - return iconDir + 'noun_10636_grey.svg' // Gret Circle - some thing + return iconDir + 'noun_10636_grey.svg' // Grey Circle - some thing } // @@ Also add icons for *properties* like home, work, email, range, domain, comment, @@ -278,7 +295,7 @@ var faviconOrDefault = function (dom, x) { (isOrigin(x) ? 'noun_15177.svg' : 'noun_681601.svg')) if (x.uri && x.uri.startsWith('https:') && (x.uri.indexOf('#') < 0)) { var res = dom.createElement('object') // favico with a fallback of a default image if no favicon - res.setAttribute('data', x.site().uri + 'favicon.ico') + res.setAttribute('data', tempSite(x) + 'favicon.ico') res.setAttribute('type', 'image/x-icon') res.appendChild(image) // fallback return res @@ -390,7 +407,7 @@ UI.widgets.askName = function (dom, kb, container, predicate, klass, noun) { // namefield.setAttribute('class', 'pendingedit') // namefield.disabled = true form.parentNode.removeChild(form) - resolve(namefield.value) + resolve(namefield.value.trim()) } namefield.addEventListener('keyup', function (e) { From 4a2dd59bade043dce8a438537afd31804b8cb633 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Mon, 3 Jun 2019 16:40:48 -0400 Subject: [PATCH 13/35] standard --- src/acl-control.js | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/acl-control.js b/src/acl-control.js index 4d9ebaccf..d95a6340d 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -89,7 +89,6 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { const bigButtonStyle = 'border-radius: 0.3em; background-color: white; border: 0.1em solid #888;' - // This is the main function which produces an editable access control. // There are two of these in all iff the defaults are separate // @@ -371,7 +370,6 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { } // @@ later -- need to addd combos not in the box? } - function renderAdditionTool (ele, lastRow) { const ns = UI.ns function removeBar () { @@ -384,26 +382,48 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { const bar = ele.appendChild(dom.createElement('div')) ele.bar = bar - var personButton = UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Individual'], 'Add Person') - bar.appendChild(personButton) + bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Individual'], 'Add Person', async event => { + let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp') + if (!name) return removeBar() // user cancelled + const domainNameRegexp = /^https?:/i + if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element + return alert('Not a http URI') + } + // @@ check it actually is a person and has an owner who agrees they own it + console.log('Adding to ACL person: ' + name) + await lastRow.addNewURI(name) + removeBar() + })) - var groupButton = UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Group'], 'Add Group') - bar.appendChild(groupButton) + bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Group'], 'Add Group', async event => { + let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp') + if (!name) return removeBar() // user cancelled + const domainNameRegexp = /^https?:/i + if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element + return alert('Not a http URI') + } + // @@ check it actually is a group and has an owner who agrees they own it + console.log('Adding to ACL group: ' + name) + await lastRow.addNewURI(name) + removeBar() + })) bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['foaf:Agent'], 'Add Everyone', async event => { statusBlock.textContent = 'Adding the general public to those who can read. Drag the globe to a different level to give them more access.' await lastRow.addNewURI(ns.foaf('Agent').uri) + removeBar() })) // AuthenticatedAgent bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_99101.svg', 'Anyone logged In', async event => { statusBlock.textContent = 'Adding the anyone logged in to those who can read. Drag the ID icon to a different level to give them more access.' await lastRow.addNewURI(ns.acl('AuthenticatedAgent').uri) + removeBar() })) // Bots bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_Robot_849764.svg', 'A Software Agent (bot)', async event => { - let name = await UI.widgets.askName(dom, kb, bar, null , ns.schema('WebApplication'), 'webapp') + let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp') if (!name) return removeBar() // user cancelled const domainNameRegexp = /^https?:/i if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element @@ -412,12 +432,13 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { // @@ check it actually is a bot and has an owner who agrees they own it console.log('Adding to ACL bot: ' + name) await lastRow.addNewURI(name) + removeBar() })) // Web Apps bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_15177.svg', 'A Web App (origin)', async event => { console.log('@@ AppButton') - let name = await UI.widgets.askName(dom, kb, bar, null , ns.schema('WebApplication'), 'webapp') + let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp') if (!name) return removeBar() // user cancelled const domainNameRegexp = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i // https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html @@ -427,12 +448,13 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { const origin = 'https://' + name console.log('Adding to ACL origin: ' + origin) await lastRow.addNewURI(origin) + removeBar() })) } function renderAddToolBar (box, lastRow) { - const toolRow = box.appendChild(dom.createElement('tr')) - var addButton = bottomLeftCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_34653_green.svg', 'Add ...', event => { + // const toolRow = box.appendChild(dom.createElement('tr')) + bottomLeftCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_34653_green.svg', 'Add ...', event => { renderAdditionTool(bottomLeftCell, lastRow) })) } From 64053af416b90a215a9fc1f6b0522d7352718c01 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Tue, 4 Jun 2019 20:42:47 -0400 Subject: [PATCH 14/35] webapp acls starting to work --- src/acl-control.js | 71 ++++++++++++++++++++++++++++++++++++++++---- src/widgets/index.js | 11 +++---- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/acl-control.js b/src/acl-control.js index d95a6340d..339230240 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -5,9 +5,10 @@ // Without this dropping anything onto a browser page will cause chrome etc to jump to diff page // throwing away all the user's work. -/* global alert */ +/* global alert window */ var UI = {} +UI.authn = require('./signin') UI.acl = require('./acl') UI.icons = require('./iconBase') UI.ns = require('./ns') @@ -158,6 +159,11 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { if (uri.startsWith('http') && uri.split('/').length === 3) { // there is no third slash return {pred: 'origin', obj: obj} // The only way to know an origin alas } + // @@ This is an almighty kludge needed because drag and drop adds extra slashes to origins + if (uri.startsWith('http') && uri.split('/').length === 4 && uri.endsWith('/')) { // there IS third slash + console.log('Assuming final slash on dragged origin URI was unintended!') + return {pred: 'origin', obj: $rdf.sym(uri.slice(0, -1))} // Fix a URI where the drag and drop system has added a spurious slash + } if (ns.vcard('WebID').uri in types) return {pred: 'agent', obj: obj} @@ -372,6 +378,16 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { function renderAdditionTool (ele, lastRow) { const ns = UI.ns + function removeOthers (button) { + button.keep = true + button.parentNode.keep = true + var removeThese = [] + for (var ele of bar.children) { + if (!ele.keep) removeThese.push(ele) + } + removeThese.forEach(e => bar.removeChild(e)) + } + function removeBar () { ele.removeChild(ele.bar) ele.bar = null @@ -382,8 +398,13 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { const bar = ele.appendChild(dom.createElement('div')) ele.bar = bar + /** Buttons to add different types of theings to have access + */ + + // Person bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Individual'], 'Add Person', async event => { - let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp') + removeOthers(event.target) + let name = await UI.widgets.askName(dom, kb, bar, ns.vcard('URI'), ns.vcard('Individual'), 'person') if (!name) return removeBar() // user cancelled const domainNameRegexp = /^https?:/i if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element @@ -395,8 +416,10 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { removeBar() })) + // Group bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Group'], 'Add Group', async event => { - let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp') + removeOthers(event.target) + let name = await UI.widgets.askName(dom, kb, bar, ns.vcard('URI'), ns.vcard('Group'), 'group') if (!name) return removeBar() // user cancelled const domainNameRegexp = /^https?:/i if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element @@ -408,6 +431,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { removeBar() })) + // General public bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['foaf:Agent'], 'Add Everyone', async event => { statusBlock.textContent = 'Adding the general public to those who can read. Drag the globe to a different level to give them more access.' await lastRow.addNewURI(ns.foaf('Agent').uri) @@ -423,7 +447,8 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { // Bots bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_Robot_849764.svg', 'A Software Agent (bot)', async event => { - let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp') + removeOthers(event.target) + let name = await UI.widgets.askName(dom, kb, bar, ns.vcard('URI'), ns.schema('Application'), 'bot') if (!name) return removeBar() // user cancelled const domainNameRegexp = /^https?:/i if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element @@ -437,8 +462,42 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { // Web Apps bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_15177.svg', 'A Web App (origin)', async event => { - console.log('@@ AppButton') - let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp') + removeOthers(event.target) + var context = {div: bar, dom} + await UI.authn.logInLoadProfile(context) + var trustedApps = kb.each(context.me, ns.acl('trustedApp')) + var trustedOrigins = trustedApps.flatMap(app => kb.each(app, ns.acl('origin'))) + + bar.appendChild(dom.createElement('p')).textContent = `You have ${trustedOrigins.length} selected web apps.` + var table = bar.appendChild(dom.createElement('table')) + trustedApps.forEach(app => { + const origin = kb.any(app, ns.acl('origin')) + var thingTR = UI.widgets.personTR(dom, ns.acl('origin'), origin, {}) + var innerTable = dom.createElement('table') + var innerRow = innerTable.appendChild(dom.createElement('tr')) + var innerLeft = innerRow.appendChild(dom.createElement('td')) + var innerMiddle = innerRow.appendChild(dom.createElement('td')) + var innerRight = innerRow.appendChild(dom.createElement('td')) + innerLeft.appendChild(thingTR) + innerMiddle.textContent = 'Give access to ' + noun + ' ' + UI.utils.label(subject) + '?' + innerRight.appendChild(UI.widgets.continueButton(dom, async event => { + await lastRow.addNewURI(origin.uri) + })) + table.appendChild(innerTable) + }) + table.style = 'margin: em; background-color: #eee;' + + // Add the Trusted App pane for managing you set of apps + var trustedAppControl = window.panes.trustedApplications.render(context.me, dom, {}) + trustedAppControl.style.borderColor = 'orange' + trustedAppControl.style.borderWidth = '0.1em' + trustedAppControl.style.borderRadius = '1em' + bar.appendChild(trustedAppControl) + const cancel = UI.widgets.cancelButton(dom, () => bar.removeChild(trustedAppControl)) + trustedAppControl.insertBefore(cancel, trustedAppControl.firstChild) + cancel.style.float = 'right' + + let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp domain') // @@ hack if (!name) return removeBar() // user cancelled const domainNameRegexp = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i // https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html diff --git a/src/widgets/index.js b/src/widgets/index.js index 38bc70877..c316e1a59 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -402,10 +402,9 @@ UI.widgets.askName = function (dom, kb, container, predicate, klass, noun) { namefield.select() // focus next user input form.appendChild(namefield) container.appendChild(form) + // namefield.focus() - var gotName = function () { - // namefield.setAttribute('class', 'pendingedit') - // namefield.disabled = true + function gotName () { form.parentNode.removeChild(form) resolve(namefield.value.trim()) } @@ -418,16 +417,18 @@ UI.widgets.askName = function (dom, kb, container, predicate, klass, noun) { form.appendChild(dom.createElement('br')) - var cancel = form.appendChild(UI.widgets.cancelButton(dom)) + const cancel = form.appendChild(UI.widgets.cancelButton(dom)) cancel.addEventListener('click', function (e) { form.parentNode.removeChild(form) resolve(null) }, false) - let continueButton = form.appendChild(UI.widgets.continueButton(dom)) + const continueButton = form.appendChild(UI.widgets.continueButton(dom)) continueButton.addEventListener('click', function (e) { gotName() }, false) + namefield.focus() + }) // Promise } From 2631e87540ae97bebe755904d5ae67d2bab22613 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Thu, 6 Jun 2019 08:29:38 -0400 Subject: [PATCH 15/35] standard js --- src/chat/infinite.js | 24 +- src/chat/message.js | 2 +- src/infiniteMessageArea.js | 1201 ----------------------------- src/noun_Camera_1618446_000000.js | 2 +- src/signin.js | 4 +- src/widgets/index.js | 26 +- 6 files changed, 23 insertions(+), 1236 deletions(-) delete mode 100644 src/infiniteMessageArea.js diff --git a/src/chat/infinite.js b/src/chat/infinite.js index e31106d6b..7663b273d 100644 --- a/src/chat/infinite.js +++ b/src/chat/infinite.js @@ -32,9 +32,6 @@ const bookmarks = require('./bookmarks') // module.exports = module.exports || {} // module.exports.infiniteMessageArea = - - - async function createIfNotExists (doc, contentType = 'text/turtle', data = '') { const fetcher = UI.store.fetcher try { @@ -60,14 +57,6 @@ async function createIfNotExists (doc, contentType = 'text/turtle', data = '') { return response } - - - - - - - - export function infiniteMessageArea (dom, kb, chatChannel, options) { kb = kb || UI.store const ns = UI.ns @@ -100,9 +89,10 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { /** Does a file exist on the web? * @returns {Boolean} */ + /* async function documentExists (doc) { try { - await kb.fetcher.load(doc) + await kb.fetcher.load(doc) } catch (err) { if (err.response.status === 404) { return false @@ -113,7 +103,7 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { } return true } - +*/ /* Form for a new message */ function newMessageForm (messageTable) { @@ -149,12 +139,12 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { sts.push(new $rdf.Statement(message, DCT('created'), dateStamp, chatDocument)) if (me) sts.push(new $rdf.Statement(message, ns.foaf('maker'), me, chatDocument)) - function sendComplete () { + function sendComplete () { var bindings = { '?msg': message, '?content': content, '?date': dateStamp, '?creator': me} - var tr = renderMessage(liveMessageTable, bindings, false, options, userContext) // not green + renderMessage(liveMessageTable, bindings, false, options, userContext) // not green if (!text) { field.value = '' // clear from out for reuse @@ -163,7 +153,7 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { field.scrollIntoView(newestFirst) // allign bottom (top) field.focus() // Start typing next line immediately field.select() - } + } } if (SERVER_MKDIRP_BUG && (kb.fetcher.requested[chatDocument.uri] === undefined || kb.fetcher.requested[chatDocument.uri] === 404)) { console.log('@@@ SERVER_MKDIRP_BUG: Should only happen once: create chat file: ' + chatDocument) @@ -302,7 +292,7 @@ export function infiniteMessageArea (dom, kb, chatChannel, options) { '?date': kb.any(message, DCT('created')), '?content': kb.any(message, ns.sioc('content')) } - var tr = renderMessage(messageTable, bindings, messageTable.fresh, options, userContext) // fresh from elsewhere + renderMessage(messageTable, bindings, messageTable.fresh, options, userContext) // fresh from elsewhere } // //////// diff --git a/src/chat/message.js b/src/chat/message.js index 050504ceb..0547a3b5d 100644 --- a/src/chat/message.js +++ b/src/chat/message.js @@ -1,4 +1,5 @@ +import { messageToolbar, sentimentStripLinked } from './messageTools' const UI = { authn: require('../signin'), icons: require('../iconBase'), @@ -20,7 +21,6 @@ const dom = UI.dom || window.document const messageBodyStyle = UI.style.messageBodyStyle // const { messageToolbar, sentimentStripLinked } = require('./messageTools') -import { messageToolbar, sentimentStripLinked } from './messageTools' const label = UI.utils.label export function elementForImageURI (imageUri, options) { diff --git a/src/infiniteMessageArea.js b/src/infiniteMessageArea.js deleted file mode 100644 index 72a978d46..000000000 --- a/src/infiniteMessageArea.js +++ /dev/null @@ -1,1201 +0,0 @@ -// Common code for a discussion are a of messages about something -// This version runs over a series of files for different time periods -// -// Parameters for the whole chat like its title are stred on -// index.ttl#this and the chats messages are stored in YYYY/MM/DD/chat.ttl -// -/* global alert confirm */ -var UI = { - authn: require('./signin'), - icons: require('./iconBase'), - log: require('./log'), - ns: require('./ns'), - media: require('./media-capture'), - pad: require('./pad'), - rdf: require('rdflib'), - store: require('./store'), - style: require('./style'), - utils: require('./utils'), - widgets: require('./widgets') -} - -// const utils = require('./utils') -const label = UI.utils.label - -// THE UNUSED ICONS are here as reminders for possible future functionality -const BOOKMARK_ICON = 'noun_45961.svg' -// const HEART_ICON = 'noun_130259.svg' -> Add this to my (private) favorites -// const MENU_ICON = 'noun_897914.svg' -// const PAPERCLIP_ICON = 'noun_25830.svg' -> add attachments to this message -// const PIN_ICON = 'noun_562340.svg' -> pin this message permanently in the chat UI -// const PENCIL_ICON = 'noun_253504.svg' -// const SPANNER_ICON = 'noun_344563.svg' -> settings -const THUMBS_UP_ICON = 'noun_1384132.svg' -const THUMBS_DOWN_ICON = 'noun_1384135.svg' - -module.exports = function (dom, kb, chatChannel, options) { - kb = kb || UI.store - const ns = UI.ns - const WF = $rdf.Namespace('http://www.w3.org/2005/01/wf/flow#') - const DCT = $rdf.Namespace('http://purl.org/dc/terms/') - const BOOK = $rdf.Namespace('http://www.w3.org/2002/01/bookmark#') - // const POSIX = $rdf.Namespace('http://www.w3.org/ns/posix/stat#') - - options = options || {} - - var newestFirst = options.newestFirst === '1' || options.newestFirst === true // hack for now - var colorizeByAuthor = options.colorizeByAuthor === '1' || options.colorizeByAuthor === true - - options.authorAboveContent = true - - // var participation // An object tracking users use and prefs - const messageBodyStyle = UI.style.messageBodyStyle - - // var messageBodyStyle = 'white-space: pre-wrap; width: 90%; font-size:100%; border: 0.07em solid #eee; padding: .2em 0.5em; margin: 0.1em 1em 0.1em 1em;' - // 'font-size: 100%; margin: 0.1em 1em 0.1em 1em; background-color: white; white-space: pre-wrap; padding: 0.1em;' - - var div = dom.createElement('div') - var menuButton - const statusArea = div.appendChild(dom.createElement('div')) - var userContext = {dom, statusArea, div: statusArea} // logged on state, pointers to user's stuff - var me - - var updater = UI.store.updater - - var anchor = function (text, term) { // If there is no link return an element anyway - var a = dom.createElement('a') - if (term && term.uri) { - a.setAttribute('href', term.uri) - a.addEventListener('click', UI.widgets.openHrefInOutlineMode, true) - a.setAttribute('style', 'color: #3B5998; text-decoration: none; ') // font-weight: bold - } - a.textContent = text - return a - } - - var mention = function mention (message, style) { - console.log(message) - var pre = dom.createElement('pre') - pre.setAttribute('style', style || 'color: grey;') - pre.appendChild(dom.createTextNode(message)) - statusArea.appendChild(pre) - } - - var announce = { - log: function (message) { mention(message, 'color: #111;') }, - warn: function (message) { mention(message, 'color: #880;') }, - error: function (message) { mention(message, 'color: #800;') } - } - - // @@@@ use the one in rdflib.js when it is avaiable and delete this - function updatePromise (del, ins) { - return new Promise(function (resolve, reject) { - kb.updater.update(del, ins, function (uri, ok, errorBody) { - if (!ok) { - reject(new Error(errorBody)) - } else { - resolve() - } - }) // callback - }) // promise - } - - /** Create a resource if it really does not exist - * Be absolutely sure something does not exist before creating a new empty file - * as otherwise existing could be deleted. - * @param doc {NamedNode} - The resource - */ - function createIfNotExists (doc) { - return new Promise(function (resolve, reject) { - kb.fetcher.load(doc).then(response => { - console.log('createIfNotExists doc exists, all good ' + doc) - // kb.fetcher.webOperation('HEAD', doc.uri).then(response => { - resolve(response) - }, err => { - if (err.response.status === 404) { - console.log('createIfNotExists doc does NOT exist, will create... ' + doc) - - kb.fetcher.webOperation('PUT', doc.uri, {data: '', contentType: 'text/turtle'}).then(response => { - // fetcher.requested[doc.uri] = 'done' // do not need to read ?? but no headers - delete kb.fetcher.requested[doc.uri] // delete cached 404 error - console.log('createIfNotExists doc created ok ' + doc) - resolve(response) - }, err => { - console.log('createIfNotExists doc FAILED: ' + doc + ': ' + err) - reject(err) - }) - } else { - console.log('createIfNotExists doc load error NOT 404: ' + doc + ': ' + err) - reject(err) - } - }) - }) - } - - /* Bookmarking - */ -/** Find a user's bookmarks -*/ - async function findBookmarkDocument (context) { - const klass = BOOK('Bookmark') - const fileTail = 'bookmarks.ttl' - const isPublic = true - - await UI.authn.findAppInstances(context, klass, isPublic) // public -- only look for public links - if (context.instances && context.instances.length > 0) { - context.bookmarkDocument = context.instances[0] - if (context.instances.length > 1) { - alert('More than one bookmark file! ' + context.instances) - } - } else { - if (userContext.publicProfile) { // publicProfile or preferencesFile - var newBookmarkFile = $rdf.sym(userContext.publicProfile.dir().uri + fileTail) - try { - console.log('Creating new bookmark file ' + newBookmarkFile) - await createIfNotExists(newBookmarkFile) - } catch (e) { - announce.error('Can\'t make fresh bookmark file:' + e) - return context - } - await UI.authn.registerInTypeIndex(userContext, newBookmarkFile, klass, true) // public - context.bookmarkDocument = newBookmarkFile - } else { - alert('You seem to have no bookmark file and not even a profile file.') - } - } - return context - } - - /** Add a bookmark - */ - - async function addBookmark (context, chatChannel) { - /* like -@prefix terms: . -@prefix bookm: . -@prefix n0: . -<> terms:references <#0.5534145389246576>. -<#0.5534145389246576> - a bookm:Bookmark; - terms:created "2019-01-26T20:26:44.374Z"^^XML:dateTime; - terms:title "Herons"; - bookm:recalls wiki:Heron; - n0:maker c:me. - */ - var title = '' - var author = kb.any(chatChannel, ns.foaf('maker')) - title = label(author) + ': ' + - kb.anyValue(chatChannel, ns.sioc('content')).slice(0, 80) // @@ add chat title too? - const bookmarkDoc = context.bookmarkDocument - const bookmark = UI.widgets.newThing(bookmarkDoc, title) - const ins = [ - $rdf.st(bookmarkDoc, UI.ns.dct('references'), bookmark, bookmarkDoc), - $rdf.st(bookmark, UI.ns.rdf('type'), BOOK('Bookmark'), bookmarkDoc), - $rdf.st(bookmark, UI.ns.dct('created'), new Date(), bookmarkDoc), - $rdf.st(bookmark, BOOK('recalls'), chatChannel, bookmarkDoc), - $rdf.st(bookmark, UI.ns.foaf('maker'), me, bookmarkDoc), - $rdf.st(bookmark, UI.ns.dct('title'), title, bookmarkDoc) - ] - try { - await updatePromise([], ins) // 20190118A - } catch (e) { - let msg = 'Making bookmark: ' + e - announce.error(msg) - return null - } - return bookmark - } - - /* Emoji in Unicode - */ - - var emoji = {} - emoji[ns.schema('AgreeAction')] = '👍' - emoji[ns.schema('DisagreeAction')] = '👎' - emoji[ns.schema('EndorseAction')] = '⭐️' - emoji[ns.schema('LikeAction')] = '❤️' - - /* Strip of sentiments expressed - */ - function sentimentStrip (target, doc) { - const actions = kb.each(null, ns.schema('target'), target, doc) - const sentiments = actions.map(a => kb.any(a, ns.rdf('type'), null, doc)) - sentiments.sort() - const strings = sentiments.map(x => emoji[x] || '') - return dom.createTextNode(strings.join(' ')) - } - /** Strip of sentiments expressed - * - * @param target {NamedNode} - The thing about which they are expressed - * @param doc {NamedNode} - The document iun which they are expressed - */ - - function sentimentStripLinked (target, doc) { - var strip = dom.createElement('span') - function refresh () { - strip.innerHTML = '' - const actions = kb.each(null, ns.schema('target'), target, doc) - const sentiments = actions.map(a => [ - kb.any(a, ns.rdf('type'), null, doc), - kb.any(a, ns.schema('agent'), null, doc)]) - sentiments.sort() - sentiments.forEach(ss => { - let [klass, agent] = ss - var res - if (agent) { - res = dom.createElement('a') - res.setAttribute('href', agent.uri) - } else { - res = dom.createTextNode('') - } - res.textContent = emoji[klass] || '*' - strip.appendChild(res) - }) - } - refresh() - strip.refresh = refresh - return strip - } - - /* Tools for doing things with a message - * Let is be cretiev here. Allow all sorts of things to - * be done to a message - linking to new or old objects in an open way - * - * Ideas: Bookmark, Like, star, pin at top of chat, reply as new thread, - * If you made it originally: edit, delete, attach - */ - function messageTools (message, messageRow) { - const div = dom.createElement('div') - function closeToolbar () { - div.parentElement.parentElement.removeChild(div.parentElement) // remive the TR - } - - async function setBookmarkButtonColor () { - await kb.fetcher.load(userContext.bookmarkDocument) - let bookmarked = kb.any(null, BOOK('recalls'), message, userContext.bookmarkDocument) - bookmarkButton.style = UI.style.buttonStyle - if (bookmarked) bookmarkButton.style.backgroundColor = 'yellow' - } - - async function deleteThingThen (x) { - let sts = kb.connectedStatements(x) - await updatePromise(sts, []) - } - - async function toggleBookmark (userContext, message) { - await kb.fetcher.load(userContext.bookmarkDocument) - let bookmarks = kb.each(null, BOOK('recalls'), message, userContext.bookmarkDocument) - if (bookmarks.length) { // delete - if (!confirm('Delete bookmark on this message?' + bookmarks.length)) return - for (let i = 0; i < bookmarks.length; i++) { - try { - await deleteThingThen(bookmarks[i]) - bookmarkButton.style.backgroundColor = 'white' - console.log('Bookmark deleted: ' + bookmarks[i]) - } catch (e) { - announce.error('Cant delete bookmark:' + e) - } - } - } else { - let bookmark = await addBookmark(userContext, message) - bookmarkButton.style.backgroundColor = 'yellow' - console.log('Bookmark added: ' + bookmark) - } - } - - // Things only the original author can do - if (me && kb.holds(message, ns.foaf('maker'), me)) { - // button to delete the message - const deleteButton = UI.widgets.deleteButtonWithCheck(dom, div, 'message', function () { - deleteMessage(message) - closeToolbar() - }) - div.appendChild(deleteButton) - } // if mine - - // Things anyone can do if they have a bookmark list - var bookmarkButton - if (userContext.bookmarkDocument) { - bookmarkButton = UI.widgets.button(dom, UI.icons.iconBase + BOOKMARK_ICON, - label(BOOK('Bookmark')), () => { - toggleBookmark(userContext, message) - }) - setBookmarkButtonColor() - div.appendChild(bookmarkButton) - } - - /** Button to allow user to express a sentiment (like, endorse, etc) about a target - * - * @param context {Object} - Provide dom and me - * @param target {NamedNode} - The thing the user expresses an opnion about - * @param icon {uristring} - The icon to be used for the button - * @param actionClass {NamedNode} - The RDF class - typically a subclass of schema:Action - * @param doc - {NamedNode} - the Solid document iunto which the data should be written - * @param mutuallyExclusive {Array} - Any RDF classes of sentimentswhich are mutiually exclusive - */ - function sentimentButton (context, target, icon, actionClass, doc, mutuallyExclusive) { - function setColor () { - button.style.backgroundColor = action ? 'yellow' : 'white' - } - var button = UI.widgets.button(dom, icon, UI.utils.label(actionClass), async function (event) { - if (action) { - await deleteThingThen(action) - action = null - setColor() - } else { // no action - action = UI.widgets.newThing(doc) - var insertMe = [ - $rdf.st(action, ns.schema('agent'), context.me, doc), - $rdf.st(action, ns.rdf('type'), actionClass, doc), - $rdf.st(action, ns.schema('target'), target, doc) - ] - await updatePromise([], insertMe) - setColor() - - if (mutuallyExclusive) { // Delete incompative sentiments - var dirty = false - for (let i = 0; i < mutuallyExclusive.length; i++) { - let a = existingAction(mutuallyExclusive[i]) - if (a) { - await deleteThingThen(a) // but how refresh? refreshTree the parent? - dirty = true - } - } - if (dirty) { - // UI.widgets.refreshTree(button.parentNode) // requires them all to be immediate siblings - UI.widgets.refreshTree(messageRow) // requires them all to be immediate siblings - } - } - } - }) - function existingAction (actionClass) { - var actions = kb.each(null, ns.schema('agent'), context.me, doc) - .filter(x => kb.holds(x, ns.rdf('type'), actionClass, doc)) - .filter(x => kb.holds(x, ns.schema('target'), target, doc)) - return actions.length ? actions[0] : null - } - function refresh () { - action = existingAction(actionClass) - setColor() - } - var action - button.refresh = refresh // If the file changes, refresh live - refresh() - return button - } - - // THUMBS_UP_ICON - // https://schema.org/AgreeAction - me = me || UI.authn.currentUser() // If already logged on - if (me) { // Things yo mnust be logged in fo - var context1 = {me, dom, div} - div.appendChild(sentimentButton(context1, message, // @@ use UI.widgets.sentimentButton - UI.icons.iconBase + THUMBS_UP_ICON, - ns.schema('AgreeAction'), - message.doc(), - [ns.schema('DisagreeAction')] - )) - // Thumbs down - div.appendChild(sentimentButton(context1, message, - UI.icons.iconBase + THUMBS_DOWN_ICON, - ns.schema('DisagreeAction'), - message.doc(), - [ns.schema('AgreeAction')] - )) - } - // X button to remove the tool UI itself - const cancelButton = div.appendChild(UI.widgets.cancelButton(dom)) - cancelButton.style.float = 'right' - cancelButton.firstChild.style.opacity = '0.3' - cancelButton.addEventListener('click', closeToolbar) - return div - } - /* Form for a new message - */ - function newMessageForm (messageTable) { - var form = dom.createElement('tr') - var lhs = dom.createElement('td') - var middle = dom.createElement('td') - var rhs = dom.createElement('td') - form.appendChild(lhs) - form.appendChild(middle) - form.appendChild(rhs) - form.AJAR_date = '9999-01-01T00:00:00Z' // ISO format for field sort - var field, sendButton - - function sendMessage (text) { - var now = new Date() - addNewTableIfNewDay(now).then(() => { - if (!text) { - field.setAttribute('style', messageBodyStyle + 'color: #bbb;') // pendingedit - field.disabled = true - } - var sts = [] - var timestamp = '' + now.getTime() - var dateStamp = $rdf.term(now) - let chatDocument = chatDocumentFromDate(now) - - var message = kb.sym(chatDocument.uri + '#' + 'Msg' + timestamp) - var content = kb.literal(text || field.value) - // if (text) field.value = text No - don't destroy half-finsihed user input - - sts.push(new $rdf.Statement(chatChannel, ns.wf('message'), message, chatDocument)) - sts.push(new $rdf.Statement(message, ns.sioc('content'), content, chatDocument)) - sts.push(new $rdf.Statement(message, DCT('created'), dateStamp, chatDocument)) - if (me) sts.push(new $rdf.Statement(message, ns.foaf('maker'), me, chatDocument)) - - var sendComplete = function (uri, success, body) { - if (!success) { - form.appendChild(UI.widgets.errorMessageBlock( - dom, 'Error writing message: ' + body)) - } else { - var bindings = { '?msg': message, - '?content': content, - '?date': dateStamp, - '?creator': me} - renderMessage(liveMessageTable, bindings, false) // not green - - if (!text) { - field.value = '' // clear from out for reuse - field.setAttribute('style', messageBodyStyle) - field.disabled = false - field.scrollIntoView(newestFirst) // allign bottom (top) - field.focus() // Start typing next line immediately - field.select() - } - } - } - updater.update([], sts, sendComplete) - }) // then - } // sendMessage - - form.appendChild(dom.createElement('br')) - - // DRAG AND DROP - function droppedFileHandler (files) { - let base = messageTable.chatDocument.dir().uri - UI.widgets.uploadFiles(kb.fetcher, files, base + 'Files', base + 'Pictures', - function (theFile, destURI) { // @@@@@@ Wait for eachif several - sendMessage(destURI) - }) - } - - // When a set of URIs are dropped on the field - var droppedURIHandler = function (uris) { - sendMessage(uris[0]) // @@@@@ wait - } - - // When we are actually logged on - function turnOnInput () { - if (options.menuHandler && menuButton) { - let menuOptions = { me, dom, div, newBase: messageTable.chatDocument.dir().uri } - menuButton.addEventListener('click', - event => { options.menuHandler(event, chatChannel, menuOptions) } - , false) - } - - // Turn on message input - creatorAndDate(lhs, me, '', null) - - field = dom.createElement('textarea') - middle.innerHTML = '' - middle.appendChild(field) - field.rows = 3 - // field.cols = 40 - field.setAttribute('style', messageBodyStyle + 'background-color: #eef;') - - // Trap the Enter BEFORE it is used ti make a newline - field.addEventListener('keydown', function (e) { // User preference? - if (e.keyCode === 13) { - if (!e.altKey) { // Alt-Enter just adds a new line - sendMessage() - } - } - }, false) - UI.widgets.makeDropTarget(field, droppedURIHandler, droppedFileHandler) - - rhs.innerHTML = '' - sendButton = UI.widgets.button(dom, UI.icons.iconBase + 'noun_383448.svg', 'Send') - sendButton.setAttribute('style', UI.style.buttonStyle + 'float: right;') - sendButton.addEventListener('click', ev => sendMessage(), false) - rhs.appendChild(sendButton) - - const chatDocument = chatDocumentFromDate(new Date()) - var imageDoc - function getImageDoc () { - imageDoc = kb.sym(chatDocument.dir().uri + 'Image_' + Date.now() + '.png') - return imageDoc - } - function tookPicture (imageDoc) { - if (imageDoc) { - sendMessage(imageDoc.uri) - } - } - middle.appendChild(UI.media.cameraButton(dom, kb, getImageDoc, tookPicture)) - - UI.pad.recordParticipation(chatChannel, chatChannel.doc()) // participation = - } // turn on inpuut - - let context = {div: middle, dom: dom} - UI.authn.logIn(context).then(context => { - me = context.me - turnOnInput() - userContext = context - findBookmarkDocument(context).then(context => { - console.log('Bookmark file: ' + context.bookmarkDocument) - }) - }) - - return form - } - - function nick (person) { - var s = UI.store.any(person, UI.ns.foaf('nick')) - if (s) return '' + s.value - return '' + label(person) - } - - function creatorAndDate (td1, creator, date, message) { - var nickAnchor = td1.appendChild(anchor(nick(creator), creator)) - if (creator.uri) { - UI.store.fetcher.nowOrWhenFetched(creator.doc(), undefined, function (ok, body) { - nickAnchor.textContent = nick(creator) - }) - } - td1.appendChild(dom.createElement('br')) - td1.appendChild(anchor(date, message)) - } - - function creatorAndDateHorizontal (td1, creator, date, message) { - var nickAnchor = td1.appendChild(anchor(label(creator), creator)) - if (creator.uri) { - UI.store.fetcher.nowOrWhenFetched(creator.doc(), undefined, function (ok, body) { - nickAnchor.textContent = nick(creator) - }) - } - const dateBit = td1.appendChild(anchor(date, message)) - dateBit.style.fontSize = '80%' - dateBit.style.marginLeft = '1em' - td1.appendChild(dom.createElement('br')) - } - - // /////////////////////////////////////////////////////////////////////// - - function syncMessages (about, messageTable) { - var displayed = {} - var ele, ele2 - for (ele = messageTable.firstChild; ele; ele = ele.nextSibling) { - if (ele.AJAR_subject) { - displayed[ele.AJAR_subject.uri] = true - } - } - - var messages = kb.statementsMatching( - about, ns.wf('message'), null, messageTable.chatDocument).map(st => { return st.object }) - var stored = {} - messages.map(function (m) { - stored[m.uri] = true - if (!displayed[m.uri]) { - addMessage(m, messageTable) - } - }) - - for (ele = messageTable.firstChild; ele;) { - ele2 = ele.nextSibling - if (ele.AJAR_subject && !stored[ele.AJAR_subject.uri]) { - messageTable.removeChild(ele) - } - ele = ele2 - } - for (ele = messageTable.firstChild; ele; ele = ele.nextSibling) { - if (ele.AJAR_subject) { // Refresh thumbs up etc - UI.widgets.refreshTree(ele) // Things inside may have changed too - } - } - } // syncMessages - - var deleteMessage = function (message) { - var deletions = kb.statementsMatching(message).concat( - kb.statementsMatching(undefined, undefined, message)) - updater.update(deletions, [], function (uri, ok, body) { - if (!ok) { - announce.error('Cant delete messages:' + body) - } else { - syncMessages(chatChannel, liveMessageTable) - } - }) - } - - var addMessage = function (message, messageTable) { - var bindings = { - '?msg': message, - '?creator': kb.any(message, ns.foaf('maker')), - '?date': kb.any(message, DCT('created')), - '?content': kb.any(message, ns.sioc('content')) - } - renderMessage(messageTable, bindings, messageTable.fresh) // fresh from elsewhere - } - - function elementForImageURI (imageUri, options) { - let img = dom.createElement('img') - let height = '10' - if (options.inlineImageHeightEms) { - height = ('' + options.inlineImageHeightEms).trim() - } - img.setAttribute('style', 'max-height: ' + height + 'em; border-radius: 1em; margin: 0.7em;') - // UI.widgets.makeDropTarget(img, handleURIsDroppedOnMugshot, droppedFileHandler) - if (imageUri) img.setAttribute('src', imageUri) - let anchor = dom.createElement('a') - anchor.setAttribute('href', imageUri) - anchor.setAttribute('target', 'images') - anchor.appendChild(img) - UI.widgets.makeDraggable(img, $rdf.sym(imageUri)) - return anchor - } - - function renderMessage (messageTable, bindings, fresh) { - var creator = bindings['?creator'] - var message = bindings['?msg'] - var date = bindings['?date'] - var content = bindings['?content'] - - var dateString = date.value - var messageRow = dom.createElement('tr') - messageRow.AJAR_date = dateString - messageRow.AJAR_subject = message - - if (options.selectedMessage && options.selectedMessage.sameTerm(message)) { - messageRow.style.backgroundColor = 'yellow' - options.selectedElement = messageRow - messageTable.selectedElement = messageRow - } - - var done = false - for (var ele = messageTable.firstChild; ; ele = ele.nextSibling) { - if (!ele) { // empty - break - } - if (((dateString > ele.AJAR_date) && newestFirst) || - ((dateString < ele.AJAR_date) && !newestFirst)) { - messageTable.insertBefore(messageRow, ele) - done = true - break - } - } - if (!done) { - messageTable.appendChild(messageRow) - } - - var td1 = dom.createElement('td') - messageRow.appendChild(td1) - if (options.authorAboveContent) { - let img = dom.createElement('img') - img.setAttribute('style', 'max-height: 2.5em; max-width: 2.5em; border-radius: 0.5em; margin: auto;') - UI.widgets.setImage(img, creator) - td1.appendChild(img) - } else { - creatorAndDate(td1, creator, UI.widgets.shortDate(dateString), message) - } - - // Render the content ot the message itself - var td2 = messageRow.appendChild(dom.createElement('td')) - - if (options.authorAboveContent) { - creatorAndDateHorizontal(td2, creator, UI.widgets.shortDate(dateString), message) - } - let text = content.value.trim() - let isURI = (/^https?:\/[^ <>]*$/i).test(text) - let para = null - if (isURI) { - var isImage = (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(text) // @@ Should use content-type not URI - if (isImage && options.expandImagesInline) { - let img = elementForImageURI(text, options) - td2.appendChild(img) - } else { // Link but not Image - let anc = td2.appendChild(dom.createElement('a')) - para = anc.appendChild(dom.createElement('p')) - anc.href = text - para.textContent = text - td2.appendChild(anc) - } - } else { // text - para = dom.createElement('p') - td2.appendChild(para) - para.textContent = text - } - if (para) { - var bgcolor = colorizeByAuthor - ? UI.pad.lightColorHash(creator) - : (fresh ? '#e8ffe8' : 'white') - para.setAttribute('style', messageBodyStyle + 'background-color: ' + bgcolor + ';') - } - // Sentiment strip - const strip = sentimentStripLinked(message, message.doc()) - if (strip.children.length) { - td2.appendChild(dom.createElement('br')) - td2.appendChild(strip) - } - - // Message tool bar button - var td3 = dom.createElement('td') - messageRow.appendChild(td3) - var toolsButton = UI.widgets.button(dom, UI.icons.iconBase + 'noun_243787.svg', '...') - td3.appendChild(toolsButton) - toolsButton.addEventListener('click', function (e) { - if (messageRow.toolTR) { // already got a toolbar? Toogle - messageRow.parentNode.removeChild(messageRow.toolTR) - delete messageRow.toolTR - return - } - const toolsTR = dom.createElement('tr') - const tools = messageTools(message, messageRow) - tools.style = 'border: 0.05em solid #888; border-radius: 0 0 0.7em 0.7em; border-top: 0; height:3.5em; background-color: #fff;' // @@ fix - if (messageRow.nextSibling) { - messageRow.parentElement.insertBefore(toolsTR, messageRow.nextSibling) - } else { - messageRow.parentElement.appendChild(toolsTR) - } - messageRow.toolTR = toolsTR - toolsTR.appendChild(dom.createElement('td')) // left - const toolsTD = toolsTR.appendChild(dom.createElement('td')) - toolsTR.appendChild(dom.createElement('td')) // right - toolsTD.appendChild(tools) - }) - } - - /* Add a new messageTable at the top/bottom - */ - async function insertPreviousMessages (backwards) { - let extremity = backwards ? earliest : latest - let date = extremity.messageTable.date// day in mssecs - - date = await loadPrevious(date, backwards) // backwards - console.log(`insertPreviousMessages: from ${backwards ? 'backwards' : 'forwards'} loadPrevious: ${date}`) - if (!date && !backwards && !liveMessageTable) { - await appendCurrentMessages() // If necessary skip to today and add that - } - if (!date) return true // done - var live = false - if (!backwards) { - let todayDoc = chatDocumentFromDate(new Date()) - let doc = chatDocumentFromDate(date) - live = doc.sameTerm(todayDoc) // Is this todays? - } - let newMessageTable = await createMessageTable(date, live) - extremity.messageTable = newMessageTable // move pointer to earliest - if (backwards ? newestFirst : !newestFirst) { // put on bottom or top - div.appendChild(newMessageTable) - } else { // put on top as we scroll back - div.insertBefore(newMessageTable, div.firstChild) - } - return live // not done - } - /* Remove message tables earlier than this one - */ - function removePreviousMessages (backwards, messageTable) { - if (backwards ? newestFirst : !newestFirst) { // it was put on bottom - while (messageTable.nextSibling) { - div.removeChild(messageTable.nextSibling) - } - } else { // it was put on top as we scroll back - while (messageTable.previousSibling) { - div.removeChild(messageTable.previousSibling) - } - } - let extr = backwards ? earliest : latest - extr.messageTable = messageTable - } - - /* Generate the chat document (rdf object) from date - * @returns: - document - */ - function chatDocumentFromDate (date) { - let isoDate = date.toISOString() // Like "2018-05-07T17:42:46.576Z" - var path = isoDate.split('T')[0].replace(/-/g, '/') // Like "2018/05/07" - path = chatChannel.dir().uri + path + '/chat.ttl' - return $rdf.sym(path) - } - - /* Generate a date object from the chat file name - */ - function dateFromChatDocument (doc) { - const head = chatChannel.dir().uri.length - const str = doc.uri.slice(head, head + 10).replace(/\//g, '-') - // let date = new Date(str + 'Z') // GMT - but fails in FF - invalid format :-( - let date = new Date(str) // not explicitly UTC but is assumed so in spec - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse - console.log('Date for ' + doc + ':' + date.toISOString()) - return date - } - - /* LOad and render message table - ** @returns DOM element generates - */ - async function createMessageTable (date, live) { - console.log(' createMessageTable for ' + date) - const chatDocument = chatDocumentFromDate(date) - try { - await kb.fetcher.load(chatDocument) - } catch (err) { - let messageTable = (dom.createElement('table')) - let statusTR = messageTable.appendChild(dom.createElement('tr')) // ### find status in exception - if (err.response && err.response.status && err.response.status === 404) { - console.log('Error 404 for chat file ' + chatDocument) - statusTR.appendChild(UI.widgets.errorMessageBlock(dom, 'no messages', 'white')) - } else { - console.log('*** Error NON 404 for chat file ' + chatDocument) - statusTR.appendChild(UI.widgets.errorMessageBlock(dom, err, 'pink')) - } - return statusTR - } - return renderMessageTable(date, live) - } - - function renderMessageTable (date, live) { - var scrollBackButton - var scrollForwardButton - -/// ///////////////// Scrooll down adding more above - - async function extendBackwards () { - let done = await insertPreviousMessages(true) - if (done) { - scrollBackButton.firstChild.setAttribute('src', UI.icons.iconBase + 'noun_T-Block_1114655_000000.svg') // T - scrollBackButton.disabled = true - messageTable.initial = true - } else { - messageTable.extendedBack = true - } - setScrollBackButtonIcon() - return done - } - function setScrollBackButtonIcon () { - let sense = messageTable.extendedBack ? !newestFirst : newestFirst - let scrollBackIcon = messageTable.initial ? 'noun_T-Block_1114655_000000.svg' - : (sense ? 'noun_1369241.svg' : 'noun_1369237.svg') - scrollBackButton.firstChild.setAttribute('src', UI.icons.iconBase + scrollBackIcon) - } - async function scrollBackButtonHandler (event) { - if (messageTable.extendedBack) { - removePreviousMessages(true, messageTable) - messageTable.extendedBack = false - setScrollBackButtonIcon() - } else { - await extendBackwards() - } - } - - /// ////////////// Scroll up adding more below - - async function extendForwards () { - let done = await insertPreviousMessages(false) - if (done) { - scrollForwardButton.firstChild.setAttribute('src', UI.icons.iconBase + 'noun_T-Block_1114655_000000.svg') - scrollForwardButton.disabled = true - messageTable.final = true - } else { - messageTable.extendedForwards = true - } - setScrollForwardButtonIcon() - return done - } - function setScrollForwardButtonIcon () { - let sense = messageTable.extendedForwards ? !newestFirst : newestFirst // noun_T-Block_1114657_000000.svg - let scrollForwardIcon = messageTable.final ? 'noun_T-Block_1114657_000000.svg' - : (!sense ? 'noun_1369241.svg' : 'noun_1369237.svg') - scrollForwardButton.firstChild.setAttribute('src', UI.icons.iconBase + scrollForwardIcon) - } - async function scrollForwardButtonHandler (event) { - if (messageTable.extendedForwards) { - removePreviousMessages(false, messageTable) - messageTable.extendedForwards = false - setScrollForwardButtonIcon() - } else { - await extendForwards() // async - latest.messageTable.scrollIntoView(newestFirst) - } - } - - /// /////////////////////// - - var messageTable = dom.createElement('table') - - messageTable.extendBackwards = extendBackwards // Make function available to scroll stuff - messageTable.extendForwards = extendForwards // Make function available to scroll stuff - // var messageButton - messageTable.date = date - var chatDocument = chatDocumentFromDate(date) - messageTable.chatDocument = chatDocument - - messageTable.fresh = false - messageTable.setAttribute('style', 'width: 100%;') // fill that div! - - if (live) { - messageTable.final = true - liveMessageTable = messageTable - latest.messageTable = messageTable - var tr = newMessageForm(messageTable) - if (newestFirst) { - messageTable.insertBefore(tr, messageTable.firstChild) // If newestFirst - } else { - messageTable.appendChild(tr) // not newestFirst - } - messageTable.inputRow = tr - } - - /// ///// Infinite scroll - // - // @@ listen for swipe past end event not just button - if (options.infinite) { - let scrollBackButtonTR = dom.createElement('tr') - let scrollBackButtonCell = scrollBackButtonTR.appendChild(dom.createElement('td')) - // up traingles: noun_1369237.svg - // down triangles: noun_1369241.svg - let scrollBackIcon = newestFirst ? 'noun_1369241.svg' : 'noun_1369237.svg' // down and up arrows respoctively - scrollBackButton = UI.widgets.button(dom, UI.icons.iconBase + scrollBackIcon, 'Previous messages ...') - scrollBackButtonCell.style = 'width:3em; height:3em;' - scrollBackButton.addEventListener('click', scrollBackButtonHandler, false) - messageTable.extendedBack = false - scrollBackButtonCell.appendChild(scrollBackButton) - setScrollBackButtonIcon() - - let dateCell = scrollBackButtonTR.appendChild(dom.createElement('td')) - dateCell.style = 'text-align: center; vertical-align: middle; color: #888; font-style: italic;' - dateCell.textContent = UI.widgets.shortDate(date.toISOString(), true) // no time, only date - - // @@@@@@@@@@@ todo move this button to other end of message cell, o - let scrollForwardButtonCell = scrollBackButtonTR.appendChild(dom.createElement('td')) - let scrollForwardIcon = newestFirst ? 'noun_1369241.svg' : 'noun_1369237.svg' // down and up arrows respoctively - scrollForwardButton = UI.widgets.button(dom, UI.icons.iconBase + scrollForwardIcon, 'Later messages ...') - scrollForwardButtonCell.appendChild(scrollForwardButton) - scrollForwardButtonCell.style = 'width:3em; height:3em;' - scrollForwardButton.addEventListener('click', scrollForwardButtonHandler, false) - messageTable.extendedForward = false - setScrollForwardButtonIcon() - - messageTable.extendedForwards = false - - if (!newestFirst) { // opposite end from the entry field - messageTable.insertBefore(scrollBackButtonTR, messageTable.firstChild) // If not newestFirst - } else { - messageTable.appendChild(scrollBackButtonTR) // newestFirst - } - } - - let sts = kb.statementsMatching(null, WF('message'), null, chatDocument) - if (!live && sts.length === 0) { // not todays - // no need buttomns at the moment - // messageTable.style.visibility = 'collapse' // Hide files with no messages - } - sts.forEach(st => { - addMessage(st.object, messageTable) - }) - messageTable.fresh = true - - // loadMessageTable(messageTable, chatDocument) - messageTable.fresh = false - return messageTable - } // renderMessageTable - -/* Track back through the YYYY/MM/DD tree to find the previous/next day -** -*/ - async function loadPrevious (date, backwards) { - async function previousPeriod (file, level) { - function younger (x) { - if (backwards ? x.uri >= file.uri : x.uri <= file.uri) return false // later than we want or same -- looking for different - return true - } - function suitable (x) { - let tail = x.uri.slice(0, -1).split('/').slice(-1)[0] - if (!'0123456789'.includes(tail[0])) return false // not numeric - return true - // return kb.anyValue(chatDocument, POSIX('size')) !== 0 // empty file? - } - async function lastNonEmpty (siblings) { - siblings = siblings.filter(suitable) - siblings.sort() // chronological order - if (!backwards) siblings.reverse() - if (level !== 3) return siblings.pop() // only length chck final leverl - while (siblings.length) { - let folder = siblings.pop() - let chatDocument = kb.sym(folder.uri + 'chat.ttl') - await kb.fetcher.load(chatDocument) - // files can have seealso links. skip ones with no messages with a date - if (kb.statementsMatching(null, DCT('created'), null, chatDocument).length > 0) { - return folder - } - } - return null - } - // console.log(' previousPeriod level' + level + ' file ' + file) - const parent = file.dir() - await kb.fetcher.load(parent) - var siblings = kb.each(parent, ns.ldp('contains')) - siblings = siblings.filter(younger) - let folder = await lastNonEmpty(siblings) - if (folder) return folder - - if (level === 0) return null // 3:day, 2:month, 1: year 0: no - - const uncle = await previousPeriod(parent, level - 1) - if (!uncle) return null // reached first ever - await kb.fetcher.load(uncle) - var cousins = kb.each(uncle, ns.ldp('contains')) - let result = await lastNonEmpty(cousins) - return result - } // previousPeriod - - let folder = chatDocumentFromDate(date).dir() - let found = await previousPeriod(folder, 3) - if (found) { - let doc = kb.sym(found.uri + 'chat.ttl') - return dateFromChatDocument(doc) - } - return null - } - - async function addNewTableIfNewDay (now) { - // let now = new Date() - // @@ Remove listener from previous table as it is now static - let newChatDocument = chatDocumentFromDate(now) - if (!newChatDocument.sameTerm(latest.messageTable.chatDocument)) { // It is a new day - if (liveMessageTable.inputRow) { - liveMessageTable.removeChild(liveMessageTable.inputRow) - delete liveMessageTable.inputRow - } - var oldChatDocument = latest.messageTable.chatDocument - await appendCurrentMessages() - // Adding a link in the document will ping listeners to add the new block too - if (!kb.holds(oldChatDocument, ns.rdfs('seeAlso'), newChatDocument, oldChatDocument)) { - let sts = [$rdf.st(oldChatDocument, ns.rdfs('seeAlso'), newChatDocument, oldChatDocument)] - updater.update([], sts, function (ok, body) { - if (!ok) { - alert('Unable to link old message block to new one.' + body) - } - }) - } - } - } -/* - function messageCount () { - var n = 0 - const tables = div.children - for (let i = 0; i < tables.length; i++) { - n += tables[i].children.length - 1 - // console.log(' table length:' + tables[i].children.length) - } - return n - } -*/ -/* Add the live message block with entry field for today -*/ - async function appendCurrentMessages () { - var now = new Date() - var chatDocument = chatDocumentFromDate(now) - try { - await createIfNotExists(chatDocument) - } catch (e) { - div.appendChild(UI.widgets.errorMessageBlock( - dom, 'Problem accessing chat file: ' + e)) - return - } - const messageTable = await createMessageTable(now, true) - div.appendChild(messageTable) - div.refresh = function () { // only the last messageTable is live - addNewTableIfNewDay(new Date()).then(() => { syncMessages(chatChannel, messageTable) }) - } // The short chat version fors live update in the pane but we do it in the widget - kb.updater.addDownstreamChangeListener(chatDocument, div.refresh) // Live update - liveMessageTable = messageTable - latest.messageTable = liveMessageTable - return messageTable - } - - var liveMessageTable - var earliest = {messageTable: null} // Stuff about each end of the loaded days - var latest = {messageTable: null} - - var lock = false - async function loadMoreWhereNeeded (event, fixScroll) { - if (lock) return - lock = true - const freeze = !fixScroll - const magicZone = 150 - // const top = div.scrollTop - // const bottom = div.scrollHeight - top - div.clientHeight - var done - - while (div.scrollTop < magicZone && - earliest.messageTable && - !earliest.messageTable.initial && - earliest.messageTable.extendBackwards) { - let scrollBottom = div.scrollHeight - div.scrollTop - console.log('infinite scroll: adding above: top ' + div.scrollTop) - done = await earliest.messageTable.extendBackwards() - if (freeze) { - div.scrollTop = div.scrollHeight - scrollBottom - } - if (fixScroll) fixScroll() - if (done) break - } - while (options.selectedMessage && // we started in the middle not at the bottom - div.scrollHeight - div.scrollTop - div.clientHeight < magicZone && // we are scrolled right to the bottom - latest.messageTable && - !latest.messageTable.final && // there is more data to come - latest.messageTable.extendForwards) { - let scrollTop = div.scrollTop - console.log('infinite scroll: adding below: bottom: ' + (div.scrollHeight - div.scrollTop - div.clientHeight)) - done = await latest.messageTable.extendForwards() // then add more data on the bottom - if (freeze) { - div.scrollTop = scrollTop // while adding below keep same things in view - } - if (fixScroll) fixScroll() - if (done) break - } - lock = false - } - - async function go () { - function yank () { - selectedMessageTable.selectedElement.scrollIntoView({block: 'center'}) - } - - // During initial load ONLY keep scroll to selected thing or bottom - function fixScroll () { - if (options.selectedElement) { - options.selectedElement.scrollIntoView({block: 'center'}) // allign tops or bopttoms - } else { - liveMessageTable.inputRow.scrollIntoView(newestFirst) // allign tops or bopttoms - } - } - - var live - if (options.selectedMessage) { - var selectedDocument = options.selectedMessage.doc() - var now = new Date() - var todayDocument = chatDocumentFromDate(now) - live = todayDocument.sameTerm(selectedDocument) - } - if (options.selectedMessage && !live) { - var selectedDate = dateFromChatDocument(selectedDocument) - var selectedMessageTable = await createMessageTable(selectedDate, live) - div.appendChild(selectedMessageTable) - earliest.messageTable = selectedMessageTable - latest.messageTable = selectedMessageTable - yank() - setTimeout(yank, 1000) // @@ kludge - restore position distubed by other cHANGES - } else { // Live end - await appendCurrentMessages() - earliest.messageTable = liveMessageTable - latest.messageTable = liveMessageTable - } - - await loadMoreWhereNeeded(null, fixScroll) - div.addEventListener('scroll', loadMoreWhereNeeded) - if (options.solo) { - document.body.addEventListener('scroll', loadMoreWhereNeeded) - } - } - go() - return div -} diff --git a/src/noun_Camera_1618446_000000.js b/src/noun_Camera_1618446_000000.js index 6d97e7929..c547e782f 100644 --- a/src/noun_Camera_1618446_000000.js +++ b/src/noun_Camera_1618446_000000.js @@ -1 +1 @@ -module.exports = 'data:image/svg;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTAwIDEwMCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PHBhdGggZD0iTTcyLjkyNiwzNC4zMzNINjIuNDc4bC0yLjUtNS42NjZINDAuMDIybC0yLjUsNS42NjZoLTIuMjc1di0zLjcwN2gtNnYzLjcwN2gtMi4xNzNjLTEuOTMsMC0zLjUsMS41Ny0zLjUsMy41djMwICAgYzAsMS45MywxLjU3LDMuNSwzLjUsMy41aDQ1Ljg1MmMxLjkzLDAsMy41LTEuNTcsMy41LTMuNXYtMzBDNzYuNDI2LDM1LjkwMyw3NC44NTUsMzQuMzMzLDcyLjkyNiwzNC4zMzN6IE03My40MjYsNjcuODMzICAgYzAsMC4yNzUtMC4yMjUsMC41LTAuNSwwLjVIMjcuMDc0Yy0wLjI3NSwwLTAuNS0wLjIyNS0wLjUtMC41di0zMGMwLTAuMjc1LDAuMjI1LTAuNSwwLjUtMC41aDEyLjQwM2wyLjUtNS42NjZoMTYuMDQ1bDIuNSw1LjY2NiAgIGgxMi40MDNjMC4yNzUsMCwwLjUsMC4yMjUsMC41LDAuNVY2Ny44MzN6Ij48L3BhdGg+PHBhdGggZD0iTTUwLDM4Ljc2NmMtNy4yOTgsMC0xMy4yMzQsNS45MzctMTMuMjM0LDEzLjIzNFM0Mi43MDIsNjUuMjM0LDUwLDY1LjIzNFM2My4yMzQsNTkuMjk4LDYzLjIzNCw1MlM1Ny4yOTgsMzguNzY2LDUwLDM4Ljc2NiAgIHogTTUwLDYyLjIzNGMtNS42NDQsMC0xMC4yMzQtNC41OTEtMTAuMjM0LTEwLjIzNFM0NC4zNTYsNDEuNzY2LDUwLDQxLjc2NlM2MC4yMzQsNDYuMzU2LDYwLjIzNCw1MlM1NS42NDQsNjIuMjM0LDUwLDYyLjIzNHoiPjwvcGF0aD48cGF0aCBkPSJNNTAuMDAxLDQzLjI0MmMtNC44MywwLTguNzYsMy45MjktOC43Niw4Ljc1OHMzLjkzLDguNzU4LDguNzYsOC43NThjNC44MjksMCw4Ljc1OC0zLjkyOSw4Ljc1OC04Ljc1OCAgIFM1NC44Myw0My4yNDIsNTAuMDAxLDQzLjI0MnogTTUwLjAwMSw1OS43NThjLTQuMjc4LDAtNy43Ni0zLjQ4LTcuNzYtNy43NThzMy40ODEtNy43NTgsNy43Ni03Ljc1OCAgIGM0LjI3NywwLDcuNzU4LDMuNDgsNy43NTgsNy43NThTNTQuMjc4LDU5Ljc1OCw1MC4wMDEsNTkuNzU4eiI+PC9wYXRoPjwvZz48L3N2Zz4=' +module.exports = 'data:image/svg;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTAwIDEwMCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PHBhdGggZD0iTTcyLjkyNiwzNC4zMzNINjIuNDc4bC0yLjUtNS42NjZINDAuMDIybC0yLjUsNS42NjZoLTIuMjc1di0zLjcwN2gtNnYzLjcwN2gtMi4xNzNjLTEuOTMsMC0zLjUsMS41Ny0zLjUsMy41djMwICAgYzAsMS45MywxLjU3LDMuNSwzLjUsMy41aDQ1Ljg1MmMxLjkzLDAsMy41LTEuNTcsMy41LTMuNXYtMzBDNzYuNDI2LDM1LjkwMyw3NC44NTUsMzQuMzMzLDcyLjkyNiwzNC4zMzN6IE03My40MjYsNjcuODMzICAgYzAsMC4yNzUtMC4yMjUsMC41LTAuNSwwLjVIMjcuMDc0Yy0wLjI3NSwwLTAuNS0wLjIyNS0wLjUtMC41di0zMGMwLTAuMjc1LDAuMjI1LTAuNSwwLjUtMC41aDEyLjQwM2wyLjUtNS42NjZoMTYuMDQ1bDIuNSw1LjY2NiAgIGgxMi40MDNjMC4yNzUsMCwwLjUsMC4yMjUsMC41LDAuNVY2Ny44MzN6Ij48L3BhdGg+PHBhdGggZD0iTTUwLDM4Ljc2NmMtNy4yOTgsMC0xMy4yMzQsNS45MzctMTMuMjM0LDEzLjIzNFM0Mi43MDIsNjUuMjM0LDUwLDY1LjIzNFM2My4yMzQsNTkuMjk4LDYzLjIzNCw1MlM1Ny4yOTgsMzguNzY2LDUwLDM4Ljc2NiAgIHogTTUwLDYyLjIzNGMtNS42NDQsMC0xMC4yMzQtNC41OTEtMTAuMjM0LTEwLjIzNFM0NC4zNTYsNDEuNzY2LDUwLDQxLjc2NlM2MC4yMzQsNDYuMzU2LDYwLjIzNCw1MlM1NS42NDQsNjIuMjM0LDUwLDYyLjIzNHoiPjwvcGF0aD48cGF0aCBkPSJNNTAuMDAxLDQzLjI0MmMtNC44MywwLTguNzYsMy45MjktOC43Niw4Ljc1OHMzLjkzLDguNzU4LDguNzYsOC43NThjNC44MjksMCw4Ljc1OC0zLjkyOSw4Ljc1OC04Ljc1OCAgIFM1NC44Myw0My4yNDIsNTAuMDAxLDQzLjI0MnogTTUwLjAwMSw1OS43NThjLTQuMjc4LDAtNy43Ni0zLjQ4LTcuNzYtNy43NThzMy40ODEtNy43NTgsNy43Ni03Ljc1OCAgIGM0LjI3NywwLDcuNzU4LDMuNDgsNy43NTgsNy43NThTNTQuMjc4LDU5Ljc1OCw1MC4wMDEsNTkuNzU4eiI+PC9wYXRoPjwvZz48L3N2Zz4=' diff --git a/src/signin.js b/src/signin.js index eb14ec20c..9299b82c7 100644 --- a/src/signin.js +++ b/src/signin.js @@ -11,7 +11,7 @@ // const Solid = require('solid-client') const SolidTls = require('solid-auth-tls') const $rdf = require('rdflib') -const error = require('./widgets/error') +// const error = require('./widgets/error') const widgets = require('./widgets/index') // const utils = require('./utils') const solidAuthClient = require('solid-auth-client') @@ -447,7 +447,7 @@ async function ensureOneTypeIndex (context, isPublic) { try { await loadOneTypeIndex(context, isPublic) - console.log('ensureOneTypeIndex: Type index exists already ' + isPublic ? context.index.public[0] : context.index.private[0] ) + console.log('ensureOneTypeIndex: Type index exists already ' + isPublic ? context.index.public[0] : context.index.private[0]) return context } catch (error) { await makeIndexIfNecesary(context, isPublic) diff --git a/src/widgets/index.js b/src/widgets/index.js index c316e1a59..61e7a4f72 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -202,17 +202,16 @@ UI.widgets.iconForClass = { // Potentially extendable by other apps, panes, etc } var tempSite = function (x) { // use only while one in rdflib fails with origins 2019 - var str = x.uri.split('#')[0] - var p = str.indexOf('//') - if (p < 0) throw new Error('This URI does not have a web site part (origin)') - var q = str.indexOf('/', p+2) - if (q < 0) { // no third slash? - return str.slice(0) + '/' // Add slash to a bare origin - } else { - return str.slice(0, q + 1) - } - } - + var str = x.uri.split('#')[0] + var p = str.indexOf('//') + if (p < 0) throw new Error('This URI does not have a web site part (origin)') + var q = str.indexOf('/', p + 2) + if (q < 0) { // no third slash? + return str.slice(0) + '/' // Add slash to a bare origin + } else { + return str.slice(0, q + 1) + } +} UI.widgets.findImageByClass = function findImageByClass (x) { const kb = UI.store @@ -247,7 +246,7 @@ UI.widgets.findImageByClass = function findImageByClass (x) { let pref = k.split(':')[0] let id = k.split(':')[1] let klass = ns[pref](id) - if (klass.uri in types || klass.uri === x.uri) { // Allow full URI in new additions + if (klass.uri in types || klass.uri === x.uri) { // Allow full URI in new additions return $rdf.uri.join(UI.widgets.iconForClass[k], UI.icons.iconBase) } } @@ -295,7 +294,7 @@ var faviconOrDefault = function (dom, x) { (isOrigin(x) ? 'noun_15177.svg' : 'noun_681601.svg')) if (x.uri && x.uri.startsWith('https:') && (x.uri.indexOf('#') < 0)) { var res = dom.createElement('object') // favico with a fallback of a default image if no favicon - res.setAttribute('data', tempSite(x) + 'favicon.ico') + res.setAttribute('data', tempSite(x) + 'favicon.ico') res.setAttribute('type', 'image/x-icon') res.appendChild(image) // fallback return res @@ -428,7 +427,6 @@ UI.widgets.askName = function (dom, kb, container, predicate, klass, noun) { gotName() }, false) namefield.focus() - }) // Promise } From c3eb166628330f4cfa80bbf347bf752a7bcf3299 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Tue, 30 Jul 2019 23:16:10 -0400 Subject: [PATCH 16/35] Add icon for filing cabinet --- src/icons/noun_Cabinet_251723.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/icons/noun_Cabinet_251723.svg diff --git a/src/icons/noun_Cabinet_251723.svg b/src/icons/noun_Cabinet_251723.svg new file mode 100644 index 000000000..b27f7597a --- /dev/null +++ b/src/icons/noun_Cabinet_251723.svg @@ -0,0 +1 @@ + \ No newline at end of file From 5dde28c6f3cf6e7e8e612f80045183e64488fdbb Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Wed, 31 Jul 2019 22:08:16 -0400 Subject: [PATCH 17/35] hand merge from select-tabs branch and bump version to match that - people published from dev not master --- package.json | 2 +- src/style.js | 3 ++- src/tabs.js | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f2ecce5c6..aac3f933f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.5", + "version": "0.12.6", "description": "UI library for writing Solid read-write-web applications", "main": "./lib/index.js", "files": [ diff --git a/src/style.js b/src/style.js index e7d5e9d33..eda4b912e 100644 --- a/src/style.js +++ b/src/style.js @@ -9,6 +9,7 @@ module.exports = { buttonStyle: 'background-color: #fff; padding: 0.5em; border: .01em solid white; font-size: 100%;', // 'background-color: #eef; // The width of the text field must bot be 100% or it switches to overlapping messageBodyStyle: 'white-space: pre-wrap; width: 99%; font-size:100%; border: 0.07em solid #eee; padding: .3em 0.5em; margin: 0.1em;', - pendingeditModifier: 'color: #bbb;' + pendingeditModifier: 'color: #bbb;', + highlightColor: '#7C4DFF' // Solid lavendar https://design.inrupt.com/atomic-core/?cat=Core } diff --git a/src/tabs.js b/src/tabs.js index 39fb569fe..41a1264c6 100644 --- a/src/tabs.js +++ b/src/tabs.js @@ -292,7 +292,21 @@ UI.tabs.tabWidget = function (options) { box.refresh = sync sync() - if (!options.startEmpty && tabContainer.children.length) { +// From select-tabs branch by hand + if (!options.startEmpty && tabContainer.children.length && options.selectedTab) { + var tab + var found = false + for (var i = 0; i < tabContainer.children.length; i++) { + tab = tabContainer.children[i] + if (tab.firstChild && tab.firstChild.dataset.name === options.selectedTab) { + tab.firstChild.click() + found = true + } + } + if (!found) { + tabContainer.children[0].firstChild.click() // Open first tab + } + } else if (!options.startEmpty && tabContainer.children.length) { tabContainer.children[0].firstChild.click() // Open first tab } return box From e7df3ca5cdd164d313bd662ddb652c4b5867a970 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Wed, 31 Jul 2019 22:09:35 -0400 Subject: [PATCH 18/35] 0.12.7 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index facd7ddb1..7fd876573 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.5", + "version": "0.12.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index aac3f933f..339317dee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.6", + "version": "0.12.7", "description": "UI library for writing Solid read-write-web applications", "main": "./lib/index.js", "files": [ From f09f540bbcedbeb8085d7c25b65908ae48909820 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Mon, 5 Aug 2019 20:59:13 +0200 Subject: [PATCH 19/35] Alternative solution to https://github.com/solid/solid-ui/issues/78 --- src/acl.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/acl.js b/src/acl.js index 71b96405d..2d5cded04 100644 --- a/src/acl.js +++ b/src/acl.js @@ -356,8 +356,10 @@ UI.acl.getACLorDefault = function (doc, callbackFunction) { // statusBlock.textContent += (" ACCESS set at " + uri + ". End search.") var defaults = kb.each(undefined, ACL('default'), kb.sym(uri), defaultACLDoc) .concat(kb.each(undefined, ACL('defaultForNew'), kb.sym(uri), defaultACLDoc)) - if (!defaults.length) { + if (!defaults.length && left < right) { tryParent(uri) // Keep searching + } else if (left >= right) { + callbackFunction(false, true, 404, 'Found no ACL resource') } else { var defaultHolder = kb.sym(uri) callbackFunction(true, false, doc, aclDoc, defaultHolder, defaultACLDoc) From 261eb7fcdec6d486323c11877abb33b7235b973d Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 11:49:17 +0200 Subject: [PATCH 20/35] Hopefully how it should work --- src/acl.js | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/acl.js b/src/acl.js index 2d5cded04..365a066aa 100644 --- a/src/acl.js +++ b/src/acl.js @@ -337,6 +337,9 @@ UI.acl.getACLorDefault = function (doc, callbackFunction) { } var right = uri.lastIndexOf('/') var left = uri.indexOf('/', uri.indexOf('//') + 2) + if (left >= right) { + return callbackFunction(false, true, 404, 'Found no ACL resource') + } uri = uri.slice(0, right + 1) var doc2 = $rdf.sym(uri) UI.acl.getACL(doc2, function (ok, status, defaultACLDoc) { @@ -345,26 +348,19 @@ UI.acl.getACLorDefault = function (doc, callbackFunction) { } else if (status === 403) { return callbackFunction(false, true, status, '( default ACL file FORBIDDEN. Stop.' + uri + ')') } else if (status === 404) { - if (left >= right) { - return callbackFunction(false, true, 499, 'Nothing to hold a default') - } else { - tryParent(uri) - } + return tryParent(uri) } else if (status !== 200) { return callbackFunction(false, true, status, "Error status '" + status + "' searching for default for " + doc2) - } else { // 200 - // statusBlock.textContent += (" ACCESS set at " + uri + ". End search.") - var defaults = kb.each(undefined, ACL('default'), kb.sym(uri), defaultACLDoc) - .concat(kb.each(undefined, ACL('defaultForNew'), kb.sym(uri), defaultACLDoc)) - if (!defaults.length && left < right) { - tryParent(uri) // Keep searching - } else if (left >= right) { - callbackFunction(false, true, 404, 'Found no ACL resource') - } else { - var defaultHolder = kb.sym(uri) - callbackFunction(true, false, doc, aclDoc, defaultHolder, defaultACLDoc) - } } + // 200 + // statusBlock.textContent += (" ACCESS set at " + uri + ". End search.") + var defaults = kb.each(undefined, ACL('default'), kb.sym(uri), defaultACLDoc) + .concat(kb.each(undefined, ACL('defaultForNew'), kb.sym(uri), defaultACLDoc)) + if (!defaults.length) { + return tryParent(uri) // Keep searching + } + var defaultHolder = kb.sym(uri) + return callbackFunction(true, false, doc, aclDoc, defaultHolder, defaultACLDoc) }) } // tryParent From 3aec54b60ede10c9ed52c06249b6cd60729187b2 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 11:55:11 +0200 Subject: [PATCH 21/35] Added one more defaultForNew+default fix Also added a TODO, for Tim to review --- src/acl-control.js | 3 ++- src/signin.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/acl-control.js b/src/acl-control.js index 7573c30d0..7fb34ae46 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -544,7 +544,8 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { } else { box.isContainer = targetDoc.uri.slice(-1) === '/' // Give default for all directories if (defa) { - var defaults = kb.each(undefined, ACL('defaultForNew'), defaultHolder, defaultACLDoc) + var defaults = kb.each(undefined, ACL('default'), defaultHolder, defaultACLDoc) + .concat(kb.each(undefined, ACL('defaultForNew'), defaultHolder, defaultACLDoc)) if (!defaults.length) { statusBlock.textContent += ' (No defaults given.)' } else { diff --git a/src/signin.js b/src/signin.js index c65a0c2a4..301816e59 100644 --- a/src/signin.js +++ b/src/signin.js @@ -776,6 +776,7 @@ function genACLText (docURI, me, aclURI, options = {}) { g.add(a, UI.ns.rdf('type'), auth('Authorization'), acl) g.add(a, auth('accessTo'), doc, acl) if (options.defaultForNew) { + // TODO: Should this be auth('default') instead? g.add(a, auth('defaultForNew'), doc, acl) } g.add(a, auth('agent'), me, acl) From 0d2806ea6f74c4915eaa4b0219069c00bc7a0b37 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 12:14:04 +0200 Subject: [PATCH 22/35] Upgrading to latest version of rdflib --- package-lock.json | 526 +++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 240 insertions(+), 288 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7fd876573..3801506e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,28 @@ } }, "@solid/cli": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@solid/cli/-/cli-0.1.0.tgz", - "integrity": "sha512-+VYDgDxsAKa48MGnoaX2CUwh0gLrTdqYY6lrxxWGwCHiFChGsg95RRbHOYz9c97+QcCltcHpMZ2AelGZrU673A==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@solid/cli/-/cli-0.1.1.tgz", + "integrity": "sha512-NRmSKWGycV2u/YDHTB2DWa7JIH9lK/WqT7HEuaRfWWhxlxSiM52p08aJw90qavEp6pWIz+2Os4kRQpQqJU2oiQ==", "requires": { - "@trust/oidc-rp": "0.6.0" + "@solid/oidc-rp": "^0.9.0" + }, + "dependencies": { + "@solid/oidc-rp": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@solid/oidc-rp/-/oidc-rp-0.9.0.tgz", + "integrity": "sha512-y2gBGp/0+f6D+TEZ+AxjEY2Qx1aqcN0smNR2W+PjaVXGio0t4HXh68kKoSPpSiGd84aMQY9GJfG2ZsVskqjdsg==", + "requires": { + "@solid/jose": "0.1.8", + "@trust/json-document": "^0.1.4", + "@trust/webcrypto": "0.9.2", + "base64url": "^3.0.0", + "node-fetch": "^2.1.2", + "standard-http-error": "^2.0.1", + "text-encoding": "^0.6.4", + "whatwg-url": "^6.4.1" + } + } } }, "@solid/jose": { @@ -46,35 +63,6 @@ "whatwg-url": "^6.4.1" } }, - "@trust/jose": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@trust/jose/-/jose-0.1.7.tgz", - "integrity": "sha512-JlWY97+Q1pU2CN08Ux5oN1/CXcvxLtQ5YkL4UhgVs4z9TR/+I4rKqhqoZQ0TDGPvCLP1QaT7F6bHbKswbDwgOQ==", - "requires": { - "@trust/json-document": "^0.1.4", - "@trust/webcrypto": "^0.0.2", - "base64url": "^2.0.0", - "text-encoding": "^0.6.1" - }, - "dependencies": { - "@trust/webcrypto": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.0.2.tgz", - "integrity": "sha1-53xpouYSudOSJRxZZscxaFN+Jmc=", - "requires": { - "base64url": "^2.0.0", - "node-rsa": "^0.4.0", - "pem-jwk": "^1.5.1", - "text-encoding": "^0.6.1" - } - }, - "base64url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" - } - } - }, "@trust/json-document": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@trust/json-document/-/json-document-0.1.4.tgz", @@ -90,47 +78,6 @@ "elliptic": "^6.4.0" } }, - "@trust/oidc-rp": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@trust/oidc-rp/-/oidc-rp-0.6.0.tgz", - "integrity": "sha512-6PgV0WI+gq6nGMjlg8oSxj7VgmS/m8Y61s6HPNBu3mX/NSVvnrXk+MqqR7KdxlBk84ti65O76HGSRNelJrRbeA==", - "requires": { - "@trust/jose": "^0.1.7", - "@trust/json-document": "^0.1.4", - "@trust/webcrypto": "0.4.0", - "base64url": "^2.0.0", - "node-fetch": "^1.7.3", - "text-encoding": "^0.6.4", - "whatwg-url": "^6.2.1" - }, - "dependencies": { - "@trust/webcrypto": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.4.0.tgz", - "integrity": "sha1-zIcSyomn5x01P877ZrJwemec9jU=", - "requires": { - "@trust/keyto": "^0.3.0", - "base64url": "^2.0.0", - "node-rsa": "^0.4.0", - "text-encoding": "^0.6.1" - } - }, - "base64url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - } - } - }, "@trust/webcrypto": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.9.2.tgz", @@ -167,9 +114,9 @@ } }, "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -294,7 +241,7 @@ }, "async": { "version": "0.9.2", - "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" }, "async-each": { @@ -1231,6 +1178,13 @@ "readdirp": "^2.0.0" } }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", + "dev": true, + "optional": true + }, "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", @@ -1308,9 +1262,9 @@ } }, "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } @@ -1636,7 +1590,7 @@ }, "es6-promise": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", "integrity": "sha1-lu258v2wGZWCKyY92KratnSBgbw=" }, "es6-set": { @@ -2033,40 +1987,6 @@ "object-assign": "^4.0.1" } }, - "file-fetch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/file-fetch/-/file-fetch-1.2.0.tgz", - "integrity": "sha512-DzwKhcH/afS7thk5hao1kVJXIqMNB2pz0DFpjpA5IlIAA0nSqi/fqFQpX++NP9IK+Te7Z1ZxA5KTWKmdzio+tA==", - "requires": { - "concat-stream": "^2.0.0", - "mime-types": "^2.1.17", - "node-fetch": "^2.3.0", - "readable-error": "^1.0.0" - }, - "dependencies": { - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "readable-stream": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", - "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -2158,10 +2078,32 @@ "map-cache": "^0.2.2" } }, - "fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==" + } + } + }, + "fs-minipass": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } }, "fs-readdir-recursive": { "version": "1.1.0", @@ -2236,13 +2178,6 @@ "concat-map": "0.0.1" } }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", - "dev": true, - "optional": true - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -2302,16 +2237,6 @@ "dev": true, "optional": true }, - "fs-minipass": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2437,27 +2362,6 @@ "dev": true, "optional": true }, - "minipass": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", - "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -2740,22 +2644,6 @@ "dev": true, "optional": true }, - "tar": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.1.tgz", - "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2779,13 +2667,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true - }, - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", - "dev": true, - "optional": true } } }, @@ -2878,8 +2759,7 @@ "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "har-schema": { "version": "2.0.0", @@ -3342,7 +3222,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isobject": { "version": "2.1.0", @@ -3386,9 +3267,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -3442,6 +3323,14 @@ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -3540,9 +3429,10 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true }, "lodash.cond": { "version": "4.5.2", @@ -3648,10 +3538,31 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "optional": true, "requires": { @@ -3694,7 +3605,7 @@ }, "n3": { "version": "0.4.5", - "resolved": "http://registry.npmjs.org/n3/-/n3-0.4.5.tgz", + "resolved": "https://registry.npmjs.org/n3/-/n3-0.4.5.tgz", "integrity": "sha1-W3DTq2ohyejUyb2io9TZCQm+tQg=" }, "nan": { @@ -3765,9 +3676,9 @@ "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" }, "node-forge": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.2.tgz", - "integrity": "sha512-mXQ9GBq1N3uDCyV1pdSzgIguwgtVpM7f5/5J4ipz12PKWElmPpVWLDuWl8iXmhysr21+WmX/OJ5UKx82wjomgg==" + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", + "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" }, "node-rsa": { "version": "0.4.2", @@ -4040,32 +3951,6 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "pem-jwk": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pem-jwk/-/pem-jwk-1.5.1.tgz", - "integrity": "sha1-eoY3/S9nqCflfAxC4cI8P9Us+wE=", - "requires": { - "asn1.js": "1.0.3" - }, - "dependencies": { - "asn1.js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-1.0.3.tgz", - "integrity": "sha1-KBuj7B8kSP52X5Kk7s+IP+E2S1Q=", - "requires": { - "bn.js": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "bn.js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-1.3.0.tgz", - "integrity": "sha1-DbTL+W+PI7dC9by50ap6mZSgXoM=", - "optional": true - } - } - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -4182,7 +4067,8 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true }, "progress": { "version": "1.1.8", @@ -4191,9 +4077,9 @@ "dev": true }, "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==" }, "punycode": { "version": "2.1.1", @@ -4243,10 +4129,11 @@ } }, "rdflib": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/rdflib/-/rdflib-0.20.1.tgz", - "integrity": "sha512-5G1t7rURqPdsYE+mjpDW2e9/H19XFMuChTIjPFPtH2tI0fma+xlJYwrSgmd1aOm45SOYKpqwJfTSIvFpmP2n+g==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/rdflib/-/rdflib-0.21.1.tgz", + "integrity": "sha512-NGcKNkeHCUKQwpbkmmNzcm+QwveCZSdb11EbaVW7XGMCwDrHni6f1AbqD5Hy0pdzRfMmidkAerORbsj02oPSdw==", "requires": { + "@babel/runtime": "^7.4.4", "async": "^0.9.x", "jsonld": "^0.4.5", "n3": "^0.4.1", @@ -4255,6 +4142,19 @@ "xmldom": "^0.1.22" }, "dependencies": { + "@babel/runtime": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", + "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + }, "solid-auth-client": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/solid-auth-client/-/solid-auth-client-2.3.0.tgz", @@ -4269,18 +4169,11 @@ } } }, - "readable-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/readable-error/-/readable-error-1.0.0.tgz", - "integrity": "sha512-CLnInu5bUphmFiZ3pD/BC6+Cg4/BzK6ZMvWfd0b2QMzYo159Z/f/nVFQ9L5IeMrqUxy0EFsp3XJ+BRfLfY13IQ==", - "requires": { - "readable-stream": "^2.3.3" - } - }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4871,9 +4764,9 @@ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "optional": true, "requires": { @@ -5038,31 +4931,30 @@ } }, "solid-auth-cli": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/solid-auth-cli/-/solid-auth-cli-0.1.13.tgz", - "integrity": "sha512-mRZ4eaWniGd9jLRzSWD3n9EwMfTcmwlfXK5MggIzSQOyXa1XJCWxHnJDL0DWTX5sMi1ovYa6RI4u6TNOzsEZiw==", + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/solid-auth-cli/-/solid-auth-cli-0.1.14.tgz", + "integrity": "sha512-G1xhi4hASPW5hib1UxN10rm0X2lTYqqNQELJ4neRjcM07qWnkKUG08Q4fMFz5xeKAWjDh8ytqhvq5M349/CZsw==", "requires": { - "@solid/cli": "^0.1.0", + "@solid/cli": "^0.1.1", "async": "^2.6.1", - "file-fetch": "^1.1.1", - "fs": "0.0.1-security", "isomorphic-fetch": "^2.2.1", "jsonld": "^1.4.0", - "n3": "^1.0.3" + "n3": "^1.0.3", + "solid-rest": "^1.0.2" }, "dependencies": { "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "requires": { - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "jsonld": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.5.4.tgz", - "integrity": "sha512-+cJ5JmXBBlf0zagJDiz1NsnMBQyd4p+LV8x8UQfjCVBhFDcL6hWduDXMUp8XOgLtt8L9LjRCbUj1NL+tU17GqA==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.6.2.tgz", + "integrity": "sha512-eMzFHqhF2kPMrMUjw8+Lz9IF1QkrxTOIfVndkP/OpuoZs31VdDtfDs8mLa5EOC/ROdemFTQGLdYPZbRtmMe2Yw==", "requires": { "rdf-canonize": "^1.0.2", "request": "^2.88.0", @@ -5070,10 +4962,15 @@ "xmldom": "0.1.19" } }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, "n3": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/n3/-/n3-1.0.5.tgz", - "integrity": "sha512-IAYc2P5UYbfYCSVoud9iITl1r2IWW/8pyMYbqTeG38RujMPlm10NfTUrz1l4YMFOsh7MbMwb+W/bibcciGYmVg==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.1.1.tgz", + "integrity": "sha512-GEJXn+wc0f4l2noP1N/rMUH9Gei1DQ8IDN03eBsH+uQKkNQUOLgL7ZJVaDjY+pP3LmbLxL1LpUg/AvZ7Kc7KVw==" }, "xmldom": { "version": "0.1.19", @@ -5104,6 +5001,58 @@ "resolved": "https://registry.npmjs.org/solid-namespace/-/solid-namespace-0.2.0.tgz", "integrity": "sha512-yQGQlTNDVtcMfzLz7OwL6Z8lJy9rN1ep0MgX28DPcja0DtA5pu1MzTpBVD9kvXl9X6eSEX73I7IYt0cUim0DrA==" }, + "solid-rest": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/solid-rest/-/solid-rest-1.0.7.tgz", + "integrity": "sha512-OiNKV1nW00RVdnd88HfCVIcY+LKI6VAc6DbY0Tujy5/eiURkCnxqAkMJXLd10lKmpQZ2NNYpsfBN/QWnoIBczA==", + "requires": { + "concat-stream": "^2.0.0", + "fs-extra": "^8.0.1", + "mime-types": "^2.1.24", + "node-fetch": "^2.6.0" + }, + "dependencies": { + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5348,6 +5297,22 @@ } } }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, "text-encoding": { "version": "0.6.4", "resolved": "http://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", @@ -5475,41 +5440,16 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "optional": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "uniq": { @@ -5518,6 +5458,11 @@ "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", "dev": true }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -5672,6 +5617,13 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "dev": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true, + "optional": true } } } diff --git a/package.json b/package.json index 339317dee..e80078384 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "escape-html": "^1.0.3", "mime-types": "^2.1.20", "node-uuid": "^1.4.7", - "rdflib": ">=0.20.1", + "rdflib": "^0.21.1", "solid-auth-client": "^2.2.13", "solid-auth-tls": "^0.1.2", "solid-namespace": "0.2.0" From 71c7b4533aa6dfac3920f88ac23bea72265bc071 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 12:14:15 +0200 Subject: [PATCH 23/35] 0.12.8-0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3801506e0..10a584e1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.7", + "version": "0.12.8-0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e80078384..d867b3572 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.7", + "version": "0.12.8-0", "description": "UI library for writing Solid read-write-web applications", "main": "./lib/index.js", "files": [ From 4df5c9ebd691bb1da0c87f4f7d6016195cb7a9bd Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 15:37:58 +0200 Subject: [PATCH 24/35] Small fixes to be able to create ACL files for folders --- src/acl-control.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/acl-control.js b/src/acl-control.js index 7fb34ae46..03c252761 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -562,11 +562,11 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { editPlease.textContent = 'Set specific sharing\nfor this ' + noun editPlease.style.cssText = bigButtonStyle editPlease.addEventListener('click', async function (event) { - kb2.forEach(st => { + kb2.statements.forEach(st => { kb.add(st.subject, st.predicate, st.object, targetACLDoc) }) try { - fetcher.putBack(targetACLDoc) + kb.fetcher.putBack(targetACLDoc) } catch (e) { let msg = ' Error writing back access control file! ' + e console.error(msg) From 9876d5382d6815224f795df5ecc11731fc1ae6a7 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 17:27:34 +0200 Subject: [PATCH 25/35] Will now refresh pane as expected But does not handle the green plus icon properly, will fix in next commit --- src/acl-control.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/acl-control.js b/src/acl-control.js index 03c252761..abe618e23 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -567,16 +567,18 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { }) try { kb.fetcher.putBack(targetACLDoc) + .then(function () { + statusBlock.textContent = ' (Now editing specific access for this ' + noun + ')' + bottomRightCell.removeChild(editPlease) + renderBox() + }) } catch (e) { let msg = ' Error writing back access control file! ' + e console.error(msg) statusBlock.textContent += msg return } - kb.fetcher.requested[targetACLDoc.uri] = 'done' // cheat - say cache is now in sync - statusBlock.textContent = ' (Now editing specific access for this ' + noun + ')' - bottomRightCell.removeChild(editPlease) - renderBox() + // kb.fetcher.requested[targetACLDoc.uri] = 'done' // cheat - say cache is now in sync }) } // defaults.length } else { // Not using defaults @@ -593,7 +595,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { kb.fetcher.delete(targetACLDoc.uri) .then(function () { statusBlock.textContent = ' The sharing for this ' + noun + ' is now the default.' - bottomRow.removeChild(useDefault) + bottomRightCell.removeChild(useDefault) box.style.cssText = 'color: #777;' renderBox() }) From b9575a87a8cf0dbf3dc434d043fdfc5478eb75cc Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 17:54:31 +0200 Subject: [PATCH 26/35] A hacky fix to the never-ending spawning green plus icons --- src/acl-control.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/acl-control.js b/src/acl-control.js index abe618e23..aeff186c3 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -597,6 +597,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { statusBlock.textContent = ' The sharing for this ' + noun + ' is now the default.' bottomRightCell.removeChild(useDefault) box.style.cssText = 'color: #777;' + bottomLeftCell.innerHTML = '' renderBox() }) .catch(function (e) { From e2eaf0dcd689935cd7493ac36dcbacadce67625c Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 17:54:31 +0200 Subject: [PATCH 27/35] Revert "A hacky fix to the never-ending spawning green plus icons" This reverts commit b9575a87a8cf0dbf3dc434d043fdfc5478eb75cc. --- src/acl-control.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/acl-control.js b/src/acl-control.js index aeff186c3..abe618e23 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -597,7 +597,6 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { statusBlock.textContent = ' The sharing for this ' + noun + ' is now the default.' bottomRightCell.removeChild(useDefault) box.style.cssText = 'color: #777;' - bottomLeftCell.innerHTML = '' renderBox() }) .catch(function (e) { From 6d21f11a4c5a19715791669f91d4bbab44986614 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 6 Aug 2019 17:54:31 +0200 Subject: [PATCH 28/35] Revert "Revert "A hacky fix to the never-ending spawning green plus icons"" This reverts commit e2eaf0dcd689935cd7493ac36dcbacadce67625c. --- src/acl-control.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/acl-control.js b/src/acl-control.js index abe618e23..aeff186c3 100644 --- a/src/acl-control.js +++ b/src/acl-control.js @@ -597,6 +597,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) { statusBlock.textContent = ' The sharing for this ' + noun + ' is now the default.' bottomRightCell.removeChild(useDefault) box.style.cssText = 'color: #777;' + bottomLeftCell.innerHTML = '' renderBox() }) .catch(function (e) { From 14e5f3cfac7e46a58c3276c5e5a9dfd42a236355 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Thu, 8 Aug 2019 12:46:13 +0200 Subject: [PATCH 29/35] Suggested icon for ScratchPad pane --- src/icons/noun_121955.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/icons/noun_121955.svg diff --git a/src/icons/noun_121955.svg b/src/icons/noun_121955.svg new file mode 100644 index 000000000..e83421bf9 --- /dev/null +++ b/src/icons/noun_121955.svg @@ -0,0 +1 @@ +Created by Filippo Gianessifrom the Noun Project \ No newline at end of file From 57ca23dd7e9d1d2c05d5727f662724316a871474 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Thu, 8 Aug 2019 13:12:25 +0200 Subject: [PATCH 30/35] Revert "Suggested icon for ScratchPad pane" This reverts commit 14e5f3cfac7e46a58c3276c5e5a9dfd42a236355. --- src/icons/noun_121955.svg | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/icons/noun_121955.svg diff --git a/src/icons/noun_121955.svg b/src/icons/noun_121955.svg deleted file mode 100644 index e83421bf9..000000000 --- a/src/icons/noun_121955.svg +++ /dev/null @@ -1 +0,0 @@ -Created by Filippo Gianessifrom the Noun Project \ No newline at end of file From ab1846b7669d59d4bd124c8cfa95913784c2d27a Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Thu, 8 Aug 2019 13:15:07 +0200 Subject: [PATCH 31/35] 0.12.8-1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10a584e1c..6e99922e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.8-0", + "version": "0.12.8-1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d867b3572..babeb8496 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.8-0", + "version": "0.12.8-1", "description": "UI library for writing Solid read-write-web applications", "main": "./lib/index.js", "files": [ From 3b406546b7ba02da8dd4fe1b660b4bb1e59cb9c6 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Thu, 8 Aug 2019 13:15:24 +0200 Subject: [PATCH 32/35] 0.12.8-2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e99922e9..675e1d47a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.8-1", + "version": "0.12.8-2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index babeb8496..210a16c3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-ui", - "version": "0.12.8-1", + "version": "0.12.8-2", "description": "UI library for writing Solid read-write-web applications", "main": "./lib/index.js", "files": [ From 46726f237022c137bed66e35c61966859806d93f Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Thu, 8 Aug 2019 13:38:46 +0200 Subject: [PATCH 33/35] Will prepend the name of the pane if multiple panes create same class --- src/create.js | 71 ++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/create.js b/src/create.js index baf30937e..cb62f1d02 100644 --- a/src/create.js +++ b/src/create.js @@ -144,42 +144,43 @@ function newThingUI (context, thePanes) { ) } // makeNewAppInstance - var iconArray = [] - for (var pn in thePanes) { - var pane = thePanes[pn] - if (pane.mintNew) { - var icon = context.div.appendChild(dom.createElement('img')) - icon.setAttribute('src', pane.icon) - var noun = pane.mintClass ? UI.utils.label(pane.mintClass) : (pane.name + ' @@') - icon.setAttribute('title', 'Make new ' + noun) - icon.setAttribute('style', iconStyle + 'display: none;') - iconArray.push(icon) - var foo = function (pane, icon, noun) { - var iconEle = icon - var thisPane = pane - var thisNoun = noun - if (!icon.disabled) { - icon.addEventListener('click', function (e) { - selectTool(iconEle) - var options = { - event: e, - folder: context.folder, - iconEle: iconEle, - pane: thisPane, - noun: thisNoun, - noIndexHTML: true, // do NOT @@ for now write a HTML file - div: context.div, - me: context.me, - dom: context.dom, - refreshTarget: context.refreshTarget - } - makeNewAppInstance(options) - }) - } - } // foo - foo(pane, icon, noun) + const iconArray = [] + const mintingPanes = Object.values(thePanes).filter(pane => pane.mintNew) + const mintingClassMap = mintingPanes.reduce((classMap, pane) => { + if (pane.mintClass) { + classMap[pane.mintClass] = (classMap[pane.mintClass] || 0) + 1 } - } + return classMap + }, {}) + mintingPanes.forEach(pane => { + const icon = context.div.appendChild(dom.createElement('img')) + icon.setAttribute('src', pane.icon) + const noun = pane.mintClass + ? (mintingClassMap[pane.mintClass] > 1 + ? `${UI.utils.label(pane.mintClass)} (using ${pane.name} pane)` + : UI.utils.label(pane.mintClass)) + : (pane.name + ' @@') + icon.setAttribute('title', 'Make new ' + noun) + icon.setAttribute('style', iconStyle + 'display: none;') + iconArray.push(icon) + if (!icon.disabled) { + icon.addEventListener('click', function (e) { + selectTool(icon) + makeNewAppInstance({ + event: e, + folder: context.folder, + iconEle: icon, + pane, + noun, + noIndexHTML: true, // do NOT @@ for now write a HTML file + div: context.div, + me: context.me, + dom: context.dom, + refreshTarget: context.refreshTarget + }) + }) + } + }) var styleTheIcons = function (style) { for (var i = 0; i < iconArray.length; i++) { From 3f687ce22ceefbbb66f534005a2e9eb2cdea1da8 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Thu, 8 Aug 2019 17:24:24 +0200 Subject: [PATCH 34/35] This should make the model correct wrt default values --- src/widgets/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/widgets/index.js b/src/widgets/index.js index 45185ff19..d5a505014 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -907,7 +907,9 @@ UI.widgets.field[UI.ns.ui('PhoneField').uri] = var obj = kb.any(subject, property, undefined, store) if (!obj) { obj = kb.any(form, ui('default')) - if (obj) kb.add(subject, property, obj, store) + if (obj) { + kb.add(subject, ui('default'), obj, store) + } } if (obj && obj.uri && params.uriPrefix) { // eg tel: or mailto: field.value = decodeURIComponent(obj.uri.replace(params.uriPrefix, '')) // should have no spaces but in case From b1c6b6b273cc7a537f1b065c7be8311cedab7671 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Thu, 8 Aug 2019 17:56:51 +0200 Subject: [PATCH 35/35] Removing faulty logic -> This should handle the default value logic --- src/widgets/index.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/widgets/index.js b/src/widgets/index.js index d5a505014..cdcbe201d 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -904,13 +904,8 @@ UI.widgets.field[UI.ns.ui('PhoneField').uri] = store = store || UI.widgets.fieldStore(subject, property, store) - var obj = kb.any(subject, property, undefined, store) - if (!obj) { - obj = kb.any(form, ui('default')) - if (obj) { - kb.add(subject, ui('default'), obj, store) - } - } + var obj = kb.any(subject, property, undefined, store) || + kb.any(form, ui('default')) if (obj && obj.uri && params.uriPrefix) { // eg tel: or mailto: field.value = decodeURIComponent(obj.uri.replace(params.uriPrefix, '')) // should have no spaces but in case .replace(/ /g, '')