<commit_before>
console.log("control.js loaded");

var token; // actually interpolated into the enclosing control.html
var $, d3; // populated in control.html
var messages = {}; // indexed by cid
var addressbook = {}; // indexed by cid
var invitations = {}; // indexed by invite-id
var current_cid = null;

function update_messages(e) {
  var data = JSON.parse(e.data); // .action, .id, .new_value

  if (data.action == "insert" || data.action == "update")
    messages[data.id] = data.new_value;
  else if (data.action == "delete")
    delete messages[data.id];

  // "value" is a row of the "messages" table
  var value = data.new_value; // .id, .cid, .payload_json, .petname
  var payload = JSON.parse(value.payload_json); // .basic
  console.log("basic message", payload.basic);

  var entries = [];
  for (var id in messages)
    entries.push(messages[id]);
  function render_message(e) {
    var payload = JSON.parse(e.payload_json); // .basic
    return e.petname+"["+e.cid+"]: "+payload.basic;
  }
  var s = d3.select("#messages").selectAll("li")
        .data(entries, function(e) {return e.id;})
        .text(render_message);
  s.enter().append("li")
    .text(render_message);
  s.exit().remove();
}

function show_contact_details(e) {
  console.log("details", e.type, e.data.id);
  $("div.contact-details-pane").show();
  $("#address-book div.entry").removeClass("selected");
  $("#address-book div."+e.rowid).addClass("selected");
  $("#contact-details-petname").text(e.data.petname);
  $("#contact-details-id").text(e.data.id);
  if (e.type === "invitation") {
    $("#contact-details-id-type").text("Invitation-ID");
    $("#contact-details-state").text("pending invitation ("+
                                     e.data.rx_msgs+")");
  } else {
    $("#contact-details-id-type").text("Contact-ID");
    if (e.data.acked) {
      $("#contact-details-state").hide();
    } else {
      $("#contact-details-state").text("State: waiting for ack");
      $("#contact-details-state").show();
    }
  }
}

function open_contact_room(e) {
  if (e.type !== "contact")
    return;
  current_cid = e.data.id;
  console.log("open_contact_room", current_cid);
  d3.select("#send-message-to").text(addressbook[current_cid].petname
                                     + " [" + current_cid + "]");
}

function update_invitations(e) {
  var data = JSON.parse(e.data); // .action, .id, .new_value

  // "value" is a subset of an "invitations" row
  if (data.action == "insert" || data.action == "update")
    invitations[data.id] = data.new_value;
  else if (data.action == "delete")
    delete invitations[data.id];

  update_combined_addressbook();
}

function update_addressbook(e) {
  var data = JSON.parse(e.data); // .action, .id, .new_value

  // "value" is a subset of an "addressbook" row
  if (data.action == "insert" || data.action == "update")
    addressbook[data.id] = data.new_value;
  else if (data.action == "delete")
    delete addressbook[data.id];

  update_combined_addressbook();
}

function update_combined_addressbook() {
  var entries = [];
  var id;
  for (id in addressbook) // id, petname, acked
    entries.push({type: "contact", data: addressbook[id], rowid: "cid-"+id});
  for (id in invitations) // id, petname, code, when_invited, rx_msgs
    entries.push({type: "invitation", data: invitations[id], rowid: "iid-"+id});

  var s = d3.select("#address-book").selectAll("div.entry")
        .data(entries, function(e) { return e.type + "-" + e.data.id; })
        .text(function(e) {return e.data.petname;})
        .attr("class", function(e) {
          if (e.type == "contact")
            return "entry contact "+e.rowid;
          else
            return "entry invitation "+e.rowid;
        })
        .on("click", show_contact_details)
        .on("dblclick", open_contact_room)
  ;

  s.enter().append("div")
    .text(function(e) {return e.data.petname;})
    .attr("class", function(e) {
      if (e.type == "contact")
        return "entry contact cid-"+e.data.id;
      else
        return "entry invitation iid-"+e.data.id;
    })
    .on("click", show_contact_details)
    .on("dblclick", open_contact_room)
  ;
  s.exit().remove();
}

function handle_invite_go(e) {
  var petname = d3.select("#invite-petname")[0][0].value;
  var code = d3.select("#invite-code")[0][0].value;
  console.log("inviting", petname, code);
  var req = {"token": token, "args": {"petname": petname, "code": code}};
  d3.json("/api/v1/invite").post(JSON.stringify(req),
                                 function(err, r) {
                                   console.log("invited", r.ok);
                                 });
  d3.select("#invite-petname")[0][0].value = "";
  d3.select("#invite-code")[0][0].value = "";
}

function handle_send_message_go(e) {
  var msg = d3.select("#send-message-body")[0][0].value;
  console.log("sending", msg, "to", current_cid);
  var req = {"token": token, "args": {"cid": current_cid, "message": msg}};
  d3.json("/api/v1/send-basic").post(JSON.stringify(req),
                                     function(err, r) {
                                       console.log("sent", r.ok);
                                     });
  d3.select("#send-message-body")[0][0].value = "";
}

function main() {
  console.log("onload");

  $("ul.nav a").click(function (e) {
    e.preventDefault();
    $(this).tab("show");
  });

  var ev;
  ev = new EventSource("/api/v1/views/addressbook?token="+token);
  ev.onmessage = update_addressbook;
  ev = new EventSource("/api/v1/views/invitations?token="+token);
  ev.onmessage = update_invitations;
  ev = new EventSource("/api/v1/views/messages?token="+token);
  ev.onmessage = update_messages;

  $("#invite").hide();
  $("#add-contact").click(function(e) {
    $("#invite").toggle("clip");
  });
  $("div.contact-details-pane").hide();

  d3.select("#invite-go")[0][0].onclick = handle_invite_go;
  d3.select("#send-message-go")[0][0].onclick = handle_send_message_go;

  console.log("setup done");
}

document.addEventListener("DOMContentLoaded", main);
<commit_msg>close the invitation panel once the button is pressed<commit_after>
console.log("control.js loaded");

var token; // actually interpolated into the enclosing control.html
var $, d3; // populated in control.html
var messages = {}; // indexed by cid
var addressbook = {}; // indexed by cid
var invitations = {}; // indexed by invite-id
var current_cid = null;

function update_messages(e) {
  var data = JSON.parse(e.data); // .action, .id, .new_value

  if (data.action == "insert" || data.action == "update")
    messages[data.id] = data.new_value;
  else if (data.action == "delete")
    delete messages[data.id];

  // "value" is a row of the "messages" table
  var value = data.new_value; // .id, .cid, .payload_json, .petname
  var payload = JSON.parse(value.payload_json); // .basic
  console.log("basic message", payload.basic);

  var entries = [];
  for (var id in messages)
    entries.push(messages[id]);
  function render_message(e) {
    var payload = JSON.parse(e.payload_json); // .basic
    return e.petname+"["+e.cid+"]: "+payload.basic;
  }
  var s = d3.select("#messages").selectAll("li")
        .data(entries, function(e) {return e.id;})
        .text(render_message);
  s.enter().append("li")
    .text(render_message);
  s.exit().remove();
}

function show_contact_details(e) {
  console.log("details", e.type, e.data.id);
  $("div.contact-details-pane").show();
  $("#address-book div.entry").removeClass("selected");
  $("#address-book div."+e.rowid).addClass("selected");
  $("#contact-details-petname").text(e.data.petname);
  $("#contact-details-id").text(e.data.id);
  if (e.type === "invitation") {
    $("#contact-details-id-type").text("Invitation-ID");
    $("#contact-details-state").text("pending invitation ("+
                                     e.data.rx_msgs+")");
  } else {
    $("#contact-details-id-type").text("Contact-ID");
    if (e.data.acked) {
      $("#contact-details-state").hide();
    } else {
      $("#contact-details-state").text("State: waiting for ack");
      $("#contact-details-state").show();
    }
  }
}

function open_contact_room(e) {
  if (e.type !== "contact")
    return;
  current_cid = e.data.id;
  console.log("open_contact_room", current_cid);
  d3.select("#send-message-to").text(addressbook[current_cid].petname
                                     + " [" + current_cid + "]");
}

function update_invitations(e) {
  var data = JSON.parse(e.data); // .action, .id, .new_value

  // "value" is a subset of an "invitations" row
  if (data.action == "insert" || data.action == "update")
    invitations[data.id] = data.new_value;
  else if (data.action == "delete")
    delete invitations[data.id];

  update_combined_addressbook();
}

function update_addressbook(e) {
  var data = JSON.parse(e.data); // .action, .id, .new_value

  // "value" is a subset of an "addressbook" row
  if (data.action == "insert" || data.action == "update")
    addressbook[data.id] = data.new_value;
  else if (data.action == "delete")
    delete addressbook[data.id];

  update_combined_addressbook();
}

function update_combined_addressbook() {
  var entries = [];
  var id;
  for (id in addressbook) // id, petname, acked
    entries.push({type: "contact", data: addressbook[id], rowid: "cid-"+id});
  for (id in invitations) // id, petname, code, when_invited, rx_msgs
    entries.push({type: "invitation", data: invitations[id], rowid: "iid-"+id});

  var s = d3.select("#address-book").selectAll("div.entry")
        .data(entries, function(e) { return e.type + "-" + e.data.id; })
        .text(function(e) {return e.data.petname;})
        .attr("class", function(e) {
          if (e.type == "contact")
            return "entry contact "+e.rowid;
          else
            return "entry invitation "+e.rowid;
        })
        .on("click", show_contact_details)
        .on("dblclick", open_contact_room)
  ;

  s.enter().append("div")
    .text(function(e) {return e.data.petname;})
    .attr("class", function(e) {
      if (e.type == "contact")
        return "entry contact cid-"+e.data.id;
      else
        return "entry invitation iid-"+e.data.id;
    })
    .on("click", show_contact_details)
    .on("dblclick", open_contact_room)
  ;
  s.exit().remove();
}

function handle_invite_go(e) {
  var petname = d3.select("#invite-petname")[0][0].value;
  var code = d3.select("#invite-code")[0][0].value;
  console.log("inviting", petname, code);
  var req = {"token": token, "args": {"petname": petname, "code": code}};
  d3.json("/api/v1/invite").post(JSON.stringify(req),
                                 function(err, r) {
                                   console.log("invited", r.ok);
                                 });
  d3.select("#invite-petname")[0][0].value = "";
  d3.select("#invite-code")[0][0].value = "";
  $("#invite").hide("clip");
}

function handle_send_message_go(e) {
  var msg = d3.select("#send-message-body")[0][0].value;
  console.log("sending", msg, "to", current_cid);
  var req = {"token": token, "args": {"cid": current_cid, "message": msg}};
  d3.json("/api/v1/send-basic").post(JSON.stringify(req),
                                     function(err, r) {
                                       console.log("sent", r.ok);
                                     });
  d3.select("#send-message-body")[0][0].value = "";
}

function main() {
  console.log("onload");

  $("ul.nav a").click(function (e) {
    e.preventDefault();
    $(this).tab("show");
  });

  var ev;
  ev = new EventSource("/api/v1/views/addressbook?token="+token);
  ev.onmessage = update_addressbook;
  ev = new EventSource("/api/v1/views/invitations?token="+token);
  ev.onmessage = update_invitations;
  ev = new EventSource("/api/v1/views/messages?token="+token);
  ev.onmessage = update_messages;

  $("#invite").hide();
  $("#add-contact").click(function(e) {
    $("#invite").toggle("clip");
  });
  $("div.contact-details-pane").hide();

  d3.select("#invite-go")[0][0].onclick = handle_invite_go;
  d3.select("#send-message-go")[0][0].onclick = handle_send_message_go;

  console.log("setup done");
}

document.addEventListener("DOMContentLoaded", main);
<|endoftext|>


<commit_before>

module.exports = function(dolphin){
  var opts = dolphin.opts;

  /**
   *
   * filters – a JSON encoded value of the filters (a map[string][]string) to process on the images list. Available filters:
      dangling=true
      label=key or label="key=value" of an image label
      before=(<image-name>[:<tag>], <image id> or <image@digest>)
      since=(<image-name>[:<tag>], <image id> or <image@digest>)
   **/
  var images = function(imageName){
    //return dolphin._list('images', filters, opts);
    return dolphin._get('images/' + imageName + '/json', null, opts);
  }

/**
 *
 *  We use docker command line since building images is quite a complex
 *  operation.
 *  */
  images.build = function (url, name, versions, opts) {
    var args = [].concat.apply([], versions.map(function(tag){
      return ['-t', name + ':' + tag];
    }));

    args.unshift('build');
    args.push(url);

    // TODO: Add options.
    return dolphin.docker(args).then(function(imageId){
      return {
        Id: imageId
      }
    })
  }

  images.push = function(image, tag){
    var url = 'images/'+image+'/push';
    if(tag){
      url += '?tag=' + tag;
    }
    return dolphin._post(url, null, opts);
  }

  return images;
}
<commit_msg>added labels support to images<commit_after>

module.exports = function(dolphin){
  var opts = dolphin.opts;

  /**
   *
   * filters – a JSON encoded value of the filters (a map[string][]string) to process on the images list. Available filters:
      dangling=true
      label=key or label="key=value" of an image label
      before=(<image-name>[:<tag>], <image id> or <image@digest>)
      since=(<image-name>[:<tag>], <image id> or <image@digest>)
   **/
  var images = function(imageName){
    //return dolphin._list('images', filters, opts);
    return dolphin._get('images/' + imageName + '/json', null, opts);
  }

/**
 *
 *  We use docker command line since building images is quite a complex
 *  operation.
 *  */
  images.build = function (url, name, versions, params, opts) {
    var args = [].concat.apply([], versions.map(function(tag){
      return ['-t', name + ':' + tag];
    }));

    if(params.Labels){
      Object.keys(params.Labels).forEach(function(label){
        args = args.concat('--label', label, params.Labels[label]);
      });
    }

    args.unshift('build');
    args.push(url);

    // TODO: Add options.
    return dolphin.docker(args).then(function(imageId){
      return {
        Id: imageId
      }
    })
  }

  images.push = function(image, tag){
    var url = 'images/'+image+'/push';
    if(tag){
      url += '?tag=' + tag;
    }
    return dolphin._post(url, null, opts);
  }

  return images;
}
<|endoftext|>


<commit_before>(function(jQuery) {
  "use strict";

  var $ = jQuery;

  if (window.top !== window) {
    // Don't run in iframes
    return;
  }

  /////////////////////////////////////////////////
  //
  // Utility functions

  // Adds http:// to a URL, if it's needed.
  // Returns false if the string is empty, null, etc.
  var verifyAndFixUrl = function(url) {
    // window.prompt returns null on some browsers,
    // empty string on others.
    if (typeof(url) === 'undefined' ||
        url === null ||
        url.trim() === "") {

      return false;
    }

    var hasProtocol = /^http(s)?:\/\//.test(url);
    if (!hasProtocol) {
      return "http://" + url;
    }
    else {
      return url;
    }
  };

  /////////////////////////////////////////////////
  //
  // Preview & parsing of Markdown

  // When the Markdown has changed, generate
  // new HTML.
  var updateHtmlFromMd = function(event) {
    var mdComment = event.data.mdComment;
    var htmlComment = event.data.htmlComment;

    var mdText = mdComment.val();
    var htmlText = md4mefi.md2html(mdText);
    htmlComment.val(htmlText);

    // The keyup event is the listener for
    // refreshing the live preview.
    htmlComment.trigger('keyup');
  };

  // Turn on the task that updates the live preview
  var turnOnLivePreview = function(event) {
    // Turn on live preview, by running the init_lp function.
    // That function is assigned to the onfocus handler.
    //
    // However, if we run jQuery's .trigger('focus')
    // function, we'll actually switch focus to the
    // htmlComment textarea. Therefore, run
    // the onfocus handler that's set through plain ol'
    // DOM manipulation.

    // TODO: Add error handling, for if we can't find the dom node.
    var htmlCommentDomNode = event.data.htmlComment.get(0);
    htmlCommentDomNode.onfocus();
  };

  var turnOffLivePreview = function(event) {
    // Turn off live preview, by running the kill_lp function,
    // which is the onBlur handler.

    var htmlCommentDomNode = event.data.htmlComment.get(0);
    htmlCommentDomNode.onblur();
  };

  /////////////////////////////////////////////////
  //
  // Event handlers

  var showHideHtml = function(event) {
    var htmlComment = event.data.htmlComment;
    var otherHtmlComment = event.data.otherHtmlComment;
    var showHtml = event.data.showHtml;

    var isShow = showHtml.prop('checked');

    var animationOptions = {
      duration: 200,
      //easing: 'easeOut',
    };

    if (isShow) {
      htmlComment.show(animationOptions);
      if (otherHtmlComment !== null){
        otherHtmlComment.show(animationOptions);
      }
    }
    else {
      htmlComment.hide(animationOptions);
      if (otherHtmlComment !== null){
        otherHtmlComment.hide(animationOptions);
      }
    }
  };

  // TODO: Clicking bold a 2nd time should turn off bold.
  var doBoldOrItalic = function(boldOrItalic, event) {
    var mdComment = event.data.mdComment;
    var selection = mdComment.selection();

    var wrapperChar, placeholderText;
    if (boldOrItalic === 'bold') {
      wrapperChar = '**';
      placeholderText = '**bold text**';
    }
    else {
      wrapperChar = '*';
      placeholderText = "*italic text*";
    }

    if (selection === "") {
      mdComment.selection('insert', {text: placeholderText, mode: 'before'});
    }
    else {
      mdComment.selection('insert', {text: wrapperChar, mode: 'before' });
      mdComment.selection('insert', {text: wrapperChar, mode: 'after' });
    }

    updateHtmlFromMd(event);
  };

  var doItalic = function(event) {
    doBoldOrItalic('italic', event);
  };

  var doBold = function(event) {
    doBoldOrItalic('bold', event);
  };

  var doLink = function(event) {
    var mdComment = event.data.mdComment;
    var selection = mdComment.selection();
    var mdText = mdComment.val();
    var nextLinkNumber = md4mefi.nextLinkNumber(mdText);
    var linkText, linkUrl, caretPos;

    linkUrl = window.prompt("Please enter the site you'd like to link.", "http://");
    console.log(linkUrl);
    linkUrl = verifyAndFixUrl(linkUrl);
    if (linkUrl === false) {
      return;
    }

    if (selection === "") {
      // Nothing selected, insert a link with placeholder text
      caretPos = mdComment.selection('getPos');

      var placeholderText = 'link text';
      linkText = "[" + placeholderText + "][" + nextLinkNumber + "]";
      mdComment.selection('insert', {text: linkText, mode: 'before'});

      // Highlight the placeholder text
      caretPos.start += 1; // move past the opening bracket
      caretPos.end = caretPos.start + placeholderText.length;
      mdComment.selection('setPos', caretPos);
    }
    else {
      // Wrap the selected text in a link

      mdComment.selection('insert', {text: '[', mode: 'before'});

      linkText = "][" + nextLinkNumber + "]";
      mdComment.selection('insert', {text: linkText, mode: 'after'});
    }

    // Insert the reference at the end of the text.
    caretPos = mdComment.selection('getPos');
    mdText = mdComment.val() + "\n\n[" + nextLinkNumber + "]: " + linkUrl + "\n";
    mdComment.val(mdText);
    // Inserting the text changed the selection. Move it back.
    mdComment.selection('setPos', caretPos);

    updateHtmlFromMd(event);
  };

  /////////////////////////////////////////////////
  //
  // Save markdown across server-side previews

  // Return true if this is a page for previewing a new
  // post (e.g., an FPP, a question on Ask)
  var isPostPreviewPage = function(url) {
    // Pages for making a new post have forms that
    // lead to one of these two pages. Use regex to avoid
    // worrying if the path has a leading slash, or a hostname,
    // or any other weird stuff.
    return /post_preview.mefi$/.test(url) ||
           /post_preview.cfm$/.test(url);
  };

  var isCommentPreviewPage = function(url) {
    // I haven't actually seen the .cfm path in use yet,
    // but include it to be safe.
    return /post_comment_preview.mefi$/.test(url) ||
           /post_comment_preview.cfm$/.test(url);
  };

  var getSessionStorageKey = function(form) {
    var linkIdInput = form.find('input[name="link_ID"]');
    var storageKeyPrefix = "md4mefi-";

    if (linkIdInput.length > 0) {
      // This is a comment thread
      return storageKeyPrefix + linkIdInput.val();
    }
    else if (isPostPreviewPage(form.attr('action'))) {
      // Store up to one new post for each site.
      return storageKeyPrefix + '-new-post-' + window.location.hostname;
    }
    else {
      return null;
    }
  };

  var saveMarkdownForPreview = function(event) {
    var mdComment = event.data.mdComment;
    var mdExtended = event.data.mdExtended;
    var form = mdComment.closest('form');
    var storageKey = getSessionStorageKey(form);

    if (storageKey === null) {
      return;
    }

    var savedDataJson = {
      comment: mdComment.val(),
      extended: (mdExtended !== null) ? mdExtended.val() : "",
      url: window.location.href,
    };
    var savedDataString = JSON.stringify(savedDataJson);

    //console.log("Saving data for preview. Key = " + storageKey + ". Value = " + savedDataString);

    window.sessionStorage.setItem(storageKey, savedDataString);
  };

  // Get the saved markdown for this session.
  var restoreMarkdown = function(form) {
    // Only restore markdown if we're doing a preview. If we just, say, opened
    // a second browser tab to the thread's page, then don't restore.
    var isAllowedPath = isPostPreviewPage(window.location.pathname) ||
        isCommentPreviewPage(window.location.pathname);
    if (!isAllowedPath) {
      return null;
    }

    var storageKey = getSessionStorageKey(form);
    if (storageKey === null) {
      return null;
    }

    var savedDataString = window.sessionStorage.getItem(storageKey);
    if (savedDataString === null) {
      return null;
    }
    window.sessionStorage.removeItem(storageKey);

    //console.log("Restored data for preview. Key = " + storageKey + ". Value = " + savedDataString);

    var savedDataJson = JSON.parse(savedDataString);
    return savedDataJson;
  };

  var deleteMarkdownForPreview = function(event) {
    var form = event.data.form;
    var storageKey = getSessionStorageKey(form);
    if (storageKey === null) {
      return;
    }
    window.sessionStorage.removeItem(storageKey);
  };

  /////////////////////////////////////////////////
  //
  // DOM manipulation

  var hideMeFiToolbar = function(htmlComment, commentTable) {
    // Hide MeFi's built-in formatting toolbar (B, I, link)
    // On the new post form, each textarea has its own
    // toolbar, both id'd #toolbar. So start searching
    // from the containing table.
    var mefiToolbar = commentTable.find('#toolbar');
    var mefiToolbarParentTr = mefiToolbar.closest('tr');

    // If the toolbar and the htmlComment are both
    // in the same tr. This can happen when doing
    // a server-side preview.
    if (mefiToolbarParentTr.has(htmlComment).length > 0) {
      mefiToolbar.hide();
    }
    else {
      // tooolbar and htmlComment are in different tr's.
      // Hide the whole tr containing the mefi toolbar,
      // to avoid taking up that vertical space.
      mefiToolbarParentTr.hide();
    }
  };

  // Replace the HTML Help link with a Markdown Help link.
  var replaceHelpLink = function() {
    var anchor = $('#commentform a[href$="//faq.metafilter.com/tags/html"]');
    anchor.attr('href', 'http://daringfireball.net/projects/markdown/basics');
    anchor.html('(Markdown help)');

    // Normal MeFi has target = self. I don't like that; open in a new tab.
    anchor.attr('target', '_blank');
  };

  // Add the Markdown comment box and (optionally) toolbar
  var setupMarkdown = function(htmlComment, otherHtmlComment, includeToolbar) {
    // On a thread page, this is a td with id #commenttd.
    // But on an FPP page, the containing td is not named.
    var commentTd = htmlComment.closest('td');
    var commentTable = commentTd.closest('table');

    var mdComment = $('<textarea>')
      .attr('rows', htmlComment.attr('rows'))
      .attr('cols', htmlComment.attr('cols'))
      .css('width', htmlComment.css('width'))
      .css('height', htmlComment.css('height'));

    //htmlComment.attr('readonly', true);

    var showHtmlLabel = $('<input type=checkbox> Show HTML')
      .prop('checked', false);
    var showHtml = showHtmlLabel.find('input');

    var boldButton = $('
         \
                          **   \
                          Bold   \
                          **   \

                                            \
                       ');
    var italicButton  = $('
      \
                            *   \
                            Italic \
                            *   \

                                            \
                        ');
    var linkButton  = $('
          \
                            [   \
                            Link   \
                            ][1]   \

                                            \
                        ');
    var buttonbar   = $('

"');
    var title       = $('
Markdown for MeFi
');
    var toolbar     = $('

');

    buttonbar
      .append(boldButton)
      .append(italicButton)
      .append(linkButton);
    toolbar
      .append(showHtmlLabel)
      .append(title)
      .append(buttonbar);

    if (includeToolbar) {
      commentTd.prepend(toolbar);
    }
    commentTd.prepend(mdComment);
    htmlComment.hide();

    replaceHelpLink();
    hideMeFiToolbar(htmlComment, commentTable);

    // Set up event handlers
    var eventData = {
      htmlComment: htmlComment,
      otherHtmlComment: otherHtmlComment,
      mdComment: mdComment,
      showHtml: showHtml,
    };
    mdComment.on('input', eventData, updateHtmlFromMd);
    mdComment.on('focus', eventData, turnOnLivePreview);
    mdComment.on('blur',  eventData, turnOffLivePreview);

    showHtml.on('change', eventData, showHideHtml);

    if (includeToolbar) {
      italicButton.click(eventData, doItalic);
      boldButton.click(eventData, doBold);
      linkButton.click(eventData, doLink);
    }

    return mdComment;
  };

  $(document).ready(function() {
    var htmlComment = $('#comment');
    var htmlExtended = $('#extended');
    var mdComment, mdExtended;

    if (htmlComment.length === 0) {
      // No text entry on this page
      return;
    }
    else if (htmlExtended.length === 0) {
      // No textarea for extended text.
      // Must be a comment page.
      mdComment = setupMarkdown(htmlComment, null, true);
      mdExtended = null;
    }
    else {
      // Both comment & extended
      mdComment = setupMarkdown(htmlComment, null, false);
      mdExtended = setupMarkdown(htmlExtended, htmlComment, true);
    }

    // Restore saved markdown (for server-side preview)
    var form = mdComment.closest('form');
    var storedJson = restoreMarkdown(form);
    if (storedJson !== null) {
      if (storedJson.hasOwnProperty('comment')) {
        mdComment.val(storedJson.comment);
      }

      if (storedJson.hasOwnProperty('extended') &&
        mdExtended !== null) {
        mdExtended.val(storedJson.extended);
      }
    }

    // There are lots of different ways the preview
    // and post buttons are represented. Hunt for
    // whatever is on this page.

    // Coment form
    var postButton = form.find("#postButton");
    // Second time visiting a new post page.
    // (First time, all you can do is preview.)
    if (postButton.length === 0) {
      postButton = form.find('input[type="submit"][value="Post"]');
    }

    // Comment form
    var previewButton = form.find('#previewButton');
    // First time visiting a new post page
    if (previewButton.length === 0) {
      // Possible labels:
      // - "Step 2: Preview the post"
      // - "Step 2: Preview your thread"
      //
      // Match anything with "step 2" in the name.
      previewButton = form.find('input[type="submit"][value^="Step 2:"]');
    }
    // Second time visiting a new post page
    if (previewButton.length === 0) {
      previewButton = form.find('input[type="submit"][value="Preview"]');
    }

    // We can't use .on('submit'), because there's no way to
    // tell if the submission is for a preview or a post.
    // Use click handlers, instead.
    // http://stackoverflow.com/a/13202018/939467
    var eventData = {
      mdComment: mdComment,
      mdExtended: mdExtended,
      form: form,
    };
    previewButton.click(eventData, saveMarkdownForPreview);
    postButton.click(eventData, deleteMarkdownForPreview);

    //form.submit(function(event) { event.preventDefault(); });
  });

// noConflict(true) will revert both $ and jQuery, making
// it possible to use this plugin in combination w/ other
// versions of jQuery. Maybe this isn't necessary b/c
// Safari protects plugins from conflicts w/ the page's
// Javascript, but it makes me feel safer.
//
// See: http://stackoverflow.com/a/7882550/939467
})(jQuery.noConflict(true));<commit_msg>UNTESTED: Prep to move the save/load stuff into its own library.<commit_after>(function(jQuery) {
  "use strict";

  var $ = jQuery;

  if (window.top !== window) {
    // Don't run in iframes
    return;
  }

  /////////////////////////////////////////////////
  //
  // Utility functions

  // Adds http:// to a URL, if it's needed.
  // Returns false if the string is empty, null, etc.
  var verifyAndFixUrl = function(url) {
    // window.prompt returns null on some browsers,
    // empty string on others.
    if (typeof(url) === 'undefined' ||
        url === null ||
        url.trim() === "") {

      return false;
    }

    var hasProtocol = /^http(s)?:\/\//.test(url);
    if (!hasProtocol) {
      return "http://" + url;
    }
    else {
      return url;
    }
  };

  /////////////////////////////////////////////////
  //
  // Preview & parsing of Markdown

  // When the Markdown has changed, generate
  // new HTML.
  var updateHtmlFromMd = function(event) {
    var mdComment = event.data.mdComment;
    var htmlComment = event.data.htmlComment;

    var mdText = mdComment.val();
    var htmlText = md4mefi.md2html(mdText);
    htmlComment.val(htmlText);

    // The keyup event is the listener for
    // refreshing the live preview.
    htmlComment.trigger('keyup');
  };

  // Turn on the task that updates the live preview
  var turnOnLivePreview = function(event) {
    // Turn on live preview, by running the init_lp function.
    // That function is assigned to the onfocus handler.
    //
    // However, if we run jQuery's .trigger('focus')
    // function, we'll actually switch focus to the
    // htmlComment textarea. Therefore, run
    // the onfocus handler that's set through plain ol'
    // DOM manipulation.

    // TODO: Add error handling, for if we can't find the dom node.
    var htmlCommentDomNode = event.data.htmlComment.get(0);
    htmlCommentDomNode.onfocus();
  };

  var turnOffLivePreview = function(event) {
    // Turn off live preview, by running the kill_lp function,
    // which is the onBlur handler.

    var htmlCommentDomNode = event.data.htmlComment.get(0);
    htmlCommentDomNode.onblur();
  };

  /////////////////////////////////////////////////
  //
  // Event handlers

  var showHideHtml = function(event) {
    var htmlComment = event.data.htmlComment;
    var otherHtmlComment = event.data.otherHtmlComment;
    var showHtml = event.data.showHtml;

    var isShow = showHtml.prop('checked');

    var animationOptions = {
      duration: 200,
      //easing: 'easeOut',
    };

    if (isShow) {
      htmlComment.show(animationOptions);
      if (otherHtmlComment !== null){
        otherHtmlComment.show(animationOptions);
      }
    }
    else {
      htmlComment.hide(animationOptions);
      if (otherHtmlComment !== null){
        otherHtmlComment.hide(animationOptions);
      }
    }
  };

  // TODO: Clicking bold a 2nd time should turn off bold.
  var doBoldOrItalic = function(boldOrItalic, event) {
    var mdComment = event.data.mdComment;
    var selection = mdComment.selection();

    var wrapperChar, placeholderText;
    if (boldOrItalic === 'bold') {
      wrapperChar = '**';
      placeholderText = '**bold text**';
    }
    else {
      wrapperChar = '*';
      placeholderText = "*italic text*";
    }

    if (selection === "") {
      mdComment.selection('insert', {text: placeholderText, mode: 'before'});
    }
    else {
      mdComment.selection('insert', {text: wrapperChar, mode: 'before' });
      mdComment.selection('insert', {text: wrapperChar, mode: 'after' });
    }

    updateHtmlFromMd(event);
  };

  var doItalic = function(event) {
    doBoldOrItalic('italic', event);
  };

  var doBold = function(event) {
    doBoldOrItalic('bold', event);
  };

  var doLink = function(event) {
    var mdComment = event.data.mdComment;
    var selection = mdComment.selection();
    var mdText = mdComment.val();
    var nextLinkNumber = md4mefi.nextLinkNumber(mdText);
    var linkText, linkUrl, caretPos;

    linkUrl = window.prompt("Please enter the site you'd like to link.", "http://");
    console.log(linkUrl);
    linkUrl = verifyAndFixUrl(linkUrl);
    if (linkUrl === false) {
      return;
    }

    if (selection === "") {
      // Nothing selected, insert a link with placeholder text
      caretPos = mdComment.selection('getPos');

      var placeholderText = 'link text';
      linkText = "[" + placeholderText + "][" + nextLinkNumber + "]";
      mdComment.selection('insert', {text: linkText, mode: 'before'});

      // Highlight the placeholder text
      caretPos.start += 1; // move past the opening bracket
      caretPos.end = caretPos.start + placeholderText.length;
      mdComment.selection('setPos', caretPos);
    }
    else {
      // Wrap the selected text in a link

      mdComment.selection('insert', {text: '[', mode: 'before'});

      linkText = "][" + nextLinkNumber + "]";
      mdComment.selection('insert', {text: linkText, mode: 'after'});
    }

    // Insert the reference at the end of the text.
    caretPos = mdComment.selection('getPos');
    mdText = mdComment.val() + "\n\n[" + nextLinkNumber + "]: " + linkUrl + "\n";
    mdComment.val(mdText);
    // Inserting the text changed the selection. Move it back.
    mdComment.selection('setPos', caretPos);

    updateHtmlFromMd(event);
  };

  /////////////////////////////////////////////////
  //
  // Save markdown across server-side previews

  // Return true if this is a page for previewing a new
  // post (e.g., an FPP, a question on Ask)
  var isPostPreviewPage = function(url) {
    // Pages for making a new post have forms that
    // lead to one of these two pages. Use regex to avoid
    // worrying if the path has a leading slash, or a hostname,
    // or any other weird stuff.
    return /post_preview.mefi$/.test(url) ||
           /post_preview.cfm$/.test(url);
  };

  var isCommentPreviewPage = function(url) {
    // I haven't actually seen the .cfm path in use yet,
    // but include it to be safe.
    return /post_comment_preview.mefi$/.test(url) ||
           /post_comment_preview.cfm$/.test(url);
  };

  var getSessionStorageKey = function(linkId, formSubmitUrl) {
    var storageKeyPrefix = "md4mefi-";

    if (linkId !== null) {
      // This is a comment thread
      return storageKeyPrefix + linkId;
    }
    else if (isPostPreviewPage(formSubmitUrl)) {
      // Store up to one new post for each site.
      return storageKeyPrefix + '-new-post-' + window.location.hostname;
    }
    else {
      return null;
    }
  };

  var saveMarkdownForPreview = function(event) {
    var mdComment = event.data.mdComment;
    var mdExtended = event.data.mdExtended;
    var storageKey = getSessionStorageKey(event.data.linkId,
                                          event.data.formSubmitUrl);

    if (storageKey === null) {
      return;
    }

    var savedDataJson = {
      comment: mdComment.val(),
      extended: (mdExtended !== null) ? mdExtended.val() : "",
      url: window.location.href,
    };
    var savedDataString = JSON.stringify(savedDataJson);

    //console.log("Saving data for preview. Key = " + storageKey + ". Value = " + savedDataString);

    window.sessionStorage.setItem(storageKey, savedDataString);
  };

  // Get the saved markdown for this session.
  var restoreMarkdown = function(linkId, formSubmitUrl) {
    // Only restore markdown if we're doing a preview. If we just, say, opened
    // a second browser tab to the thread's page, then don't restore.
    var isAllowedPath = isPostPreviewPage(window.location.pathname) ||
        isCommentPreviewPage(window.location.pathname);
    if (!isAllowedPath) {
      return null;
    }

    var storageKey = getSessionStorageKey(linkId, formSubmitUrl);
    if (storageKey === null) {
      return null;
    }

    var savedDataString = window.sessionStorage.getItem(storageKey);
    if (savedDataString === null) {
      return null;
    }
    window.sessionStorage.removeItem(storageKey);

    //console.log("Restored data for preview. Key = " + storageKey + ". Value = " + savedDataString);

    var savedDataJson = JSON.parse(savedDataString);
    return savedDataJson;
  };

  var deleteMarkdownForPreview = function(event) {
    var form = event.data.form;
    var storageKey = getSessionStorageKey(form);
    if (storageKey === null) {
      return;
    }
    window.sessionStorage.removeItem(storageKey);
  };

  /////////////////////////////////////////////////
  //
  // DOM manipulation

  var hideMeFiToolbar = function(htmlComment, commentTable) {
    // Hide MeFi's built-in formatting toolbar (B, I, link)
    // On the new post form, each textarea has its own
    // toolbar, both id'd #toolbar. So start searching
    // from the containing table.
    var mefiToolbar = commentTable.find('#toolbar');
    var mefiToolbarParentTr = mefiToolbar.closest('tr');

    // If the toolbar and the htmlComment are both
    // in the same tr. This can happen when doing
    // a server-side preview.
    if (mefiToolbarParentTr.has(htmlComment).length > 0) {
      mefiToolbar.hide();
    }
    else {
      // tooolbar and htmlComment are in different tr's.
      // Hide the whole tr containing the mefi toolbar,
      // to avoid taking up that vertical space.
      mefiToolbarParentTr.hide();
    }
  };

  // Replace the HTML Help link with a Markdown Help link.
  var replaceHelpLink = function() {
    var anchor = $('#commentform a[href$="//faq.metafilter.com/tags/html"]');
    anchor.attr('href', 'http://daringfireball.net/projects/markdown/basics');
    anchor.html('(Markdown help)');

    // Normal MeFi has target = self. I don't like that; open in a new tab.
    anchor.attr('target', '_blank');
  };

  // Add the Markdown comment box and (optionally) toolbar
  var setupMarkdown = function(htmlComment, otherHtmlComment, includeToolbar) {
    // On a thread page, this is a td with id #commenttd.
    // But on an FPP page, the containing td is not named.
    var commentTd = htmlComment.closest('td');
    var commentTable = commentTd.closest('table');

    var mdComment = $('<textarea>')
      .attr('rows', htmlComment.attr('rows'))
      .attr('cols', htmlComment.attr('cols'))
      .css('width', htmlComment.css('width'))
      .css('height', htmlComment.css('height'));

    //htmlComment.attr('readonly', true);

    var showHtmlLabel = $('<input type=checkbox> Show HTML')
      .prop('checked', false);
    var showHtml = showHtmlLabel.find('input');

    var boldButton = $('
         \
                          **   \
                          Bold   \
                          **   \

                                            \
                       ');
    var italicButton  = $('
      \
                            *   \
                            Italic \
                            *   \

                                            \
                        ');
    var linkButton  = $('
          \
                            [   \
                            Link   \
                            ][1]   \

                                            \
                        ');
    var buttonbar   = $('

"');
    var title       = $('
Markdown for MeFi
');
    var toolbar     = $('

');

    buttonbar
      .append(boldButton)
      .append(italicButton)
      .append(linkButton);
    toolbar
      .append(showHtmlLabel)
      .append(title)
      .append(buttonbar);

    if (includeToolbar) {
      commentTd.prepend(toolbar);
    }
    commentTd.prepend(mdComment);
    htmlComment.hide();

    replaceHelpLink();
    hideMeFiToolbar(htmlComment, commentTable);

    // Set up event handlers
    var eventData = {
      htmlComment: htmlComment,
      otherHtmlComment: otherHtmlComment,
      mdComment: mdComment,
      showHtml: showHtml,
    };
    mdComment.on('input', eventData, updateHtmlFromMd);
    mdComment.on('focus', eventData, turnOnLivePreview);
    mdComment.on('blur',  eventData, turnOffLivePreview);

    showHtml.on('change', eventData, showHideHtml);

    if (includeToolbar) {
      italicButton.click(eventData, doItalic);
      boldButton.click(eventData, doBold);
      linkButton.click(eventData, doLink);
    }

    return mdComment;
  };

  $(document).ready(function() {
    var htmlComment = $('#comment');
    var htmlExtended = $('#extended');
    var mdComment, mdExtended;

    if (htmlComment.length === 0) {
      // No text entry on this page
      return;
    }
    else if (htmlExtended.length === 0) {
      // No textarea for extended text.
      // Must be a comment page.
      mdComment = setupMarkdown(htmlComment, null, true);
      mdExtended = null;
    }
    else {
      // Both comment & extended
      mdComment = setupMarkdown(htmlComment, null, false);
      mdExtended = setupMarkdown(htmlExtended, htmlComment, true);
    }

    // Restore saved markdown (for server-side preview)
    var form = mdComment.closest('form');
    // A hidden input field that contains the thread number.
    var linkIdInput = form.find('input[name="link_ID"]');
    var lnkId = (linkIdInput.length > 0) ? linkIdInput.val() : null;
    var formSubmitUrl = form.attr('action');

    var storedJson = restoreMarkdown(linkId, formSubmitUrl);
    if (storedJson !== null) {
      if (storedJson.hasOwnProperty('comment')) {
        mdComment.val(storedJson.comment);
      }

      if (storedJson.hasOwnProperty('extended') &&
        mdExtended !== null) {
        mdExtended.val(storedJson.extended);
      }
    }

    // There are lots of different ways the preview
    // and post buttons are represented. Hunt for
    // whatever is on this page.

    // Coment form
    var postButton = form.find("#postButton");
    // Second time visiting a new post page.
    // (First time, all you can do is preview.)
    if (postButton.length === 0) {
      postButton = form.find('input[type="submit"][value="Post"]');
    }

    // Comment form
    var previewButton = form.find('#previewButton');
    // First time visiting a new post page
    if (previewButton.length === 0) {
      // Possible labels:
      // - "Step 2: Preview the post"
      // - "Step 2: Preview your thread"
      //
      // Match anything with "step 2" in the name.
      previewButton = form.find('input[type="submit"][value^="Step 2:"]');
    }
    // Second time visiting a new post page
    if (previewButton.length === 0) {
      previewButton = form.find('input[type="submit"][value="Preview"]');
    }

    // We can't use .on('submit'), because there's no way to
    // tell if the submission is for a preview or a post.
    // Use click handlers, instead.
    // http://stackoverflow.com/a/13202018/939467

    var eventData = {
      mdComment: mdComment,
      mdExtended: mdExtended,
      linkId: linkId,
    };
    previewButton.click(eventData, saveMarkdownForPreview);
    postButton.click(eventData, deleteMarkdownForPreview);

    //form.submit(function(event) { event.preventDefault(); });
  });

// noConflict(true) will revert both $ and jQuery, making
// it possible to use this plugin in combination w/ other
// versions of jQuery. Maybe this isn't necessary b/c
// Safari protects plugins from conflicts w/ the page's
// Javascript, but it makes me feel safer.
//
// See: http://stackoverflow.com/a/7882550/939467
})(jQuery.noConflict(true));<|endoftext|>


<commit_before>var insert = module.exports = function(payload) {
  var before = "",
      after;

  function insert(body) {
    var out = insert.payload.apply(null, arguments);
    if (!out) {
      // console.log("(empty payload)");
      return body;
    }

    if (before) {
      return body.replace(before, out + before);
    } else if (after) {
      return body.replace(after, after + out);
    } else {
      return body + out;
    }
  }

  insert.payload = function() {
    return (typeof payload === "function")
      ? payload.apply(this, arguments)
      : String(payload);
  };

  insert.before = function(marker) {
    if (!arguments.length) return before;
    after = null;
    before = marker;
    return insert;
  };

  insert.after = function(marker) {
    if (!arguments.length) return after;
    before = null;
    after = marker;
    return insert;
  };

  return insert;
};

// insert an HTML element by name
insert.html = function(name, _attrs) {
  var attrs = {},
      closed = false,
      ownLine = true,
      content;

  var html = insert(function() {
    var out = ["<", name];
    if (ownLine) out.unshift("\n");
    for (var key in attrs) {
      if (defined(attrs[key])) {
        out.push(" ", key, '="', escapeAttr(attrs[key]), '"');
      }
    }
    if (content) {
      out.push(">", content, "</", name, ">");
    } else if (closed) {
      out.push("/>");
    } else {
      out.push("></", name, ">");
    }
    if (ownLine) out.push("\n");
    return out.join("");
  });

  html.attrs = function(d) {
    if (!arguments.length) return attrs;
    for (var key in d) {
      attrs[key] = d[key];
    }
    return html;
  };

  html.attr = function(key, val) {
    if (arguments.length === 2) {
      attrs[key] = val;
    } else if (arguments.length === 1) {
      return attrs[key];
    } else {
      throw new Error("Invalid # of arguments (" + arguments.length + ")");
    }
    return html;
  };

  html.content = function(d) {
    if (!arguments.length) return content;
    content = d;
    closed = false;
    return html;
  };

  html.closed = function(d) {
    if (!arguments.length) return closed;
    closed = d;
    return html;
  };

  function escapeAttr(value) {
    return String(value)
      .replace(/"/g, '\\"')
      .replace(/&/g, "&");
  }

  if (typeof _attrs === "object") {
    html.attrs(_attrs);
  }
  return html;
};

// insert a (java)script by URL
insert.script = function(url) {
  return insert.html("script")
    .attr("src", url)
    .before("");
};

// insert a stylesheet by URL
insert.style = function(url, element) {
  var style;
  switch (element) {
    case "style":
      style = insert.html("style")
        .attr("type", "text/css")
        .content("@import url('" + url + "')");
      break;

    case "link":
    default:
      style = insert.html("link")
        .closed(true)
        .attr("rel", "stylesheet")
        .attr("href", url);
  }

  return style
    .before("");
};

function defined(d) {
  return d !== null && typeof d !== "undefined";
}
<commit_msg>stubbing out insert.file()<commit_after>var insert = module.exports = function(payload) {
  var before = "",
      after;

  function insert(body) {
    var out = insert.payload.apply(null, arguments);
    if (!out) {
      // console.log("(empty payload)");
      return body;
    }

    if (before) {
      return body.replace(before, out + before);
    } else if (after) {
      return body.replace(after, after + out);
    } else {
      return body + out;
    }
  }

  insert.payload = function() {
    return (typeof payload === "function")
      ? payload.apply(this, arguments)
      : String(payload);
  };

  insert.before = function(marker) {
    if (!arguments.length) return before;
    after = null;
    before = marker;
    return insert;
  };

  insert.after = function(marker) {
    if (!arguments.length) return after;
    before = null;
    after = marker;
    return insert;
  };

  return insert;
};

insert.file = function(filename) {
  try {
    var content = fs.readFileSync(filename).toString();
  } catch (err) {
    throw new Error("Unable to read file: " + filename + " (" + err + ")");
  }
  return insert(content);
};

// insert an HTML element by name
insert.html = function(name, _attrs) {
  var attrs = {},
      closed = false,
      ownLine = true,
      content;

  var html = insert(function() {
    var out = ["<", name];
    if (ownLine) out.unshift("\n");
    for (var key in attrs) {
      if (defined(attrs[key])) {
        out.push(" ", key, '="', escapeAttr(attrs[key]), '"');
      }
    }
    if (content) {
      out.push(">", content, "</", name, ">");
    } else if (closed) {
      out.push("/>");
    } else {
      out.push("></", name, ">");
    }
    if (ownLine) out.push("\n");
    return out.join("");
  });

  html.attrs = function(d) {
    if (!arguments.length) return attrs;
    for (var key in d) {
      attrs[key] = d[key];
    }
    return html;
  };

  html.attr = function(key, val) {
    if (arguments.length === 2) {
      attrs[key] = val;
    } else if (arguments.length === 1) {
      return attrs[key];
    } else {
      throw new Error("Invalid # of arguments (" + arguments.length + ")");
    }
    return html;
  };

  html.content = function(d) {
    if (!arguments.length) return content;
    content = d;
    closed = false;
    return html;
  };

  html.closed = function(d) {
    if (!arguments.length) return closed;
    closed = d;
    return html;
  };

  function escapeAttr(value) {
    return String(value)
      .replace(/"/g, '\\"')
      .replace(/&/g, "&");
  }

  if (typeof _attrs === "object") {
    html.attrs(_attrs);
  }
  return html;
};

// insert a (java)script by URL
insert.script = function(url) {
  return insert.html("script")
    .attr("src", url)
    .before("");
};

// insert a stylesheet by URL
insert.style = function(url, element) {
  var style;
  switch (element) {
    case "style":
      style = insert.html("style")
        .attr("type", "text/css")
        .content("@import url('" + url + "')");
      break;

    case "link":
    default:
      style = insert.html("link")
        .closed(true)
        .attr("rel", "stylesheet")
        .attr("href", url);
  }

  return style
    .before("");
};

function defined(d) {
  return d !== null && typeof d !== "undefined";
}
<|endoftext|>


<commit_before>/*!
 * parseurl
 * Copyright(c) 2014 Jonathan Ong
 * Copyright(c) 2014 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict'

/**
 * Module dependencies.
 */

var url = require('url')
var parse = url.parse
var Url = url.Url

/**
 * Pattern for a simple path case.
 * See: https://github.com/joyent/node/pull/7878
 */

var simplePathRegExp = /^(\/\/?(?!\/)[^\?#\s]*)(\?[^#\s]*)?$/

/**
 * Exports.
 */

module.exports = parseurl
module.exports.original = originalurl

/**
 * Parse the `req` url with memoization.
 *
 * @param {ServerRequest} req
 * @return {Object}
 * @api public
 */

function parseurl(req) {
    var url = req.url

    if (url === undefined) {
        // URL is undefined
        return undefined
    }

    var parsed = req._parsedUrl

    if (fresh(url, parsed)) {
        // Return cached URL parse
        return parsed
    }

    // Parse the URL
    parsed = fastparse(url)
    parsed._raw = url

    return req._parsedUrl = parsed
};

/**
 * Parse the `req` original url with fallback and memoization.
 *
 * @param {ServerRequest} req
 * @return {Object}
 * @api public
 */

function originalurl(req) {
    var url = req.originalUrl

    if (typeof url !== 'string') {
        // Fallback
        return parseurl(req)
    }

    var parsed = req._parsedOriginalUrl

    if (fresh(url, parsed)) {
        // Return cached URL parse
        return parsed
    }

    // Parse the URL
    parsed = fastparse(url)
    parsed._raw = url

    return req._parsedOriginalUrl = parsed
};

/**
 * Parse the `str` url with fast-path short-cut.
 *
 * @param {string} str
 * @return {Object}
 * @api private
 */

function fastparse(str) {
    // Try fast path regexp
    // See: https://github.com/joyent/node/pull/7878
    var simplePath = typeof str === 'string' && simplePathRegExp.exec(str)

    // Construct simple URL
    if (simplePath) {
        var pathname = simplePath[1]
        var search = simplePath[2] || null
        var url = Url !== undefined ? new Url() : {}
        url.path = str
        url.href = str
        url.pathname = pathname
        url.search = search
        url.query = search && search.substr(1)

        return url
    }

    return parse(str)
}

/**
 * Determine if parsed is still fresh for url.
 *
 * @param {string} url
 * @param {object} parsedUrl
 * @return {boolean}
 * @api private
 */

function fresh(url, parsedUrl) {
    return typeof parsedUrl === 'object' && parsedUrl !== null && (Url ===
        undefined || parsedUrl instanceof Url) && parsedUrl._raw === url
}
<commit_msg>remove parseurl<commit_after><|endoftext|>


<commit_before>let floatData = {};
let floatTimer;
let steamListingInfo = {};
let listingInfoPromises = [];

class Queue {
    constructor() {
        this.queue = [];
        this.running = false;
        this.concurrency = 10;
        this.processing = 0;
    }

    addJob(link, listingId) {
        if (listingId in floatData) {
            return Promise.resolve({ iteminfo: floatData[listingId] });
        }

        const job = {
            link,
            listingId
        };

        const promise = new Promise((resolve, reject) => {
            job.resolve = resolve;
            job.reject = reject;
        });

        this.queue.push(job);
        this.checkQueue();

        return promise;
    }

    checkQueue() {
        if (!this.running) return;

        if (this.queue.length > 0 && this.processing < this.concurrency) {
            // there is a free bot, process the job
            let job = this.queue.shift();

            this.processing += 1;

            const floatDiv = document.querySelector(`#item_${job.listingId}_floatdiv`);

            // Changed pages, cancel request
            if (!floatDiv) {
                this.processing -= 1;
                this.checkQueue();
                return;
            }

            const buttonText = floatDiv.querySelector('span');
            if (buttonText) buttonText.innerText = 'Fetching';

            chrome.runtime.sendMessage({'inspectLink': job.link}, (data) => {
                if (data && data.iteminfo) {
                    floatData[job.listingId] = data.iteminfo;
                    showFloat(job.listingId);
                } else {
                    // Reset the button text for this itemid
                    if (buttonText) buttonText.innerText = 'Get Float';

                    // Change the message div for this item to the error
                    if (floatDiv) {
                        floatDiv.querySelector('.floatmessage').innerText = data.error || 'Unknown Error';
                    }
                }

                this.processing -= 1;
                this.checkQueue();
            });
        }
    }

    start() {
        if (!this.running) {
            this.running = true;
            this.checkQueue();
        }
    }
}

// retrieve g_rgListingInfo from page script
window.addEventListener('message', (e) => {
    if (e.data.type == 'listingInfo') {
        steamListingInfo = e.data.listingInfo;

        // resolve listingInfoPromises
        for (let promise of listingInfoPromises) promise(steamListingInfo);

        listingInfoPromises = [];
    }
});

const retrieveListingInfoFromPage = function(listingId) {
    if (listingId != null && (listingId in steamListingInfo)) {
        return Promise.resolve(steamListingInfo);
    }

    window.postMessage({
        type: 'requestListingInfo'
    }, '*');

    return new Promise((resolve, reject) => {
        listingInfoPromises.push(resolve);
    });
};

const showFloat = function(listingId) {
    let itemInfo = floatData[listingId];

    let floatDiv = document.querySelector(`#item_${listingId}_floatdiv`);

    if (floatDiv) {
        // Remove the "get float" button
        let floatButton = floatDiv.querySelector('.floatbutton');
        if (floatButton) floatDiv.removeChild(floatButton);

        // Remove message div
        let msgdiv = floatDiv.querySelector('.floatmessage');
        if (msgdiv) floatDiv.removeChild(msgdiv);

        // Add the float value
        let itemFloatDiv = floatDiv.querySelector('.itemfloat');
        if (itemFloatDiv) itemFloatDiv.innerText = `Float: ${itemInfo.floatvalue}`;

        // Add the paint seed
        let seedDiv = floatDiv.querySelector('.itemseed');
        if (seedDiv) seedDiv.innerText = `Paint Seed: ${itemInfo.paintseed}`;
    }
};

// Puts all of the available items on the page into the queue for float retrieval
const getAllFloats = function() {
    retrieveListingInfoFromPage()
    .then((steamListingData) => {
        // Get all current items on the page (in proper order)
        let listingRows = document.querySelectorAll('#searchResultsRows .market_listing_row.market_recent_listing_row');

        for (let row of listingRows) {
            let id = row.id.replace('listing_', '');

            let listingData = steamListingData[id];

            let inspectLink = listingData.asset.market_actions[0].link
            .replace('%listingid%', id)
            .replace('%assetid%', listingData.asset.id);

            queue.addJob(inspectLink, id);
        }
    });
};

// Adds the "Get all floats" button
const addAllFloatButton = function() {
    let parentDiv = document.createElement('div');
    parentDiv.style.padding = '10px';
    parentDiv.style.marginTop = '10px';
    parentDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';

    let allFloatButton = document.createElement('a');
    allFloatButton.id = 'allfloatbutton';
    allFloatButton.classList.add('btn_green_white_innerfade');
    allFloatButton.classList.add('btn_small');
    allFloatButton.addEventListener('click', getAllFloats);
    parentDiv.appendChild(allFloatButton);

    let allFloatSpan = document.createElement('span');
    allFloatSpan.innerText = 'Get All Floats';
    allFloatButton.appendChild(allFloatSpan);

    let githubLink = document.createElement('a');
    githubLink.style.marginLeft = '10px';
    githubLink.style.textDecoration = 'underline';
    githubLink.style.fontFamily = `'Motiva Sans', sans-serif`;
    githubLink.href = 'https://github.com/Step7750/CSGOFloat';
    githubLink.innerText = 'Powered by CSGOFloat';
    parentDiv.appendChild(githubLink);

    document.querySelector('#searchResultsTable').insertBefore(parentDiv, document.querySelector('#searchResultsRows'));
};

const getFloatButtonClicked = function(e) {
    let row = e.currentTarget.parentElement.parentElement.parentElement;
    let id = row.id.replace('listing_', '');

    retrieveListingInfoFromPage(id)
    .then((steamListingData) => {
        let listingData = steamListingData[id];

        if (!listingData) return;

        let inspectLink = listingData.asset.market_actions[0].link
        .replace('%listingid%', id)
        .replace('%assetid%', listingData.asset.id);

        queue.addJob(inspectLink, id);
    });
};

// If an item on the current page doesn't have the float div/buttons, this function adds it
const addButtons = function() {
    // Iterate through each item on the page
    let listingRows = document.querySelectorAll('#searchResultsRows .market_listing_row.market_recent_listing_row');

    for (let row of listingRows) {
        let id = row.id.replace('listing_', '');

        if (row.querySelector(`#item_${id}_floatdiv`)) { continue; }

        let listingNameElement = row.querySelector(`#listing_${id}_name`);

        let buttonDiv = document.createElement('div');
        buttonDiv.style.display = 'inline';
        buttonDiv.style.textAlign = 'left';
        buttonDiv.id = `item_${id}_floatdiv`;
        listingNameElement.parentElement.appendChild(buttonDiv);

        let getFloatButton = document.createElement('a');
        getFloatButton.classList.add('btn_green_white_innerfade');
        getFloatButton.classList.add('btn_small');
        getFloatButton.classList.add('floatbutton');
        getFloatButton.addEventListener('click', getFloatButtonClicked);
        buttonDiv.appendChild(getFloatButton);

        let buttonText = document.createElement('span');
        buttonText.innerText = 'Get Float';
        getFloatButton.appendChild(buttonText);

        // Create divs the following class names and append them to the button div
        let divClassNames = ['floatmessage', 'itemfloat', 'itemseed'];

        for (let className of divClassNames) {
            let div = document.createElement('div');
            div.classList.add(className);
            buttonDiv.appendChild(div);
        }

        // check if we already have the float for this item
        if (id in floatData) {
            showFloat(id);
        }
    }

    // Add show all button if it doesn't exist and we have valid items
    if (!document.querySelector('#allfloatbutton') && listingRows.length > 0) {
        addAllFloatButton();
    }
};

// register the message listener in the page scope
let script = document.createElement('script');
script.innerText = `
    window.addEventListener('message', (e) => {
        if (e.data.type == 'requestListingInfo') {
            window.postMessage({
                type: 'listingInfo',
                listingInfo: g_rgListingInfo
            }, '*');
        }
    });
`;
document.head.appendChild(script);

const queue = new Queue();
queue.start();

floatTimer = setInterval(() => { addButtons(); }, 250);

const logStyle = 'background: #222; color: #fff;';
console.log('%c CSGOFloat Market Checker (v1.1.4) by Step7750 ', logStyle);
console.log('%c Changelog can be found here: https://github.com/Step7750/CSGOFloat-Extension ', logStyle);
<commit_msg>Fixes Showing Floats on Cache<commit_after>let floatData = {};
let floatTimer;
let steamListingInfo = {};
let listingInfoPromises = [];

class Queue {
    constructor() {
        this.queue = [];
        this.running = false;
        this.concurrency = 10;
        this.processing = 0;
    }

    addJob(link, listingId) {
        if (listingId in floatData) {
            showFloat(listingId);
            return;
        }

        const job = {
            link,
            listingId
        };

        const promise = new Promise((resolve, reject) => {
            job.resolve = resolve;
            job.reject = reject;
        });

        this.queue.push(job);
        this.checkQueue();

        return promise;
    }

    checkQueue() {
        if (!this.running) return;

        if (this.queue.length > 0 && this.processing < this.concurrency) {
            // there is a free bot, process the job
            let job = this.queue.shift();

            this.processing += 1;

            const floatDiv = document.querySelector(`#item_${job.listingId}_floatdiv`);

            // Changed pages, cancel request
            if (!floatDiv) {
                this.processing -= 1;
                this.checkQueue();
                return;
            }

            const buttonText = floatDiv.querySelector('span');
            if (buttonText) buttonText.innerText = 'Fetching';

            chrome.runtime.sendMessage({'inspectLink': job.link}, (data) => {
                if (data && data.iteminfo) {
                    floatData[job.listingId] = data.iteminfo;
                    showFloat(job.listingId);
                } else {
                    // Reset the button text for this itemid
                    if (buttonText) buttonText.innerText = 'Get Float';

                    // Change the message div for this item to the error
                    if (floatDiv) {
                        floatDiv.querySelector('.floatmessage').innerText = data.error || 'Unknown Error';
                    }
                }

                this.processing -= 1;
                this.checkQueue();
            });
        }
    }

    start() {
        if (!this.running) {
            this.running = true;
            this.checkQueue();
        }
    }
}

// retrieve g_rgListingInfo from page script
window.addEventListener('message', (e) => {
    if (e.data.type == 'listingInfo') {
        steamListingInfo = e.data.listingInfo;

        // resolve listingInfoPromises
        for (let promise of listingInfoPromises) promise(steamListingInfo);

        listingInfoPromises = [];
    }
});

const retrieveListingInfoFromPage = function(listingId) {
    if (listingId != null && (listingId in steamListingInfo)) {
        return Promise.resolve(steamListingInfo);
    }

    window.postMessage({
        type: 'requestListingInfo'
    }, '*');

    return new Promise((resolve, reject) => {
        listingInfoPromises.push(resolve);
    });
};

const showFloat = function(listingId) {
    let itemInfo = floatData[listingId];

    let floatDiv = document.querySelector(`#item_${listingId}_floatdiv`);

    if (floatDiv) {
        // Remove the "get float" button
        let floatButton = floatDiv.querySelector('.floatbutton');
        if (floatButton) floatDiv.removeChild(floatButton);

        // Remove message div
        let msgdiv = floatDiv.querySelector('.floatmessage');
        if (msgdiv) floatDiv.removeChild(msgdiv);

        // Add the float value
        let itemFloatDiv = floatDiv.querySelector('.itemfloat');
        if (itemFloatDiv) itemFloatDiv.innerText = `Float: ${itemInfo.floatvalue}`;

        // Add the paint seed
        let seedDiv = floatDiv.querySelector('.itemseed');
        if (seedDiv) seedDiv.innerText = `Paint Seed: ${itemInfo.paintseed}`;
    }
};

// Puts all of the available items on the page into the queue for float retrieval
const getAllFloats = function() {
    retrieveListingInfoFromPage()
    .then((steamListingData) => {
        // Get all current items on the page (in proper order)
        let listingRows = document.querySelectorAll('#searchResultsRows .market_listing_row.market_recent_listing_row');

        for (let row of listingRows) {
            let id = row.id.replace('listing_', '');

            let listingData = steamListingData[id];

            let inspectLink = listingData.asset.market_actions[0].link
            .replace('%listingid%', id)
            .replace('%assetid%', listingData.asset.id);

            queue.addJob(inspectLink, id);
        }
    });
};

// Adds the "Get all floats" button
const addAllFloatButton = function() {
    let parentDiv = document.createElement('div');
    parentDiv.style.padding = '10px';
    parentDiv.style.marginTop = '10px';
    parentDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';

    let allFloatButton = document.createElement('a');
    allFloatButton.id = 'allfloatbutton';
    allFloatButton.classList.add('btn_green_white_innerfade');
    allFloatButton.classList.add('btn_small');
    allFloatButton.addEventListener('click', getAllFloats);
    parentDiv.appendChild(allFloatButton);

    let allFloatSpan = document.createElement('span');
    allFloatSpan.innerText = 'Get All Floats';
    allFloatButton.appendChild(allFloatSpan);

    let githubLink = document.createElement('a');
    githubLink.style.marginLeft = '10px';
    githubLink.style.textDecoration = 'underline';
    githubLink.style.fontFamily = `'Motiva Sans', sans-serif`;
    githubLink.href = 'https://github.com/Step7750/CSGOFloat';
    githubLink.innerText = 'Powered by CSGOFloat';
    parentDiv.appendChild(githubLink);

    document.querySelector('#searchResultsTable').insertBefore(parentDiv, document.querySelector('#searchResultsRows'));
};

const getFloatButtonClicked = function(e) {
    let row = e.currentTarget.parentElement.parentElement.parentElement;
    let id = row.id.replace('listing_', '');

    retrieveListingInfoFromPage(id)
    .then((steamListingData) => {
        let listingData = steamListingData[id];

        if (!listingData) return;

        let inspectLink = listingData.asset.market_actions[0].link
        .replace('%listingid%', id)
        .replace('%assetid%', listingData.asset.id);

        queue.addJob(inspectLink, id);
    });
};

// If an item on the current page doesn't have the float div/buttons, this function adds it
const addButtons = function() {
    // Iterate through each item on the page
    let listingRows = document.querySelectorAll('#searchResultsRows .market_listing_row.market_recent_listing_row');

    for (let row of listingRows) {
        let id = row.id.replace('listing_', '');

        if (row.querySelector(`#item_${id}_floatdiv`)) { continue; }

        let listingNameElement = row.querySelector(`#listing_${id}_name`);

        let buttonDiv = document.createElement('div');
        buttonDiv.style.display = 'inline';
        buttonDiv.style.textAlign = 'left';
        buttonDiv.id = `item_${id}_floatdiv`;
        listingNameElement.parentElement.appendChild(buttonDiv);

        let getFloatButton = document.createElement('a');
        getFloatButton.classList.add('btn_green_white_innerfade');
        getFloatButton.classList.add('btn_small');
        getFloatButton.classList.add('floatbutton');
        getFloatButton.addEventListener('click', getFloatButtonClicked);
        buttonDiv.appendChild(getFloatButton);

        let buttonText = document.createElement('span');
        buttonText.innerText = 'Get Float';
        getFloatButton.appendChild(buttonText);

        // Create divs the following class names and append them to the button div
        let divClassNames = ['floatmessage', 'itemfloat', 'itemseed'];

        for (let className of divClassNames) {
            let div = document.createElement('div');
            div.classList.add(className);
            buttonDiv.appendChild(div);
        }

        // check if we already have the float for this item
        if (id in floatData) {
            showFloat(id);
        }
    }

    // Add show all button if it doesn't exist and we have valid items
    if (!document.querySelector('#allfloatbutton') && listingRows.length > 0) {
        addAllFloatButton();
    }
};

// register the message listener in the page scope
let script = document.createElement('script');
script.innerText = `
    window.addEventListener('message', (e) => {
        if (e.data.type == 'requestListingInfo') {
            window.postMessage({
                type: 'listingInfo',
                listingInfo: g_rgListingInfo
            }, '*');
        }
    });
`;
document.head.appendChild(script);

const queue = new Queue();
queue.start();

floatTimer = setInterval(() => { addButtons(); }, 250);

const logStyle = 'background: #222; color: #fff;';
console.log('%c CSGOFloat Market Checker (v1.1.4) by Step7750 ', logStyle);
console.log('%c Changelog can be found here: https://github.com/Step7750/CSGOFloat-Extension ', logStyle);
<|endoftext|>


<commit_before>var ApiService          = require("services/ApiService");
var NotificationService = require("services/NotificationService");
var ModalService        = require("services/ModalService");
var ValidationService   = require("services/ValidationService");

Vue.component("bank-data-select", {

    template: "#vue-bank-data-select",

    props: [
        "userBankData",
        "contactId"
    ],

    data: function()
    {
        return {
            bankInfoModal: {},
            bankDeleteModal: {},
            updateBankData: {},
            selectedBankData: {},
            updateBankIndex: 0,
            doUpdate: null,
            headline : ""
        };
    },

    /**
     * Select the first bank-data
     */
    created: function()
    {
        this.selectedBankData = this.userBankData[0];
    },

    /**
     * Select the address modal
     */
    ready: function()
    {
        this.bankInfoModal = ModalService.findModal(this.$els.bankInfoModal);
        this.bankDeleteModal = ModalService.findModal(this.$els.bankDeleteModal);
    },

    methods: {

        /**
         * Set the selected bank-data
         */
        changeSelecting: function(bankData)
        {
            this.selectedBankData = bankData;
        },

        /**
         * Open the modal to add new bank-data
         */
        openAddBank: function()
        {
            this.headline = Translations.Callisto.addBankDataTitle;
            this.openModal(false);
        },

        /**
         * Set data to update and open the modal
         * @param index
         * @param bankdata
         */
        openUpdateBank: function(index, bankData)
        {
            this.headline = Translations.Callisto.updateBankDataTitle;

            this.setUpdateData(index, bankData);
            this.openModal(true);
        },

        /**
         * Set data to remove and open the modal
         * @param index
         * @param bankdata
         */
        openDeleteBank: function(index, bankData)
        {
            this.setUpdateData(index, bankData);

            this.doUpdate = false;
            this.bankDeleteModal.show();
        },

        /**
         * Open the modal
         * @param doUpdate
         */
        openModal: function(doUpdate)
        {
            this.doUpdate = doUpdate;
            this.bankInfoModal.show();
        },

        /**
         * Set data to change
         * @param index
         * @param bankdata
         */
        setUpdateData: function(index, bankData)
        {
            this.updateBankData = JSON.parse(JSON.stringify(bankData));
            this.updateBankIndex = index;
        },

        /**
         * Validate the input-fields-data
         */
        validateInput: function()
        {
            var _self = this;

            ValidationService.validate($("#my-bankForm"))
                .done(function()
                {
                    if (_self.doUpdate)
                    {
                        _self.updateBankInfo();
                    }
                    else
                    {
                        _self.addBankInfo();
                    }
                })
                .fail(function(invalidFields)
                {
                    ValidationService.markInvalidFields(invalidFields, "error");
                });
        },

        /**
         * Update bank-data
         */
        updateBankInfo: function()
        {
            var _self = this;

            this.updateBankData.lastUpdateBy = "customer";

            ApiService.put("/rest/customer/bank_data/" + this.updateBankData.id, this.updateBankData)
                .done(function(response)
                {
                    _self.userBankData.splice(_self.updateBankIndex, 1, response);
                    _self.bankInfoModal.hide();

                    _self.successNotification("Bankdaten wurden aktualisiert");
                })
                .fail(function()
                {
                    _self.bankInfoModal.hide();
                    _self.errorNotification("Bankdaten konnten nicht aktualisiert werden");
                });
        },

        /**
         * Add new bank-data
         */
        addBankInfo: function()
        {
            var _self = this;

            this.updateBankData.lastUpdateBy = "customer";
            this.updateBankData.contactId = this.contactId;

            ApiService.post("/rest/customer/bank_data", this.updateBankData)
                .done(function(response)
                {
                    _self.userBankData.push(response);
                    _self.bankInfoModal.hide();

                    _self.successNotification("Bankdaten wurde angelegt");
                })
                .fail(function()
                {
                    _self.bankInfoModal.hide();

                    _self.errorNotification("Bankdaten konnten nicht angelegt werden");
                });
        },

        /**
         * Delete bank-data
         */
        removeBankInfo: function()
        {
            var _self = this;

            ApiService.delete("/rest/customer/bank_data/" + this.updateBankData.id)
                .done(function(response)
                {
                    _self.userBankData.splice(_self.updateBankIndex, 1);
                    _self.bankDeleteModal.hide();

                    _self.successNotification("Bankdaten wurde gelöscht");

                })
                .fail(function()
                {
                    _self.bankDeleteModal.hide();

                    _self.errorNotification("Bankdaten konnte nicht gelöscht werden");
                });
        },

        /**
         * Reset the updateBankData and updateBankIndex
         */
        resetData: function()
        {
            this.updateBankData = {};
            this.updateBankIndex = 0;
        },

        /**
         * Close the current bank-delete-modal
         */
        closeDeleteModal: function()
        {
            this.bankDeleteModal.hide();
            this.resetData();
        },

        /**
         * Show success notification
         * @param message
         */
        successNotification: function(message)
        {
            NotificationService.success(message).closeAfter(3000);
        },

        /**
         * Show error notification
         * @param error
         */
        errorNotification: function(message)
        {
            NotificationService.error(message).closeAfter(5000);
        }
    }
});
<commit_msg>- changed comment<commit_after>var ApiService          = require("services/ApiService");
var NotificationService = require("services/NotificationService");
var ModalService        = require("services/ModalService");
var ValidationService   = require("services/ValidationService");

Vue.component("bank-data-select", {

    template: "#vue-bank-data-select",

    props: [
        "userBankData",
        "contactId"
    ],

    data: function()
    {
        return {
            bankInfoModal: {},
            bankDeleteModal: {},
            updateBankData: {},
            selectedBankData: {},
            updateBankIndex: 0,
            doUpdate: null,
            headline : ""
        };
    },

    /**
     * Select the first bank-data
     */
    created: function()
    {
        this.selectedBankData = this.userBankData[0];
    },

    /**
     * Select the address modal
     */
    ready: function()
    {
        this.bankInfoModal = ModalService.findModal(this.$els.bankInfoModal);
        this.bankDeleteModal = ModalService.findModal(this.$els.bankDeleteModal);
    },

    methods: {

        /**
         * Set the selected bank-data
         */
        changeSelecting: function(bankData)
        {
            this.selectedBankData = bankData;
        },

        /**
         * Open the modal to add new bank-data
         */
        openAddBank: function()
        {
            this.headline = Translations.Callisto.addBankDataTitle;
            this.openModal(false);
        },

        /**
         * Set data to update and open the modal
         * @param index
         * @param bankdata
         */
        openUpdateBank: function(index, bankData)
        {
            this.headline = Translations.Callisto.updateBankDataTitle;

            this.setUpdateData(index, bankData);
            this.openModal(true);
        },

        /**
         * Set data to remove and open the modal
         * @param index
         * @param bankdata
         */
        openDeleteBank: function(index, bankData)
        {
            this.setUpdateData(index, bankData);

            this.doUpdate = false;
            this.bankDeleteModal.show();
        },

        /**
         * Open the modal
         * @param doUpdate
         */
        openModal: function(doUpdate)
        {
            this.doUpdate = doUpdate;
            this.bankInfoModal.show();
        },

        /**
         * Set data to change
         * @param index
         * @param bankdata
         */
        setUpdateData: function(index, bankData)
        {
            this.updateBankData = JSON.parse(JSON.stringify(bankData));
            this.updateBankIndex = index;
        },

        /**
         * Validate the input-fields-data
         */
        validateInput: function()
        {
            var _self = this;

            ValidationService.validate($("#my-bankForm"))
                .done(function()
                {
                    if (_self.doUpdate)
                    {
                        _self.updateBankInfo();
                    }
                    else
                    {
                        _self.addBankInfo();
                    }
                })
                .fail(function(invalidFields)
                {
                    ValidationService.markInvalidFields(invalidFields, "error");
                });
        },

        /**
         * Update bank-data
         */
        updateBankInfo: function()
        {
            var _self = this;

            this.updateBankData.lastUpdateBy = "customer";

            ApiService.put("/rest/customer/bank_data/" + this.updateBankData.id, this.updateBankData)
                .done(function(response)
                {
                    _self.userBankData.splice(_self.updateBankIndex, 1, response);
                    _self.bankInfoModal.hide();

                    _self.successNotification("Bankdaten wurden aktualisiert");
                })
                .fail(function()
                {
                    _self.bankInfoModal.hide();
                    _self.errorNotification("Bankdaten konnten nicht aktualisiert werden");
                });
        },

        /**
         * Add new bank-data
         */
        addBankInfo: function()
        {
            var _self = this;

            this.updateBankData.lastUpdateBy = "customer";
            this.updateBankData.contactId = this.contactId;

            ApiService.post("/rest/customer/bank_data", this.updateBankData)
                .done(function(response)
                {
                    _self.userBankData.push(response);
                    _self.bankInfoModal.hide();

                    _self.successNotification("Bankdaten wurde angelegt");
                })
                .fail(function()
                {
                    _self.bankInfoModal.hide();

                    _self.errorNotification("Bankdaten konnten nicht angelegt werden");
                });
        },

        /**
         * Delete bank-data
         */
        removeBankInfo: function()
        {
            var _self = this;

            ApiService.delete("/rest/customer/bank_data/" + this.updateBankData.id)
                .done(function(response)
                {
                    _self.userBankData.splice(_self.updateBankIndex, 1);
                    _self.bankDeleteModal.hide();

                    _self.successNotification("Bankdaten wurde gelöscht");

                })
                .fail(function()
                {
                    _self.bankDeleteModal.hide();

                    _self.errorNotification("Bankdaten konnte nicht gelöscht werden");
                });
        },

        /**
         * Reset the updateBankData and updateBankIndex
         */
        resetData: function()
        {
            this.updateBankData = {};
            this.updateBankIndex = 0;
        },

        /**
         * Close the current bank-delete-modal
         */
        closeDeleteModal: function()
        {
            this.bankDeleteModal.hide();
            this.resetData();
        },

        /**
         * Show success notification
         * @param message
         */
        successNotification: function(message)
        {
            NotificationService.success(message).closeAfter(3000);
        },

        /**
         * Show error notification
         * @param message
         */
        errorNotification: function(message)
        {
            NotificationService.error(message).closeAfter(5000);
        }
    }
});
<|endoftext|>


<commit_before>export function isMobile (agent = navigator.userAgent) {
	return /Android|iPhone|iPad|iPod/i.test(agent)
}

export function isNode (target) {
	return typeof window.Node === 'object'
		? target instanceof window.Node
		: target !== null
			&& typeof target === 'object'
			&& typeof target.nodeType === 'number'
			&& typeof target.nodeName === 'string'
}

export function isNodeList (target) {
	const prototypeToString = Object.prototype.toString.call(target)
	const regex = /^\[object (HTMLCollection|NodeList|Object)\]$/

	return typeof window.NodeList === 'object'
		? target instanceof window.NodeList
		: typeof target === 'object'
			&& typeof target.length === 'number'
			&& regex.test(prototypeToString)
			&& (target.length === 0 || isNode(target[0]))
}

export function transformSupported () {
	const style = document.documentElement.style
	return 'transform' in style || 'WebkitTransform' in style
}

export function transitionSupported () {
	const style = document.documentElement.style
	return 'transition' in style || 'WebkitTransition' in style
}
<commit_msg>add skeleton for new browser utility<commit_after>export function isMobile (agent = navigator.userAgent) {
	return /Android|iPhone|iPad|iPod/i.test(agent)
}

export function isNode (target) {
	return typeof window.Node === 'object'
		? target instanceof window.Node
		: target !== null
			&& typeof target === 'object'
			&& typeof target.nodeType === 'number'
			&& typeof target.nodeName === 'string'
}

export function isNodeList (target) {
	const prototypeToString = Object.prototype.toString.call(target)
	const regex = /^\[object (HTMLCollection|NodeList|Object)\]$/

	return typeof window.NodeList === 'object'
		? target instanceof window.NodeList
		: typeof target === 'object'
			&& typeof target.length === 'number'
			&& regex.test(prototypeToString)
			&& (target.length === 0 || isNode(target[0]))
}

// export const getComputedProperty = (name => {
// 	const properties = {}
//
// 	return () => {
// 		if (properties[name]) return properties[name]
//
// 		if (typeof name === 'string') {
// 			const computed = window.getComputedStyle()
// 			if (typeof computed[name] === 'string')
// 				return properties[name] = name
// 			if (typeof computed[`-webkit-${name}`] === 'string')
// 				return properties[name] = `-webkit-${name}`
//
// 			throw new RangeError(`Unable to find "${name}" in computed style properties.`)
// 		}
// 		throw new Error('Expected a string.')
// 	}
// })()

export function transformSupported () {
	const style = document.documentElement.style
	return 'transform' in style || 'WebkitTransform' in style
}

export function transitionSupported () {
	const style = document.documentElement.style
	return 'transition' in style || 'WebkitTransition' in style
}
<|endoftext|>


<commit_before>const
  path = require('path'),
  fs = require('fs'),
  { logError, writeFile, kebabCase } = require('./build.utils')

function resolveType ({ type, values }) {
  // TODO transform Object with "values" and arrays Objects with values
  if (Array.isArray(type)) {
    return type.map(type => resolveType({ type })).join('|')
  }
  if (type === 'String' && values) {
    return values.map(v => v === null ? 'null' : `'${v}'`).join('|')
  }
  if (['Any', 'String', 'Boolean', 'Number', 'Object'].includes(type)) {
    return type.toLowerCase()
  }
  if (type === 'Array') {
    return 'any[]'
  }
  return type
}

function getDescription (propApi) {
  return propApi.examples
    ? propApi.desc + '\n\nExamples:\n' + propApi.examples.join('\n')
    : propApi.desc
}

module.exports.generate = function (data) {
  try {
    const webtypes = JSON.stringify({
      $schema: '',
      framework: 'vue',
      name: 'quasar',
      version: process.env.VERSION || require('../package.json').version,
      contributions: {
        html: {
          'types-syntax': 'typescript',
          tags: data.components.map(({ api: { events, props, scopedSlots, slots }, name }) => {
            let slotTypes = []
            if (slots) {
              Object.entries(slots).forEach(([name, slotApi]) => {
                slotTypes.push({
                  name,
                  description: getDescription(slotApi),
                  'doc-url': 'https://quasar.dev'
                })
              })
            }

            if (scopedSlots) {
              Object.entries(scopedSlots).forEach(([name, slotApi]) => {
                slotTypes.push({
                  name,
                  'vue-properties': slotApi.scope && Object.entries(slotApi.scope).map(([name, api]) => ({
                    name,
                    type: resolveType(api),
                    description: getDescription(api),
                    'doc-url': 'https://quasar.dev'
                  })),
                  description: getDescription(slotApi),
                  'doc-url': 'https://quasar.dev'
                })
              })
            }

            const result = {
              name,
              source: {
                module: 'quasar',
                symbol: name
              },
              attributes: props && Object.entries(props).map(([name, propApi]) => {
                let result = {
                  name,
                  value: {
                    kind: 'expression',
                    type: resolveType(propApi)
                  },
                  description: getDescription(propApi),
                  'doc-url': 'https://quasar.dev'
                }
                if (propApi.required) {
                  result.required = true
                }
                if (propApi.default) {
                  result.default = JSON.stringify(propApi.default)
                }
                if (propApi.type === 'Boolean') {
                  // Deprecated but used for compatibility with WebStorm 2019.2.
                  result.type = 'boolean'
                }
                return result
              }),
              events: events && Object.entries(events).map(([name, eventApi]) => ({
                name,
                arguments: eventApi.params && Object.entries(eventApi.params).map(([paramName, paramApi]) => ({
                  name: paramName,
                  type: resolveType(paramApi),
                  description: getDescription(paramApi),
                  'doc-url': 'https://quasar.dev'
                })),
                description: getDescription(eventApi),
                'doc-url': 'https://quasar.dev'
              })),
              slots: slotTypes,
              description: `${name} - Quasar component`,
              'doc-url': 'https://quasar.dev'
            }
            if (props && props.value && ((events && events.input) || props.value.category === 'model')) {
              result['vue-model'] = {
                prop: 'value',
                event: 'input'
              }
            }
            Object.entries(result).forEach(([key, v]) => {
              if (!v) {
                delete result[key]
              }
            })

            return result
          }),
          attributes: data.directives.map(({ name, api: { modifiers, value } }) => {
            let valueType = value.type
            let result = {
              name: 'v-' + kebabCase(name),
              source: {
                module: 'quasar',
                symbol: name
              },
              required: false, // Directive is never required
              description: `${name} - Quasar directive`,
              'doc-url': 'https://quasar.dev'
            }
            if (modifiers) {
              result['vue-modifiers'] = Object.entries(modifiers).map(([name, api]) => ({
                name,
                description: getDescription(api),
                'doc-url': 'https://quasar.dev'
              }))
            }
            if (valueType !== 'Boolean') {
              result.value = {
                kind: 'expression',
                type: resolveType(value)
              }
            }
            return result
          })
        }
      }
    }, null, 2)
    let webTypesPath = path.resolve(__dirname, '../dist/web-types')

    if (!fs.existsSync(webTypesPath)) {
      fs.mkdirSync(webTypesPath)
    }
    writeFile(path.resolve(webTypesPath, 'web-types.json'), webtypes)
  }
  catch (err) {
    logError(`build.web-types.js: something went wrong...`)
    console.log()
    console.error(err)
    console.log()
    process.exit(1)
  }
}
<commit_msg>update web types with direct urls (#5714)<commit_after>const
  path = require('path'),
  fs = require('fs'),
  { logError, writeFile, kebabCase } = require('./build.utils')

function resolveType ({ type, values }) {
  // TODO transform Object with "values" and arrays Objects with values
  if (Array.isArray(type)) {
    return type.map(type => resolveType({ type })).join('|')
  }
  if (type === 'String' && values) {
    return values.map(v => v === null ? 'null' : `'${v}'`).join('|')
  }
  if (['Any', 'String', 'Boolean', 'Number', 'Object'].includes(type)) {
    return type.toLowerCase()
  }
  if (type === 'Array') {
    return 'any[]'
  }
  return type
}

function getDescription (propApi) {
  return propApi.examples
    ? propApi.desc + '\n\nExamples:\n' + propApi.examples.join('\n')
    : propApi.desc
}

module.exports.generate = function (data) {
  try {
    const webtypes = JSON.stringify({
      $schema: '',
      framework: 'vue',
      name: 'quasar',
      version: process.env.VERSION || require('../package.json').version,
      contributions: {
        html: {
          'types-syntax': 'typescript',
          tags: data.components.map(({ api: { events, props, scopedSlots, slots, meta }, name }) => {
            let slotTypes = []
            if (slots) {
              Object.entries(slots).forEach(([name, slotApi]) => {
                slotTypes.push({
                  name,
                  description: getDescription(slotApi),
                  'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
                })
              })
            }

            if (scopedSlots) {
              Object.entries(scopedSlots).forEach(([name, slotApi]) => {
                slotTypes.push({
                  name,
                  'vue-properties': slotApi.scope && Object.entries(slotApi.scope).map(([name, api]) => ({
                    name,
                    type: resolveType(api),
                    description: getDescription(api),
                    'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
                  })),
                  description: getDescription(slotApi),
                  'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
                })
              })
            }

            const result = {
              name,
              source: {
                module: 'quasar',
                symbol: name
              },
              attributes: props && Object.entries(props).map(([name, propApi]) => {
                let result = {
                  name,
                  value: {
                    kind: 'expression',
                    type: resolveType(propApi)
                  },
                  description: getDescription(propApi),
                  'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
                }
                if (propApi.required) {
                  result.required = true
                }
                if (propApi.default) {
                  result.default = JSON.stringify(propApi.default)
                }
                if (propApi.type === 'Boolean') {
                  // Deprecated but used for compatibility with WebStorm 2019.2.
                  result.type = 'boolean'
                }
                return result
              }),
              events: events && Object.entries(events).map(([name, eventApi]) => ({
                name,
                arguments: eventApi.params && Object.entries(eventApi.params).map(([paramName, paramApi]) => ({
                  name: paramName,
                  type: resolveType(paramApi),
                  description: getDescription(paramApi),
                  'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
                })),
                description: getDescription(eventApi),
                'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
              })),
              slots: slotTypes,
              description: `${name} - Quasar component`,
              'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
            }
            if (props && props.value && ((events && events.input) || props.value.category === 'model')) {
              result['vue-model'] = {
                prop: 'value',
                event: 'input'
              }
            }
            Object.entries(result).forEach(([key, v]) => {
              if (!v) {
                delete result[key]
              }
            })

            return result
          }),
          attributes: data.directives.map(({ name, api: { modifiers, value, meta } }) => {
            let valueType = value.type
            let result = {
              name: 'v-' + kebabCase(name),
              source: {
                module: 'quasar',
                symbol: name
              },
              required: false, // Directive is never required
              description: `${name} - Quasar directive`,
              'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
            }
            if (modifiers) {
              result['vue-modifiers'] = Object.entries(modifiers).map(([name, api]) => ({
                name,
                description: getDescription(api),
                'doc-url': meta.docsUrl || 'https://v1.quasar.dev'
              }))
            }
            if (valueType !== 'Boolean') {
              result.value = {
                kind: 'expression',
                type: resolveType(value)
              }
            }
            return result
          })
        }
      }
    }, null, 2)
    let webTypesPath = path.resolve(__dirname, '../dist/web-types')

    if (!fs.existsSync(webTypesPath)) {
      fs.mkdirSync(webTypesPath)
    }
    writeFile(path.resolve(webTypesPath, 'web-types.json'), webtypes)
  }
  catch (err) {
    logError(`build.web-types.js: something went wrong...`)
    console.log()
    console.error(err)
    console.log()
    process.exit(1)
  }
}
<|endoftext|>