<commit_before>/* global APP */
/**
 * Implements API class that communicates with external api class
 * and provides interface to access Jitsi Meet features by external
 * applications that embed Jitsi Meet
 */

/**
 * List of the available commands.
 * @type {{
 *              displayName: inputDisplayNameHandler,
 *              toggleAudio: toggleAudio,
 *              toggleVideo: toggleVideo,
 *              toggleFilmStrip: toggleFilmStrip,
 *              toggleChat: toggleChat,
 *              toggleContactList: toggleContactList
 *          }}
 */
var commands = {};

function initCommands() {
    commands = {
        displayName: APP.UI.inputDisplayNameHandler,
        toggleAudio: APP.conference.toggleAudioMuted,
        toggleVideo: APP.conference.toggleVideoMuted,
        toggleFilmStrip: APP.UI.toggleFilmStrip,
        toggleChat: APP.UI.toggleChat,
        toggleContactList: APP.UI.toggleContactList
    };
}

/**
 * Maps the supported events and their status
 * (true it the event is enabled and false if it is disabled)
 * @type {{
 *              incomingMessage: boolean,
 *              outgoingMessage: boolean,
 *              displayNameChange: boolean,
 *              participantJoined: boolean,
 *              participantLeft: boolean
 *      }}
 */
const events = {
    incomingMessage: false,
    outgoingMessage:false,
    displayNameChange: false,
    participantJoined: false,
    participantLeft: false
};

/**
 * Processes commands from external application.
 * @param message the object with the command
 */
function processCommand(message) {
    if (message.action != "execute") {
        console.error("Unknown action of the message");
        return;
    }
    for (var key in message) {
        if(commands[key])
            commands[key].apply(null, message[key]);
    }
}

/**
 * Processes events objects from external applications
 * @param event the event
 */
function processEvent(event) {
    if (!event.action) {
        console.error("Event with no action is received.");
        return;
    }

    var i = 0;
    switch(event.action) {
        case "add":
            for (; i < event.events.length; i++) {
                events[event.events[i]] = true;
            }
            break;
        case "remove":
            for (; i < event.events.length; i++) {
                events[event.events[i]] = false;
            }
            break;
        default:
            console.error("Unknown action for event.");
    }
}

/**
 * Sends message to the external application.
 * @param object
 */
function sendMessage(object) {
    window.parent.postMessage(JSON.stringify(object), "*");
}

/**
 * Processes a message event from the external application
 * @param event the message event
 */
function processMessage(event) {
    var message;
    try {
        message = JSON.parse(event.data);
    } catch (e) {}

    if(!message.type)
        return;
    switch (message.type) {
        case "command":
            processCommand(message);
            break;
        case "event":
            processEvent(message);
            break;
        default:
            console.error("Unknown type of the message");
            return;
    }
}

/**
 * Check whether the API should be enabled or not.
 * @returns {boolean}
 */
function isEnabled () {
    let hash = location.hash;
    return hash && hash.indexOf("external") > -1 && window.postMessage;
}

/**
 * Checks whether the event is enabled ot not.
 * @param name the name of the event.
 * @returns {*}
 */
function isEventEnabled (name) {
    return events[name];
}

/**
 * Sends event object to the external application that has been subscribed
 * for that event.
 * @param name the name event
 * @param object data associated with the event
 */
function triggerEvent (name, object) {
    if (this.isEnabled() && isEventEnabled(name)) {
        sendMessage({
            type: "event",
            action: "result",
            event: name,
            result: object
        });
    }
}

export default {
    /**
     * Initializes the APIConnector. Setups message event listeners that will
     * receive information from external applications that embed Jitsi Meet.
     * It also sends a message to the external application that APIConnector
     * is initialized.
     */
    init: function () {
        if (!isEnabled()) {
            return;
        }
        initCommands();
        if (window.addEventListener) {
            window.addEventListener('message', processMessage, false);
        } else {
            window.attachEvent('onmessage', processMessage);
        }
        sendMessage({type: "system", loaded: true});
    },

    /**
     * Notify external application (if API is enabled) that message was sent.
     * @param {string} body message body
     */
    notifySendingChatMessage (body) {
        triggerEvent("outgoingMessage", {"message": body});
    },

    /**
     * Notify external application (if API is enabled) that
     * message was received.
     * @param {string} id user id
     * @param {string} nick user nickname
     * @param {string} body message body
     * @param {number} ts message creation timestamp
     */
    notifyReceivedChatMessage (id, nick, body, ts) {
        if (APP.conference.isLocalId(id)) {
            return;
        }

        triggerEvent(
            "incomingMessage",
            {"from": id, "nick": nick, "message": body, "stamp": ts}
        );
    },

    /**
     * Notify external application (if API is enabled) that
     * user joined the conference.
     * @param {string} id user id
     */
    notifyUserJoined (id) {
        triggerEvent("participantJoined", {id});
    },

    /**
     * Notify external application (if API is enabled) that
     * user left the conference.
     * @param {string} id user id
     */
    notifyUserLeft (id) {
        triggerEvent("participantLeft", {id});
    },

    /**
     * Notify external application (if API is enabled) that
     * user changed their nickname.
     * @param {string} id user id
     * @param {string} displayName user nickname
     */
    notifyDisplayNameChanged (id, displayName) {
        triggerEvent("displayNameChange", {id, displayname: displayName});
    },

    /**
     * Removes the listeners.
     */
    dispose: function () {
        if (!isEnabled()) {
            return;
        }

        if (window.removeEventListener) {
            window.removeEventListener("message", processMessage, false);
        } else {
            window.detachEvent('onmessage', processMessage);
        }
    }
};
<commit_msg>Fixes JS error in API module<commit_after>/* global APP */
/**
 * Implements API class that communicates with external api class
 * and provides interface to access Jitsi Meet features by external
 * applications that embed Jitsi Meet
 */

/**
 * List of the available commands.
 * @type {{
 *              displayName: inputDisplayNameHandler,
 *              toggleAudio: toggleAudio,
 *              toggleVideo: toggleVideo,
 *              toggleFilmStrip: toggleFilmStrip,
 *              toggleChat: toggleChat,
 *              toggleContactList: toggleContactList
 *          }}
 */
var commands = {};

function initCommands() {
    commands = {
        displayName: APP.UI.inputDisplayNameHandler,
        toggleAudio: APP.conference.toggleAudioMuted,
        toggleVideo: APP.conference.toggleVideoMuted,
        toggleFilmStrip: APP.UI.toggleFilmStrip,
        toggleChat: APP.UI.toggleChat,
        toggleContactList: APP.UI.toggleContactList
    };
}

/**
 * Maps the supported events and their status
 * (true it the event is enabled and false if it is disabled)
 * @type {{
 *              incomingMessage: boolean,
 *              outgoingMessage: boolean,
 *              displayNameChange: boolean,
 *              participantJoined: boolean,
 *              participantLeft: boolean
 *      }}
 */
const events = {
    incomingMessage: false,
    outgoingMessage:false,
    displayNameChange: false,
    participantJoined: false,
    participantLeft: false
};

/**
 * Processes commands from external application.
 * @param message the object with the command
 */
function processCommand(message) {
    if (message.action != "execute") {
        console.error("Unknown action of the message");
        return;
    }
    for (var key in message) {
        if(commands[key])
            commands[key].apply(null, message[key]);
    }
}

/**
 * Processes events objects from external applications
 * @param event the event
 */
function processEvent(event) {
    if (!event.action) {
        console.error("Event with no action is received.");
        return;
    }

    var i = 0;
    switch(event.action) {
        case "add":
            for (; i < event.events.length; i++) {
                events[event.events[i]] = true;
            }
            break;
        case "remove":
            for (; i < event.events.length; i++) {
                events[event.events[i]] = false;
            }
            break;
        default:
            console.error("Unknown action for event.");
    }
}

/**
 * Sends message to the external application.
 * @param object
 */
function sendMessage(object) {
    window.parent.postMessage(JSON.stringify(object), "*");
}

/**
 * Processes a message event from the external application
 * @param event the message event
 */
function processMessage(event) {
    var message;
    try {
        message = JSON.parse(event.data);
    } catch (e) {}

    if(!message.type)
        return;
    switch (message.type) {
        case "command":
            processCommand(message);
            break;
        case "event":
            processEvent(message);
            break;
        default:
            console.error("Unknown type of the message");
            return;
    }
}

/**
 * Check whether the API should be enabled or not.
 * @returns {boolean}
 */
function isEnabled () {
    let hash = location.hash;
    return hash && hash.indexOf("external") > -1 && window.postMessage;
}

/**
 * Checks whether the event is enabled ot not.
 * @param name the name of the event.
 * @returns {*}
 */
function isEventEnabled (name) {
    return events[name];
}

/**
 * Sends event object to the external application that has been subscribed
 * for that event.
 * @param name the name event
 * @param object data associated with the event
 */
function triggerEvent (name, object) {
    if (isEnabled() && isEventEnabled(name)) {
        sendMessage({
            type: "event",
            action: "result",
            event: name,
            result: object
        });
    }
}

export default {
    /**
     * Initializes the APIConnector. Setups message event listeners that will
     * receive information from external applications that embed Jitsi Meet.
     * It also sends a message to the external application that APIConnector
     * is initialized.
     */
    init: function () {
        if (!isEnabled()) {
            return;
        }
        initCommands();
        if (window.addEventListener) {
            window.addEventListener('message', processMessage, false);
        } else {
            window.attachEvent('onmessage', processMessage);
        }
        sendMessage({type: "system", loaded: true});
    },

    /**
     * Notify external application (if API is enabled) that message was sent.
     * @param {string} body message body
     */
    notifySendingChatMessage (body) {
        triggerEvent("outgoingMessage", {"message": body});
    },

    /**
     * Notify external application (if API is enabled) that
     * message was received.
     * @param {string} id user id
     * @param {string} nick user nickname
     * @param {string} body message body
     * @param {number} ts message creation timestamp
     */
    notifyReceivedChatMessage (id, nick, body, ts) {
        if (APP.conference.isLocalId(id)) {
            return;
        }

        triggerEvent(
            "incomingMessage",
            {"from": id, "nick": nick, "message": body, "stamp": ts}
        );
    },

    /**
     * Notify external application (if API is enabled) that
     * user joined the conference.
     * @param {string} id user id
     */
    notifyUserJoined (id) {
        triggerEvent("participantJoined", {id});
    },

    /**
     * Notify external application (if API is enabled) that
     * user left the conference.
     * @param {string} id user id
     */
    notifyUserLeft (id) {
        triggerEvent("participantLeft", {id});
    },

    /**
     * Notify external application (if API is enabled) that
     * user changed their nickname.
     * @param {string} id user id
     * @param {string} displayName user nickname
     */
    notifyDisplayNameChanged (id, displayName) {
        triggerEvent("displayNameChange", {id, displayname: displayName});
    },

    /**
     * Removes the listeners.
     */
    dispose: function () {
        if (!isEnabled()) {
            return;
        }

        if (window.removeEventListener) {
            window.removeEventListener("message", processMessage, false);
        } else {
            window.detachEvent('onmessage', processMessage);
        }
    }
};
<|endoftext|>


<commit_before>function slugify(string) {
  return string.replace(/\s+/g,'-').replace(/[^a-zA-Z0-9\-]/g,'').toLowerCase();
}

function ajaxHandler( data ) {
  if ( data.update ) {
    $.each(data.update,function(k,v){
      $(k).html(v);
    });
  }
}

function dismiss_notification(notification_id) {
  $.get("/notifications/dismiss/" + notification_id + "/", {},
    function(data, textStatus, xhr) {
      $("#notification-" + notification_id).slideUp(function(){$(this).remove()});
    });
}

function load_recipe_template(project_id) {
  return (function(node) {
    if ($('#available_recipes').val() != "") {
      $("#recipe_template_container").load("/projects/" + project_id +
          "/recipe_template/" +
          $('#available_recipes').val() + '/');
    }
  })
}

function show_buildout_result(node) {
  return (function(data_or_xhr, textStatus) {
    var to_append = '';
    switch (textStatus) {
      case null:
      case 'timeout':
      case 'error':
      case 'notmodified':
      case 'parsererror':
        to_append = '' + data_or_xhr.responseText + '';
        break;
      default:
        to_append = '' + data_or_xhr + '';
        break;
    }
    to_append = $(to_append).css('display','none');
    $(to_append).appendTo(node.parents('li')).slideDown().delay(2000).slideUp(function(){$(this).remove();});
  });
}

jQuery(function($){
  $('.autosubmit').change(function(){
    $(this).parents('form').first().submit();
  });

  $('div.projects .favourite').live('click', function(e){
    e.preventDefault();
    var href = $(this).attr('href') + '?' + $(this).parents('form').serialize();
    $.post( href, { update: '{"#project-list":"projects"}' }, ajaxHandler );
  });

  $('div.dashboard .projects .favourite').live('click', function(e){
    e.preventDefault();
    $.post( $(this).attr('href'), {
      update: '{"#project-list":"home-projects","#favourite-project-list":"favourite-projects"}'
    }, ajaxHandler );
  });

  $('div.dashboard .projects .buildout, div.dashboard .projects .tests').live('click', function(e){
    e.preventDefault();
    $.ajax({
      url: $(this).attr('href'),
      success: show_buildout_result($(this)),
      error: show_buildout_result($(this))
    });
  })

  $('div.dashboard .tests').ekko({url: '/notifications/ajax/'},
    function(data) {
      $(data).each(function(_,n){
        if ( n.id && $("#"+n.id).size() == 0 ) {
          $(n).css('display','none').prependTo($('div.dashboard ul.tests')).slideDown();
        }
      })
      $('div.dashboard ul.tests li:nth-child(11n)').slideUp(function(){$(this).remove()});
  });

  $("#id_eggs, #id_extra_paths").lineeditor();

  // if ($('.notifications').size()) {
  //   var paper = Raphael(
  //     $(".notifications .svg").get(0),
  //     100,
  //     Math.max($('.successes').height(), $('.errors').height())
  //   );
  //   var $paper = $(".notifications .svg").first();
  //   var first_success = parseInt($('.successes li').first().attr('id').replace(/\D*/,''));
  //   var first_error = parseInt($('.errors li').first().attr('id').replace(/\D*/,''));
  //
  //   var cur_id = Math.max(first_success, first_error);
  //   do {
  //     var node = $('#notification-' + cur_id);
  //     var prev_node = $('#notification-' + (cur_id + 1));
  //     if ( node.size() && prev_node.size() && node.attr('class') != prev_node.attr('class')) {
  //       var line = paper.path("M{0},{1} L{2},{3}",
  //         prev_node.hasClass('error')?100:0, prev_node.position().top - $paper.position().top + (prev_node.height()/2),
  //         node.hasClass('error')?100:0, node.position().top - $paper.position().top + (node.height()/2)
  //       ).attr('stroke', prev_node.children('a').css('background-color')).attr('stroke-width', 2);
  //     }
  //     cur_id--;
  //   } while ( node.size() )
  // }
});

<commit_msg>We'll use a widget instead.<commit_after>function slugify(string) {
  return string.replace(/\s+/g,'-').replace(/[^a-zA-Z0-9\-]/g,'').toLowerCase();
}

function ajaxHandler( data ) {
  if ( data.update ) {
    $.each(data.update,function(k,v){
      $(k).html(v);
    });
  }
}

function dismiss_notification(notification_id) {
  $.get("/notifications/dismiss/" + notification_id + "/", {},
    function(data, textStatus, xhr) {
      $("#notification-" + notification_id).slideUp(function(){$(this).remove()});
    });
}

function load_recipe_template(project_id) {
  return (function(node) {
    if ($('#available_recipes').val() != "") {
      $("#recipe_template_container").load("/projects/" + project_id +
          "/recipe_template/" +
          $('#available_recipes').val() + '/');
    }
  })
}

function show_buildout_result(node) {
  return (function(data_or_xhr, textStatus) {
    var to_append = '';
    switch (textStatus) {
      case null:
      case 'timeout':
      case 'error':
      case 'notmodified':
      case 'parsererror':
        to_append = '' + data_or_xhr.responseText + '';
        break;
      default:
        to_append = '' + data_or_xhr + '';
        break;
    }
    to_append = $(to_append).css('display','none');
    $(to_append).appendTo(node.parents('li')).slideDown().delay(2000).slideUp(function(){$(this).remove();});
  });
}

jQuery(function($){
  $('.autosubmit').change(function(){
    $(this).parents('form').first().submit();
  });

  $('div.projects .favourite').live('click', function(e){
    e.preventDefault();
    var href = $(this).attr('href') + '?' + $(this).parents('form').serialize();
    $.post( href, { update: '{"#project-list":"projects"}' }, ajaxHandler );
  });

  $('div.dashboard .projects .favourite').live('click', function(e){
    e.preventDefault();
    $.post( $(this).attr('href'), {
      update: '{"#project-list":"home-projects","#favourite-project-list":"favourite-projects"}'
    }, ajaxHandler );
  });

  $('div.dashboard .projects .buildout, div.dashboard .projects .tests').live('click', function(e){
    e.preventDefault();
    $.ajax({
      url: $(this).attr('href'),
      success: show_buildout_result($(this)),
      error: show_buildout_result($(this))
    });
  })

  $('div.dashboard .tests').ekko({url: '/notifications/ajax/'},
    function(data) {
      $(data).each(function(_,n){
        if ( n.id && $("#"+n.id).size() == 0 ) {
          $(n).css('display','none').prependTo($('div.dashboard ul.tests')).slideDown();
        }
      })
      $('div.dashboard ul.tests li:nth-child(11n)').slideUp(function(){$(this).remove()});
  });

  // if ($('.notifications').size()) {
  //   var paper = Raphael(
  //     $(".notifications .svg").get(0),
  //     100,
  //     Math.max($('.successes').height(), $('.errors').height())
  //   );
  //   var $paper = $(".notifications .svg").first();
  //   var first_success = parseInt($('.successes li').first().attr('id').replace(/\D*/,''));
  //   var first_error = parseInt($('.errors li').first().attr('id').replace(/\D*/,''));
  //
  //   var cur_id = Math.max(first_success, first_error);
  //   do {
  //     var node = $('#notification-' + cur_id);
  //     var prev_node = $('#notification-' + (cur_id + 1));
  //     if ( node.size() && prev_node.size() && node.attr('class') != prev_node.attr('class')) {
  //       var line = paper.path("M{0},{1} L{2},{3}",
  //         prev_node.hasClass('error')?100:0, prev_node.position().top - $paper.position().top + (prev_node.height()/2),
  //         node.hasClass('error')?100:0, node.position().top - $paper.position().top + (node.height()/2)
  //       ).attr('stroke', prev_node.children('a').css('background-color')).attr('stroke-width', 2);
  //     }
  //     cur_id--;
  //   } while ( node.size() )
  // }
});

<|endoftext|>


<commit_before>dojo.provide("dojox.grid.tests.performance.module");

doh.register("dojox.grid.tests.performance.module", []);

try{
	var numRows = 100;

	// 5-minute timeout on each of these - since they can take quite a while...
	doh.registerUrl("Grid Creation - " + numRows + " Rows, Single Layout",
		dojo.moduleUrl("dojox.grid.tests.performance", "creation.html") + "?rows=" + numRows + "&layout=single&rowSelector=false",
		300000);
	doh.registerUrl("Grid Creation - " + numRows + " Rows, Dual Layout",
		dojo.moduleUrl("dojox.grid.tests.performance", "creation.html") + "?rows=" + numRows + "&layout=dual&rowSelector=false",
		300000);
	doh.registerUrl("Grid Creation - " + numRows + " Rows, Single Layout w/ selector",
		dojo.moduleUrl("dojox.grid.tests.performance", "creation.html") + "?rows=" + numRows + "&layout=single&rowSelector=true",
		300000);
	doh.registerUrl("Grid Creation - " + numRows + " Rows, Dual Layout w/ selector",
		dojo.moduleUrl("dojox.grid.tests.performance", "creation.html") + "?rows=" + numRows + "&layout=dual&rowSelector=true",
		300000);
}catch(e){
	doh.debug(e);
}
<commit_msg>Update module.js to be a bit more clean moving forward.<commit_after>dojo.provide("dojox.grid.tests.performance.module");

doh.register("dojox.grid.tests.performance.module", []);

try{
	var numRows = [100];
	var layouts = ["Single", "Dual"];
	var selectors = [false, true];
	dojo.forEach(numRows, function(r){
		dojo.forEach(layouts, function(l){
			dojo.forEach(selectors, function(s){
				// 5-minute timeout on each of these - since they can take quite a while...
				doh.registerUrl("Grid Creation - " + r + " Rows, " + l + " Layout" + (s ? "w/ selector" : ""),
					dojo.moduleUrl("dojox.grid.tests.performance", "creation.html") +
									"?rows=" + r + "&layout=" + l.toLowerCase() + "&rowSelector=" + s,
					300000);
			});
		});
	});
}catch(e){
	doh.debug(e);
}
<|endoftext|>


<commit_before>import {Pos, spanStylesAt, sliceBefore, sliceAfter, sliceBetween} from "../model"

import {TransformResult, Transform} from "./transform"
import {defineStep, Step} from "./step"
import {PosMap, MovedRange, ReplacedRange} from "./map"
import {replaceHasEffect, samePathDepth} from "./tree"

function findMovedChunks(oldNode, oldPath, newNode, startDepth) {
  let moved = []
  let newPath = oldPath.path.slice(0, startDepth)

  for (let depth = startDepth;; depth++) {
    let joined = depth == oldPath.depth ? 0 : 1
    let cut = depth == oldPath.depth ? oldPath.offset : oldPath.path[depth]
    let afterCut = oldNode.maxOffset - cut
    let newOffset = newNode.maxOffset - afterCut

    let from = oldPath.shorten(depth, joined)
    let to = new Pos(newPath, newOffset + joined)
    if (from.cmp(to)) moved.push(new MovedRange(from, afterCut - joined, to))

    if (!joined) return moved

    oldNode = oldNode.child(cut)
    newNode = newNode.child(newOffset)
    newPath = newPath.concat(newOffset)
  }
}

export function replace(node, from, to, root, repl, depth = 0) {
  if (depth == root.length) {
    let before = sliceBefore(node, from, depth)
    let after = sliceAfter(node, to, depth), result
    if (!repl.nodes.every(n => before.type.canContain(n))) return null
    if (repl.nodes.length)
      result = before.append(repl.nodes, Math.min(repl.openLeft, from.depth - depth))
                     .append(after.children, Math.min(repl.openRight, to.depth - depth))
    else
      result = before.append(after.children, Math.min(to.depth, from.depth) - depth)
    return {doc: result, moved: findMovedChunks(node, to, result, depth)}
  } else {
    let pos = root[depth]
    let result = replace(node.child(pos), from, to, root, repl, depth + 1)
    if (!result) return null
    return {doc: node.replace(pos, result.doc), moved: result.moved}
  }
}

const nullRepl = {nodes: [], openLeft: 0, openRight: 0}

defineStep("replace", {
  apply(doc, step) {
    let rootPos = step.pos, root = rootPos.path
    if (step.from.depth < root.length || step.to.depth < root.length)
      return null
    for (let i = 0; i < root.length; i++)
      if (step.from.path[i] != root[i] || step.to.path[i] != root[i])
        return null

    let result = replace(doc, step.from, step.to, rootPos.path, step.param || nullRepl)
    if (!result) return null
    let {doc: out, moved} = result
    let end = moved.length ? moved[moved.length - 1].dest : step.to
    let replaced = new ReplacedRange(step.from, step.to, step.from, end, rootPos, rootPos)
    return new TransformResult(out, new PosMap(moved, [replaced]))
  },
  invert(step, oldDoc, map) {
    let depth = step.pos.depth
    let between = sliceBetween(oldDoc, step.from, step.to, false)
    for (let i = 0; i < depth; i++) between = between.firstChild
    return new Step("replace", step.from, map.map(step.to).pos, step.from.shorten(depth), {
      nodes: between.children,
      openLeft: step.from.depth - depth,
      openRight: step.to.depth - depth
    })
  },
  paramToJSON(param) {
    return param && {nodes: param.nodes && param.nodes.map(n => n.toJSON()),
                     openLeft: param.openLeft, openRight: param.openRight}
  },
  paramFromJSON(schema, json) {
    return json && {nodes: json.nodes && json.nodes.map(schema.nodeFromJSON),
                    openLeft: json.openLeft, openRight: json.openRight}
  }
})

function shiftFromStack(stack, depth) {
  let shifted = stack[depth] = stack[depth].splice(0, 1, [])
  for (let i = depth - 1; i >= 0; i--)
    shifted = stack[i] = stack[i].replace(0, shifted)
}

// FIXME find a not so horribly confusing way to express this
function buildInserted(nodesLeft, source, start, end) {
  let sliced = sliceBetween(source, start, end, false)
  let nodesRight = []
  for (let node = sliced, i = 0; i <= start.path.length; i++, node = node.firstChild)
    nodesRight.push(node)
  let same = samePathDepth(start, end)
  let searchLeft = nodesLeft.length - 1, searchRight = nodesRight.length - 1
  let result = null

  let inner = nodesRight[searchRight]
  if (inner.isTextblock && inner.length && nodesLeft[searchLeft].isTextblock) {
    result = nodesLeft[searchLeft--].copy(inner.children)
    --searchRight
    shiftFromStack(nodesRight, searchRight)
  }

  for (;;) {
    let node = nodesRight[searchRight], type = node.type, matched = null
    let outside = searchRight <= same
    for (let i = searchLeft; i >= 0; i--) {
      let left = nodesLeft[i]
      if (outside ? left.type.canContainChildren(node) : left.type == type) {
        matched = i
        break
      }
    }
    if (matched != null) {
      if (!result) {
        result = nodesLeft[matched].copy(node.children)
        searchLeft = matched - 1
      } else {
        while (searchLeft >= matched) {
          result = nodesLeft[searchLeft].copy(searchLeft == matched ? [result].concat(node.children) : [result])
          searchLeft--
        }
      }
    }
    if (matched != null || node.length == 0) {
      if (outside) break
      if (searchRight) shiftFromStack(nodesRight, searchRight - 1)
    }
    searchRight--
  }

  let repl = {nodes: result ? result.children : [],
              openLeft: start.depth - searchRight,
              openRight: end.depth - searchRight}
  return {repl, depth: searchLeft + 1}
}

function moveText(tr, doc, before, after) {
  let root = samePathDepth(before, after)
  let cutAt = after.shorten(null, 1)
  while (cutAt.path.length > root && doc.path(cutAt.path).length == 1)
    cutAt = cutAt.shorten(null, 1)
  tr.split(cutAt, cutAt.path.length - root)
  let start = after, end = new Pos(start.path, doc.path(start.path).maxOffset)
  let parent = doc.path(start.path.slice(0, root))
  let wanted = parent.pathNodes(before.path.slice(root))
  let existing = parent.pathNodes(start.path.slice(root))
  while (wanted.length && existing.length && wanted[0].sameMarkup(existing[0])) {
    wanted.shift()
    existing.shift()
  }
  if (existing.length || wanted.length)
    tr.step("ancestor", start, end, null, {
      depth: existing.length,
      wrappers: wanted.map(n => n.copy())
    })
  for (let i = root; i < before.path.length; i++)
    tr.join(before.shorten(i, 1))
}

/**
 * Delete content between two positions.
 *
 * @param  {Pos} from
 * @param  {Pos} to
 * @return this
 */
Transform.prototype.delete = function(from, to) {
  if (from.cmp(to)) this.replace(from, to)
  return this
}

/**
 * Replace the content between two positions.
 */
Transform.prototype.replace = function(from, to, source, start, end) {
  let repl, depth, doc = this.doc, maxDepth = samePathDepth(from, to)
  if (source) {
    ;({repl, depth} = buildInserted(doc.pathNodes(from.path), source, start, end))
    while (depth > maxDepth) {
      if (repl.nodes.length)
        repl = {nodes: [doc.path(from.path.slice(0, depth)).copy(repl.nodes)],
                openLeft: repl.openLeft + 1, openRight: repl.openRight + 1}
      depth--
    }
  } else {
    repl = nullRepl
    depth = maxDepth
  }
  let root = from.shorten(depth), docAfter = doc, after = to
  if (repl.nodes.length || replaceHasEffect(doc, from, to)) {
    let result = this.step("replace", from, to, root, repl)
    docAfter = result.doc
    after = result.map.map(to).pos
  }

  // If no text nodes before or after end of replacement, don't glue text
  if (!doc.path(to.path).isTextblock) return this
  if (!(repl.nodes.length ? source.path(end.path).isTextblock : doc.path(from.path).isTextblock)) return this

  let nodesAfter = doc.path(root.path).pathNodes(to.path.slice(depth)).slice(1)
  let nodesBefore
  if (repl.nodes.length) {
    let inserted = repl.nodes
    nodesBefore = []
    for (let i = 0; i < repl.openRight; i++) {
      let last = inserted[inserted.length - 1]
      nodesBefore.push(last)
      inserted = last.children
    }
  } else {
    nodesBefore = doc.path(root.path).pathNodes(from.path.slice(depth)).slice(1)
  }
  if (nodesAfter.length != nodesBefore.length ||
      !nodesAfter.every((n, i) => n.sameMarkup(nodesBefore[i]))) {
    let before = Pos.before(docAfter, after.shorten(null, 0))
    moveText(this, docAfter, before, after)
  }
  return this
}

Transform.prototype.replaceWith = function(from, to, nodes) {
  if (!Array.isArray(nodes)) nodes = [nodes]
  if (!Pos.samePath(from.path, to.path)) return this
  this.step("replace", from, to, from, {nodes: nodes, openLeft: 0, openRight: 0})
  return this
}

/**
 * Insert a node at a given position.
 *
 * @param  {Pos}   pos
 * @param  {mixed} nodes
 * @return {this}
 */
Transform.prototype.insert = function(pos, nodes) {
  return this.replaceWith(pos, pos, nodes)
}

Transform.prototype.insertInline = function(pos, nodes) {
  if (!Array.isArray(nodes)) nodes = [nodes]
  let styles = spanStylesAt(this.doc, pos)
  nodes = nodes.map(n => n.styled(styles))
  return this.insert(pos, nodes)
}

Transform.prototype.insertText = function(pos, text) {
  return this.insertInline(pos, this.doc.type.schema.text(text))
}
<commit_msg>Another step in the direction of dropping Pos.near<commit_after>import {Pos, spanStylesAt, sliceBefore, sliceAfter, sliceBetween, childrenBetween} from "../model"

import {TransformResult, Transform} from "./transform"
import {defineStep, Step} from "./step"
import {PosMap, MovedRange, ReplacedRange} from "./map"
import {replaceHasEffect, samePathDepth} from "./tree"

function findMovedChunks(oldNode, oldPath, newNode, startDepth) {
  let moved = []
  let newPath = oldPath.path.slice(0, startDepth)

  for (let depth = startDepth;; depth++) {
    let joined = depth == oldPath.depth ? 0 : 1
    let cut = depth == oldPath.depth ? oldPath.offset : oldPath.path[depth]
    let afterCut = oldNode.maxOffset - cut
    let newOffset = newNode.maxOffset - afterCut

    let from = oldPath.shorten(depth, joined)
    let to = new Pos(newPath, newOffset + joined)
    if (from.cmp(to)) moved.push(new MovedRange(from, afterCut - joined, to))

    if (!joined) return moved

    oldNode = oldNode.child(cut)
    newNode = newNode.child(newOffset)
    newPath = newPath.concat(newOffset)
  }
}

export function replace(node, from, to, root, repl, depth = 0) {
  if (depth == root.length) {
    let before = sliceBefore(node, from, depth)
    let after = sliceAfter(node, to, depth), result
    if (!repl.nodes.every(n => before.type.canContain(n))) return null
    if (repl.nodes.length)
      result = before.append(repl.nodes, Math.min(repl.openLeft, from.depth - depth))
                     .append(after.children, Math.min(repl.openRight, to.depth - depth))
    else
      result = before.append(after.children, Math.min(to.depth, from.depth) - depth)
    return {doc: result, moved: findMovedChunks(node, to, result, depth)}
  } else {
    let pos = root[depth]
    let result = replace(node.child(pos), from, to, root, repl, depth + 1)
    if (!result) return null
    return {doc: node.replace(pos, result.doc), moved: result.moved}
  }
}

const nullRepl = {nodes: [], openLeft: 0, openRight: 0}

defineStep("replace", {
  apply(doc, step) {
    let rootPos = step.pos, root = rootPos.path
    if (step.from.depth < root.length || step.to.depth < root.length)
      return null
    for (let i = 0; i < root.length; i++)
      if (step.from.path[i] != root[i] || step.to.path[i] != root[i])
        return null

    let result = replace(doc, step.from, step.to, rootPos.path, step.param || nullRepl)
    if (!result) return null
    let {doc: out, moved} = result
    let end = moved.length ? moved[moved.length - 1].dest : step.to
    let replaced = new ReplacedRange(step.from, step.to, step.from, end, rootPos, rootPos)
    return new TransformResult(out, new PosMap(moved, [replaced]))
  },
  invert(step, oldDoc, map) {
    let depth = step.pos.depth
    return new Step("replace", step.from, map.map(step.to).pos, step.from.shorten(depth), {
      nodes: childrenBetween(oldDoc.path(step.pos.path), step.from, step.to, depth),
      openLeft: step.from.depth - depth,
      openRight: step.to.depth - depth
    })
  },
  paramToJSON(param) {
    return param && {nodes: param.nodes && param.nodes.map(n => n.toJSON()),
                     openLeft: param.openLeft, openRight: param.openRight}
  },
  paramFromJSON(schema, json) {
    return json && {nodes: json.nodes && json.nodes.map(schema.nodeFromJSON),
                    openLeft: json.openLeft, openRight: json.openRight}
  }
})

function shiftFromStack(stack, depth) {
  let shifted = stack[depth] = stack[depth].splice(0, 1, [])
  for (let i = depth - 1; i >= 0; i--)
    shifted = stack[i] = stack[i].replace(0, shifted)
}

// FIXME find a not so horribly confusing way to express this
function buildInserted(nodesLeft, source, start, end) {
  let sliced = sliceBetween(source, start, end, false)
  let nodesRight = []
  for (let node = sliced, i = 0; i <= start.path.length; i++, node = node.firstChild)
    nodesRight.push(node)
  let same = samePathDepth(start, end)
  let searchLeft = nodesLeft.length - 1, searchRight = nodesRight.length - 1
  let result = null

  let inner = nodesRight[searchRight]
  if (inner.isTextblock && inner.length && nodesLeft[searchLeft].isTextblock) {
    result = nodesLeft[searchLeft--].copy(inner.children)
    --searchRight
    shiftFromStack(nodesRight, searchRight)
  }

  for (;;) {
    let node = nodesRight[searchRight], type = node.type, matched = null
    let outside = searchRight <= same
    for (let i = searchLeft; i >= 0; i--) {
      let left = nodesLeft[i]
      if (outside ? left.type.canContainChildren(node) : left.type == type) {
        matched = i
        break
      }
    }
    if (matched != null) {
      if (!result) {
        result = nodesLeft[matched].copy(node.children)
        searchLeft = matched - 1
      } else {
        while (searchLeft >= matched) {
          result = nodesLeft[searchLeft].copy(searchLeft == matched ? [result].concat(node.children) : [result])
          searchLeft--
        }
      }
    }
    if (matched != null || node.length == 0) {
      if (outside) break
      if (searchRight) shiftFromStack(nodesRight, searchRight - 1)
    }
    searchRight--
  }

  let repl = {nodes: result ? result.children : [],
              openLeft: start.depth - searchRight,
              openRight: end.depth - searchRight}
  return {repl, depth: searchLeft + 1}
}

function moveText(tr, doc, before, after) {
  let root = samePathDepth(before, after)
  let cutAt = after.shorten(null, 1)
  while (cutAt.path.length > root && doc.path(cutAt.path).length == 1)
    cutAt = cutAt.shorten(null, 1)
  tr.split(cutAt, cutAt.path.length - root)
  let start = after, end = new Pos(start.path, doc.path(start.path).maxOffset)
  let parent = doc.path(start.path.slice(0, root))
  let wanted = parent.pathNodes(before.path.slice(root))
  let existing = parent.pathNodes(start.path.slice(root))
  while (wanted.length && existing.length && wanted[0].sameMarkup(existing[0])) {
    wanted.shift()
    existing.shift()
  }
  if (existing.length || wanted.length)
    tr.step("ancestor", start, end, null, {
      depth: existing.length,
      wrappers: wanted.map(n => n.copy())
    })
  for (let i = root; i < before.path.length; i++)
    tr.join(before.shorten(i, 1))
}

/**
 * Delete content between two positions.
 *
 * @param  {Pos} from
 * @param  {Pos} to
 * @return this
 */
Transform.prototype.delete = function(from, to) {
  if (from.cmp(to)) this.replace(from, to)
  return this
}

/**
 * Replace the content between two positions.
 */
Transform.prototype.replace = function(from, to, source, start, end) {
  let repl, depth, doc = this.doc, maxDepth = samePathDepth(from, to)
  if (source) {
    ;({repl, depth} = buildInserted(doc.pathNodes(from.path), source, start, end))
    while (depth > maxDepth) {
      if (repl.nodes.length)
        repl = {nodes: [doc.path(from.path.slice(0, depth)).copy(repl.nodes)],
                openLeft: repl.openLeft + 1, openRight: repl.openRight + 1}
      depth--
    }
  } else {
    repl = nullRepl
    depth = maxDepth
  }
  let root = from.shorten(depth), docAfter = doc, after = to
  if (repl.nodes.length || replaceHasEffect(doc, from, to)) {
    let result = this.step("replace", from, to, root, repl)
    docAfter = result.doc
    after = result.map.map(to).pos
  }

  // If no text nodes before or after end of replacement, don't glue text
  if (!doc.path(to.path).isTextblock) return this
  if (!(repl.nodes.length ? source.path(end.path).isTextblock : doc.path(from.path).isTextblock)) return this

  let nodesAfter = doc.path(root.path).pathNodes(to.path.slice(depth)).slice(1)
  let nodesBefore
  if (repl.nodes.length) {
    let inserted = repl.nodes
    nodesBefore = []
    for (let i = 0; i < repl.openRight; i++) {
      let last = inserted[inserted.length - 1]
      nodesBefore.push(last)
      inserted = last.children
    }
  } else {
    nodesBefore = doc.path(root.path).pathNodes(from.path.slice(depth)).slice(1)
  }

  if (nodesBefore.length &&
      (nodesAfter.length != nodesBefore.length ||
       !nodesAfter.every((n, i) => n.sameMarkup(nodesBefore[i])))) {
    let {path, offset} = after.shorten(root.depth), before
    for (let node = docAfter.path(path), i = 0;; i++) {
      if (i == nodesBefore.length) {
        before = new Pos(path, offset)
        break
      }
      path.push(offset - 1)
      node = node.child(offset - 1)
      offset = node.maxOffset
    }
    moveText(this, docAfter, before, after)
  }
  return this
}

Transform.prototype.replaceWith = function(from, to, nodes) {
  if (!Array.isArray(nodes)) nodes = [nodes]
  if (!Pos.samePath(from.path, to.path)) return this
  this.step("replace", from, to, from, {nodes: nodes, openLeft: 0, openRight: 0})
  return this
}

/**
 * Insert a node at a given position.
 *
 * @param  {Pos}   pos
 * @param  {mixed} nodes
 * @return {this}
 */
Transform.prototype.insert = function(pos, nodes) {
  return this.replaceWith(pos, pos, nodes)
}

Transform.prototype.insertInline = function(pos, nodes) {
  if (!Array.isArray(nodes)) nodes = [nodes]
  let styles = spanStylesAt(this.doc, pos)
  nodes = nodes.map(n => n.styled(styles))
  return this.insert(pos, nodes)
}

Transform.prototype.insertText = function(pos, text) {
  return this.insertInline(pos, this.doc.type.schema.text(text))
}
<|endoftext|>


<commit_before>/*
 * VenoBox - jQuery Plugin
 * version: 1.4.0
 * @requires jQuery
 *
 * Examples at http://lab.veno.it/venobox/
 * License: Creative Commons Attribution 3.0 License
 * License URI: http://creativecommons.org/licenses/by/3.0/
 * Copyright 2013-2014 Nicola Franchini - @nicolafranchini
 *
 */
(function($){

    var ios, ie9, overlayColor, overlay, vwrap, container, content, core, dest, top, sonH, finH, margine, prima, framewidth, frameheight, border, bgcolor, type, thisgall, items, thenext, theprev, title, nextok, prevok, keyNavigationDisabled, blocktitle, blocknum, numeratio, evitanext, evitaprev, evitacontent, figliall, infinigall, extraCss;

    $.fn.extend({
        //plugin name - venobox
        venobox: function(options) {

          // default options
          var defaults = {
              framewidth: '',
              frameheight: '',
              border: '0',
              bgcolor: '#fff',
              numeratio: false,
              infinigall: false
          };
          var options = $.extend(defaults, options);

            return this.each(function() {
                  var obj = $(this);

                  obj.addClass('vbox-item');
                  obj.data('framewidth', options.framewidth);
                  obj.data('frameheight', options.frameheight);
                  obj.data('border', options.border);
                  obj.data('bgcolor', options.bgcolor);
                  obj.data('numeratio', options.numeratio);
                  obj.data('infinigall', options.infinigall);

                  ios = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false);

                  // Idiot Explorer 9 or less
                  ie9 = ((document.all && !window.atob) ? true : false);

                  obj.click(function(e){
                    e.stopPropagation();
                    e.preventDefault();
                    obj = $(this);
                    overlayColor = obj.data('overlay');
                    framewidth = obj.data('framewidth');
                    frameheight = obj.data('frameheight');
                    border = obj.data('border');
                    bgcolor = obj.data('bgcolor');
                    nextok = false;
                    prevok = false;
                    keyNavigationDisabled = false;
                    dest = obj.attr('href');
                    top = $(window).scrollTop();
                    top = -top;
                    extraCss = obj.data( 'css' ) || "";

                    $('body').wrapInner('

')

                    vwrap = $('.vwrap');
                    core = '

Loading...

0/0

X

next

prev

';

                    $('body').append(core);

                    overlay = $('.vbox-overlay');
                    container = $('.vbox-container');
                    content = $('.vbox-content');
                    blocknum = $('.vbox-num');
                    blocktitle = $('.vbox-title');

                    content.html('');
                    content.css('opacity', '0');

                    checknav();

                    overlay.css('min-height', $(window).outerHeight());

                    if (ie9) {
                      overlay.animate({opacity:1}, 250, function(){
                        overlay.css({
                          'min-height': $(window).outerHeight(),
                          height : 'auto'
                        });
                        if(obj.data('type') == 'iframe'){
                          loadIframe();
                        }else if (obj.data('type') == 'inline'){
                          loadInline();
                        }else if (obj.data('type') == 'ajax'){
                          loadAjax();
                        }else if (obj.data('type') == 'vimeo'){
                          loadVimeo();
                        }else if (obj.data('type') == 'youtube'){
                          loadYoutube();
                        } else {
                          content.html('');
                          preloadFirst();
                        }
                      });
                    } else {
                      overlay.bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){
                        overlay.css({
                          'min-height': $(window).outerHeight(),
                          height : 'auto'
                        });
                        if(obj.data('type') == 'iframe'){
                          loadIframe();
                        }else if (obj.data('type') == 'inline'){
                          loadInline();
                        }else if (obj.data('type') == 'ajax'){
                          loadAjax();
                        }else if (obj.data('type') == 'vimeo'){
                          loadVimeo();
                        }else if (obj.data('type') == 'youtube'){
                          loadYoutube();
                        } else {
                          content.html('');
                          preloadFirst();
                        }
                      });
                      overlay.css('opacity', '1');
                    }

                    if (ios) {
                      vwrap.css({
                        'position': 'fixed',
                        'top': top,
                        'opacity': '0'
                      }).data('top', top);
                    } else {
                      vwrap.css({
                        'position': 'fixed',
                        'top': top,
                      }).data('top', top);
                      $(window).scrollTop(0);
                    }

                    /* -------- CHECK NEXT / PREV -------- */
                    function checknav(){

                      thisgall = obj.data('gall');
                      numeratio = obj.data('numeratio');
                      infinigall = obj.data('infinigall');

                      items = $('.vbox-item[data-gall="' + thisgall + '"]');

                      if(items.length > 0 && numeratio === true){
                        blocknum.html(items.index(obj)+1 + ' / ' + items.length);
                        blocknum.fadeIn();
                      }else{
                        blocknum.fadeOut();
                      }

                      thenext = items.eq( items.index(obj) + 1 );
                      theprev = items.eq( items.index(obj) - 1 );

                      if(obj.attr('title')){
                        title = obj.attr('title');
                        blocktitle.fadeIn();
                      }else{
                        title = '';
                        blocktitle.fadeOut();
                      }

                      if (items.length > 0 && infinigall === true) {

                        nextok = true;
                        prevok = true;

                        if(thenext.length < 1 ){
                          thenext = items.eq(0);
                        }
                        if(items.index(obj) < 1 ){
                          theprev = items.eq( items.index(items.length) );
                        }

                      } else {

                        if(thenext.length > 0 ){
                          $('.vbox-next').css('display', 'block');
                          nextok = true;
                        }else{
                          $('.vbox-next').css('display', 'none');
                          nextok = false;
                        }
                        if(items.index(obj) > 0 ){
                          $('.vbox-prev').css('display', 'block');
                          prevok = true;
                        }else{
                          $('.vbox-prev').css('display', 'none');
                          prevok = false;
                        }
                      }
                    }

                     /* -------- NAVIGATION CODE -------- */
                    var gallnav = {

                      prev: function() {

                        keyNavigationDisabled = true;

                        overlayColor = theprev.data('overlay');

                        framewidth = theprev.data('framewidth');
                        frameheight = theprev.data('frameheight');
                        border = theprev.data('border');
                        bgcolor = theprev.data('bgcolor');

                        dest = theprev.attr('href');

                        if(theprev.attr('title')){
                          title = theprev.attr('title');
                        }else{
                          title = '';
                        }

                        if (overlayColor === undefined ) {
                          overlayColor = "";
                        }

                        overlay.css('min-height', $(window).outerHeight());

                        content.animate({ opacity:0}, 500, function(){
                        overlay.css('min-height', $(window).outerHeight()).css('background',overlayColor);

                          if (theprev.data('type') == 'iframe') {
                            loadIframe();
                          } else if (theprev.data('type') == 'inline'){
                            loadInline();
                          } else if (theprev.data('type') == 'ajax'){
                            loadAjax();
                          } else if (theprev.data('type') == 'youtube'){
                            loadYoutube();
                          } else if (theprev.data('type') == 'vimeo'){
                            loadVimeo();
                          }else{
                            content.html('');
                            preloadFirst();
                          }
                          obj = theprev;
                          checknav();
                          keyNavigationDisabled = false;
                        });

                      },

                      next: function() {

                        keyNavigationDisabled = true;

                        overlayColor = thenext.data('overlay');

                        framewidth = thenext.data('framewidth');
                        frameheight = thenext.data('frameheight');
                        border = thenext.data('border');
                        bgcolor = thenext.data('bgcolor');

                        dest = thenext.attr('href');

                        if(thenext.attr('title')){
                          title = thenext.attr('title');
                        }else{
                          title = '';
                        }

                        if (overlayColor === undefined ) {
                          overlayColor = "";
                        }

                        overlay.css('min-height', $(window).outerHeight());

                        content.animate({ opacity:0}, 500, function(){
                        overlay.css('min-height', $(window).outerHeight()).css('background',overlayColor);

                          if (thenext.data('type') == 'iframe') {
                            loadIframe();
                          } else if (thenext.data('type') == 'inline'){
                            loadInline();
                          } else if (thenext.data('type') == 'ajax'){
                            loadAjax();
                          } else if (thenext.data('type') == 'youtube'){
                            loadYoutube();
                          } else if (thenext.data('type') == 'vimeo'){
                            loadVimeo();
                          }else{
                            content.html('');
                            preloadFirst();
                          }
                          obj = thenext;
                          checknav();
                          keyNavigationDisabled = false;
                        });

                      }

                    }

                    /* -------- NAVIGATE WITH ARROW KEYS -------- */
                    $('body').keydown(function(e) {
                      if (keyNavigationDisabled) return;

                      if(e.keyCode == 37 && prevok == true) { // left
                        gallnav.prev();
                      }
                      if(e.keyCode == 39 && nextok == true) { // right
                        gallnav.next();
                      }
                    });

                    /* -------- PREVGALL -------- */
                    $('.vbox-prev').click(function(){
                      gallnav.prev();
                    });

                    /* -------- NEXTGALL -------- */
                    $('.vbox-next').click(function(){
                      gallnav.next();
                    });

                    /* -------- ESCAPE HANDLER -------- */
                    function escapeHandler(e) {
                      if(e.keyCode === 27) {
                        closeVbox();
                      }
                    }

                    /* -------- CLOSE VBOX -------- */

                    function closeVbox(){

                      $('body').unbind('keydown', escapeHandler);

                      if (ie9) {

                        overlay.animate({opacity:0}, 500, function(){
                          overlay.remove();
                          $('.vwrap').children().unwrap();
                          $(window).scrollTop(-top);
                          keyNavigationDisabled = false;
                          obj.focus();
                        });

                      } else {

                        overlay.unbind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd");
                        overlay.bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){
                          overlay.remove();

                          if (ios) {
                            $('.vwrap').bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){
                              $('.vwrap').children().unwrap();
                              $(window).scrollTop(-top);
                            });
                            $('.vwrap').css('opacity', '1');
                          }else{
                            $('.vwrap').children().unwrap();
                            $(window).scrollTop(-top);
                          }
                          keyNavigationDisabled = false;
                          obj.focus();
                        });
                        overlay.css('opacity', '0');

                      }

                    }

                    /* -------- CHIUDI -------- */

                    $('.vbox-close, .vbox-overlay').click(function(e){

                      evitacontent = '.figlio';
                      evitaprev = '.vbox-prev';
                      evitanext = '.vbox-next';
                      figliall = '.figlio *';

                      if( !$(e.target).is(evitacontent) && !$(e.target).is(evitanext) && !$(e.target).is(evitaprev)&& !$(e.target).is(figliall) ){
                        closeVbox();
                      }

                    });

                    $('body').keydown(escapeHandler);

                    return false;

                  });
            });
        }
    });

    /* -------- LOAD AJAX -------- */
    function loadAjax(){
      $.ajax({
      url: dest,
      cache: false
      }).done(function( msg ) {
          content.html('
'+ msg +'
');
          updateoverlay(true);

      }).fail(function() {
          content.html('

Error retrieving contents, please retry
');
          updateoverlay(true);
      })
    }

    /* -------- LOAD IFRAME -------- */
    function loadIframe(){
      content.html('');
    //  $('.venoframe').load(function(){ // valid only for iFrames in same domain
      updateoverlay();
    //  });
    }

    /* -------- LOAD VIMEO -------- */
    function loadVimeo(){
      var pezzi = dest.split('/');
      var videoid = pezzi[pezzi.length-1];
      content.html('')
      updateoverlay();
    }

    /* -------- LOAD YOUTUBE -------- */
    function loadYoutube(){
      var pezzi = dest.split('/');
      var videoid = pezzi[pezzi.length-1];
      content.html('')
      updateoverlay();
    }

    /* -------- LOAD INLINE -------- */
    function loadInline(){
      content.html('
'+$(dest).html()+'
');
      updateoverlay();
    }

    /* -------- PRELOAD IMAGE -------- */
    function preloadFirst(){
        prima = $('.vbox-content').find('img');
        prima.one('load', function() {
          updateoverlay();
        }).each(function() {
          if(this.complete) $(this).load();
        });
    }

    /* -------- CENTER ON LOAD -------- */
    function updateoverlay(notopzero){
      notopzero = notopzero || false;
      if (notopzero != true) {
        $(window).scrollTop(0);
      }

      blocktitle.html(title);
      content.find(">:first-child").addClass('figlio');
      $('.figlio').css('width', framewidth).css('height', frameheight).css('padding', border).css('background', bgcolor);

      sonH = content.outerHeight();
      finH = $(window).height();

      if(sonH+80 < finH){
        margine = (finH - sonH)/2;
        content.css('margin-top', margine);
        content.css('margin-bottom', margine);

      }else{
        content.css('margin-top', '40px');
        content.css('margin-bottom', '40px');
      }
      content.animate({
        'opacity': '1'
      },'slow');
    }

    /* -------- CENTER ON RESIZE -------- */
    function updateoverlayresize(){
      if($('.vbox-content').length){
        sonH = content.height();
        finH = $(window).height();

        if(sonH+80 < finH){
          margine = (finH - sonH)/2;
          content.css('margin-top', margine);
          content.css('margin-bottom', margine);
        }else{
          content.css('margin-top', '40px');
          content.css('margin-bottom', '40px');
        }
      }
    }

    $(window).resize(function(){
      updateoverlayresize();
    });

})(jQuery);<commit_msg>allow using a custom attribute for the lightbox title<commit_after>/*
 * VenoBox - jQuery Plugin
 * version: 1.4.0
 * @requires jQuery
 *
 * Examples at http://lab.veno.it/venobox/
 * License: Creative Commons Attribution 3.0 License
 * License URI: http://creativecommons.org/licenses/by/3.0/
 * Copyright 2013-2014 Nicola Franchini - @nicolafranchini
 *
 */
(function($){

    var ios, ie9, overlayColor, overlay, vwrap, container, content, core, dest, top, sonH, finH, margine, prima, framewidth, frameheight, border, bgcolor, type, thisgall, items, thenext, theprev, title, nextok, prevok, keyNavigationDisabled, blocktitle, blocknum, numeratio, evitanext, evitaprev, evitacontent, figliall, infinigall, extraCss;

    $.fn.extend({
        //plugin name - venobox
        venobox: function(options) {

          // default options
          var defaults = {
              framewidth: '',
              frameheight: '',
              border: '0',
              bgcolor: '#fff',
              titleattr: 'title',
              numeratio: false,
              infinigall: false
          };

          var options = $.extend(defaults, options);

            return this.each(function() {
                  var obj = $(this);

                  obj.addClass('vbox-item');
                  obj.data('framewidth', options.framewidth);
                  obj.data('frameheight', options.frameheight);
                  obj.data('border', options.border);
                  obj.data('bgcolor', options.bgcolor);
                  obj.data('numeratio', options.numeratio);
                  obj.data('infinigall', options.infinigall);

                  ios = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false);

                  // Idiot Explorer 9 or less
                  ie9 = ((document.all && !window.atob) ? true : false);

                  obj.click(function(e){
                    e.stopPropagation();
                    e.preventDefault();
                    obj = $(this);
                    overlayColor = obj.data('overlay');
                    framewidth = obj.data('framewidth');
                    frameheight = obj.data('frameheight');
                    border = obj.data('border');
                    bgcolor = obj.data('bgcolor');
                    nextok = false;
                    prevok = false;
                    keyNavigationDisabled = false;
                    dest = obj.attr('href');
                    top = $(window).scrollTop();
                    top = -top;
                    extraCss = obj.data( 'css' ) || "";

                    $('body').wrapInner('

')

                    vwrap = $('.vwrap');
                    core = '

Loading...

0/0

X

next

prev

';

                    $('body').append(core);

                    overlay = $('.vbox-overlay');
                    container = $('.vbox-container');
                    content = $('.vbox-content');
                    blocknum = $('.vbox-num');
                    blocktitle = $('.vbox-title');

                    content.html('');
                    content.css('opacity', '0');

                    checknav();

                    overlay.css('min-height', $(window).outerHeight());

                    if (ie9) {
                      overlay.animate({opacity:1}, 250, function(){
                        overlay.css({
                          'min-height': $(window).outerHeight(),
                          height : 'auto'
                        });
                        if(obj.data('type') == 'iframe'){
                          loadIframe();
                        }else if (obj.data('type') == 'inline'){
                          loadInline();
                        }else if (obj.data('type') == 'ajax'){
                          loadAjax();
                        }else if (obj.data('type') == 'vimeo'){
                          loadVimeo();
                        }else if (obj.data('type') == 'youtube'){
                          loadYoutube();
                        } else {
                          content.html('');
                          preloadFirst();
                        }
                      });
                    } else {
                      overlay.bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){
                        overlay.css({
                          'min-height': $(window).outerHeight(),
                          height : 'auto'
                        });
                        if(obj.data('type') == 'iframe'){
                          loadIframe();
                        }else if (obj.data('type') == 'inline'){
                          loadInline();
                        }else if (obj.data('type') == 'ajax'){
                          loadAjax();
                        }else if (obj.data('type') == 'vimeo'){
                          loadVimeo();
                        }else if (obj.data('type') == 'youtube'){
                          loadYoutube();
                        } else {
                          content.html('');
                          preloadFirst();
                        }
                      });
                      overlay.css('opacity', '1');
                    }

                    if (ios) {
                      vwrap.css({
                        'position': 'fixed',
                        'top': top,
                        'opacity': '0'
                      }).data('top', top);
                    } else {
                      vwrap.css({
                        'position': 'fixed',
                        'top': top,
                      }).data('top', top);
                      $(window).scrollTop(0);
                    }

                    /* -------- CHECK NEXT / PREV -------- */
                    function checknav(){

                      thisgall = obj.data('gall');
                      numeratio = obj.data('numeratio');
                      infinigall = obj.data('infinigall');

                      items = $('.vbox-item[data-gall="' + thisgall + '"]');

                      if(items.length > 0 && numeratio === true){
                        blocknum.html(items.index(obj)+1 + ' / ' + items.length);
                        blocknum.fadeIn();
                      }else{
                        blocknum.fadeOut();
                      }

                      thenext = items.eq( items.index(obj) + 1 );
                      theprev = items.eq( items.index(obj) - 1 );

                      if(obj.attr(options.titleattr)){
                        title = obj.attr(options.titleattr);
                        blocktitle.fadeIn();
                      }else{
                        title = '';
                        blocktitle.fadeOut();
                      }

                      if (items.length > 0 && infinigall === true) {

                        nextok = true;
                        prevok = true;

                        if(thenext.length < 1 ){
                          thenext = items.eq(0);
                        }
                        if(items.index(obj) < 1 ){
                          theprev = items.eq( items.index(items.length) );
                        }

                      } else {

                        if(thenext.length > 0 ){
                          $('.vbox-next').css('display', 'block');
                          nextok = true;
                        }else{
                          $('.vbox-next').css('display', 'none');
                          nextok = false;
                        }
                        if(items.index(obj) > 0 ){
                          $('.vbox-prev').css('display', 'block');
                          prevok = true;
                        }else{
                          $('.vbox-prev').css('display', 'none');
                          prevok = false;
                        }
                      }
                    }

                     /* -------- NAVIGATION CODE -------- */
                    var gallnav = {

                      prev: function() {

                        keyNavigationDisabled = true;

                        overlayColor = theprev.data('overlay');

                        framewidth = theprev.data('framewidth');
                        frameheight = theprev.data('frameheight');
                        border = theprev.data('border');
                        bgcolor = theprev.data('bgcolor');

                        dest = theprev.attr('href');

                        if(theprev.attr(options.titleattr)){
                          title = theprev.attr(options.titleattr);
                        }else{
                          title = '';
                        }

                        if (overlayColor === undefined ) {
                          overlayColor = "";
                        }

                        overlay.css('min-height', $(window).outerHeight());

                        content.animate({ opacity:0}, 500, function(){
                        overlay.css('min-height', $(window).outerHeight()).css('background',overlayColor);

                          if (theprev.data('type') == 'iframe') {
                            loadIframe();
                          } else if (theprev.data('type') == 'inline'){
                            loadInline();
                          } else if (theprev.data('type') == 'ajax'){
                            loadAjax();
                          } else if (theprev.data('type') == 'youtube'){
                            loadYoutube();
                          } else if (theprev.data('type') == 'vimeo'){
                            loadVimeo();
                          }else{
                            content.html('');
                            preloadFirst();
                          }
                          obj = theprev;
                          checknav();
                          keyNavigationDisabled = false;
                        });

                      },

                      next: function() {

                        keyNavigationDisabled = true;

                        overlayColor = thenext.data('overlay');

                        framewidth = thenext.data('framewidth');
                        frameheight = thenext.data('frameheight');
                        border = thenext.data('border');
                        bgcolor = thenext.data('bgcolor');

                        dest = thenext.attr('href');

                        if(thenext.attr(options.titleattr)){
                          title = thenext.attr(options.titleattr);
                        }else{
                          title = '';
                        }

                        if (overlayColor === undefined ) {
                          overlayColor = "";
                        }

                        overlay.css('min-height', $(window).outerHeight());

                        content.animate({ opacity:0}, 500, function(){
                        overlay.css('min-height', $(window).outerHeight()).css('background',overlayColor);

                          if (thenext.data('type') == 'iframe') {
                            loadIframe();
                          } else if (thenext.data('type') == 'inline'){
                            loadInline();
                          } else if (thenext.data('type') == 'ajax'){
                            loadAjax();
                          } else if (thenext.data('type') == 'youtube'){
                            loadYoutube();
                          } else if (thenext.data('type') == 'vimeo'){
                            loadVimeo();
                          }else{
                            content.html('');
                            preloadFirst();
                          }
                          obj = thenext;
                          checknav();
                          keyNavigationDisabled = false;
                        });

                      }

                    }

                    /* -------- NAVIGATE WITH ARROW KEYS -------- */
                    $('body').keydown(function(e) {
                      if (keyNavigationDisabled) return;

                      if(e.keyCode == 37 && prevok == true) { // left
                        gallnav.prev();
                      }
                      if(e.keyCode == 39 && nextok == true) { // right
                        gallnav.next();
                      }
                    });

                    /* -------- PREVGALL -------- */
                    $('.vbox-prev').click(function(){
                      gallnav.prev();
                    });

                    /* -------- NEXTGALL -------- */
                    $('.vbox-next').click(function(){
                      gallnav.next();
                    });

                    /* -------- ESCAPE HANDLER -------- */
                    function escapeHandler(e) {
                      if(e.keyCode === 27) {
                        closeVbox();
                      }
                    }

                    /* -------- CLOSE VBOX -------- */

                    function closeVbox(){

                      $('body').unbind('keydown', escapeHandler);

                      if (ie9) {

                        overlay.animate({opacity:0}, 500, function(){
                          overlay.remove();
                          $('.vwrap').children().unwrap();
                          $(window).scrollTop(-top);
                          keyNavigationDisabled = false;
                          obj.focus();
                        });

                      } else {

                        overlay.unbind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd");
                        overlay.bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){
                          overlay.remove();

                          if (ios) {
                            $('.vwrap').bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){
                              $('.vwrap').children().unwrap();
                              $(window).scrollTop(-top);
                            });
                            $('.vwrap').css('opacity', '1');
                          }else{
                            $('.vwrap').children().unwrap();
                            $(window).scrollTop(-top);
                          }
                          keyNavigationDisabled = false;
                          obj.focus();
                        });
                        overlay.css('opacity', '0');

                      }

                    }

                    /* -------- CHIUDI -------- */

                    $('.vbox-close, .vbox-overlay').click(function(e){

                      evitacontent = '.figlio';
                      evitaprev = '.vbox-prev';
                      evitanext = '.vbox-next';
                      figliall = '.figlio *';

                      if( !$(e.target).is(evitacontent) && !$(e.target).is(evitanext) && !$(e.target).is(evitaprev)&& !$(e.target).is(figliall) ){
                        closeVbox();
                      }

                    });

                    $('body').keydown(escapeHandler);

                    return false;

                  });
            });
        }
    });

    /* -------- LOAD AJAX -------- */
    function loadAjax(){
      $.ajax({
      url: dest,
      cache: false
      }).done(function( msg ) {
          content.html('
'+ msg +'
');
          updateoverlay(true);

      }).fail(function() {
          content.html('

Error retrieving contents, please retry
');
          updateoverlay(true);
      })
    }

    /* -------- LOAD IFRAME -------- */
    function loadIframe(){
      content.html('');
    //  $('.venoframe').load(function(){ // valid only for iFrames in same domain
      updateoverlay();
    //  });
    }

    /* -------- LOAD VIMEO -------- */
    function loadVimeo(){
      var pezzi = dest.split('/');
      var videoid = pezzi[pezzi.length-1];
      content.html('')
      updateoverlay();
    }

    /* -------- LOAD YOUTUBE -------- */
    function loadYoutube(){
      var pezzi = dest.split('/');
      var videoid = pezzi[pezzi.length-1];
      content.html('')
      updateoverlay();
    }

    /* -------- LOAD INLINE -------- */
    function loadInline(){
      content.html('
'+$(dest).html()+'
');
      updateoverlay();
    }

    /* -------- PRELOAD IMAGE -------- */
    function preloadFirst(){
        prima = $('.vbox-content').find('img');
        prima.one('load', function() {
          updateoverlay();
        }).each(function() {
          if(this.complete) $(this).load();
        });
    }

    /* -------- CENTER ON LOAD -------- */
    function updateoverlay(notopzero){
      notopzero = notopzero || false;
      if (notopzero != true) {
        $(window).scrollTop(0);
      }

      blocktitle.html(title);
      content.find(">:first-child").addClass('figlio');
      $('.figlio').css('width', framewidth).css('height', frameheight).css('padding', border).css('background', bgcolor);

      sonH = content.outerHeight();
      finH = $(window).height();

      if(sonH+80 < finH){
        margine = (finH - sonH)/2;
        content.css('margin-top', margine);
        content.css('margin-bottom', margine);

      }else{
        content.css('margin-top', '40px');
        content.css('margin-bottom', '40px');
      }
      content.animate({
        'opacity': '1'
      },'slow');
    }

    /* -------- CENTER ON RESIZE -------- */
    function updateoverlayresize(){
      if($('.vbox-content').length){
        sonH = content.height();
        finH = $(window).height();

        if(sonH+80 < finH){
          margine = (finH - sonH)/2;
          content.css('margin-top', margine);
          content.css('margin-bottom', margine);
        }else{
          content.css('margin-top', '40px');
          content.css('margin-bottom', '40px');
        }
      }
    }

    $(window).resize(function(){
      updateoverlayresize();
    });

})(jQuery);<|endoftext|>


<commit_before>/**
 * Mapping between GQL primitive types and JSON Schema property types
 *
 * @type       {<type>}
 */
const PRIMITIVES = {
  Int: 'integer',
  Float: 'number',
  String: 'string',
  Boolean: 'boolean',
  ID: 'string'
};

/**
 * returns a JSON schema property type for a given GQL field type
 *
 * @param      {object}  type    The GQL type object
 * @return     {Object}  the property type object or a reference to a type definition
 */
const getPropertyType = type => {
  switch (type.kind) {
    case 'NonNullType':
      return Object.assign(getPropertyType(type.type), { required: true });
    case 'ListType':
      return {
        type: 'array',
        items: {
          type: getPropertyType(type.type)
        }
      }
    default:
      if (type.name.value in PRIMITIVES) {
        return {
          type: PRIMITIVES[type.name.value],
          required: false
        };
      }
      else {
        return { $ref: `#/definitions/${type.name.value}` };
      }
  }
}

/**
 * converts the GQL arguments array into a plain JSON schema array
 *
 * @param      {Array}  _arguments  The GQL arguments
 * @return     {Object}  a plain JSON array
 */
const toFieldArguments = _arguments => {
  return _arguments.map(a => {
    return {
      title: a.name.value,
      type: getPropertyType(a.type),
      defaultValue: a.defaultValue
    };
  });
}

/**
 * maps a GQL type field onto a JSON Schema property
 *
 * @param      {object}  field   The GQL field object
 * @return     {Object}  a plain JS object containing the property schema or a reference to another definition
 */
const toSchemaProperty = field => {
  let propertyType = getPropertyType(field.type);

  if ('$ref' in propertyType) propertyType = { allOf: [propertyType, { title: field.name.value }] };

  return Object.assign(
    propertyType,
    { title: field.name.value },
    field.arguments ? { arguments: toFieldArguments(field.arguments) } : {}
  );
}

/**
 * Converts a single GQL definition into a plain JS schema object
 *
 * @param      {Object}  definition  The GQL definition object
 * @return     {Object}  A plain JS schema object
 */
const toSchemaObject = definition => {
  if (definition.kind === 'ScalarTypeDefinition') {
    return {
      title: definition.name.value,
      type: 'GRAPHQL_SCALAR'
    }
  }
  else if (definition.kind === 'UnionTypeDefinition') {
    return {
      title: definition.name.value,
      type: 'GRAPHQL_UNION',
      oneOf: definition.types.map(getPropertyType)
    }
  }
  else if (definition.kind === 'EnumTypeDefinition') {
    return {
      title: definition.name.value,
      type: 'GRAPHQL_ENUM',
      enum: definition.values.map(v => v.name.value)
    };
  }

  const fields = definition.fields.map(toSchemaProperty);

  const properties = {};
  for (let f of fields) properties[f.title] = f.allOf ? { allOf: f.allOf } : f;

  const required = fields
    .filter(f => f.required)
    .map(f => f.title);

  return {
    title: definition.name.value,
    type: 'object',
    properties,
    required
  }
}

/**
 * GQL -> JSON Schema transform
 *
 * @param      {Document}  document  The GraphQL document returned by the parse function of graphql/language
 * @return     {object}  A plain JavaScript object which conforms to JSON Schema
 */
const transform = document => {
  const definitions = document.definitions.map(toSchemaObject);

  const schema = {
    $schema: 'http://json-schema.org/draft-04/schema#',
    definitions: {}
  };

  for (let def of definitions) {
    schema.definitions[def.title] = def;
  }

  return schema;
};

module.exports = transform;
<commit_msg>adding input: true if object is an input'<commit_after>/**
 * Mapping between GQL primitive types and JSON Schema property types
 *
 * @type       {<type>}
 */
const PRIMITIVES = {
  Int: 'integer',
  Float: 'number',
  String: 'string',
  Boolean: 'boolean',
  ID: 'string'
};

/**
 * returns a JSON schema property type for a given GQL field type
 *
 * @param      {object}  type    The GQL type object
 * @return     {Object}  the property type object or a reference to a type definition
 */
const getPropertyType = type => {
  switch (type.kind) {
    case 'NonNullType':
      return Object.assign(getPropertyType(type.type), { required: true });
    case 'ListType':
      return {
        type: 'array',
        items: {
          type: getPropertyType(type.type)
        }
      }
    default:
      if (type.name.value in PRIMITIVES) {
        return {
          type: PRIMITIVES[type.name.value],
          required: false
        };
      }
      else {
        return { $ref: `#/definitions/${type.name.value}` };
      }
  }
}

/**
 * converts the GQL arguments array into a plain JSON schema array
 *
 * @param      {Array}  _arguments  The GQL arguments
 * @return     {Object}  a plain JSON array
 */
const toFieldArguments = _arguments => {
  return _arguments.map(a => {
    return {
      title: a.name.value,
      type: getPropertyType(a.type),
      defaultValue: a.defaultValue
    };
  });
}

/**
 * maps a GQL type field onto a JSON Schema property
 *
 * @param      {object}  field   The GQL field object
 * @return     {Object}  a plain JS object containing the property schema or a reference to another definition
 */
const toSchemaProperty = field => {
  let propertyType = getPropertyType(field.type);

  if ('$ref' in propertyType) propertyType = { allOf: [propertyType, { title: field.name.value }] };

  return Object.assign(
    propertyType,
    { title: field.name.value },
    field.arguments ? { arguments: toFieldArguments(field.arguments) } : {}
  );
}

/**
 * Converts a single GQL definition into a plain JS schema object
 *
 * @param      {Object}  definition  The GQL definition object
 * @return     {Object}  A plain JS schema object
 */
const toSchemaObject = definition => {
  if (definition.kind === 'ScalarTypeDefinition') {
    return {
      title: definition.name.value,
      type: 'GRAPHQL_SCALAR'
    }
  }
  else if (definition.kind === 'UnionTypeDefinition') {
    return {
      title: definition.name.value,
      type: 'GRAPHQL_UNION',
      oneOf: definition.types.map(getPropertyType)
    }
  }
  else if (definition.kind === 'EnumTypeDefinition') {
    return {
      title: definition.name.value,
      type: 'GRAPHQL_ENUM',
      enum: definition.values.map(v => v.name.value)
    };
  }

  const fields = definition.fields.map(toSchemaProperty);

  const properties = {};
  for (let f of fields) properties[f.title] = f.allOf ? { allOf: f.allOf } : f;

  const required = fields
    .filter(f => f.required)
    .map(f => f.title);

  let schemaObject = {
    title: definition.name.value,
    type: 'object',
    properties,
    required,
  };

  if (definition.kind === 'InputObjectTypeDefinition') {
    Object.assign(schemaObject, { input: true });
  }

  return schemaObject;
}

/**
 * GQL -> JSON Schema transform
 *
 * @param      {Document}  document  The GraphQL document returned by the parse function of graphql/language
 * @return     {object}  A plain JavaScript object which conforms to JSON Schema
 */
const transform = document => {
  const definitions = document.definitions.map(toSchemaObject);

  const schema = {
    $schema: 'http://json-schema.org/draft-04/schema#',
    definitions: {}
  };

  for (let def of definitions) {
    schema.definitions[def.title] = def;
  }

  return schema;
};

module.exports = transform;
<|endoftext|>


<commit_before>///////////////////////////////////////
// Tab creation and deletion functions
///////////////////////////////////////

function newTabCallback(tab) {
	// Add tab info to tabs key
	var currentTabs = localStorage.getObject('tabs');
	if(currentTabs === null){
		currentTabs = {};
	}
	var currentTime = Date.now();
	currentTabs[tab.id] = {
		createdAt: currentTime,
		updatedAt: currentTime,
		pinned: false
	};
	localStorage.setObject('tabs', currentTabs);
}

function closeTab(tabId, windowId) {
	chrome.tabs.remove(tabId);
	deleteTabCallback(tabId, windowId);
}

function deleteTabCallback(tabId, windowId) {
	deleteTab(tabId, windowId);
}

////////////////////////////////////
// Pinning and unpinning functions
////////////////////////////////////

function pinCallback(tab) {
	if (getTabById(tab.id) && getTabKey(tab.id,'pinned')) {
		console.log("Unpinning");
		unpinTab(tab);
		getDuplicateTabs(tab);
	} else {
		console.log("Pinning");
		pinTab(tab);
	}
}

function pinTab(tab) {
  console.log('Pinning tab: ' + tab.url);
  if (getTabById(tab.id) === undefined) {
  	newTabCallback(tab);
  }
  setTabKey(tab.id, 'pinned', true);
  chrome.browserAction.setIcon({path: "/images/icon-jtab.png"});
}

function unpinTab(tab) {
  console.log('Unpinning tab: ' + tab.url);
  setTabKey(tab.id, 'pinned', false);
  chrome.browserAction.setIcon({path: "/images/icon-jtab-disabled.png"});
}

//////////////////////
// Deduping functions
//////////////////////

function getDuplicateTabs(tab) {
	console.log("Checking tab duplicates for: " + tab.url);
	chrome.tabs.query({url: tab.url}, function(results) {
		removeDuplicateTabs(results, tab.id);
	});
}

function removeDuplicateTabs(results, tabId) {
	for (i = 0; i < results.length; i++) {
		tab = results[i];
		pinned = getTabKey(tab.id, "pinned");
		if (tab.id !== tabId && !pinned) {
			console.log("Duplicate tab: " + tab.id + " " + tab.url);
			closeTab(tab.id);
		}
	}
}

////////////////////////////////////
// Removing old tabs functions
////////////////////////////////////

function clearTabAge() {
	localStorage.setObject('tabAges', []);
}

function updateTabAge(tabId, windowId) {
	var tabAges = localStorage.getObject('tabAges');
	if (tabAges === null) {
		tabAges = {};
	}
	console.log("Tab ages before update:" + tabAges);

	var tabAgesByWindow = tabAges[windowId] || [];
	if (tabAgesByWindow.indexOf(tabId) > -1) {
		tabAgesByWindow.splice(tabAgesByWindow.indexOf(tabId), 1);
	}
	tabAgesByWindow.splice(0, 0, tabId);
	tabAges[windowId] = tabAgesByWindow;
	localStorage.setObject('tabAges', tabAges);
	console.log("Tab ages after update:" + tabAges);
}

function deleteOldTabCallback(tab) {
	if (getOption('close_old_tabs')) {
		// TODO: first check if max tabs set in options
		var maxTabs = 10; // TODO: get max value from options
		checkNumTabs(maxTabs, tab.windowId);
	}
}

function checkNumTabs(maxTabs, windowId) {
	chrome.tabs.query({currentWindow: true}, function(results) {
		if (results.length > maxTabs) {
			deleteOldTabs(maxTabs, results, windowId);
		}
	})
}

function deleteOldTabs(maxTabs, results, windowId) {
	console.log('Deleting old tabs');
	var tabAges = localStorage.getObject('tabAges');
	if (tabAges && tabAges[windowId] && tabAges[windowId].length > maxTabs) {
		var tabAgesByWindow = tabAges[windowId];
		var i = tabAgesByWindow.length - 1;
		var numToDelete = tabAgesByWindow.length - maxTabs;
		var tabsToDelete = [];
		while (i >= 0 && tabsToDelete.length < numToDelete) {
			var pinned = getTabKey(tabAgesByWindow[i], "pinned");
			if (!pinned) {
				tabsToDelete.push(tabAgesByWindow[i]);
			}
			i--;
		}

		for (i = 0; i < tabsToDelete.length; i++) {
			closeTab(tabsToDelete[i]);
		}
		console.log("Tab ages after deleting:" + tabAges);
	}
}
<commit_msg>Incorporate recycling<commit_after>///////////////////////////////////////
// Tab creation and deletion functions
///////////////////////////////////////

function newTabCallback(tab) {
	// Add tab info to tabs key
	var currentTabs = localStorage.getObject('tabs');
	if(currentTabs === null){
		currentTabs = {};
	}
	var currentTime = Date.now();
	currentTabs[tab.id] = {
		createdAt: currentTime,
		updatedAt: currentTime,
		pinned: false
	};
	localStorage.setObject('tabs', currentTabs);
}

function closeTab(tabId, windowId) {
	var recycled = 'enabled';
	if (recycled === 'enabled') {
		recycleTab(tabId);
	} else {
		chrome.tabs.remove(tabId);
	}
	deleteTabCallback(tabId, windowId);
}

function deleteTabCallback(tabId, windowId) {
	deleteTab(tabId, windowId);
}

////////////////////////////////////
// Pinning and unpinning functions
////////////////////////////////////

function pinCallback(tab) {
	if (getTabById(tab.id) && getTabKey(tab.id,'pinned')) {
		console.log("Unpinning");
		unpinTab(tab);
		getDuplicateTabs(tab);
	} else {
		console.log("Pinning");
		pinTab(tab);
	}
}

function pinTab(tab) {
  console.log('Pinning tab: ' + tab.url);
  if (getTabById(tab.id) === undefined) {
  	newTabCallback(tab);
  }
  setTabKey(tab.id, 'pinned', true);
  chrome.browserAction.setIcon({path: "/images/icon-jtab.png"});
}

function unpinTab(tab) {
  console.log('Unpinning tab: ' + tab.url);
  setTabKey(tab.id, 'pinned', false);
  chrome.browserAction.setIcon({path: "/images/icon-jtab-disabled.png"});
}

//////////////////////
// Deduping functions
//////////////////////

function getDuplicateTabs(tab) {
	console.log("Checking tab duplicates for: " + tab.url);
	chrome.tabs.query({url: tab.url}, function(results) {
		removeDuplicateTabs(results, tab.id);
	});
}

function removeDuplicateTabs(results, tabId) {
	for (i = 0; i < results.length; i++) {
		tab = results[i];
		pinned = getTabKey(tab.id, "pinned");
		if (tab.id !== tabId && !pinned) {
			console.log("Duplicate tab: " + tab.id + " " + tab.url);
			closeTab(tab.id);
		}
	}
}

////////////////////////////////////
// Removing old tabs functions
////////////////////////////////////

function clearTabAge() {
	localStorage.setObject('tabAges', []);
}

function updateTabAge(tabId, windowId) {
	var tabAges = localStorage.getObject('tabAges');
	if (tabAges === null) {
		tabAges = {};
	}
	console.log("Tab ages before update:" + tabAges);

	var tabAgesByWindow = tabAges[windowId] || [];
	if (tabAgesByWindow.indexOf(tabId) > -1) {
		tabAgesByWindow.splice(tabAgesByWindow.indexOf(tabId), 1);
	}
	tabAgesByWindow.splice(0, 0, tabId);
	tabAges[windowId] = tabAgesByWindow;
	localStorage.setObject('tabAges', tabAges);
	console.log("Tab ages after update:" + tabAges);
}

function deleteOldTabCallback(tab) {
	if (getOption('close_old_tabs')) {
		// TODO: first check if max tabs set in options
		var maxTabs = 10; // TODO: get max value from options
		checkNumTabs(maxTabs, tab.windowId);
	}
}

function checkNumTabs(maxTabs, windowId) {
	chrome.tabs.query({currentWindow: true}, function(results) {
		if (results.length > maxTabs) {
			deleteOldTabs(maxTabs, results, windowId);
		}
	})
}

function deleteOldTabs(maxTabs, results, windowId) {
	console.log('Deleting old tabs');
	var tabAges = localStorage.getObject('tabAges');
	if (tabAges && tabAges[windowId] && tabAges[windowId].length > maxTabs) {
		var tabAgesByWindow = tabAges[windowId];
		var i = tabAgesByWindow.length - 1;
		var numToDelete = tabAgesByWindow.length - maxTabs;
		var tabsToDelete = [];
		while (i >= 0 && tabsToDelete.length < numToDelete) {
			var pinned = getTabKey(tabAgesByWindow[i], "pinned");
			if (!pinned) {
				tabsToDelete.push(tabAgesByWindow[i]);
			}
			i--;
		}

		for (i = 0; i < tabsToDelete.length; i++) {
			closeTab(tabsToDelete[i]);
		}
		console.log("Tab ages after deleting:" + tabAges);
	}
}
<|endoftext|>