// soyMAIL.js
//
// All vars in this code beginning "soy_" or "lang_" get (re)set by in-line
// <script>...</script> supplied in the executable HTML output, otherwise
// they are working storage.
//
// VERSION LOG (don't forget to update soy_CompatibleVersions)
// -----------
// 02-OCT-2023  MGD  imageInlineClick()
// 05-MAR-2023  MGD  TargetBlank() Safari requires ".rel = 'opener'"
// 11-MAY-2019  MGD  composePreview()
// 10-MAR-2017  MGD  push NEWMAIL notification on non-focused soyMAIL
// 10-JUN-2016  MGD  revisit refreshNewMailRequest()
//                   use 'ch' units for compose <textarea> widths
// 25-SEP-2015  MGD  confirm before send
// 18-APR-2014  MGD  integrate TinyMCE 4.n
// 24-SEP-2013  MGD  bugfix; msgResizeHTML() test for iframe.contentDocument
// 16-JUN-2013  MGD  v1.7.0, initial (un-inlined from executable)

// ----------------------------------------------------------------------------
// infrastructure
// ----------------------------------------------------------------------------

// versions of the executable this JavaScript is compatible with
var soy_CompatibleVersions = new Array ("2.0.0","2.0.1","2.0.2","2.0.3",
                                        "2.0.4","2.0.5","2.0.6","2.0.7",
                                        "2.0.8","2.1.0","2.2.0");

window.onerror = function(msg,url,lnum)
{
   if (lnum == 0) return;  // Safari 10.1
   var errep = 'ERROR=' + msg + '\nURL=' + url + '\nLINE=' + lnum;
   alert (errep);
   logger (errep);
}

// seems as if the only (reliable) method of dobeforeunload="" is to add
// <body onload="window.addEventListener('beforeunload',<function>)" ...>
function doBeforeUnload (event)
{
   event.preventDefault();
   event.returnValue = lang_Close + '!' + lang_Sure;
}

// shortcut
var $byId = document.getElementById.bind(document);

var consoleLog;
function logger (msg)
{
   // some browsers have a console but keep it to themselves
   if (!consoleLog) {
      try { consoleLog = Function.prototype.bind.call(console.log,console); }
      catch (e) { }
   }
   if (consoleLog) consoleLog(new Date()+': '+msg);
}

// only appears in later MSIEs (10+)
if (!('indexOf' in Array.prototype))
{
    Array.prototype.indexOf= function(find, i /*opt*/) {
        if (i===undefined) i= 0;
        if (i<0) i+= this.length;
        if (i<0) i= 0;
        for (var n= this.length; i<n; i++)
            if (i in this && this[i]===find)
                return i;
        return -1;
    };
}

// ----------------------------------------------------------------------------
// on body load
// ----------------------------------------------------------------------------

function soyBodyLoad (msg)
{
   if (soy_CompatibleVersions.indexOf(soy_ExeVersion) == -1)
   {
      alert(lang_JavaScriptIncompat);
      return;
   }
}

// ----------------------------------------------------------------------------
// general support
// ----------------------------------------------------------------------------

var lang_All;
var lang_AllMessages;
var lang_Close;
var lang_JavaScriptIncompat;
var lang_LogoutDisabled;
var lang_NoMoreMessages;
var lang_NotApplicable;
var lang_Sure;
var lang_WorkingOnIt;

var soy_RefreshSoyMailAction;
var soy_SoyMailPath;

var refreshSoyMailFormTmo;

function refreshSoyMailForm ()
{
   clearTimeout(refreshSoyMailFormTmo);
   var obj = $byId('soymail_form');
   obj.target = '';
   obj.action = soy_RefreshSoyMailAction;
   if ((obj = $byId('msg_btn_popup')) != null) obj.value = '';
   if ((obj = $byId('folder_list_change')) != null) obj.value = '';
   if ((obj = $byId('range_list_change')) != null) obj.value = '';
}

function refreshStatus (str)
{
   if (str.substr(0,1) == '!')
      $byId('status_info').innerHTML =
         '<span class="css_warning">' + str.substr(1) + '</span>';
   else
      $byId('status_info').innerHTML = str;
}

function refreshClock ()
{
   var nowDate = new Date;
   var dateStr = nowDate.toString();
   $byId('status_clock').innerHTML =
      dateStr.substring(0,dateStr.lastIndexOf(':'));
   setTimeout('refreshClock()',(60 - nowDate.getSeconds()) * 1000);
}

var refreshWorkingCnt = 0;
var refreshWorkingDot = '';
var refreshWorkingTmo;

function startWorking ()
{
   setWorking (lang_WorkingOnIt);
}

function setWorking (msg)
{
   refreshWorkingCnt += 2;
   refreshWorkingDot = msg + ' .';
   refreshWorkingTmo = setTimeout('refreshWorking()', 2000);
   return true;
}

function clearWorking ()
{
   refreshWorkingCnt = 0;
   clearTimeout (refreshWorkingTmo);
}

var workingPeriodMax = 300;
function refreshWorking ()
{
   if (refreshWorkingCnt++ > workingPeriodMax) {
      refreshWorkingDot += ' ???';
      clearWorking();
   }
   else {
      refreshWorkingDot += ' .';
      refreshWorkingTmo = setTimeout('refreshWorking()', 1000);
   }
   refreshStatus (refreshWorkingDot);
}

function noMoreMessages ()
{
   refreshStatus ('!'+lang_NoMoreMessages);
   return false;
}

function notApplicable ()
{
   refreshStatus ('!'+lang_NotApplicable);
   return false;
}

function logoutDisabled ()
{
   refreshStatus ('!'+lang_LogoutDisabled);
   return false;
}

function targetBlank ()
{
   $byId('soymail_form').target = '_blank';
   $byId('soymail_form').rel = 'opener';
   refreshSoyMailFormTmo = setTimeout('refreshSoyMailForm()', 5000);
}

function clientTime()
{
   // this is the target of the form's onsubmit=
   var now = new Date();
   $byId('client_time').value =
       now.getFullYear() + ':' + (now.getMonth()+1) + ':' + now.getDate() +
       ':' + now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds() +
       ':' + now.getTimezoneOffset()*(-60);
   return true;
}

function showStats()
{
   var stats = $byId('status_stats');
   if (stats.style.display == 'none') {
      $byId('status_stats').innerHTML = statusStats;
      stats.style.display = 'block';
   }
   else
      stats.style.display = 'none';
}

// all this because Opera does not support window.opener!!
function childWindow ()
{
   if (!window.opera) return window.opener;
   obj = $byId('opera_child');
   if (obj.value == 0) return false;
   if (!history.length) obj.value = 2;
   return (obj.value == 2);
}

// ----------------------------------------------------------------------------
// new mail checking
// ----------------------------------------------------------------------------

var lang_CheckingNewMail;
var lang_CheckNewMailIn;
var lang_Minutes;
var soy_CgiServerName;
var soy_CheckNewMailMins;
var soy_CurrentNewMessages;
var soy_YouGotMailPath;

var refreshNewMailMins;
var refreshNewMailTmo;

function refreshNewMail (mins)
{
   if (mins)
      refreshNewMailMins = mins;
   else
      refreshNewMailMins--;
   if (refreshNewMailMins > 0) {
      if (!mins) refreshNewMailStatus(null);
      clearTimeout(refreshNewMailTmo);
      refreshNewMailTmo = setTimeout('refreshNewMail(0)',60000);
      return;
   }
   setWorking(lang_CheckingNewMail);
   refreshNewMailRequest();
}

function refreshNewMailStatus (str)
{
   if (str == null)
      str = lang_CheckNewMailIn + ' ' + refreshNewMailMins + ' ' + lang_Minutes;
   refreshStatus (str);
}

var refreshNewMailRequestXhr;

function refreshNewMailRequest ()
{
   if (window.XMLHttpRequest)
      var xhr = new XMLHttpRequest();
   else {
      refreshNewMailStatus('!XMLHttpRequest():?');
      return;
   }
   refreshNewMailRequestXhr = xhr;
   xhr.timeout = 30000;
   xhr.ontimeout = refreshNewMailFailure;
   xhr.onerror = refreshNewMailFailure;
   xhr.onabort = refreshNewMailFailure;
   xhr.onload = refreshNewMailResponse;
   xhr.open('GET','/cgiplus-bin/soymail/~?'+soy_CurrentNewMessages,true);
   xhr.send(null);
}

function refreshNewMailResponse ()
{
   if (this.status == 202) {
      // 202 is NEW mail
      $byId('newmail_refresh').value='NEWMAIL';
      setTimeout(refreshNewMailResponseSubmit,500);
   }
   else
   if (this.status == 204 || this.status > 999) {
      // 204 is NO new mail - MSIE changes this to some bizarre code!
      clearWorking();
      refreshNewMail(soy_CheckNewMailMins);
      refreshNewMailStatus(null);
   }
   else
   if (this.status == 205) {
      // 205 is a decrement in count!
      $byId('newmail_refresh').value='DECREMENT';
      setTimeout(refreshNewMailResponseSubmit,500);
   }
   else
   if (this.status == 403) {
      // 403 is a (autogenous) login fail
      setTimeout(refreshNewMailResponseSubmit,500);
   }
   else {
      clearWorking();
      refreshNewMail(soy_CheckNewMailMins);
      refreshNewMailStatus('!NEWMAIL ' + this.status);
      logger('NEWMAIL '+this.status);
      // try again in 60 seconds
      setTimeout(refreshNewMailRequest,60000);
   }
}

function refreshNewMailResponseSubmit ()
{
   // Safari sometimes seems to have difficulty submitting this
   $byId('soymail_form').submit();
}

function refreshNewMailFailure ()
{
   refreshNewMailStatus('!' + soy_CgiServerName + ' ?');
   logger(soy_CgiServerName + ' ?');
   // try again in 60 seconds
   setTimeout(refreshNewMailRequest,60000);
   return true;
}

function playYouGotMail(src)
{
   if (!src) src = soy_YouGotMailPath;
   if (!src) return;

   // HTML5 audio (sometimes) only plays in a focused tab/window
   if (typeof document.hidden != 'undefined' && document.hidden)
   {
      // so, when the tab/window is not focused
      if ('Notification' in window)
      {    
         // attempt to send a NEWMAIL *notification* to the client
         if (Notification.permission !== 'granted' &&
             Notification.permission !== 'denied')
            Notification.requestPermission();

         if (Notification.permission === 'granted')
         {
            title = lang_n_e_w_m_a_i_l;
            options = {
               body : 'soyMAIL v' + soy_ExeVersion,
               icon: '/soymail/-/newmail.png',
               sound : src
            };
            var note = new Notification (title, options);
         }
      }
   }

   if (src.substr(0,7) == '(beep!)')
      beepYouGotMail();
   else
   {
      var audio, embed, obj;
      audio = document.createElement('audio');
      if (audio && audio.canPlayType && audio.canPlayType('audio/mpeg'))
      {
         audio.setAttribute('src', src);
         audio.setAttribute('autoplay', true);
         audio.setAttribute('controls', false);
         audio.load();
         audio.play();
      }
      else
      if ((obj = document.createElement('object')) != null)
      {
         obj.setAttribute('type','audio/mpeg');
         obj.setAttribute('data', src);
         obj.setAttribute('autostart', true);
         document.getElementsByTagName('body')[0].appendChild(obj);
      }
      else
      if ((embed = document.createElement('embed')) != null)
      {
         embed.setAttribute('type','audio/mpeg');
         embed.setAttribute('src', src);
         embed.setAttribute('autostart', true);
         document.getElementsByTagName('body')[0].appendChild(embed);
      }
   }
}

function beepYouGotMail()
{
   // let's not a little thing like Java get in our way!
   try {
      java.awt.Toolkit.getDefaultToolkit().beep();
   }
   catch (err) {
      return;
   }
   for (cnt = 2; cnt; cnt--) {
       setTimeout('beepGotMail()',cnt*500);
   }
}

function beepGotMail()
{
   java.awt.Toolkit.getDefaultToolkit().beep();
}

// ----------------------------------------------------------------------------
// message list and display
// ----------------------------------------------------------------------------

var soy_TargetMsgNum;
var soy_TargetPreviewTimer;
var soy_TargetPreviewUri;
var soy_TargetMsgDisplay;

function msgPreviewOn (evt,msgNum,msgHash)
{
   if (!document.hasFocus()) return;
   clearTimeout(soy_TargetPreviewTimer);
   soy_TargetPreviewTimer =
      setTimeout('msgPreviewGet(' + msgNum + ',' +  msgHash + ')',1000);
   soy_TargetMsgNum = msgNum;
}

// a little slight-of-hand to allow for [0001][^] transitions
function msgPreviewOff (removeChild,msgNum)
{
   if (!removeChild) {
      clearTimeout(soy_TargetPreviewTimer);
      soy_TargetPreviewTimer = null;
      soy_TargetMsgNum = 0;
      setTimeout ('msgPreviewOff(true,' + msgNum + ')',10);
      return;
   }
   if (msgNum == soy_TargetMsgNum) return;
   $byId('preview_row_'+msgNum).style.display = 'none';
}

function msgPreviewGet (msgNum,msgHash)
{
   if ($byId('preview_msg_'+msgNum).innerHTML == '') {
      var pviframe = $byId('preview_iframe');
      if (pviframe) pviframe.src = 
         soy_TargetPreviewUri + msgNum + '.' + msgHash + '?preview';
   }
   else
      $byId('preview_row_'+msgNum).style.display =
         soy_TargetMsgDisplay;
}

function msgPreviewText (msgNum,msgText)
{
   $byId('preview_msg_'+msgNum).innerHTML = msgText;
   $byId('preview_row_'+msgNum).style.display =
      soy_TargetMsgDisplay;
}

function msgResizeHTML(obj)
{
   iframe = $byId('iframeHTML');
   if (!iframe || !iframe.contentDocument) return;
   docHeight = iframe.contentDocument.body.scrollHeight;
   obj.style.height = docHeight + 'px';
   obj.style.overflow = 'hidden';
}

function targetMsgOn (msgNum)
{
   var dateObj = $byId('msg_date_'+msgNum);
   var fromObj = $byId('msg_from_'+msgNum);
   var subjObj = $byId('msg_subj_'+msgNum);
   if (!(dateObj && fromObj && subjObj)) return;
   dateObj.style.textDecoration = fromObj.style.textDecoration =
      subjObj.style.textDecoration = 'underline';
}

function targetMsgOff (msgNum)
{
   var dateObj = $byId('msg_date_'+msgNum);
   var fromObj = $byId('msg_from_'+msgNum);
   var subjObj = $byId('msg_subj_'+msgNum);
   dateObj.style.textDecoration = fromObj.style.textDecoration =
      subjObj.style.textDecoration = 'none';
}

function popupMsgBtn (msgNum)
{
   document.write('<input class="css_button_panel_1" ' +
      'style="border-left-width:0px;" type="submit" id="msg_btn_pop_' +
      msgNum + '" value="^" onclick="targetPopup(' + msgNum + ');">');
}

function targetMsgPop (msgNum)
{
   obj = $byId('msg_pop_'+msgNum);
   if (obj) obj.style.fontWeight = 'normal';
   $byId('msg_btn_pop_'+msgNum).click();
}

function targetPopup (msgNum)
{
   obj = $byId('msg_pop_'+msgNum);
   if (obj) obj.style.fontWeight = 'normal';
   $byId('msg_btn_popup').value = msgNum;
   if (msgNum) targetBlank();
}

function checkAllId ()
{
   soyMailFormObj = $byId('soymail_form');
   for (idx = 0; idx < soyMailFormObj.elements.length; idx++)
   {
      if (soyMailFormObj.elements[idx].type == 'checkbox' &&
          soyMailFormObj.elements[idx].name.substring(0,8) == 'msg_cbx_' &&
          !isNaN(parseInt(soyMailFormObj.elements[idx].name.substring(8,9))))
         soyMailFormObj.elements[idx].checked =
            $byId('msg_cbx_page').checked;
   }
}

// if only MSIE6 supported STYLE width-adjusting drop-down
// SELECT element and OPTION element responding to events!!!
//
// This kludge is necessary because MSIE6 <option> element does not respond to
// and ONCLICK event as it is supposed to.  This works around that so that
// desired functionality at least appears to work as expected on all platforms.
// If the selected option ends with "/mail" then it's different mail file and
// the folder list needs to be regenerated.

function folderDestListChanged()
{
   $byId('folder_text_dest').value = '';
   if ($byId('file_text_dest'))
      $byId('file_text_dest').value = '';
   flob = $byId('folder_list_dest');
   opt = flob.options[flob.selectedIndex].value;
   if (opt.length >= 5 && opt.substr(opt.length-5,5) != '/MAIL')
   {
      msg = 'Destination folder &quot;%s&quot; selected.';
      oidx = opt.indexOf('/');
      midx = msg.indexOf('%s');
      txt =  msg.substr(0,midx);
      if (oidx != -1) txt += opt.substr(0,oidx) + ' / ';
      txt += opt.substr(oidx+1,999) + msg.substr(midx+2,999);
      refreshStatus (txt);
      return false;
   }
   $byId('list_dest_change').value=true;
   startWorking();
   $byId('soymail_form').submit();
   return true;
}

function checkAllMsg (action)
{
   if (!$byId('msg_cbx_all') || !$byId('msg_cbx_all').checked) {
      startWorking();
      return true;
   }
   var prompt = action + lang_All + ' ' + lang_AllMessages + ' ' + lang_Sure;
   var ok = confirm(prompt);
   if (ok) startWorking();
   return ok;
}

function imageInlineLoad (part,width,uri,alt)
{
   var img = '';
   img += '<img id="image_inline_' + part + '"';
   img += 'class="css_inline_image" ';
   img += 'style="width:' + width + '%;" src="';
   img += uri + '?';
   img += document.getElementById('part_type_' + part).value;
   img += '" alt="' + alt + '" ';
   img += 'onclick="imageInlineClick(' + part + ',' + width + ')">';
   img += '</img>';
   var load = document.getElementById("image_load_" + part);
   load.innerHTML = img;
}

function imageInlineClick (part,width)
{
   var ident = 'image_inline_' + part;
   var obj = document.getElementById(ident);
   var percent = parseInt(obj.style.width);
   var realWidth = obj.naturalWidth;
   if (percent == width)
      obj.style.width = '100%';
   else
   if (percent == 100)
      obj.style.width = realWidth + 'px';
   else
      obj.style.width = width + '%';
}

// ----------------------------------------------------------------------------
// options support
// ----------------------------------------------------------------------------

function listenYouGotMail ()
{
   var idx = document.soymail_form.option_soy_select_yougotmail.selectedIndex;
   if (idx == 0) return false;
   if (idx == 1) {
      playYouGotMail();
      return false;
   }
   if (idx == 2)
      var src = soy_YouGotMailPath;
   else
      var src = soy_SoyMailPath + 'theme/' +
document.soymail_form.option_soy_select_yougotmail.options[idx].text + '.mp3';
   playYouGotMail(src);
   return false;
}

// Firefox 3.0, Opera 9.5 and MSIE 7/6 were happy to return table .scrollWidth
// indicative of the width of the *displayed* table.  Mozilla 1.7 and
// Firefox 2.0 seem to return a .scrollWidth of 100% of the page width but
// does return a representative *displayed* row width.  So find the maximum
// using row widths but set using table widths!

var soy_OptionsPanelMaxWidth = 0;

function resizeOptionsPanels()
{
   if (soy_OptionsPanelMaxWidth)
      var maxWidth = soy_OptionsPanelMaxWidth + '%';
   else {
      var oGen = $byId('options_general_tr');
      var oMan = $byId('options_management_tr');
      var oCom = $byId('options_composition_tr');
      var oPer = $byId('options_ident_tr');
      var oVms = $byId('options_vms_tr');
      var maxWidth = oGen.scrollWidth;
      if (oMan.scrollWidth > maxWidth) maxWidth = oMan.scrollWidth;
      if (oCom.scrollWidth > maxWidth) maxWidth = oCom.scrollWidth;
      if (oPer.scrollWidth > maxWidth) maxWidth = oPer.scrollWidth;
      if (oVms && oVms.scrollWidth > maxWidth) maxWidth = oVms.scrollWidth;
      maxWidth += 35;
   }
   oGen = $byId('options_general_tab');
   oMan = $byId('options_management_tab');
   oCom = $byId('options_composition_tab');
   oPer = $byId('options_persona_tab');
   oVms = $byId('options_vms_tab');
   oGen.width = oMan.width = oCom.width = oPer.width = maxWidth;
   if (oVms) oVms.width = maxWidth;
}

// ----------------------------------------------------------------------------
// compose support
// ----------------------------------------------------------------------------

var lang_ComposeAlreadySent;
var lang_ComposeNoRecipient;
var lang_ComposeNoSubject;
var lang_ComposeNoText;
var lang_ComposeAddNone;
var lang_ComposeAdd;
var lang_ComposeBccVmsAddress;
var lang_ComposeReplyToVmsAddress;
var lang_ComposeResetConfirm;
var lang_ComposeClearConfirm;
var lang_ComposeClearDone;
var lang_ComposeNewConfirm;
var lang_ComposeNewDone;
var lang_ComposeResetConfirm;
var lang_ComposeSignDone;
var lang_ComposeWrapOn;
var lang_ComposeWrapOff;
var soy_ComposeAlreadySent;
var soy_ComposeContactsSize;
var soy_ComposeContactsAfter;
var soy_ComposeContactsWidth;

function confirmSend()
{
   var send = true;
   if ($byId('compose_send_confirm').checked)
      send = confirm(lang_ComposeSend+'. '+lang_Sure);
   if (send && soy_ComposeAlreadySent)
      send = confirm(lang_ComposeAlreadySent + ' ' + lang_Sure);
   if (send && $byId('compose_to').value == '')
      send = confirm(lang_ComposeNoRecipient+lang_Sure);
   if (send && $byId('compose_subj').value == '')
      send = confirm(lang_ComposeNoSubject+lang_Sure);
   if (send && (htmlEditorContent() == '') &&
       $byId('compose_text').value == '')
     send = confirm(lang_ComposeNoText+lang_Sure);
   return send;
}

function confirmSendClose()
{
   if (childWindow()) $byId('compose_send_close_child').value = true;
   return confirmSend();
}

function contactListResize()
{
   h1 = $byId('contact_id_1').offsetHeight;
   h2 = $byId('contact_id_2').offsetHeight;
   h3 = $byId('contact_list').offsetHeight;
   if (h2 > h1) h1 = h2;
   h1 -= h3;
   $byId('contact_addr').style.height = h1 + 'px';
}

var contactAddrEach = 0;
var contactListMaximised = false;

function contactListFocus()
{
   if (contactListMaximised) return;
   contactCount = parseInt($byId('contact_count').value);
   if (contactCount < 5) return;
   contactListMaximised = true;
   var caddr = $byId('contact_addr');
   if (contactCount < soy_ComposeContactsSize)
      caddr.style.height = (contactCount+4) + 'em';
   else
      caddr.style.height = soy_ComposeContactsSize + 'em';
   contactAddrEach = caddr.scrollHeight / caddr.length;
   $byId('contact_addr_key_span').innerHTML =
'<br><input id="contact_addr_key" type="text" value="" \
style="margin-top:1px;min-width:12em;width:' + contactListWidth +
'" onfocus="contactListKey();" onblur="contactListFocus()" \
onkeypress="return contactDisableEnter(event)" \
onkeyup="contactListKey(this.value)">';
   $byId('contact_addr_key').focus()
}

function contactListMinimise()
{
   if (!contactListMaximised) return;
   contactListMaximised = false;
   var caddr = $byId('contact_addr');
   $byId('contact_addr_key_span').innerHTML = '';
   contactListResize();
   caddr.selectedIndex = -1;
   caddr.scrollTop = 0;
}

function contactDisableEnter(keyEvent)
{
  if((key = keyEvent.which) == null) key = window.event.keyCode;
  if (key == 13) return false;
  return true;
}

var contactListIdx = 0;
var contactListPrev = null;

function contactListKey(key)
{
   var caddr = $byId('contact_addr');
   var opts = caddr.options;
   var selidx = caddr.selectedIndex;
   if (!key || key.length == 0) return;
   if (selidx >= 0) opts[selidx].selected = false;
   key = key.toLowerCase();
   if (key != contactListPrev) contactListIdx = 0;
   contactListPrev = key;
   if (key.substr(0,1) == '|' || key.substr(0,1) == '!') {
      key = key.substr(1);
      for (idx = contactListIdx; idx < opts.length; idx++)
         if (opts[idx].text.toLowerCase().indexOf(key,0) == 0) break;
   }
   else {
      for (idx = contactListIdx; idx < opts.length; idx++)
         if (opts[idx].text.toLowerCase().indexOf(key,0) >= 0) break;
   }
   if (idx >= opts.length) {
      contactListIdx = 0;
      return;
   }
   opts[idx].selected = true;
   contactListIdx = idx + 1;
   caddr.scrollTop = (contactAddrEach * idx) - caddr.offsetHeight / 2;
}

function contactNone()
{
   refreshStatus (lang_ComposeNoRecipient);
   return false;
}

function contactNonInternet(addr,field)
{
   refreshStatus ('!' + field + ': ' +
addr.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') +
'</span>');
   return false;
}

function contactReportAdd(descr,addr)
{
   refreshStatus (descr + ' ' + lang_ComposeAdd + ': ' +
addr.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'));
   return false;
}

function contactAdd (which,descr)
{
   var retval = contactAddNow(which,descr);
   var caddrkey = $byId('contact_addr_key');
   if (caddrkey && caddrkey.value.length > 0) caddrkey.focus();
   return retval;
}

function contactAddNow (which,descr)
{
   var caddr = $byId('contact_addr');
   var trow = $byId(which+'_tr');
   var waddr = $byId(which);
   if (which.indexOf('compose_reply_to') != -1) waddr.value = '';
   if (caddr.selectedIndex == -1) {
      if (waddr.value == '') {
         if (which.indexOf('compose_to') != -1) return contactNone();
         if (trow) {
            if (trow.style.display)
               trow.style.display = '';
            else
               trow.style.display = 'none';
         }
         if (!contactListMaximised) contactListResize();
         return false;
      }
      return contactNone();
   }
   var addr = caddr.options[caddr.selectedIndex].value;
   var toval = $byId('compose_to').value;
   if (caddr.selectedIndex == 0 &&
       toval.length > 0 &&
       toval.indexOf('@') == -1) return true;
   if (which.indexOf('compose_bcc') != -1 &&
       addr.indexOf('@') == -1)
      return contactNonInternet(addr,lang_ComposeBccVmsAddress);
   if (which.indexOf('compose_reply_to') != -1 &&
       addr.indexOf('@') == -1)
      return contactNonInternet(addr,lang_ReplyToVmsAddress);
   if (waddr.value.indexOf(addr) >= 0)
      return false;
   if (waddr.value != '') waddr.value += '\n';
   waddr.value += addr;
   if (trow && trow.style.display) {
      trow.style.display = '';
      if (!contactListMaximised) contactListResize();
   }
   return contactReportAdd(descr,addr);
}

function composeReset()
{
   if (!confirm(lang_ComposeResetConfirm+lang_Sure)) return false;
   ct = $byId('compose_text');
   ct.value = ct.defaultValue;
   return false;
}

function composeClear()
{
   if (!confirm(lang_ComposeClearConfirm+lang_Sure)) return false;
   $byId('compose_text').value ='';
   refreshStatus (lang_ComposeClearDone);
   return false;
}

function composeNew()
{
   if (!confirm(lang_ComposeNewConfirm+lang_Sure)) return false;
   if ($byId('compose_fwd_count')) return true;
   $byId('contact_addr').selectedIndex = -1;
   $byId('compose_to').value = '';
   $byId('compose_cc').value = '';
   $byId('compose_cc_tr').style.display = 'none';
   $byId('compose_bcc').value = '';
   $byId('compose_bcc_tr').style.display = 'none';
   $byId('compose_reply_to').value = '';
   $byId('compose_reply_to_tr').style.display = 'none';
   $byId('compose_subj').value = '';
   $byId('compose_text').value = '';
   $byId('compose_to').focus();
   contactListResize();
   refreshStatus (lang_ComposeNewDone);
   return false;
}

function signatureAppend()
{
   var tid = $byId('compose_text');
   var sid = $byId('compose_signature');
   var fid = $byId('compose_sigfile');
   var did = $byId('compose_sigfile_selected');
   if (fid && did && fid.options[fid.selectedIndex].value != did.value)
      return true;
   tid.value = tid.value + '\n' + sid.value;
   refreshStatus (lang_ComposeSignDone);
   return false;
}

var soy_SetWrapMozilla;
var soy_SetWrapMsie;
var soy_SetWrapValues = new Array();

var setWrapText = null;
var setWrapValue = null;

function setWrapAtAst()
{
   if (setWrapValue == null) return;
   setWrapText.value = setWrapValue;
   setWrapValue = null;
}

function setWrapAt()
{
   var btn = $byId('compose_btn_wrap');
   var wat = $byId('compose_wrap_at');
   var wrapAt = parseInt(btn.value.substr(btn.value.search(/\d/)));
   if (isNaN(wrapAt)) wrapAt = 0;
   var setWrapPrev = wrapAt;
   for (idx = 0; soy_SetWrapValues[idx+1] != undefined; idx++) {
      if (soy_SetWrapValues[idx] != wrapAt) continue;
      wrapAt = soy_SetWrapValues[idx+1];
      break;
   }
   if (soy_SetWrapValues[idx+1] == undefined) wrapAt = soy_SetWrapValues[0];
   if ((wrapAt == 0 && setWrapPrev > 0) ||
       (wrapAt > 0 && setWrapPrev == 0))
   {
      // only cater for Mozilla/Firefox and MSIE vagaries!
      if (!(soy_SetWrapMozilla || soy_SetWrapMsie)) return true;
      setWrapText = $byId('compose_text');
      setWrapValue = setWrapText.value;
      setWrapText.value = '';
      if (wrapAt) {
         if (soy_SetWrapMsie)
            setWrapText.setAttribute('wrap','soft');
         else {
            setWrapText.setAttribute('wrap','virtual');
            setWrapText.style.overflow = 'visible';
         }
      }
      else {
         if (soy_SetWrapMsie)
            setWrapText.setAttribute('wrap','off');
         else {
            setWrapText.setAttribute('wrap','off');
            setWrapText.style.overflow = 'auto';
         }
      }
      setTimeout('setWrapAtAst();',250);
   }
   wat.value = wrapAt;
   if (wrapAt)
      btn.value = lang_ComposeWrapOn + ' ' + wrapAt;
   else
      btn.value = lang_ComposeWrapOff;
   setWrapPrev = wrapAt;
   return false;
}

var soy_SetRowsValues = new Array();

function setEditRows(rows)
{
   var btn = $byId('compose_btn_rows');
   var ct = $byId('compose_text');
   if (rows)
      ct.rows = rows;
   else
   {
      for (idx = 0; soy_SetRowsValues[idx+1]; idx++)
      {
         if (soy_SetRowsValues[idx] != ct.rows) continue;
         ct.rows = soy_SetRowsValues[idx+1];
         break;
      }
      if (!soy_SetRowsValues[idx+1]) ct.rows = soy_SetRowsValues[0];
   }
   $byId('compose_edit_rows').value = ct.rows;
   btn.value = '   ' + ct.rows;
   //Mozilla silliness!
   ct.focus();
   return false;
}

var soy_SetColsValues = new Array();

function setEditCols(cols)
{
   var btn = $byId('compose_btn_cols');
   var ct = $byId('compose_text');
   if (cols)
   {
      ct.cols = cols;
      // the extra 0.3 of a character is for Safari 9.1
      ct.style.setProperty ('width', cols + '.3ch', 'important');
   }
   else
   {
      for (idx = 0; soy_SetColsValues[idx+1]; idx++) {
         if (soy_SetColsValues[idx] != ct.cols) continue;
         ct.cols = soy_SetColsValues[idx+1];
         ct.style.setProperty ('width', soy_SetColsValues[idx+1] + '.3ch',
                               'important');
         break;
      }
      if (!soy_SetColsValues[idx+1])
      {
         ct.cols = soy_SetColsValues[0];
         ct.style.setProperty ('width', soy_SetColsValues[0] + '.3ch',
                               'important');
      }
   }
   $byId('compose_edit_cols').value = ct.cols;
   btn.value = ct.cols + '  ';
   ct.focus();
   return false;
}

function resetRowsCols()
{
   setEditRows(soy_SetRowsValues[0]);
   setEditCols(soy_SetColsValues[0]);
   return false;
}

function composeTextDirection(thisway)
{
   var ct = null;
   if (typeof tinyMCE != 'undefined') ct = tinyMCE.get('compose_text');
   if (ct)
      ct.getBody().dir = thisway;
  else
      $byId('compose_text').dir = thisway;
  $byId('compose_dir').value = thisway;
}


function composePreview(charset,msg,title)
{
   var preview = window.open ('javascript:void(0);','_blank');
   if (!preview) return;
   if (charset.length) preview.document.characterSet = charset;
console.log(hexdump(msg));
console.log(preview.document.characterSet);
   preview.document.title = title;
   preview.document.open("text/html","replace");
   preview.document.write(msg);
   preview.document.close();
}

function hexdump (buffer, blockSize)
{
   blockSize = blockSize || 16;
   var lines = [];
   var hex = "0123456789ABCDEF";
   for (var b = 0; b < buffer.length; b += blockSize)
   {
       var block = buffer.slice(b, Math.min(b + blockSize, buffer.length));
       var addr = ("0000" + b.toString(16)).slice(-4);
       var codes = block.split('').map(function (ch)
       {
           var code = ch.charCodeAt(0);
           return " " + hex[(0xF0 & code) >> 4] + hex[0x0F & code];
       }).join("");
       codes += "   ".repeat(blockSize - block.length);
       var chars = block.replace(/[\x00-\x1F\x20]/g, '.');
       chars +=  " ".repeat(blockSize - block.length);
       lines.push(addr + " " + codes + "  " + chars);
   }
   return lines.join("\n");
}

// ----------------------------------------------------------------------------
// TinyMCE 4.n support
// ----------------------------------------------------------------------------

var tinyJavaScript = null;
var tinyMCEurl = '/soymail/-/tinymce/js/tinymce/tinymce.min.js';

// dynamically load the TinyMCE JavaScript 
function tinyMCEload(callback)
{
   if (tinyJavaScript) return false;
   var oHead = document.getElementsByTagName('HEAD')[0];
   tinyJavaScript = document.createElement('script');
   tinyJavaScript.setAttribute('type','text/javascript');
   tinyJavaScript.setAttribute('language','JavaScript');
   tinyJavaScript.setAttribute('src',tinyMCEurl);
   tinyJavaScript.setAttribute('onload',callback);
   oHead.appendChild(tinyJavaScript);
   return true;
}

function aboutTinyMCE()
{
   if (tinyMCEload('aboutTinyMCE()')) return;
   if (typeof tinyMCE == 'undefined') return;
   var about = $byId('about_TinyMCE');
   about.innerHTML = tinyMCE.majorVersion + '.' + tinyMCE.minorVersion;
}

function htmlEditorLoad()
{
   if (tinyMCEload('htmlEditorLoad()')) return;

   var ta = $byId('compose_text');
   var dir = ta.getAttribute('dir');
   if (!dir) dir = 'ltr';

   var initers = {
      selector : 'textarea#compose_text',
      mode : 'exact',
      theme : 'modern',
      statusbar : false,
      menubar : false,
      width : 750,
      directionality : dir,
      plugins: [
         'advlist autolink code directionality lists link charmap preview ' +
         'hr anchor pagebreak searchreplace wordcount visualblocks ' +
         'visualchars code fullscreen insertdatetime media nonbreaking ' +
         'table contextmenu directionality emoticons template paste textcolor'
      ],
      toolbar1 : 'undo redo | cut copy paste | bold italic underline | ' +
                 'alignleft aligncenter alignright alignjustify | ' +
                 'bullist numlist outdent indent | removeformat | preview',
      toolbar2 : 'fontselect fontsizeselect styleselect | ' +
                 'forecolor backcolor | ' +
                 'table link unlink inserttime charmap code searchreplace',
      init_instance_callback : 'htmlEditorResizeDeferred'
   };

   window.tinymce.dom.Event.domLoaded = true;
   tinyMCE.init (initers);
}

// give the composition page a few moments to settle down
function htmlEditorResizeDeferred ()
{
   setTimeout('htmlEditorResize()',500);
}

function htmlEditorResize ()
{
   var cols = $byId('compose_edit_cols').value;
   var rows = $byId('compose_edit_rows').value;
   var ta = document.createElement('textarea');
   ta.rows = rows;
   ta.cols = cols;
   ta.style.display = 'hidden';
   var sf = $byId('soymail_form');
   sf.appendChild(ta);
   var tahp = ta.offsetHeight/rows;
   var tawp = ta.offsetWidth / (cols * 0.65);
   sf.removeChild(ta);
   tinyMCE.get('compose_text').theme.resizeTo(cols*tawp, rows*tahp);
}

function htmlEditorContent()
{
   if (typeof tinyMCE == 'undefined') return '';
   return tinyMCE.get('compose_text').getContent();
}

// -- end ---------------------------------------------------------------------
