Artifact [cd3be36997]
Not logged in

Artifact cd3be36997075c1b3048b2acaba18c6571194bfb4d825922ae0760679f198585:


(function(){
  const form = document.querySelector('#chat-form');
  let mxMsg = 0;
  // let _me = "%string($me)";
  form.addEventListener('submit',(e)=>{
    e.preventDefault();
    if( form.msg.value.length>0 || form.file.value.length>0 ){
      fetch("%string($submiturl)",{
        method: 'POST',
        body: new FormData(form)
      });
    }
    form.msg.value = "";
    form.file.value = "";
    form.msg.focus();
  });
  const rxUrl = /\b(?:https?|ftp):\/\/\[a-z0-9-+&@\#\/%?=~_|!:,.;]*\[a-z0-9-+&@\#\/%=~_|]/gim;
  const rxAtName = /@\w+/gmi;
  // ^^^ achtung, extra backslashes needed for the outer TCL.
  const textNode = (T)=>document.createTextNode(T);

  // Converts a message string to a message-containing DOM element
  // and returns that element, which may contain child elements.
  // If 2nd arg is passed, it must be a DOM element to which all
  // child elements are appended.
  const messageToDOM = function f(str, tgtElem){
    "use strict";
    if(!f.rxUrl){
      f.rxUrl = rxUrl;
      f.rxAt = rxAtName;
      f.rxNS = /\S/;
      f.ce = (T)=>document.createElement(T);
      f.ct = (T)=>document.createTextNode(T);
      f.replaceUrls = function ff(sub, offset, whole){
        if(offset > ff.prevStart){
          f.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
        }
        const a = f.ce('a');
        a.setAttribute('href',sub);
        a.setAttribute('target','_blank');
        a.appendChild(f.ct(sub));
        f.accum.push(a);
        ff.prevStart = offset + sub.length + 1;
      };
      f.replaceAtName = function ff(sub, offset,whole){
        if(offset > ff.prevStart){
          ff.accum.push((ff.prevStart?' ':'')+whole.substring(ff.prevStart, offset-1)+' ');
        }else if(offset && f.rxNS.test(whole[offset-1])){
          // Sigh: https://stackoverflow.com/questions/52655367
          ff.accum.push(sub);
          return;
        }
        const e = f.ce('span');
        e.classList.add('at-name');
        e.appendChild(f.ct(sub));
        ff.accum.push(e);
        ff.prevStart = offset + sub.length + 1;
      };
    }
    f.accum = []; // accumulate strings and DOM elements here.
    f.rxUrl.lastIndex = f.replaceUrls.prevStart = 0; // reset regex cursor
    str.replace(f.rxUrl, f.replaceUrls);
    // Push remaining non-URL part of the string to the queue...
    if(f.replaceUrls.prevStart < str.length){
      f.accum.push((f.replaceUrls.prevStart?' ':'')+str.substring(f.replaceUrls.prevStart));
    }
    // Pass 2: process @NAME references...
    // TODO: only match NAME if it's the name of a currently participating
    // user. Add a second class if NAME == current user, and style that one
    // differently so that people can more easily see when they're spoken to.
    const accum2 = f.replaceAtName.accum = [];
    //console.debug("f.accum =",f.accum);
    f.accum.forEach(function(v){
      //console.debug("v =",v);
      if('string'===typeof v){
        f.rxAt.lastIndex = f.replaceAtName.prevStart = 0;
        v.replace(f.rxAt, f.replaceAtName);
        if(f.replaceAtName.prevStart < v.length){
          accum2.push((f.replaceAtName.prevStart?' ':'')+v.substring(f.replaceAtName.prevStart));
        }
      }else{
        accum2.push(v);
      }
      //console.debug("accum2 =",accum2);
    });
    delete f.accum;
    //console.debug("accum2 =",accum2);
    const span = tgtElem || f.ce('span');
    accum2.forEach(function(e){
      if('string'===typeof e) e = f.ct(e);
      span.appendChild(e);
    });
    //console.debug("span =",span.innerHTML);
    return span;
  }/*end messageToDOM()*/;
  /* Injects element e as a new row in the chat, at the top of the list */
  const injectMessage = function f(e){
    if(!f.injectPoint){
      f.injectPoint = document.querySelector('#message-inject-point');
    }
    if(f.injectPoint.nextSibling){
      f.injectPoint.parentNode.insertBefore(e, f.injectPoint.nextSibling);
    }else{
      f.injectPoint.parentNode.appendChild(e);
    }
  };
  /** Returns the local time string of Date object d, defaulting
      to the current time. */
  const localTimeString = function ff(d){
    if(!ff.pad){
      ff.pad = (x)=>(''+x).length>1 ? x : '0'+x;
    }
    d || (d = new Date());
    return [
      d.getFullYear(),'-',ff.pad(d.getMonth()+1/*sigh*/),
      '-',ff.pad(d.getDate()),
      ' ',ff.pad(d.getHours()),':',ff.pad(d.getMinutes()),
      ':',ff.pad(d.getSeconds())
    ].join('');
  };
  function newcontent(jx){
    var i;
    for(i=0; i<jx.msgs.length; ++i){
      let m = jx.msgs[i];
      let row = document.createElement("fieldset");
      if( m.msgid>mxMsg ) mxMsg = m.msgid;
      row.classList.add('message-row');
      injectMessage(row);
      const eWho = document.createElement('legend');
      eWho.setAttribute('align', (m.xfrom===_me ? 'right' : 'left'));
      row.appendChild(eWho);
      eWho.classList.add('message-user');
      let whoName;
      if( m.xfrom===_me ){
        whoName = 'me';
        row.classList.add('user-is-me');
      }else{
        whoName = m.xfrom;
      }
      eWho.append(textNode(
                  whoName+' @ '+
                  localTimeString(new Date(Date.parse(m.mtime+".000Z"))))
      );
      let span = document.createElement("div");
      span.classList.add('message-content');
      row.appendChild(span);
      if( m.fsize>0 ){
        if( m.fmime && m.fmime.startsWith("image/") ){
          let img = document.createElement("img");
          img.src = "%string($downloadurl)/" + m.msgid;
          span.appendChild(img);
        }else{
          let a = document.createElement("a");
          let txt = "(" + m.fname + " " + m.fsize + " bytes)";
          a.href = "%string($downloadurl)/" + m.msgid;
          a.appendChild(document.createTextNode(txt));
          span.appendChild(a);
        }
        let br = document.createElement("br");
        br.style.clear = "both";
        span.appendChild(br);
      }
      if(m.xmsg){
        messageToDOM(m.xmsg, span);
      }
      span.classList.add('chat-message');
      if( m.xfrom!=_me ){
        span.classList.add('chat-mx');
      }else{
        span.classList.add('chat-ms');
      }
    }
  }
  async function poll(){
    if(poll.running) return;
    poll.running = true;
    fetch("%string($pollurl)/" + mxMsg)
    .then(x=>x.json())
    .then(y=>newcontent(y))
    .finally(()=>poll.running=false)
  }
  setInterval(poll, 1000);
})();</script>