// Add Event Manager for simple pub/sub pattern; DATE:2018-11-11 var XoEvtMgr = (function(){ var funcs = []; function XoEvtMgr() { } XoEvtMgr.prototype.sub = function(func) { funcs.push(func); } XoEvtMgr.prototype.pub = function() { var funcsIdx = 0; var funcsLen = funcs.length; for (funcsIdx = 0; funcsIdx < funcsLen; funcsIdx++) { var func = funcs[funcsIdx]; func(arguments); } } return XoEvtMgr; }()); if (!window.xowa) { window.xowa = { root_dir : xowa_root_dir, js : { jquery : { init_done: false, }, mediaWiki: { init_done: false, }, doc: { }, win: { }, selection : { }, xtn : { }, }, css : {}, cfg : {}, cookie: {}, gfs : {}, app : {}, page : {}, server : {}, }; xowa.css.add = function(css) { var s = document.createElement('style'); document.getElementsByTagName('head')[0].appendChild(s); s.appendChild(document.createTextNode(css)); }; xowa.cfg.get = function(key) { return window.xowa_global_values[key] || null; }; xowa.cfg.set = function(key, val) { window.xowa_global_values[key] = val; }; xowa.cookie = function(key, val, cookieData) { if (val == null) // accessor; EX: $.cookie('key'); return xowa.cfg.get(key); else // mutator; EX: $.cookie('key', 'val'); xowa.cfg.set(key, val); }; xowa.js.load_lib = function(file, callback) { var script = document.createElement('script'); if (callback) { script.onload = callback; } script.async = false; script.setAttribute('src', file); document.getElementsByTagName('head')[0].appendChild(script); }; xowa.js.jquery.init_callback = function() { jQuery.cookie = xowa.cookie; jQuery.ready(); //fire the ready event }; xowa.js.jquery.init = function(document) { if (xowa.js.jquery.init_done) return; xowa.js.load_lib(xowa.root_dir + 'bin/any/xowa/html/res/lib/jquery/jquery-1.11.3.min.js', xowa.js.jquery.init_callback); xowa.js.jquery.init_done = true; }; xowa.js.mediaWiki.init = function(){ if (xowa.js.mediaWiki.init_done) return; window.mediaWiki = { msg: xowa.cfg.get, log: function () {}, config: { get: xowa.cfg.get }, //simulate mediaWiki.hook: Execute functions queued for 'wikipage.content' directly, and ignore anything else hook: function (name) { return name === 'wikipage.content' ? { add: function (f) { f(window.jQuery ? jQuery('#mw-content-text') : null); }, remove: function () {}, fire: function () {} } : name === 'wikipage.collapsibleContent' ? { add: function (f) { //fc = f; f(mw.collapsibleContent); // f(window.jQuery ? $collapsible.find( '> .mw-collapsible-content' ) : null); }, remove: function () {}, fire: function (t) { // different order!!!! wrong order HACK mw.collapsibleContent = t; //fc(t); } } : { add: function () {}, remove: function () {}, fire: function () {} }; } }; xowa.js.mediaWiki.init_done = true; } xowa.gfs.arg_yn = function(v) {return v ? "'y'" : "'n'";} xowa.gfs.arg_str = function(v) {return "'" + v.replace("'", "''") + "'";} xowa.gfs.http_cmd_url = null; xowa.gfs.exec = function(cbk, cmd) { if (xowa.cfg.get('mode_is_gui')) { var rv = xowa_exec('cmd', cmd); if (cbk != null) cbk(rv); } else { if (xowa.cfg.get('mode_is_http')) { var http_cmd_url = xowa.gfs.http_cmd_url; if (http_cmd_url == null) { xowa.js.jquery.init(); http_cmd_url = 'http://localhost:' + xowa.cfg.get('http-port') + '/xowa-cmd:'; xowa.gfs.http_cmd_url = http_cmd_url; } $.get(http_cmd_url + cmd).done(function(data) { if (cbk != null) cbk(rv); }); } else throw 'xowa offline'; } }; // PURPOSE: focus body so that keyboard up / down will scroll html content after page loads xowa.js.win.focus_body = function() { var body_elems = document.getElementsByTagName('body'); if (body_elems == null || body_elems.length == 0) {return false;} // no body found; shouldn't happen var body_elem = body_elems[0]; if (body_elem.contentEditable == 'true') { body_elem.selectionStart = 0; body_elem.selectionEnd = 0; window.setTimeout(function(){body_elem.focus();}, 250); } else { var active_element_name = document.activeElement.nodeName.toLowerCase(); if (active_element_name == 'html' || active_element_name == 'body') { // no anchor selected var elems = document.getElementsByTagName("a"); if (elems != null && elems.length > 0) { elems[0].focus(); // focus first } } } }; // PURPOSE: get vpos for restoring page position when moving forward / backward through pages xowa.js.win.vpos_get = function() { var getIndex = function(node) { var parent = node.parentNode, i = -1, child; while (parent && (child = parent.childNodes[++i])) if (child == node) return i; return -1; } var getPath = function(node) { var parent, path = [], index = getIndex(node); (parent = node.parentNode) && (path = getPath(parent)); index > -1 && path.push(index); return path; } var sel = window.getSelection(); if (sel.rangeCount == 0) return document.documentElement.scrollTop + '|0'; // occurs during view-only mode var rng = sel.getRangeAt(0); var rng_bgn = rng.startContainer; var pos = getPath(rng_bgn); return document.documentElement.scrollTop + '|' + pos.toString(); }; // PURPOSE: set vpos for restoring page position when moving forward / backward through pages xowa.js.win.vpos_set = function(node_path, scroll_top) { var getNode = function(path) { var node = document.documentElement, i = 0, index = 0; while((index = path[++i]) > -1) { node = node.childNodes[index]; } return node; } var sel = window.getSelection(); var rng = document.createRange(); var path = new Array(); path = [node_path]; var nde = getNode(path); rng.selectNodeContents(nde); rng.setEnd(nde, 0); // removes selection sel.removeAllRanges(); sel.addRange(rng); document.documentElement.scrollTop = scroll_top; return true; }; // PURPOSE: find text in textarea (Edit / HTML modes) xowa.js.win.find_in_textarea_ctx = {}; xowa.js.win.find_in_textarea = function(find_text, dir_fwd, case_match, wrap_find) { var find_in_textarea_main = function(find_text, dir_fwd, case_match, wrap_find) { var text_area = document.getElementById('xowa_edit_data_box'); var full_txt = text_area.value; var ctx = xowa.js.win.find_in_textarea_ctx; var find_text_is_same = find_text == ctx.prv_find_text; ctx.prv_find_text = find_text; if (!case_match) { find_text = find_text.toLowerCase(); full_txt = full_txt.toLowerCase(); } var find_bgn = dir_fwd ? 0 : 9999999; var sel_bgn = text_area.selectionStart; if (sel_bgn > -1 && find_text_is_same) // selection active and find_text_is same; move pos forward to select next item find_bgn = sel_bgn + (dir_fwd ? 1 : -1); var found = find_text_in_textarea(text_area, dir_fwd, find_text, full_txt, find_bgn); if (!found && wrap_find) { find_bgn = dir_fwd ? 0 : 9999999; found = find_text_in_textarea(text_area, dir_fwd, find_text, full_txt, find_bgn); } return find_bgn; } var find_text_in_textarea = function(text_area, dir_fwd, find_text, full_txt, find_bgn) { if (dir_fwd) find_bgn = full_txt.indexOf(find_text, find_bgn); else find_bgn = full_txt.lastIndexOf(find_text, find_bgn); if (find_bgn == -1) return false; var find_end = find_bgn + find_text.length; text_area.setSelectionRange(find_bgn, find_bgn + 1); // select first character of find var ev = document.createEvent('KeyEvents'); ev.initKeyEvent('keypress', true, true, window, false, false, false, false, 0, text_area.value.charCodeAt(find_bgn)); // simulate keypress to replace firstchar with itself; text_area.dispatchEvent(ev); // scrolls text_area.setSelectionRange(find_bgn, find_end); return true; } return find_in_textarea_main(find_text, dir_fwd, case_match, wrap_find); }; // PURPOSE: scroll elem with id into view; used when navigating to Page#Header and scrolling page to #Header xowa.js.win.scroll_elem_into_view = function(elem_id) { var select_by_id_proc = function(id) { var sel = window.getSelection(); sel.removeAllRanges(); var nde = document.getElementById(id); if (nde == null) return false; highlight_nde(nde); return true; } var highlight_nde = function(nde) { var rng = document.createRange(); if (nde.childNodes.length > 0) nde = nde.childNodes[0]; rng.selectNodeContents(nde); rng.setStart(nde, 0); // removes selection rng.setEnd(nde, 0); // removes selection // set selection var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(rng); // scroll to selection NOTE: this will not work for

's that are taller than the window // window.setTimeout(function(){scrollIntoView_proc(nde);}, 100); scrollIntoView_proc(nde); } var scrollIntoView_proc = function (t) { if (typeof(t) != 'object') return; if (t.getRangeAt) { // t is selection if (t.rangeCount == 0) return; t = t.getRangeAt(0); } // if t is not an element, then move up hierarchy until element which accepts scrollIntoView() o = t; while (o && o.nodeType != 1) o = o.previousSibling; t = o || t.parentNode; if (t) t.scrollIntoView(true); } return select_by_id_proc(elem_id); }; // PURPOSE: print preview xowa.js.win.print_preview = function() { window.print(); }; // PURPOSE: copy xowa.js.selection.get_text_or_href = function() { var sel = window.getSelection(); // check if anything is highlighted if (sel != null) { var sel_str = sel.toString(); if (sel_str != "") return "1|" + sel; } var nde = document.activeElement; // default to active anchor; typically this will be whatever is "clicked" if (nde == null) return "0|"; return (nde.tagName == "A") // nothing clicked ? "2|" + nde.href : "0|" ; }; // PURPOSE: open selected text in new tab; Ctrl+G,Ctrl+F xowa.js.selection.get_href_or_text = function() { var sel_obj = window.getSelection(); if (sel_obj == null) return "0|"; var sel_text = "1|" + sel_obj.toString(); if (sel_obj.rangeCount == 0) return sel_text; var range = sel_obj.getRangeAt(0); if (range == null) return sel_text; var count = 0; var node = range.commonAncestorContainer; while (count < 8) { // NOTE: needs to be at least 3 to handle nested html inside anchor; EX.WP:[[Portal:Baseball/Categories and Main topics|'''Categories & Topics''']]; also EX.WP:Wikipedia:Featured topics/Battlecruisers of Germany; if (node == null) return sel_text; if (node.attributes != null) { var atr = node.attributes['href']; if (atr != null) return "2|" + atr.value; } count = count + 1; node = node.parentNode; } return sel_text; }; // PURPOSE: (1) middle-click on link to open in new tab; (2) highlight text and open in new tab xowa.js.selection.get_active_or_selection = function() { var nde = document.activeElement; // default to active anchor; typically this will be whatever is "clicked" if (nde != null && nde.tagName == "A") // anchor clicked return "2|" + nde.href; // return anchor's href var sel = window.getSelection(); // check if anything is highlighted if (sel != null) { var sel_str = sel.toString(); if (sel_str != "") return "1|" + sel; // return highlighted text } return "0|"; // return nothing }; // PURPOSE: save file as xowa.js.selection.get_src_or_empty = function() { var nde = document.activeElement; // default to active anchor; typically this will be whatever is "right-clicked" if (nde == null) return ''; if (nde.tagName != 'A') return ''; var subElements = nde.getElementsByTagName('IMG'); if (subElements == null) return ''; var subElement = subElements[0]; if (subElement == null) return ''; return subElement.src; }; // PURPOSE: keyboard command: Ctrl+G,Ctrl+G xowa.js.selection.toggle_focus_for_anchor = function() { // selects node with blue background around text (same as highlighting text with mouse) var select_nde = function(nde2) { // create rng var rng = document.createRange(); rng.selectNodeContents(nde2); var child_len = nde2.childNodes.length; if (child_len == 0) { // no children; shouldn't happen rng.setStart(nde2, 0); rng.setEnd(nde2, nde2.textContent.length); } else { // children exists; select 1st and nth; rng.setStart(nde2.childNodes[0], 0); var nth_nde = nde2.childNodes[child_len - 1]; rng.setEnd(nth_nde, nth_nde.textContent.length); } // set selection var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(rng); } var sel = window.getSelection(); if (sel.rangeCount == 0) return; // nothing selected; exit var rng = sel.getRangeAt(0); var nde1 = rng.startContainer; var count = 0; while (count < 8) { // traverse up dom from selected node; 8 is arbitrary limit if (nde1 == null) return; // null-check, but also guards against not finding in current selection; if (nde1.attributes != null) { var href = nde1.attributes['href']; if (href != null) { // nde1 has href if (count == 0) { // nde1 was when count == 0; indicates that was selected select_nde(nde1); // select "internal text" nde1.blur(); document.getElementsByTagName('body')[0].focus(); } else { // nde1 was "internal text" when count == 0; note that nde1 is now at count == 1 nde1.focus(); // focus nde1 which is now an } return; } } count = count + 1; nde1 = nde1.parentNode; } } // PURPOSE: get active link when in editable mode (needs changes to Swt_browser) DATE:2015-06-23 xowa.js.selection.get_active_for_editable_mode = function(atr_key, or_val) { var sel_obj = window.getSelection(); if (sel_obj == null) return or_val; if (sel_obj.rangeCount == 0) return or_val; var range = sel_obj.getRangeAt(0); if (range == null) return or_val; var count = 0; var node = range.commonAncestorContainer; // NOTE: needs to be at least 3 to handle nested html inside anchor // PAGE:en.w:Portal:Baseball/Categories and Main topics|'''Categories & Topics''']] // PAGE:en.w:Wikipedia:Featured topics/Battlecruisers of Germany while (count < 8) { if (node == null) return or_val; if (node.attributes != null) { var atr = node.attributes[atr_key]; if (atr != null) return atr.value; } count = count + 1; node = node.parentNode; } return or_val; }; xowa.js.doc.evtElemAdd = new XoEvtMgr(); // PURPOSE: used when clicking on file to get xowa_title xowa.js.doc.root_html_get = function() { return document.getElementsByTagName("html")[0].innerHTML; }; // PURPOSE: focuses html_box when leaving other gui widgets (escape key) xowa.js.doc.elem_focus = function(elem_id) { var nde = document.getElementById(elem_id); if (nde != null) nde.focus(); }; // PURPOSE: Math and Score to delete

 after replacing with content
  xowa.js.doc.elem_delete = function(elem_id) {
    var elem = document.getElementById(elem_id);
    elem.parentNode.removeChild(elem);
    return true;
  };

  // PURPOSE: async search
  xowa.js.doc.elem_append_above = function(elem_id, html) {
    var elem = document.getElementById(elem_id);
    elem.insertAdjacentHTML('beforebegin', html);
    
    xowa.js.doc.process_new_elem(elem.parentNode);  // NOTE: elem is placeholder item; html is inserted after it; need to call process_new_elem on parentNode; DATE:2015-08-03
    return true;
  };
  // PURPOSE: process new element such as adding bindings; DATE:2015-07-09
  xowa.js.doc.process_new_elem = function(elem) {
     xowa.js.doc.evtElemAdd.pub(elem);
  }
  
  // PURPOSE: async search; gallery; imap
  xowa.js.doc.elem_replace_html = function(elem_id, html) {
    var elem = document.getElementById(elem_id);
    elem.insertAdjacentHTML('beforebegin', html);
    elem.parentNode.removeChild(elem); 
  };

  // PURPOSE: show image on page
  xowa.js.doc.elem_img_update = function(elem_id, elem_src, elem_width, elem_height) {
    if (document == null) return false;
    var elem = document.getElementById(elem_id);
    if (elem == null) return false;
    elem.src = elem_src;
    elem.width = elem_width;
    elem.height = elem_height;
    return true;
  };

  // PURPOSE: prefs_mgr; get edit_box val
  xowa.js.doc.atr_get_as_obj = function(elem_id, atr_key) {
    return document.getElementById(elem_id)[atr_key];
  };
  
  // PURPOSE: prefs_mgr; note that toString() is needed for bool which somehow comes back strangely through SWT
  xowa.js.doc.atr_get_to_str = function(elem_id, atr_key) {
    return document.getElementById(elem_id)[atr_key].toString();
  };

  // PURPOSE: search; set cancel_icon
  xowa.js.doc.atr_set = function(elem_id, atr_key, atr_val) {
    document.getElementById(elem_id).setAttribute(atr_key, atr_val);
    return true;
  };

  // PURPOSE: append val or set it if empty; used by redlink
  xowa.js.doc.atr_append_or_set = function(elem_id, atr_key, val) {
    var elem = document.getElementById(elem_id);
    var atr_val = elem[atr_key];
    if (atr_val && atr_val.length > 0)
      atr_val = atr_val + ' ' + val;
    else
      atr_val = val;
    elem.setAttribute(atr_key, atr_val);
    return true;
  };

  // PURPOSE: called by packed gallery. EX: en.w:National_Gallery_of_Art
  xowa.js.xtn.gallery_packed_exec = function() {
    gallery_packed_exec(jQuery, mediaWiki);
  };
  
  // PURPOSE: get or create object by path; EX: "obj__get('xowa', 'usr', 'bookmarks');" will create window.xowa.usr.bookmarks; DATE:2015-07-19
  obj__get = function(owner) {
    var len = arguments.length;
    for (var i = 1; i < len; ++i) {
      var arg = arguments[i];
      var next_owner = owner[arg];
      if (!next_owner) {
        next_owner = {};
        owner[arg] = next_owner;
      }
      owner = next_owner;
    }
    return owner;
  }

  function xowa_toggle_visible(prefix, visible) {
    var icon = document.getElementById(prefix + '-toggle-icon');
    var elem = document.getElementById(prefix + '-toggle-elem');
    if (visible == null)                       // visible not passed
      visible = !(elem.style.display === '');  // default to reverse of current visible state
    if (visible) {
      elem.style.display = '';
      icon.title = xowa_global_values.show;      
      icon.src = xowa_root_dir + 'bin/any/xowa/file/app.general/twisty_down.png';
    }
    else {
      elem.style.display = 'none';
      icon.title = xowa_global_values.hide;
      icon.src = xowa_root_dir + 'bin/any/xowa/file/app.general/twisty_right.png';
    }
    // xowa.gfs.exec(null, 'xowa.api.html.page.toggles.get(' + xowa.gfs.arg_str(prefix) + ').visible = ' + xowa.gfs.arg_yn(visible) + ';');
    xowa.gfs.exec(null, "app.cfg.set('app', 'xowa.html.toggles." + prefix + "', " + xowa.gfs.arg_yn(visible) + ");");
  };
  
  function xowa_elem_select(elem) {
    var rng = document.createRange();
    rng.selectNodeContents(elem);// select entire range
    rng.setEnd(elem, 0); // remove selection
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(rng);
  }

  // 2021-01-11|ISSUE#:829|Fix SWT not encoding non-ASCII characters in image name
  xowa.js.doc.fixSwtImgEncoding = function() {
    // only run for GUI mode
    if (!xowa_global_values.mode_is_gui) return;
    
    // encodes each character in a string in hex notation (%0F)
    function urlEncode(input) {
      var output = [];
      for (var n = 0; n < input.length; n++) {
        var hex = Number(input.charCodeAt(n)).toString(16);
        output.push('%' + hex);
      }
      return output.join('');
    }

    // get all images
    var imgs = document.images;
    for (var i = 0; i < imgs.length; i++) {
      // get img src
      var img = imgs[i];
      var imgSrc = img.src;
      
      // encode each char in img.src using hex-notation; note that this is necessary for encoding bad SWT encodings like such as in `File:Bevölkerungspyramide_Georgien_2016.png`
      // * real chr: `ö`
      // * fail SWT: `ö`
      // * pass hex: `%c3%b6`
      var encodedSrc = urlEncode(imgSrc);
      
      // now decode hex-notation back to regular chars; the JS function will properly decode `%c3%b6` as `ö`
      var decodedSrc = decodeURIComponent(encodedSrc);
      if (imgSrc != decodedSrc) {
        img.src = decodedSrc;
      }
    }
  }

  window.navigate_to = function(href) { // XOWA: expose publicly for alertify
    window.location = href;
  };
}