michielbdejong/solid-ui

View on GitHub
src/chat/messageTools.js

Summary

Maintainability
D
2 days
Test Coverage
/* global $rdf */
/*    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('../authn/authn'),
  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
 */
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()
  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
 */

export 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 => {
      const [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
}

export function messageToolbar (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 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
   *
   * @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<NamedNode>} - 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++) {
              const 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
}