<commit_before>var websiteNotFound = {
	name: "Server not found",
	message: "Min couldn't find this website. If you think there should be something here, you can search on archive.org for this page."
}

var sslError = {
	name: "This website is not available",
	message: "Min couldn't connect securely to this website."
}

var dnsError = {
	name: "Website not found",
	messge: "A DNS error occured"
}

//list: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h&sq=package:chromium&type=cs
const errorCodes = {
	"-104": {
		message: "Min couldn't connect to the website."
	},
	"-105": websiteNotFound,
	"-106": {
		name: "You are offline",
		message: "Reconnect to the internet and try again"
	},
	"-109": websiteNotFound,
	"-110": sslError, //this is actually the error code for "server requested a client certificate", but we don't support that yet,
	"-112": sslError,
	"-113": sslError,
	"-116": sslError,
	"-117": sslError,
	"-200": sslError,
	"-201": {
		name: "This website is not available",
		message: "Min couldn't connect securely to this website. This could be caused by an incorrectly set clock on your computer."
	},
	"-202": sslError,
	"-203": sslError,
	"-204": sslError,
	"-205": sslError,
	"-206": sslError,
	"-207": sslError,
	"-208": sslError,
	"-210": sslError,
	"-211": sslError,
	"-212": sslError,
	"-213": sslError,
	"-300": {
		name: "This address is invalid."
	},
	"-501": sslError,
	"-800": dnsError,
	"-801": dnsError,
	"-802": dnsError,
	"-803": dnsError,
	"-804": dnsError,
	"-805": dnsError,
	"-806": dnsError,
}

//http://stackoverflow.com/a/2091331

function getQueryVariable(variable) {
	var query = window.location.search.substring(1);
	var vars = query.split('&');
	for (var i = 0; i < vars.length; i++) {
		var pair = vars[i].split('=');
		if (decodeURIComponent(pair[0]) == variable) {
			return decodeURIComponent(pair[1]);
		}
	}
	console.log('Query variable %s not found', variable);
}

//show the error message and detail

var h1 = document.getElementById("error-name");
var h2 = document.getElementById("error-desc");
var button = document.getElementById("ta-button");

var ec = getQueryVariable("ec");
var url = getQueryVariable("url");

if (errorCodes[ec]) {
	h1.innerHTML += errorCodes[ec].name || "";
	h2.innerHTML += errorCodes[ec].message || "";
} else {
	h1.innerHTML += "An error occured";
}

var link = document.getElementById("archivesearch-link");

if (link) {
	link.href = "https://web.archive.org/web/*/" + url;
}

//if an ssl error occured, "try again" should go to the http:// version, which might work

if (errorCodes[ec] == sslError) {
	url = url.replace("https://", "http://");
}

if (url) {
	button.addEventListener("click", function () {
		window.location = url;
	});
}
<commit_msg>bug fixes<commit_after>var websiteNotFound = {
	name: "Server not found",
	message: "Min couldn't find this website. If you think there should be something here, you can search on archive.org for this page."
}

var sslError = {
	name: "This website is not available",
	message: "Min couldn't connect securely to this website."
}

var dnsError = {
	name: "Website not found",
	messge: "A DNS error occured."
}

//list: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h&sq=package:chromium&type=cs
const errorCodes = {
	"-104": {
		message: "Min couldn't connect to the website."
	},
	"-105": websiteNotFound,
	"-106": {
		name: "You are offline",
		message: "Reconnect to the internet and try again."
	},
	"-109": websiteNotFound,
	"-110": sslError, //this is actually the error code for "server requested a client certificate", but we don't support that yet,
	"-112": sslError,
	"-113": sslError,
	"-116": sslError,
	"-117": sslError,
	"-200": sslError,
	"-201": {
		name: "This website is not available",
		message: "Min couldn't connect securely to this website. Please make sure your computer's clock is set correctly."
	},
	"-202": sslError,
	"-203": sslError,
	"-204": sslError,
	"-205": sslError,
	"-206": sslError,
	"-207": sslError,
	"-208": sslError,
	"-210": sslError,
	"-211": sslError,
	"-212": sslError,
	"-213": sslError,
	"-300": {
		name: "This address is invalid."
	},
	"-501": sslError,
	"-800": dnsError,
	"-801": dnsError,
	"-802": dnsError,
	"-803": dnsError,
	"-804": dnsError,
	"-805": dnsError,
	"-806": dnsError,
}

//http://stackoverflow.com/a/2091331

function getQueryVariable(variable) {
	var query = window.location.search.substring(1);
	var vars = query.split('&');
	for (var i = 0; i < vars.length; i++) {
		var pair = vars[i].split('=');
		if (decodeURIComponent(pair[0]) == variable) {
			return decodeURIComponent(pair[1]);
		}
	}
	console.log('Query variable %s not found', variable);
}

//show the error message and detail

var h1 = document.getElementById("error-name");
var h2 = document.getElementById("error-desc");
var button = document.getElementById("ta-button");

var ec = getQueryVariable("ec");
var url = getQueryVariable("url");

if (errorCodes[ec]) {
	h1.innerHTML += errorCodes[ec].name || "";
	h2.innerHTML += errorCodes[ec].message || "";
} else {
	h1.innerHTML += "An error occured";
}

var link = document.getElementById("archivesearch-link");

if (link) {
	link.href = "https://web.archive.org/web/*/" + url;
}

//if an ssl error occured, "try again" should go to the http:// version, which might work

if (errorCodes[ec] == sslError) {
	url = url.replace("https://", "http://");
}

if (url) {
	button.addEventListener("click", function () {
		window.location = url;
	});
}
<|endoftext|>


<commit_before>const Promise = require('bluebird');
const mapValues = require('lodash/mapValues');
const passThrough = require('lodash/identity');
const fsort = require('redis-filtered-sort');
const { ActionTransport } = require('@microfleet/plugin-router');

const handlePipeline = require('../utils/pipeline-error');
const redisKey = require('../utils/key');
const {
  USERS_INDEX,
  USERS_PUBLIC_INDEX,
  USERS_REFERRAL_INDEX,
  USERS_METADATA,
} = require('../constants');

// helper
const JSONParse = (data) => JSON.parse(data);

// fetches basic ids
async function fetchIds() {
  const {
    redis,
    keys,
    args,
    keyOnly,
  } = this;

  // ensure that we have keyOnly set to true, otherwise undefined
  if (keyOnly) args.push('1');

  /**
   * args -> [criteria, order, strFilter, currentTime, offset, limit, expiration, keyOnly]
   */
  const preFilter = [...args];
  preFilter[2] = '{}';
  const result = await redis.fsort(keys, preFilter);
  if (args[2] === '{}') {
    return result;
  }

  return redis.fsort(keys, args);
}

async function redisSearchIds() {
  const {
    service,
    redis,
    keys,
    args: request, // [criteria, order, filter, strFilter, currentTime, offset, limit, expiration]
    offset,
    limit,
  } = this;

  // TODO implement it
  const indexName = keys && keys[1]; // TODO get from keys [0, metaKey] ?
  const args = ['FT.SEARCH', indexName];
  const query = [];
  const params = [];

  // TODO populate query & params

  // for (const [_propName, actionTypeOrValue] of Object.entries(filter)) {
  // let propName = _propName;
  // if (propName === '#') {
  //   propName = FILES_ID_FIELD;
  // } else if (propName === '#multi') {
  //   propName = actionTypeOrValue.fields.join('|');
  // } else if (propName === 'alias') {
  //   propName = 'alias_tag';
  // }
  //
  // }

  if (query.length > 0) {
    args.push(query.join(' '));
  } else {
    args.push('*');
  }

  if (params.length > 0) {
    args.push('PARAMS', params.length.toString(), ...params);
    args.push('DIALECT', '2');
  }

  // sort the response
  if (request.criteria) {
    args.push('SORTBY', request.criteria, request.order);
  } else {
    // args.push('SORTBY', FILES_ID_FIELD, request.order);
  }

  // limits
  args.push('LIMIT', offset, limit);

  // we'll fetch the data later
  args.push('NOCONTENT');

  // [total, [ids]]
  service.log.info({ search: args }, 'search query');

  const [total, ...ids] = await redis.call(...args);

  service.log.info({ total }, 'search result');

  return ids;
}

function remapData(id, idx) {
  const data = this.props[idx];
  const account = {
    id,
    metadata: {
      [this.audience]: data ? mapValues(data, JSONParse) : {},
    },
  };

  return account;
}

// fetches user data
function fetchUserData(ids) {
  const {
    redis,
    audience,
    offset,
    limit,
    userIdsOnly,
  } = this;

  const length = +ids.pop();

  // fetch extra data
  let userIds;
  if (length === 0 || ids.length === 0 || userIdsOnly === true) {
    userIds = Promise.resolve();
  } else {
    userIds = redis.pipeline()
      .addBatch(ids.map((id) => [
        'hgetall', redisKey(id, USERS_METADATA, audience),
      ]))
      .exec()
      .then(handlePipeline);
  }

  return userIds.then((props) => ({
    users: userIdsOnly === true ? ids : ids.map(remapData, { audience, props }),
    cursor: offset + limit,
    page: Math.floor(offset / limit) + 1,
    pages: Math.ceil(length / limit),
    total: length,
  }));
}

/**
 * @api {amqp} <prefix>.list Retrieve Registered Users
 * @apiVersion 1.0.0
 * @apiName ListUsers
 * @apiGroup Users
 *
 * @apiDescription This method allows to list user that are registered and activated in the system. They can be sorted & filtered by
 * any metadata field. Furthermore, it retrieves metadata based on the supplied audience and returns array of users similar to `info`
 * endpoint
 *
 * @apiParam (Payload) {Number} [offset=0] - cursor for pagination
 * @apiParam (Payload) {Number} [limit=10] - profiles per page
 * @apiParam (Payload) {String="ASC","DESC"} [order=ASC] - sort order
 * @apiParam (Payload) {String} [criteria] - if supplied, sort will be performed based on this field
 * @apiParam (Payload) {String} audience - which namespace of metadata should be used for filtering & retrieving
 * @apiParam (Payload) {Mixed} [public=false] - when `true` returns only publicly marked users, if set to string - then uses referral index
 * @apiParam (Payload) {Object} [filter] to use, consult https://github.com/makeomatic/redis-filtered-sort, can already be stringified
 * @apiParam (Payload) {Boolean} [userIdsOnly=false] if set to true - will only return userIds
 */
module.exports = function iterateOverActiveUsers({ params }) {
  const {
    redis,
    config,

  } = this;
  const {
    criteria,
    audience,
    filter,
    userIdsOnly,
    expiration,
    keyOnly,
    limit = 10,
    offset = 0,
    order = 'ASC',
  } = params;

  const strFilter = typeof filter === 'string' ? filter : fsort.filter(filter || {});
  const metaKey = redisKey('*', USERS_METADATA, audience);
  const currentTime = Date.now();

  let index;
  switch (params.public) {
    case true:
      index = USERS_PUBLIC_INDEX;
      break;

    case undefined:
    case false:
      index = USERS_INDEX;
      break;

    default:
      index = `${USERS_REFERRAL_INDEX}:${params.public}`;
      break;
  }

  const ctx = {
    // service parts
    redis,
    service: this,

    // input parts for lua script
    keys: [
      index, metaKey,
    ],

    args: [
      criteria, order, filter, strFilter, currentTime, offset, limit, expiration,
    ],

    // used in 2 places, hence separate args
    offset,
    limit,

    // extra settings
    keyOnly,
    userIdsOnly,

    // extra args
    audience,
  };

  const findUserIds = config.redisSearch.enabled ? redisSearchIds : fetchIds;

  return Promise
    .bind(ctx)
    .then(findUserIds)
    .then(keyOnly ? passThrough : fetchUserData);
};

module.exports.transports = [ActionTransport.amqp, ActionTransport.internal];
<commit_msg>fix: sort lua script paramenters<commit_after>const Promise = require('bluebird');
const mapValues = require('lodash/mapValues');
const passThrough = require('lodash/identity');
const fsort = require('redis-filtered-sort');
const { ActionTransport } = require('@microfleet/plugin-router');

const handlePipeline = require('../utils/pipeline-error');
const redisKey = require('../utils/key');
const {
  USERS_INDEX,
  USERS_PUBLIC_INDEX,
  USERS_REFERRAL_INDEX,
  USERS_METADATA,
} = require('../constants');

// helper
const JSONParse = (data) => JSON.parse(data);

// fetches basic ids
async function fetchIds() {
  const {
    redis,
    keys,
    args,
    keyOnly,
  } = this;

  // ensure that we have keyOnly set to true, otherwise undefined
  if (keyOnly) args.push('1');

  /**
   * args -> [criteria, order, strFilter, currentTime, offset, limit, expiration, keyOnly]
   */
  const preFilter = [...args];
  preFilter[2] = '{}';
  const result = await redis.fsort(keys, preFilter);
  if (args[2] === '{}') {
    return result;
  }

  return redis.fsort(keys, args);
}

async function redisSearchIds() { //
  // TODO implement it
  return Promise.resolve();
}

function remapData(id, idx) {
  const data = this.props[idx];
  const account = {
    id,
    metadata: {
      [this.audience]: data ? mapValues(data, JSONParse) : {},
    },
  };

  return account;
}

// fetches user data
function fetchUserData(ids) {
  const {
    redis,
    audience,
    offset,
    limit,
    userIdsOnly,
  } = this;

  const length = +ids.pop();

  // fetch extra data
  let userIds;
  if (length === 0 || ids.length === 0 || userIdsOnly === true) {
    userIds = Promise.resolve();
  } else {
    userIds = redis.pipeline()
      .addBatch(ids.map((id) => [
        'hgetall', redisKey(id, USERS_METADATA, audience),
      ]))
      .exec()
      .then(handlePipeline);
  }

  return userIds.then((props) => ({
    users: userIdsOnly === true ? ids : ids.map(remapData, { audience, props }),
    cursor: offset + limit,
    page: Math.floor(offset / limit) + 1,
    pages: Math.ceil(length / limit),
    total: length,
  }));
}

/**
 * @api {amqp} <prefix>.list Retrieve Registered Users
 * @apiVersion 1.0.0
 * @apiName ListUsers
 * @apiGroup Users
 *
 * @apiDescription This method allows to list user that are registered and activated in the system. They can be sorted & filtered by
 * any metadata field. Furthermore, it retrieves metadata based on the supplied audience and returns array of users similar to `info`
 * endpoint
 *
 * @apiParam (Payload) {Number} [offset=0] - cursor for pagination
 * @apiParam (Payload) {Number} [limit=10] - profiles per page
 * @apiParam (Payload) {String="ASC","DESC"} [order=ASC] - sort order
 * @apiParam (Payload) {String} [criteria] - if supplied, sort will be performed based on this field
 * @apiParam (Payload) {String} audience - which namespace of metadata should be used for filtering & retrieving
 * @apiParam (Payload) {Mixed} [public=false] - when `true` returns only publicly marked users, if set to string - then uses referral index
 * @apiParam (Payload) {Object} [filter] to use, consult https://github.com/makeomatic/redis-filtered-sort, can already be stringified
 * @apiParam (Payload) {Boolean} [userIdsOnly=false] if set to true - will only return userIds
 */
module.exports = function iterateOverActiveUsers({ params }) {
  const {
    redis,
    config,

  } = this;
  const {
    criteria,
    audience,
    filter,
    userIdsOnly,
    expiration,
    keyOnly,
    limit = 10,
    offset = 0,
    order = 'ASC',
  } = params;

  const strFilter = typeof filter === 'string' ? filter : fsort.filter(filter || {});
  const metaKey = redisKey('*', USERS_METADATA, audience);
  const currentTime = Date.now();

  let index;
  switch (params.public) {
    case true:
      index = USERS_PUBLIC_INDEX;
      break;

    case undefined:
    case false:
      index = USERS_INDEX;
      break;

    default:
      index = `${USERS_REFERRAL_INDEX}:${params.public}`;
      break;
  }

  const ctx = {
    // service parts
    redis,
    service: this,

    // input parts for lua script
    keys: [
      index, metaKey,
    ],

    args: [
      criteria, order, strFilter, currentTime, offset, limit, expiration,
    ],

    // used in 2 places, hence separate args
    offset,
    limit,

    // extra settings
    filter,
    keyOnly,
    userIdsOnly,

    // extra args
    audience,
  };

  const findUserIds = config.redisSearch.enabled ? redisSearchIds : fetchIds;

  return Promise
    .bind(ctx)
    .then(findUserIds)
    .then(keyOnly ? passThrough : fetchUserData);
};

module.exports.transports = [ActionTransport.amqp, ActionTransport.internal];
<|endoftext|>


<commit_before>jaxon.ajax.handler = {
    /*
    Object: jaxon.ajax.handler.handlers

    An array that is used internally in the jaxon.fn.handler object
    to keep track of command handlers that have been registered.
    */
    handlers: [],

    /*
    Function: jaxon.ajax.handler.execute

    Perform a lookup on the command specified by the response command
    object passed in the first parameter.  If the command exists, the
    function checks to see if the command references a DOM object by
    ID; if so, the object is located within the DOM and added to the
    command data.  The command handler is then called.

    If the command handler returns true, it is assumed that the command
    completed successfully.  If the command handler returns false, then the
    command is considered pending; jaxon enters a wait state.  It is up
    to the command handler to set an interval, timeout or event handler
    which will restart the jaxon response processing.

    Parameters:

    obj - (object):  The response command to be executed.

    Returns:

    true - The command completed successfully.
    false - The command signalled that it needs to pause processing.
    */
    execute: function(command) {
        if (jaxon.ajax.handler.isRegistered(command)) {
            // it is important to grab the element here as the previous command
            // might have just created the element
            if (command.id)
                command.target = jaxon.$(command.id);
            // process the command
            if (false == jaxon.ajax.handler.call(command)) {
                jaxon.tools.queue.pushFront(jaxon.response, command);
                return false;
            }
        }
        return true;
    },

    /*
    Function: jaxon.ajax.handler.register

    Registers a new command handler.
    */
    register: function(shortName, func) {
        jaxon.ajax.handler.handlers[shortName] = func;
    },

    /*
    Function: jaxon.ajax.handler.unregister

    Unregisters and returns a command handler.

    Parameters:
        shortName - (string): The name of the command handler.

    Returns:
        func - (function): The unregistered function.
    */
    unregister: function(shortName) {
        var func = jaxon.ajax.handler.handlers[shortName];
        delete jaxon.ajax.handler.handlers[shortName];
        return func;
    },

    /*
    Function: jaxon.ajax.handler.isRegistered

    Parameters:
        command - (object):
            - cmd: The Name of the function.

    Returns:

    boolean - (true or false): depending on whether a command handler has
    been created for the specified command (object).

    */
    isRegistered: function(command) {
        var shortName = command.cmd;
        if (jaxon.ajax.handler.handlers[shortName])
            return true;
        return false;
    },

    /*
    Function: jaxon.ajax.handler.call

    Calls the registered command handler for the specified command
    (you should always check isRegistered before calling this function)

    Parameters:
        command - (object):
            - cmd: The Name of the function.

    Returns:
        true - (boolean) :
    */
    call: function(command) {
        var shortName = command.cmd;
        return jaxon.ajax.handler.handlers[shortName](command);
    }
};

jaxon.ajax.handler.register('rcmplt', function(args) {
    jaxon.ajax.response.complete(args.request);
    return true;
});

jaxon.ajax.handler.register('css', function(args) {
    args.fullName = 'includeCSS';
    if ('undefined' == typeof args.media)
        args.media = 'screen';
    return jaxon.cmd.style.add(args.data, args.media);
});
jaxon.ajax.handler.register('rcss', function(args) {
    args.fullName = 'removeCSS';
    if ('undefined' == typeof args.media)
        args.media = 'screen';
    return jaxon.cmd.style.remove(args.data, args.media);
});
jaxon.ajax.handler.register('wcss', function(args) {
    args.fullName = 'waitForCSS';
    return jaxon.cmd.style.waitForCSS(args);
});

jaxon.ajax.handler.register('as', function(args) {
    args.fullName = 'assign/clear';
    try {
        return jaxon.cmd.node.assign(args.target, args.prop, args.data);
    } catch (e) {
        // do nothing, if the debug module is installed it will
        // catch and handle the exception
    }
    return true;
});
jaxon.ajax.handler.register('ap', function(args) {
    args.fullName = 'append';
    return jaxon.cmd.node.append(args.target, args.prop, args.data);
});
jaxon.ajax.handler.register('pp', function(args) {
    args.fullName = 'prepend';
    return jaxon.cmd.node.prepend(args.target, args.prop, args.data);
});
jaxon.ajax.handler.register('rp', function(args) {
    args.fullName = 'replace';
    return jaxon.cmd.node.replace(args.id, args.prop, args.data);
});
jaxon.ajax.handler.register('rm', function(args) {
    args.fullName = 'remove';
    return jaxon.cmd.node.remove(args.id);
});
jaxon.ajax.handler.register('ce', function(args) {
    args.fullName = 'create';
    return jaxon.cmd.node.create(args.id, args.data, args.prop);
});
jaxon.ajax.handler.register('ie', function(args) {
    args.fullName = 'insert';
    return jaxon.cmd.node.insert(args.id, args.data, args.prop);
});
jaxon.ajax.handler.register('ia', function(args) {
    args.fullName = 'insertAfter';
    return jaxon.cmd.node.insertAfter(args.id, args.data, args.prop);
});

jaxon.ajax.handler.register('DSR', jaxon.cmd.tree.startResponse);
jaxon.ajax.handler.register('DCE', jaxon.cmd.tree.createElement);
jaxon.ajax.handler.register('DSA', jaxon.cmd.tree.setAttribute);
jaxon.ajax.handler.register('DAC', jaxon.cmd.tree.appendChild);
jaxon.ajax.handler.register('DIB', jaxon.cmd.tree.insertBefore);
jaxon.ajax.handler.register('DIA', jaxon.cmd.tree.insertAfter);
jaxon.ajax.handler.register('DAT', jaxon.cmd.tree.appendText);
jaxon.ajax.handler.register('DRC', jaxon.cmd.tree.removeChildren);
jaxon.ajax.handler.register('DER', jaxon.cmd.tree.endResponse);

jaxon.ajax.handler.register('c:as', jaxon.cmd.node.contextAssign);
jaxon.ajax.handler.register('c:ap', jaxon.cmd.node.contextAppend);
jaxon.ajax.handler.register('c:pp', jaxon.cmd.node.contextPrepend);

jaxon.ajax.handler.register('s', jaxon.cmd.script.sleep);
jaxon.ajax.handler.register('ino', jaxon.cmd.script.includeScriptOnce);
jaxon.ajax.handler.register('in', jaxon.cmd.script.includeScript);
jaxon.ajax.handler.register('rjs', jaxon.cmd.script.removeScript);
jaxon.ajax.handler.register('wf', jaxon.cmd.script.waitFor);
jaxon.ajax.handler.register('js', jaxon.cmd.script.execute);
jaxon.ajax.handler.register('jc', jaxon.cmd.script.call);
jaxon.ajax.handler.register('sf', jaxon.cmd.script.setFunction);
jaxon.ajax.handler.register('wpf', jaxon.cmd.script.wrapFunction);
jaxon.ajax.handler.register('al', function(args) {
    args.fullName = 'alert';
    alert(args.data);
    return true;
});
jaxon.ajax.handler.register('cc', jaxon.cmd.script.confirmCommands);

jaxon.ajax.handler.register('ci', jaxon.cmd.form.createInput);
jaxon.ajax.handler.register('ii', jaxon.cmd.form.insertInput);
jaxon.ajax.handler.register('iia', jaxon.cmd.form.insertInputAfter);

jaxon.ajax.handler.register('ev', jaxon.cmd.event.setEvent);

jaxon.ajax.handler.register('ah', jaxon.cmd.event.addHandler);
jaxon.ajax.handler.register('rh', jaxon.cmd.event.removeHandler);

jaxon.ajax.handler.register('dbg', function(args) {
    args.fullName = 'debug message';
    return true;
});<commit_msg>Debug messages are printed in browser console.<commit_after>jaxon.ajax.handler = {
    /*
    Object: jaxon.ajax.handler.handlers

    An array that is used internally in the jaxon.fn.handler object
    to keep track of command handlers that have been registered.
    */
    handlers: [],

    /*
    Function: jaxon.ajax.handler.execute

    Perform a lookup on the command specified by the response command
    object passed in the first parameter.  If the command exists, the
    function checks to see if the command references a DOM object by
    ID; if so, the object is located within the DOM and added to the
    command data.  The command handler is then called.

    If the command handler returns true, it is assumed that the command
    completed successfully.  If the command handler returns false, then the
    command is considered pending; jaxon enters a wait state.  It is up
    to the command handler to set an interval, timeout or event handler
    which will restart the jaxon response processing.

    Parameters:

    obj - (object):  The response command to be executed.

    Returns:

    true - The command completed successfully.
    false - The command signalled that it needs to pause processing.
    */
    execute: function(command) {
        if (jaxon.ajax.handler.isRegistered(command)) {
            // it is important to grab the element here as the previous command
            // might have just created the element
            if (command.id)
                command.target = jaxon.$(command.id);
            // process the command
            if (false == jaxon.ajax.handler.call(command)) {
                jaxon.tools.queue.pushFront(jaxon.response, command);
                return false;
            }
        }
        return true;
    },

    /*
    Function: jaxon.ajax.handler.register

    Registers a new command handler.
    */
    register: function(shortName, func) {
        jaxon.ajax.handler.handlers[shortName] = func;
    },

    /*
    Function: jaxon.ajax.handler.unregister

    Unregisters and returns a command handler.

    Parameters:
        shortName - (string): The name of the command handler.

    Returns:
        func - (function): The unregistered function.
    */
    unregister: function(shortName) {
        var func = jaxon.ajax.handler.handlers[shortName];
        delete jaxon.ajax.handler.handlers[shortName];
        return func;
    },

    /*
    Function: jaxon.ajax.handler.isRegistered

    Parameters:
        command - (object):
            - cmd: The Name of the function.

    Returns:

    boolean - (true or false): depending on whether a command handler has
    been created for the specified command (object).

    */
    isRegistered: function(command) {
        var shortName = command.cmd;
        if (jaxon.ajax.handler.handlers[shortName])
            return true;
        return false;
    },

    /*
    Function: jaxon.ajax.handler.call

    Calls the registered command handler for the specified command
    (you should always check isRegistered before calling this function)

    Parameters:
        command - (object):
            - cmd: The Name of the function.

    Returns:
        true - (boolean) :
    */
    call: function(command) {
        var shortName = command.cmd;
        return jaxon.ajax.handler.handlers[shortName](command);
    }
};

jaxon.ajax.handler.register('rcmplt', function(args) {
    jaxon.ajax.response.complete(args.request);
    return true;
});

jaxon.ajax.handler.register('css', function(args) {
    args.fullName = 'includeCSS';
    if ('undefined' == typeof args.media)
        args.media = 'screen';
    return jaxon.cmd.style.add(args.data, args.media);
});
jaxon.ajax.handler.register('rcss', function(args) {
    args.fullName = 'removeCSS';
    if ('undefined' == typeof args.media)
        args.media = 'screen';
    return jaxon.cmd.style.remove(args.data, args.media);
});
jaxon.ajax.handler.register('wcss', function(args) {
    args.fullName = 'waitForCSS';
    return jaxon.cmd.style.waitForCSS(args);
});

jaxon.ajax.handler.register('as', function(args) {
    args.fullName = 'assign/clear';
    try {
        return jaxon.cmd.node.assign(args.target, args.prop, args.data);
    } catch (e) {
        // do nothing, if the debug module is installed it will
        // catch and handle the exception
    }
    return true;
});
jaxon.ajax.handler.register('ap', function(args) {
    args.fullName = 'append';
    return jaxon.cmd.node.append(args.target, args.prop, args.data);
});
jaxon.ajax.handler.register('pp', function(args) {
    args.fullName = 'prepend';
    return jaxon.cmd.node.prepend(args.target, args.prop, args.data);
});
jaxon.ajax.handler.register('rp', function(args) {
    args.fullName = 'replace';
    return jaxon.cmd.node.replace(args.id, args.prop, args.data);
});
jaxon.ajax.handler.register('rm', function(args) {
    args.fullName = 'remove';
    return jaxon.cmd.node.remove(args.id);
});
jaxon.ajax.handler.register('ce', function(args) {
    args.fullName = 'create';
    return jaxon.cmd.node.create(args.id, args.data, args.prop);
});
jaxon.ajax.handler.register('ie', function(args) {
    args.fullName = 'insert';
    return jaxon.cmd.node.insert(args.id, args.data, args.prop);
});
jaxon.ajax.handler.register('ia', function(args) {
    args.fullName = 'insertAfter';
    return jaxon.cmd.node.insertAfter(args.id, args.data, args.prop);
});

jaxon.ajax.handler.register('DSR', jaxon.cmd.tree.startResponse);
jaxon.ajax.handler.register('DCE', jaxon.cmd.tree.createElement);
jaxon.ajax.handler.register('DSA', jaxon.cmd.tree.setAttribute);
jaxon.ajax.handler.register('DAC', jaxon.cmd.tree.appendChild);
jaxon.ajax.handler.register('DIB', jaxon.cmd.tree.insertBefore);
jaxon.ajax.handler.register('DIA', jaxon.cmd.tree.insertAfter);
jaxon.ajax.handler.register('DAT', jaxon.cmd.tree.appendText);
jaxon.ajax.handler.register('DRC', jaxon.cmd.tree.removeChildren);
jaxon.ajax.handler.register('DER', jaxon.cmd.tree.endResponse);

jaxon.ajax.handler.register('c:as', jaxon.cmd.node.contextAssign);
jaxon.ajax.handler.register('c:ap', jaxon.cmd.node.contextAppend);
jaxon.ajax.handler.register('c:pp', jaxon.cmd.node.contextPrepend);

jaxon.ajax.handler.register('s', jaxon.cmd.script.sleep);
jaxon.ajax.handler.register('ino', jaxon.cmd.script.includeScriptOnce);
jaxon.ajax.handler.register('in', jaxon.cmd.script.includeScript);
jaxon.ajax.handler.register('rjs', jaxon.cmd.script.removeScript);
jaxon.ajax.handler.register('wf', jaxon.cmd.script.waitFor);
jaxon.ajax.handler.register('js', jaxon.cmd.script.execute);
jaxon.ajax.handler.register('jc', jaxon.cmd.script.call);
jaxon.ajax.handler.register('sf', jaxon.cmd.script.setFunction);
jaxon.ajax.handler.register('wpf', jaxon.cmd.script.wrapFunction);
jaxon.ajax.handler.register('al', function(args) {
    args.fullName = 'alert';
    alert(args.data);
    return true;
});
jaxon.ajax.handler.register('cc', jaxon.cmd.script.confirmCommands);

jaxon.ajax.handler.register('ci', jaxon.cmd.form.createInput);
jaxon.ajax.handler.register('ii', jaxon.cmd.form.insertInput);
jaxon.ajax.handler.register('iia', jaxon.cmd.form.insertInputAfter);

jaxon.ajax.handler.register('ev', jaxon.cmd.event.setEvent);

jaxon.ajax.handler.register('ah', jaxon.cmd.event.addHandler);
jaxon.ajax.handler.register('rh', jaxon.cmd.event.removeHandler);

jaxon.ajax.handler.register('dbg', function(args) {
    args.fullName = 'debug message';
    console.log(args.data);
    return true;
});
<|endoftext|>


<commit_before>/**
 * Created by shellus on 2016-03-16.
 */
angular.module('ngMQTT', [])
    .config(['$provide', function($provide){
        $provide.provider('MQTT', function(){

            var settings = {
                href: ""
            };

            this.setHref = function(href){
                settings.href = href;
            };
            this.$get = function() {
                return settings;
            };
        });
    }])

    .service('MQTTService',
        ['$q', '$rootScope', 'MQTT', function($q, $rootScope, MQTT) {
            var Service = {};
            var callbacks = {};

            var client = mqtt.connect(MQTT.href); // you add a ws:// url here

            client.on("message", function(topic, payload) {
                try {
                    var data = JSON.parse(payload.toString());
                }catch (e){
                    var data = payload.toString();
                }
                angular.forEach(callbacks,function(callback, name){
                    var regexpStr = name.replace(new RegExp('(#)|(\\*)|(\\+)'),function(str){
                      switch (str) {
                        case "#":
                          return ".*?"
                          break;
                        case "*":
                          return ".*?"
                          break;
                        case "+":
                          return ".*"
                          break;
                        default:
                          break;
                    }
                  });
                    if(topic.match(regexpStr)){
                        $rootScope.$apply(function() {
                            callback(data);
                        });
                    }
                })
            });

            client.publish("time", (new Date()).getDate());

            Service.on = function(name, callback){
                callbacks[name] = callback;
                client.subscribe(name);
            };
            Service.send = function(name, data){
                client.publish(name, JSON.stringify(data));
            };
            Service.drop = function(name, callback){
                callbacks[name] = callback;
                client.unsubscribe(name);
            };
            return Service;
        }]);
<commit_msg>add topic return in subscribe callback<commit_after>/**
 * Created by shellus on 2016-03-16.
 */
angular.module('ngMQTT', [])
    .config(['$provide', function($provide){
        $provide.provider('MQTT', function(){

            var settings = {
                href: ""
            };

            this.setHref = function(href){
                settings.href = href;
            };
            this.$get = function() {
                return settings;
            };
        });
    }])

    .service('MQTTService',
        ['$q', '$rootScope', 'MQTT', function($q, $rootScope, MQTT) {
            var Service = {};
            var callbacks = {};

            var client = mqtt.connect(MQTT.href); // you add a ws:// url here

            client.on("message", function(topic, payload) {
                try {
                    var data = JSON.parse(payload.toString());
                }catch (e){
                    var data = payload.toString();
                }
                angular.forEach(callbacks,function(callback, name){
                    var regexpStr = name.replace(new RegExp('(#)|(\\*)|(\\+)'),function(str){
                      switch (str) {
                        case "#":
                          return ".*?"
                          break;
                        case "*":
                          return ".*?"
                          break;
                        case "+":
                          return ".*"
                          break;
                        default:
                          break;
                    }
                  });
                    if(topic.match(regexpStr)){
                        $rootScope.$apply(function() {
                            callback(data, topic);
                        });
                    }
                })
            });

            client.publish("time", (new Date()).getDate());

            Service.on = function(name, callback){
                callbacks[name] = callback;
                client.subscribe(name);
            };
            Service.send = function(name, data){
                client.publish(name, JSON.stringify(data));
            };
            Service.drop = function(name, callback){
                callbacks[name] = callback;
                client.unsubscribe(name);
            };
            return Service;
        }]);
<|endoftext|>


<commit_before>var CheckBoxOption, NonEmptyTextOption, NumberOption, TextOption, importSettings;

Option.saveOptions = function() {
  Option.all.forEach(function(option) {
    option.save();
  });
};

Option.needSaveOptions = function() {
  return !Option.all.every(function(option) {
    return option.areEqual(option.readValueFromElement(), option.previous);
  });
};

NumberOption = (function(_super) {
  __extends(NumberOption, _super);

  function NumberOption() {
    NumberOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("input", this.onUpdated);
  }

  NumberOption.prototype.populateElement = function(value) {
    return this.element.value = value;
  };

  NumberOption.prototype.readValueFromElement = function() {
    return parseFloat(this.element.value);
  };

  return NumberOption;

})(Option);

TextOption = (function(_super) {
  __extends(TextOption, _super);

  function TextOption() {
    TextOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("input", this.onUpdated);
  }

  TextOption.prototype.populateElement = function(value) {
    return this.element.value = value.replace(/\n /g, '\n\xa0');
  };

  TextOption.prototype.readValueFromElement = function() {
    return this.element.value.trim().replace(/\xa0/g, ' ');
  };

  return TextOption;

})(Option);

NonEmptyTextOption = (function(_super) {
  __extends(NonEmptyTextOption, _super);

  function NonEmptyTextOption() {
    NonEmptyTextOption.__super__.constructor.apply(this, arguments);
  }

  NonEmptyTextOption.prototype.readValueFromElement = function() {
    var value = NonEmptyTextOption.__super__.readValueFromElement.call(this);
    if (value) {
      return value;
    } else {
      bgSettings.set(this.field, bgSettings.defaults[this.field]);
      this.fetch();
      return this.previous;
    }
  };

  return NonEmptyTextOption;

})(TextOption);

CheckBoxOption = (function(_super) {
  __extends(CheckBoxOption, _super);

  function CheckBoxOption() {
    CheckBoxOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("change", this.onUpdated);
  }

  CheckBoxOption.prototype.populateElement = function(value) {
    return this.element.checked = value;
  };

  CheckBoxOption.prototype.readValueFromElement = function() {
    return this.element.checked;
  };

  return CheckBoxOption;

})(Option);

(function() {
  var advancedMode, element, name, onUpdated, saveOptions, type, _i, _ref, status = 0;

  onUpdated = function() {
    var saveBtn;
    if (this.areEqual(this.readValueFromElement(), this.previous)) {
      if (status == 1 && !Option.needSaveOptions()) {
        saveBtn = $("saveOptions");
        saveBtn.disabled = true;
        saveBtn.innerHTML = "No Changes";
        $("exportButton").disabled = false;
        status = 0;
      }
      return;
    } else if (status == 1) {
      return;
    }
    status = 1;
    saveBtn = $("saveOptions");
    saveBtn.removeAttribute("disabled");
    saveBtn.innerHTML = "Save Changes";
    $("exportButton").disabled = true;
  };

  $("showCommands").onclick = function(event) {
    var node;
    DomUtils.suppressEvent(event);
    if (node = document.querySelector('.vimHelpCommandName')) {
      node.click();
      return;
    }
    MainPort.sendMessage({
      handler: "initHelp",
      unbound: true,
      names: true,
      title: "Command Listing"
    }, function(response) {
      if (node = $("vimHelpDialog")) {
        MainPort.Listener({
          name: "execute",
          command: "showHelp",
          count: 1
        });
      }
      MainPort.Listener(response);
    });
  };

  saveOptions = function() {
    var btn = $("saveOptions");
    if (!btn.disabled) {
      Option.saveOptions();
    }
    btn.disabled = true;
    btn.innerHTML = "No Changes";
    $("exportButton").disabled = false;
    status = 0;
    setTimeout(function () {
      window.onfocus();
      if (Option.syncToFrontend) {
        bgSettings.postUpdate("bufferToLoad", null);
        bgSettings.postUpdate("broadcast", {
          name: "settings",
          load: bgSettings.bufferToLoad
        });
        Option.syncToFrontend = false;
      }
    }, 100);
  };
  $("saveOptions").onclick = saveOptions;

  _ref = document.getElementsByClassName("nonEmptyTextOption");
  for (_i = _ref.length; 0 <= --_i; ) {
    element = _ref[_i];
    element.className = element.className + " example info";
    element.innerHTML = "Leave empty to reset this option.";
  }

  window.onbeforeunload = function() {
    if (status !== 0 && Option.needSaveOptions()) {
      return "You have unsaved changes to options.";
    }
  };
  document.addEventListener("keyup", function(event) {
    if (event.ctrlKey && event.keyCode === 13) {
      var element = document.activeElement;
      if (element && element.blur) {
        element.blur();
      }
      if (status != 0) {
        saveOptions();
      }
    }
  });

  _ref = {
    exclusionRules: ExclusionRulesOption,
    filterLinkHints: CheckBoxOption,
    hideHud: CheckBoxOption,
    keyMappings: TextOption,
    linkHintCharacters: NonEmptyTextOption,
    linkHintNumbers: NonEmptyTextOption,
    newTabUrl: NonEmptyTextOption,
    nextPatterns: NonEmptyTextOption,
    previousPatterns: NonEmptyTextOption,
    regexFindMode: CheckBoxOption,
    scrollStepSize: NumberOption,
    smoothScroll: CheckBoxOption,
    showActionIcon: CheckBoxOption,
    searchEngines: TextOption,
    searchUrl: NonEmptyTextOption,
    userDefinedCss: TextOption
  };
  for (name in _ref) {
    type = _ref[name];
    new type(name, onUpdated);
  }

  advancedMode = bgSettings.get("showAdvancedOptions");
  element = $("advancedOptionsButton");
  element.onclick = function(_0, init) {
    if (!init) {
      advancedMode = !advancedMode;
      bgSettings.set("showAdvancedOptions", advancedMode);
    }
    if (advancedMode) {
      $("advancedOptions").style.display = "";
      this.innerHTML = "Hide Advanced Options";
    } else {
      $("advancedOptions").style.display = "none";
      this.innerHTML = "Show Advanced Options";
    }
  };
  element.onclick(null, true);

  element = $("filterLinkHints");
  element.onclick = function() {
    var set = function(el, stat) {
      $(el).parentNode.parentNode.style.display = stat ? "" : "none";
    };
    set("linkHintCharacters", !this.checked);
    set("linkHintNumbers", this.checked);
  };
  element.onclick();

  element = null;
})();

$("exportButton").onclick = function() {
  var exported_object, exported_data, file_name, force2, d, nodeA;
  exported_object = {__proto__: null, name: "Vimium++", time: 0};
  (function() {
    var storage = localStorage, i, len, key, mark_head, all = bgSettings.defaults;
    mark_head = BG.Marks.getMarkKey("");
    for (i = 0, len = storage.length; i < len; i++) {
      key = storage.key(i);
      if (key === "name" || key === "time" || key.startsWith(mark_head)) {
        continue;
      }
      exported_object[key] = (key in all) ? bgSettings.get(key) : storage.getItem(key);
    }
  })();
  delete exported_object.findModeRawQuery;
  d = new Date();
  exported_object.time = d.getTime();
  exported_data = JSON.stringify(exported_object, null, '\t');
  exported_object = null;
  force2 = function(i) { return ((i <= 9) ? '0'  : '') + i; }
  file_name = 'vimium++_' + d.getFullYear() + force2(d.getMonth() + 1) + force2(d.getDate())
    + '_' + force2(d.getHours()) + force2(d.getMinutes()) + force2(d.getSeconds()) + '.json';

  nodeA = document.createElement("a");
  nodeA.download = file_name;
  nodeA.href = "data:application/json;charset=UTF-8," + encodeURIComponent(exported_data);
  nodeA.click();
  console.log("EXPORT settings to", file_name, "at", d);
};

importSettings = function() {
  var new_data;
  try {
    new_data = JSON.parse(this.result);
  } catch (e) {}
  if (!new_data || new_data.name !== "Vimium++" || !(new_data.time > 10000)) {
    VHUD.showForDuration("No settings data found!", 2000);
    return;
  } else if (!confirm(
    "You are loading a settings copy exported at:\n        "
    + new Date(new_data.time - new Date().getTimezoneOffset() * 1000 * 60
      ).toJSON().substring(0, 16).replace('T', ' ')
    + "\n\nAre you sure you want to continue?"
  )) {
    VHUD.showForDuration("You cancelled importing.", 1000);
    return;
  }

  var storage = localStorage, i, key, new_value, func, all = bgSettings.defaults;
  func = function(val) {
    return typeof val !== "string" ? val : val.substring(0, 72);
  };
  console.log("IMPORT settings at", new Date(new_data.time));
  delete new_data.name;
  delete new_data.time;
  delete new_data.findModeRawQuery;
  for (i = storage.length; 0 <= --i; ) {
    key = storage.key(i);
    if (!(key in new_data)) {
      new_data[key] = null;
    }
  }
  Option.all.forEach(function(item) {
    var key = item.field, new_value = new_data[key];
    delete new_data[key];
    if (new_value == null) {
      new_value = all[key];
    }
    if (!item.areEqual(bgSettings.get(key), new_value)) {
      console.log("import", key, func(new_value));
      bgSettings.set(key, new_value);
    }
    item.fetch();
  });
  for (key in new_data) {
    new_value = new_data[key];
    if (new_value == null) {
      if (key in all) {
        new_value = all[key];
        if (bgSettings.get(key) !== new_value) {
          bgSettings.set(key, new_value);
          console.log("reset", key, func(new_value));
          continue;
        }
        new_value = bgSettings.get(key);
      } else {
        new_value = storage.getItem(key);
      }
      storage.removeItem(key);
      console.log("remove", key, "<=", func(new_value));
    } else if (key in all) {
      if (bgSettings.get(key) !== new_value) {
        bgSettings.set(key, new_value);
        console.log("update", key, func(new_value));
      }
    } else {
      storage.setItem(key, new_value);
      console.log("save", key, func(new_value));
    }
  }
  var btn = $("saveOptions");
  btn.disabled = true;
  btn.click();
  VHUD.showForDuration("Import settings data: OK!", 1000);
};

$("settingsFile").onchange = function() {
  var file = this.files[0], reader;
  this.value = "";
  if (!file) { return; }
  reader = new FileReader();
  reader.onload = importSettings;
  reader.readAsText(file);
};
$("importButton").onclick = function() {
  $("settingsFile").click();
};
<commit_msg>fix a bug caused by removing window.onfocus<commit_after>var CheckBoxOption, NonEmptyTextOption, NumberOption, TextOption, importSettings;

Option.saveOptions = function() {
  Option.all.forEach(function(option) {
    option.save();
  });
};

Option.needSaveOptions = function() {
  return !Option.all.every(function(option) {
    return option.areEqual(option.readValueFromElement(), option.previous);
  });
};

NumberOption = (function(_super) {
  __extends(NumberOption, _super);

  function NumberOption() {
    NumberOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("input", this.onUpdated);
  }

  NumberOption.prototype.populateElement = function(value) {
    return this.element.value = value;
  };

  NumberOption.prototype.readValueFromElement = function() {
    return parseFloat(this.element.value);
  };

  return NumberOption;

})(Option);

TextOption = (function(_super) {
  __extends(TextOption, _super);

  function TextOption() {
    TextOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("input", this.onUpdated);
  }

  TextOption.prototype.populateElement = function(value) {
    return this.element.value = value.replace(/\n /g, '\n\xa0');
  };

  TextOption.prototype.readValueFromElement = function() {
    return this.element.value.trim().replace(/\xa0/g, ' ');
  };

  return TextOption;

})(Option);

NonEmptyTextOption = (function(_super) {
  __extends(NonEmptyTextOption, _super);

  function NonEmptyTextOption() {
    NonEmptyTextOption.__super__.constructor.apply(this, arguments);
  }

  NonEmptyTextOption.prototype.readValueFromElement = function() {
    var value = NonEmptyTextOption.__super__.readValueFromElement.call(this);
    if (value) {
      return value;
    } else {
      bgSettings.set(this.field, bgSettings.defaults[this.field]);
      this.fetch();
      return this.previous;
    }
  };

  return NonEmptyTextOption;

})(TextOption);

CheckBoxOption = (function(_super) {
  __extends(CheckBoxOption, _super);

  function CheckBoxOption() {
    CheckBoxOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("change", this.onUpdated);
  }

  CheckBoxOption.prototype.populateElement = function(value) {
    return this.element.checked = value;
  };

  CheckBoxOption.prototype.readValueFromElement = function() {
    return this.element.checked;
  };

  return CheckBoxOption;

})(Option);

(function() {
  var advancedMode, element, name, onUpdated, saveOptions, type, _i, _ref, status = 0;

  onUpdated = function() {
    var saveBtn;
    if (this.areEqual(this.readValueFromElement(), this.previous)) {
      if (status == 1 && !Option.needSaveOptions()) {
        saveBtn = $("saveOptions");
        saveBtn.disabled = true;
        saveBtn.innerHTML = "No Changes";
        $("exportButton").disabled = false;
        status = 0;
      }
      return;
    } else if (status == 1) {
      return;
    }
    status = 1;
    saveBtn = $("saveOptions");
    saveBtn.removeAttribute("disabled");
    saveBtn.innerHTML = "Save Changes";
    $("exportButton").disabled = true;
  };

  $("showCommands").onclick = function(event) {
    var node;
    DomUtils.suppressEvent(event);
    if (node = document.querySelector('.vimHelpCommandName')) {
      node.click();
      return;
    }
    MainPort.sendMessage({
      handler: "initHelp",
      unbound: true,
      names: true,
      title: "Command Listing"
    }, function(response) {
      if (node = $("vimHelpDialog")) {
        MainPort.Listener({
          name: "execute",
          command: "showHelp",
          count: 1
        });
      }
      MainPort.Listener(response);
    });
  };

  saveOptions = function() {
    var btn = $("saveOptions");
    if (!btn.disabled) {
      Option.saveOptions();
    }
    btn.disabled = true;
    btn.innerHTML = "No Changes";
    $("exportButton").disabled = false;
    status = 0;
    setTimeout(function () {
      var event = new FocusEvent("focus");
      window.dispatchEvent(event)
      if (Option.syncToFrontend) {
        bgSettings.postUpdate("bufferToLoad", null);
        bgSettings.postUpdate("broadcast", {
          name: "settings",
          load: bgSettings.bufferToLoad
        });
        Option.syncToFrontend = false;
      }
    }, 100);
  };
  $("saveOptions").onclick = saveOptions;

  _ref = document.getElementsByClassName("nonEmptyTextOption");
  for (_i = _ref.length; 0 <= --_i; ) {
    element = _ref[_i];
    element.className = element.className + " example info";
    element.innerHTML = "Leave empty to reset this option.";
  }

  window.onbeforeunload = function() {
    if (status !== 0 && Option.needSaveOptions()) {
      return "You have unsaved changes to options.";
    }
  };
  document.addEventListener("keyup", function(event) {
    if (event.ctrlKey && event.keyCode === 13) {
      var element = document.activeElement;
      if (element && element.blur) {
        element.blur();
      }
      if (status != 0) {
        saveOptions();
      }
    }
  });

  _ref = {
    exclusionRules: ExclusionRulesOption,
    filterLinkHints: CheckBoxOption,
    hideHud: CheckBoxOption,
    keyMappings: TextOption,
    linkHintCharacters: NonEmptyTextOption,
    linkHintNumbers: NonEmptyTextOption,
    newTabUrl: NonEmptyTextOption,
    nextPatterns: NonEmptyTextOption,
    previousPatterns: NonEmptyTextOption,
    regexFindMode: CheckBoxOption,
    scrollStepSize: NumberOption,
    smoothScroll: CheckBoxOption,
    showActionIcon: CheckBoxOption,
    searchEngines: TextOption,
    searchUrl: NonEmptyTextOption,
    userDefinedCss: TextOption
  };
  for (name in _ref) {
    type = _ref[name];
    new type(name, onUpdated);
  }

  advancedMode = bgSettings.get("showAdvancedOptions");
  element = $("advancedOptionsButton");
  element.onclick = function(_0, init) {
    if (!init) {
      advancedMode = !advancedMode;
      bgSettings.set("showAdvancedOptions", advancedMode);
    }
    if (advancedMode) {
      $("advancedOptions").style.display = "";
      this.innerHTML = "Hide Advanced Options";
    } else {
      $("advancedOptions").style.display = "none";
      this.innerHTML = "Show Advanced Options";
    }
  };
  element.onclick(null, true);

  element = $("filterLinkHints");
  element.onclick = function() {
    var set = function(el, stat) {
      $(el).parentNode.parentNode.style.display = stat ? "" : "none";
    };
    set("linkHintCharacters", !this.checked);
    set("linkHintNumbers", this.checked);
  };
  element.onclick();

  element = null;
})();

$("exportButton").onclick = function() {
  var exported_object, exported_data, file_name, force2, d, nodeA;
  exported_object = {__proto__: null, name: "Vimium++", time: 0};
  (function() {
    var storage = localStorage, i, len, key, mark_head, all = bgSettings.defaults;
    mark_head = BG.Marks.getMarkKey("");
    for (i = 0, len = storage.length; i < len; i++) {
      key = storage.key(i);
      if (key === "name" || key === "time" || key.startsWith(mark_head)) {
        continue;
      }
      exported_object[key] = (key in all) ? bgSettings.get(key) : storage.getItem(key);
    }
  })();
  delete exported_object.findModeRawQuery;
  d = new Date();
  exported_object.time = d.getTime();
  exported_data = JSON.stringify(exported_object, null, '\t');
  exported_object = null;
  force2 = function(i) { return ((i <= 9) ? '0'  : '') + i; }
  file_name = 'vimium++_' + d.getFullYear() + force2(d.getMonth() + 1) + force2(d.getDate())
    + '_' + force2(d.getHours()) + force2(d.getMinutes()) + force2(d.getSeconds()) + '.json';

  nodeA = document.createElement("a");
  nodeA.download = file_name;
  nodeA.href = "data:application/json;charset=UTF-8," + encodeURIComponent(exported_data);
  nodeA.click();
  console.log("EXPORT settings to", file_name, "at", d);
};

importSettings = function() {
  var new_data;
  try {
    new_data = JSON.parse(this.result);
  } catch (e) {}
  if (!new_data || new_data.name !== "Vimium++" || !(new_data.time > 10000)) {
    VHUD.showForDuration("No settings data found!", 2000);
    return;
  } else if (!confirm(
    "You are loading a settings copy exported at:\n        "
    + new Date(new_data.time - new Date().getTimezoneOffset() * 1000 * 60
      ).toJSON().substring(0, 16).replace('T', ' ')
    + "\n\nAre you sure you want to continue?"
  )) {
    VHUD.showForDuration("You cancelled importing.", 1000);
    return;
  }

  var storage = localStorage, i, key, new_value, func, all = bgSettings.defaults;
  func = function(val) {
    return typeof val !== "string" ? val : val.substring(0, 72);
  };
  console.log("IMPORT settings at", new Date(new_data.time));
  delete new_data.name;
  delete new_data.time;
  delete new_data.findModeRawQuery;
  for (i = storage.length; 0 <= --i; ) {
    key = storage.key(i);
    if (!(key in new_data)) {
      new_data[key] = null;
    }
  }
  Option.all.forEach(function(item) {
    var key = item.field, new_value = new_data[key];
    delete new_data[key];
    if (new_value == null) {
      new_value = all[key];
    }
    if (!item.areEqual(bgSettings.get(key), new_value)) {
      console.log("import", key, func(new_value));
      bgSettings.set(key, new_value);
    }
    item.fetch();
  });
  for (key in new_data) {
    new_value = new_data[key];
    if (new_value == null) {
      if (key in all) {
        new_value = all[key];
        if (bgSettings.get(key) !== new_value) {
          bgSettings.set(key, new_value);
          console.log("reset", key, func(new_value));
          continue;
        }
        new_value = bgSettings.get(key);
      } else {
        new_value = storage.getItem(key);
      }
      storage.removeItem(key);
      console.log("remove", key, "<=", func(new_value));
    } else if (key in all) {
      if (bgSettings.get(key) !== new_value) {
        bgSettings.set(key, new_value);
        console.log("update", key, func(new_value));
      }
    } else {
      storage.setItem(key, new_value);
      console.log("save", key, func(new_value));
    }
  }
  var btn = $("saveOptions");
  btn.disabled = true;
  btn.click();
  VHUD.showForDuration("Import settings data: OK!", 1000);
};

$("settingsFile").onchange = function() {
  var file = this.files[0], reader;
  this.value = "";
  if (!file) { return; }
  reader = new FileReader();
  reader.onload = importSettings;
  reader.readAsText(file);
};
$("importButton").onclick = function() {
  $("settingsFile").click();
};
<|endoftext|>


<commit_before>"use strict";

$("showCommands").onclick = function(event) {
  if (!window.VDom) { return; }
  var node, root = VDom.UI.root;
  event && event.preventDefault();
  if (!root) {}
  else if (root.querySelector('.HelpCommandName')) {
    node = root.getElementById("HelpDialog");
    VDom.UI.addElement(node);
    node.click();
    return;
  } else if (node = root.getElementById("HClose")) {
    node.onclick();
  }
  VPort.port.postMessage({
    handler: "initHelp",
    unbound: true,
    names: true,
    title: "Command Listing"
  });
  setTimeout(function() {
    var node = VDom.UI.root && VDom.UI.root.getElementById("HelpDialog");
    if (!node) { return; }
    node.onclick = function(event) {
      var target = event.target, str;
      if (target.classList.contains("HelpCommandName")) {
        str = target.innerText.slice(1, -1);
        VPort.port.postMessage({
          handler: "copyToClipboard",
          data: str
        });
        VHUD.showCopied(str);
      }
    };
  }, 50);
};

ExclusionRulesOption.prototype.sortRules = function(element) {
  if (element && element.timer) { return; }
  var rules = this.readValueFromElement(), _i, rule, key, arr
    , hostRe = /^([:^]?[a-z\-?*]+:\/\/)?([^\/]+)(\/.*)?/;
  for (_i = 0; _i < rules.length; _i++) {
    rule = rules[_i];
    if ((arr = hostRe.exec(key = rule.pattern)) && arr[1] && arr[2]) {
      key = arr[3] || "";
      arr = arr[2].split(".");
      arr.reverse();
      key = arr.join(".") + key;
    }
    rule.key = key;
  }
  rules.sort(function(a, b) { return a.key < b.key ? -1 : a.key === b.key ? 0 : 1; });
  this.populateElement(rules);
  if (!element) { return; }
  element.timer = setTimeout(function(el, text) {
    el.firstChild.data = text, el.timer = 0;
  }, 1000, element, element.firstChild.data);
  element.firstChild.data = "(Sorted)";
};

$("exclusionSortButton").onclick = function() { Option.all.exclusionRules.sortRules(this); };

var formatDate = function(time) {
  return new Date(time - new Date().getTimezoneOffset() * 1000 * 60
    ).toJSON().substring(0, 19).replace('T', ' ');
};

$("exportButton").onclick = function(event) {
  var exported_object, exported_data, file_name, d, nodeA;
  exported_object = Object.create(null);
  exported_object.name = "Vimium++";
  exported_object.time = 0;
  exported_object.environment = {
    chrome: bgSettings.CONST.ChromeVersion,
    extension: parseFloat(bgSettings.CONST.CurrentVersion),
    platform: bgSettings.CONST.Platform
  };
  (function() {
    var storage = localStorage, i, len, key, storedVal, all = bgSettings.defaults;
    for (i = 0, len = storage.length; i < len; i++) {
      key = storage.key(i);
      if (key.indexOf("|") >= 0 || key.substring(key.length - 2) === "_f") {
        continue;
      }
      storedVal = storage.getItem(key);
      if (typeof all[key] !== "string") {
        exported_object[key] = (key in all) ? bgSettings.get(key) : storedVal;
      } else if (storage.getItem(key).indexOf("\n") > 0) {
        exported_object[key] = storedVal.split("\n");
        exported_object[key].push("");
      } else {
        exported_object[key] = storedVal;
      }
    }
  })();
  delete exported_object.findModeRawQueryList;
  d = new Date();
  exported_object.time = d.getTime();
  exported_data = JSON.stringify(exported_object, null, '\t');
  exported_object = null;
  file_name = 'vimium++_';
  if (event && (event.ctrlKey || event.shiftKey)) {
    file_name += "settings";
  } else {
    file_name += formatDate(d).replace(/[\-:]/g, "").replace(" ", "_");
  }
  file_name += '.json';

  nodeA = document.createElement("a");
  nodeA.download = file_name;
  nodeA.href = URL.createObjectURL(new Blob([exported_data]));
  nodeA.click();
  URL.revokeObjectURL(nodeA.href);
  console.info("EXPORT settings to %c%s%c at %c%s%c."
    , "color: darkred", file_name, "color: auto"
    , "color: darkblue", formatDate(d), "color: auto");
};

var importSettings = function(time, new_data, is_recommended) {
  time = +new Date(new_data && new_data.time || time) || 0;
  if (!new_data || new_data.name !== "Vimium++" || (time < 10000 && time > 0)) {
    key = "No settings data found!";
    window.VHUD ? VHUD.showForDuration(key, 2000) : alert(new_data ? key : "Fail to parse the settings");
    return;
  } else if (!confirm(
    (is_recommended !== true ? "You are loading a settings copy exported"
      + (time ? " at:\n        " + formatDate(time) : " before.")
      : "You are loading the recommended settings.")
    + "\n\nAre you sure you want to continue?"
  )) {
    window.VHUD && VHUD.showForDuration("You cancelled importing.", 1000);
    return;
  }

  var storage = localStorage, i, key, new_value, logUpdate, all = bgSettings.defaults
    , _ref = Option.all, _key, item;
  logUpdate = function(method, key, val) {
    var args = [].slice.call(arguments, 2);
    val = args.pop();
    val = typeof val !== "string" || val.length <= 72 ? val
      : val.substring(0, 68).trimRight() + " ...";
    args.push(val);
    args = ["%s %c%s%c", method, "color: darkred", key, "color: auto"].concat(args);
    console.log.apply(console, args);
  };
  if (time > 10000) {
    console.info("IMPORT settings saved at %c%s%c"
      , "color: darkblue", formatDate(new Date(time)), "color: auto");
  } else {
    console.info("IMPORT settings:", is_recommended ? "recommended" : "saved before");
  }

  Object.setPrototypeOf(new_data, null);
  delete new_data.name;
  delete new_data.time;
  delete new_data.environment;
  delete new_data.author;
  delete new_data.description;
  for (i = storage.length; 0 <= --i; ) {
    key = storage.key(i);
    if (key.indexOf("|") >= 0) { continue; }
    if (!(key in new_data)) {
      new_data[key] = null;
    }
  }
  delete new_data.findModeRawQuery;
  delete new_data.findModeRawQueryList;
  delete new_data.newTabUrl_f;
  for (_key in _ref) {
    item = _ref[_key];
    key = item.field;
    new_value = new_data[key];
    delete new_data[key];
    if (new_value == null) {
      // NOTE: we assume all nullable settings have the same default value: null
      new_value = all[key];
    } else if (new_value.join && typeof all[key] === "string") {
      new_value = new_value.join("\n").trim();
    }
    if (!item.areEqual(bgSettings.get(key), new_value)) {
      logUpdate("import", key, new_value);
      bgSettings.set(key, new_value);
      if (key in bgSettings.bufferToLoad) {
        Option.syncToFrontend.push(key);
      }
    } else if (item.saved) {
      continue;
    }
    item.fetch();
  }
  for (key in new_data) {
    new_value = new_data[key];
    if (new_value == null) {
      if (key in all) {
        new_value = all[key];
        if (bgSettings.get(key) !== new_value) {
          bgSettings.set(key, new_value);
          logUpdate("reset", key, new_value);
          continue;
        }
        new_value = bgSettings.get(key);
      } else {
        new_value = storage.getItem(key);
      }
      storage.removeItem(key);
      logUpdate("remove", key, ":=", new_value);
      continue;
    }
    if (new_value.join && typeof all[key] === "string") {
      new_value = new_value.join("\n").trim();
    }
    if (key in all) {
      if (bgSettings.get(key) !== new_value) {
        bgSettings.set(key, new_value);
        logUpdate("update", key, new_value);
      }
    } else {
      storage.setItem(key, new_value);
      logUpdate("save", key, new_value);
    }
  }
  $("saveOptions").onclick(false);
  if ($("advancedOptionsButton").getAttribute("data-checked") != '' + bgSettings.get("showAdvancedOptions")) {
    $("advancedOptionsButton").onclick(null, true);
  }
  window.VHUD && VHUD.showForDuration("Import settings data: OK!", 1000);
  console.info("IMPORT settings: finished.");
};

var _el = $("settingsFile");
_el.onclick = null;
_el.onchange = function() {
  var file = this.files[0], reader, lastModified;
  this.value = "";
  if (!file) { return; }
  reader = new FileReader();
  lastModified = file.lastModified;
  reader.onload = function() {
    var result = this.result, data;
    try {
      data = result && JSON.parse(result);
    } catch (e) {}
    setTimeout(importSettings, 17, lastModified, data, false);
  };
  reader.readAsText(file);
};

_el = $("importOptions");
_el.onclick = null;
_el.onchange = function() {
  if (this.value === "Exported File") {
    $("settingsFile").click();
    return;
  }
  var req = new XMLHttpRequest();
  req.open("GET", "../settings_template.json", true);
  req.responseType = "json";
  req.onload = function() {
    setTimeout(importSettings, 17, 0, this.response, true);
  };
  req.send();
};

window._delayed && (function() {
  var arr = window._delayed, node, event;
  delete window._delayed;
  node = $(arr[0]);
  event = arr[1];
  node.onclick && node.onclick(event);
})();
<commit_msg>export: not record time if needing a static one<commit_after>"use strict";

$("showCommands").onclick = function(event) {
  if (!window.VDom) { return; }
  var node, root = VDom.UI.root;
  event && event.preventDefault();
  if (!root) {}
  else if (root.querySelector('.HelpCommandName')) {
    node = root.getElementById("HelpDialog");
    VDom.UI.addElement(node);
    node.click();
    return;
  } else if (node = root.getElementById("HClose")) {
    node.onclick();
  }
  VPort.port.postMessage({
    handler: "initHelp",
    unbound: true,
    names: true,
    title: "Command Listing"
  });
  setTimeout(function() {
    var node = VDom.UI.root && VDom.UI.root.getElementById("HelpDialog");
    if (!node) { return; }
    node.onclick = function(event) {
      var target = event.target, str;
      if (target.classList.contains("HelpCommandName")) {
        str = target.innerText.slice(1, -1);
        VPort.port.postMessage({
          handler: "copyToClipboard",
          data: str
        });
        VHUD.showCopied(str);
      }
    };
  }, 50);
};

ExclusionRulesOption.prototype.sortRules = function(element) {
  if (element && element.timer) { return; }
  var rules = this.readValueFromElement(), _i, rule, key, arr
    , hostRe = /^([:^]?[a-z\-?*]+:\/\/)?([^\/]+)(\/.*)?/;
  for (_i = 0; _i < rules.length; _i++) {
    rule = rules[_i];
    if ((arr = hostRe.exec(key = rule.pattern)) && arr[1] && arr[2]) {
      key = arr[3] || "";
      arr = arr[2].split(".");
      arr.reverse();
      key = arr.join(".") + key;
    }
    rule.key = key;
  }
  rules.sort(function(a, b) { return a.key < b.key ? -1 : a.key === b.key ? 0 : 1; });
  this.populateElement(rules);
  if (!element) { return; }
  element.timer = setTimeout(function(el, text) {
    el.firstChild.data = text, el.timer = 0;
  }, 1000, element, element.firstChild.data);
  element.firstChild.data = "(Sorted)";
};

$("exclusionSortButton").onclick = function() { Option.all.exclusionRules.sortRules(this); };

var formatDate = function(time) {
  return new Date(time - new Date().getTimezoneOffset() * 1000 * 60
    ).toJSON().substring(0, 19).replace('T', ' ');
};

$("exportButton").onclick = function(event) {
  var exported_object, exported_data, file_name, d, nodeA, all_static;
  all_static = event ? event.ctrlKey || event.shiftKey : false;
  exported_object = Object.create(null);
  exported_object.name = "Vimium++";
  if (!all_static) {
    exported_object.time = 0;
  }
  exported_object.environment = {
    chrome: bgSettings.CONST.ChromeVersion,
    extension: parseFloat(bgSettings.CONST.CurrentVersion),
    platform: bgSettings.CONST.Platform
  };
  (function() {
    var storage = localStorage, i, len, key, storedVal, all = bgSettings.defaults;
    for (i = 0, len = storage.length; i < len; i++) {
      key = storage.key(i);
      if (key.indexOf("|") >= 0 || key.substring(key.length - 2) === "_f") {
        continue;
      }
      storedVal = storage.getItem(key);
      if (typeof all[key] !== "string") {
        exported_object[key] = (key in all) ? bgSettings.get(key) : storedVal;
      } else if (storage.getItem(key).indexOf("\n") > 0) {
        exported_object[key] = storedVal.split("\n");
        exported_object[key].push("");
      } else {
        exported_object[key] = storedVal;
      }
    }
  })();
  delete exported_object.findModeRawQueryList;
  d = new Date();
  if (!all_static) {
    exported_object.time = d.getTime();
  }
  exported_data = JSON.stringify(exported_object, null, '\t');
  exported_object = null;
  file_name = 'vimium++_';
  if (all_static) {
    file_name += "settings";
  } else {
    file_name += formatDate(d).replace(/[\-:]/g, "").replace(" ", "_");
  }
  file_name += '.json';

  nodeA = document.createElement("a");
  nodeA.download = file_name;
  nodeA.href = URL.createObjectURL(new Blob([exported_data]));
  nodeA.click();
  URL.revokeObjectURL(nodeA.href);
  console.info("EXPORT settings to %c%s%c at %c%s%c."
    , "color: darkred", file_name, "color: auto"
    , "color: darkblue", formatDate(d), "color: auto");
};

var importSettings = function(time, new_data, is_recommended) {
  time = +new Date(new_data && new_data.time || time) || 0;
  if (!new_data || new_data.name !== "Vimium++" || (time < 10000 && time > 0)) {
    key = "No settings data found!";
    window.VHUD ? VHUD.showForDuration(key, 2000) : alert(new_data ? key : "Fail to parse the settings");
    return;
  } else if (!confirm(
    (is_recommended !== true ? "You are loading a settings copy exported"
      + (time ? " at:\n        " + formatDate(time) : " before.")
      : "You are loading the recommended settings.")
    + "\n\nAre you sure you want to continue?"
  )) {
    window.VHUD && VHUD.showForDuration("You cancelled importing.", 1000);
    return;
  }

  var storage = localStorage, i, key, new_value, logUpdate, all = bgSettings.defaults
    , _ref = Option.all, _key, item;
  logUpdate = function(method, key, val) {
    var args = [].slice.call(arguments, 2);
    val = args.pop();
    val = typeof val !== "string" || val.length <= 72 ? val
      : val.substring(0, 68).trimRight() + " ...";
    args.push(val);
    args = ["%s %c%s%c", method, "color: darkred", key, "color: auto"].concat(args);
    console.log.apply(console, args);
  };
  if (time > 10000) {
    console.info("IMPORT settings saved at %c%s%c"
      , "color: darkblue", formatDate(new Date(time)), "color: auto");
  } else {
    console.info("IMPORT settings:", is_recommended ? "recommended" : "saved before");
  }

  Object.setPrototypeOf(new_data, null);
  delete new_data.name;
  delete new_data.time;
  delete new_data.environment;
  delete new_data.author;
  delete new_data.description;
  for (i = storage.length; 0 <= --i; ) {
    key = storage.key(i);
    if (key.indexOf("|") >= 0) { continue; }
    if (!(key in new_data)) {
      new_data[key] = null;
    }
  }
  delete new_data.findModeRawQuery;
  delete new_data.findModeRawQueryList;
  delete new_data.newTabUrl_f;
  for (_key in _ref) {
    item = _ref[_key];
    key = item.field;
    new_value = new_data[key];
    delete new_data[key];
    if (new_value == null) {
      // NOTE: we assume all nullable settings have the same default value: null
      new_value = all[key];
    } else if (new_value.join && typeof all[key] === "string") {
      new_value = new_value.join("\n").trim();
    }
    if (!item.areEqual(bgSettings.get(key), new_value)) {
      logUpdate("import", key, new_value);
      bgSettings.set(key, new_value);
      if (key in bgSettings.bufferToLoad) {
        Option.syncToFrontend.push(key);
      }
    } else if (item.saved) {
      continue;
    }
    item.fetch();
  }
  for (key in new_data) {
    new_value = new_data[key];
    if (new_value == null) {
      if (key in all) {
        new_value = all[key];
        if (bgSettings.get(key) !== new_value) {
          bgSettings.set(key, new_value);
          logUpdate("reset", key, new_value);
          continue;
        }
        new_value = bgSettings.get(key);
      } else {
        new_value = storage.getItem(key);
      }
      storage.removeItem(key);
      logUpdate("remove", key, ":=", new_value);
      continue;
    }
    if (new_value.join && typeof all[key] === "string") {
      new_value = new_value.join("\n").trim();
    }
    if (key in all) {
      if (bgSettings.get(key) !== new_value) {
        bgSettings.set(key, new_value);
        logUpdate("update", key, new_value);
      }
    } else {
      storage.setItem(key, new_value);
      logUpdate("save", key, new_value);
    }
  }
  $("saveOptions").onclick(false);
  if ($("advancedOptionsButton").getAttribute("data-checked") != '' + bgSettings.get("showAdvancedOptions")) {
    $("advancedOptionsButton").onclick(null, true);
  }
  window.VHUD && VHUD.showForDuration("Import settings data: OK!", 1000);
  console.info("IMPORT settings: finished.");
};

var _el = $("settingsFile");
_el.onclick = null;
_el.onchange = function() {
  var file = this.files[0], reader, lastModified;
  this.value = "";
  if (!file) { return; }
  reader = new FileReader();
  lastModified = file.lastModified;
  reader.onload = function() {
    var result = this.result, data;
    try {
      data = result && JSON.parse(result);
    } catch (e) {}
    setTimeout(importSettings, 17, lastModified, data, false);
  };
  reader.readAsText(file);
};

_el = $("importOptions");
_el.onclick = null;
_el.onchange = function() {
  if (this.value === "Exported File") {
    $("settingsFile").click();
    return;
  }
  var req = new XMLHttpRequest();
  req.open("GET", "../settings_template.json", true);
  req.responseType = "json";
  req.onload = function() {
    setTimeout(importSettings, 17, 0, this.response, true);
  };
  req.send();
};

window._delayed && (function() {
  var arr = window._delayed, node, event;
  delete window._delayed;
  node = $(arr[0]);
  event = arr[1];
  node.onclick && node.onclick(event);
})();
<|endoftext|>


<commit_before>var module = angular.module('commafeed.directives', []);

module.directive('focus', ['$timeout', function($timeout) {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {
			scope.$watch(attrs.focus, function(value) {
				if (!value)
					return;
				$timeout(function() {
					$(element).focus();
				});
			});
		}
	};
}]);

/**
 * Open a popup window pointing to the url in the href attribute
 */
module.directive('popup', function() {
	var opts = 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=800';
	return {
		link : function(scope, elm, attrs) {
			elm.bind('click', function(event) {
				window.open(this.href, '', opts);
				event.preventDefault();
			});
		}
	};
});

/**
 * Reusable favicon component
 */
module.directive('favicon', function() {
	var tpl = '';
	return {
		restrict : 'E',
		scope : {
			url : '='
		},
		replace : true,
		template : tpl
	};
});

/**
 * Support for the blur event
 */
module.directive('ngBlur', function() {
	return {
		restrict : 'A',
		link : function(scope, elm, attrs) {
			elm.bind('blur', function() {
				scope.$apply(attrs.ngBlur);
			});
		}
	};
});

/**
 * Fired when the top of the element is not visible anymore
 */
module.directive('onScrollMiddle', function() {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {

			var w = $(window);
			var e = $(element);
			var d = $(document);

			var offset = parseInt(attrs.onScrollMiddleOffset, 10);

			var down = function() {
				var docTop = w.scrollTop();
				var elemTop = e.offset().top;
				var threshold = docTop === 0 ? elemTop - 1 : docTop + offset;
				return (elemTop > threshold) ? 'below' : 'above';
			};
			var up = function() {
				var docTop = w.scrollTop();
				var elemTop = e.offset().top;
				var elemBottom = elemTop + e.height();
				var threshold = docTop === 0 ? elemBottom - 1 : docTop + offset;
				return (elemBottom > threshold) ? 'below' : 'above';
			};

			if (!w.data.scrollInit) {
				w.data.scrollPosition = d.scrollTop();
				w.data.scrollDirection = 'down';

				var onScroll = function(e) {
					var scroll = d.scrollTop();
					w.data.scrollDirection = (scroll - w.data.scrollPosition > 0) ? 'down' : 'up';
					w.data.scrollPosition = scroll;
					scope.$apply();
				};
				w.bind('scroll', _.throttle(onScroll, 500));
				w.data.scrollInit = true;
			}
			scope.$watch(down, function downCallback(value, oldValue) {
				if (value != oldValue && value == 'above')
					scope.$eval(attrs.onScrollMiddle);
			});
			scope.$watch(up, function upCallback(value, oldValue) {
				if (value != oldValue && value == 'below')
					scope.$eval(attrs.onScrollMiddle);
			});
		}
	};
});

/**
 * Scrolls to the element if the value is true and the attribute is not fully
 * visible, unless the attribute scroll-to-force is true
 */
module.directive('scrollTo', ['$timeout', function($timeout) {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {
			scope.$watch(attrs.scrollTo, function(value) {
				if (!value)
					return;
				var force = scope.$eval(attrs.scrollToForce);

				// timeout here to execute after dom update
				$timeout(function() {
					var docTop = $(window).scrollTop();
					var docBottom = docTop + $(window).height();

					var elemTop = $(element).offset().top;
					var elemBottom = elemTop + $(element).height();

					if (!force && (elemTop > docTop) && (elemBottom < docBottom)) {
						// element is entirely visible
						return;
					} else {
						var offset = parseInt(attrs.scrollToOffset, 10);
						var scrollTop = $(element).offset().top + offset;
						$('html, body').animate({
							scrollTop : scrollTop
						}, 0);
					}
				});
			});
		}
	};
}]);

/**
 * Prevent mousewheel scrolling from propagating to the parent when scrollbar
 * reaches top or bottom
 */
module.directive('mousewheelScrolling', function() {
	return {
		restrict : 'A',
		link : function(scope, elem, attr) {
			elem.bind('mousewheel', function(e, d) {
				var t = $(this);
				if (d > 0 && t.scrollTop() === 0) {
					e.preventDefault();
				} else {
					if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
						e.preventDefault();
					}
				}
			});
		}
	};
});

/**
 * Needed to use recursive directives. Wrap a recursive element with a
 * <recursive> tag
 */
module.directive('recursive', ['$compile', function($compile) {
	return {
		restrict : 'E',
		priority : 100000,
		compile : function(tElement, tAttr) {
			var contents = tElement.contents().remove();
			var compiledContents = null;
			return function(scope, iElement, iAttr) {
				if (!compiledContents) {
					compiledContents = $compile(contents);
				}
				iElement.append(compiledContents(scope, function(clone) {
					return clone;
				}));
			};
		}
	};
}]);

/**
 * Reusable category component
 */
module.directive('category', [function() {
	return {
		scope : {
			node : '=',
			level : '=',
			selectedType : '=',
			selectedId : '=',
			showLabel : '=',
			showChildren : '=',
			unreadCount : '&'
		},
		restrict : 'E',
		replace : true,
		templateUrl : 'templates/_category.html',
		controller : ['$scope', '$state', '$dialog', 'FeedService', 'CategoryService', 'SettingsService', 'MobileService',
				function($scope, $state, $dialog, FeedService, CategoryService, SettingsService, MobileService) {
					$scope.settingsService = SettingsService;

					$scope.getClass = function(level) {
						if ($scope.showLabel) {
							return 'indent' + level;
						}
					};

					$scope.categoryLabel = function(category) {
						return $scope.showLabel !== true ? $scope.showLabel : category.name;
					};

					$scope.categoryCountLabel = function(category) {
						var count = $scope.unreadCount({
							category : category
						});
						var label = '';
						if (count > 0) {
							label = '(' + count + ')';
						}
						return label;
					};

					$scope.feedCountLabel = function(feed) {
						var label = '';
						if (feed.unread > 0) {
							label = '(' + feed.unread + ')';
						}
						return label;
					};

					$scope.feedClicked = function(id, event) {
						// Could be called by a middle click
						if (!event || (!event.ctrlKey && event.which == 1)) {
							MobileService.toggleLeftMenu();
							if ($scope.selectedType == 'feed' && id == $scope.selectedId) {
								$scope.$emit('emitReload');
							} else {
								$state.transitionTo('feeds.view', {
									_type : 'feed',
									_id : id
								});
							}

							if (event) {
								event.preventDefault();
								event.stopPropagation();
							}
						}
					};

					$scope.categoryClicked = function(id) {
						MobileService.toggleLeftMenu();
						if ($scope.selectedType == 'category' && id == $scope.selectedId) {
							$scope.$emit('emitReload');
						} else {
							$state.transitionTo('feeds.view', {
								_type : 'category',
								_id : id
							});
						}
					};

					$scope.showFeedDetails = function(feed) {
						$state.transitionTo('feeds.feed_details', {
							_id : feed.id
						});
					};

					$scope.showCategoryDetails = function(category) {
						$state.transitionTo('feeds.category_details', {
							_id : category.id
						});
					};

					$scope.toggleCategory = function(category, event) {
						event.preventDefault();
						event.stopPropagation();
						category.expanded = !category.expanded;
						if (category.id == 'all') {
							return;
						}
						CategoryService.collapse({
							id : category.id,
							collapse : !category.expanded
						});
					};
				}]
	};
}]);

/**
 * Reusable spinner component
 */
module.directive('spinner', function() {
	return {
		scope : {
			shown : '='
		},
		restrict : 'A',
		link : function($scope, element) {
			element.addClass('spinner');
			var opts = {
				lines : 11, // The number of lines to draw
				length : 5, // The length of each line
				width : 3, // The line thickness
				radius : 8, // The radius of the inner circle
				corners : 1, // Corner roundness (0..1)
				rotate : 0, // The rotation offset
				color : '#000', // #rgb or #rrggbb
				speed : 1.3, // Rounds per second
				trail : 60, // Afterglow percentage
				shadow : false, // Whether to render a shadow
				hwaccel : true, // Whether to use hardware acceleration
				zIndex : 2e9, // The z-index (defaults to 2000000000)
				top : 'auto', // Top position relative to parent in px
				left : 'auto' // Left position relative to parent in px
			};
			var spinner = new Spinner(opts);
			$scope.$watch('shown', function(shown) {
				if (shown) {
					spinner.spin();
					element.append(spinner.el);
				} else {
					spinner.stop();
				}
			});
		}
	};
});

module.directive('draggable', function() {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {
			element.draggable({
				revert : 'invalid',
				helper : 'clone',
				distance : 10,
				axis : 'y'
			}).data('source', scope.$eval(attrs.draggable));
		}
	};
});

module.directive('droppable', ['CategoryService', 'FeedService', function(CategoryService, FeedService) {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {
			element.droppable({
				tolerance : 'pointer',
				over : function(event, ui) {

				},
				drop : function(event, ui) {
					var draggable = angular.element(ui.draggable);

					var source = draggable.data('source');
					var target = scope.$eval(attrs.droppable);

					if (angular.equals(source, target)) {
						return;
					}

					var data = {
						id : source.id,
						name : source.name
					};

					if (source.children) {
						// source is a category

					} else {
						// source is a feed

						if (target.children) {
							// target is a category
							data.categoryId = target.id;
							data.position = 0;
						} else {
							// target is a feed
							data.categoryId = target.categoryId;
							data.position = target.position;
						}

						FeedService.modify(data, function() {
							CategoryService.init();
						});
					}
					scope.$apply();
				}
			});
		}
	};
}]);

module.filter('highlight', function() {
	return function(html, keywords) {
		if (keywords) {
			var tokens = keywords.split(' ');
			for ( var i = 0; i < tokens.length; i++) {
				var expr = new RegExp(tokens[i], 'gi');
				var container = $('').html(html);
				var elements = container.find('*').addBack();
				var textNodes = elements.not('iframe').contents().not(elements);
				textNodes.each(function() {
					var replaced = this.nodeValue.replace(expr, '$&');
					$('').html(replaced).insertBefore(this);
					$(this).remove();
				});
				return container.html();
			}
		}
		return html;
	};
});<commit_msg>fix jslint warning<commit_after>var module = angular.module('commafeed.directives', []);

module.directive('focus', ['$timeout', function($timeout) {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {
			scope.$watch(attrs.focus, function(value) {
				if (!value)
					return;
				$timeout(function() {
					$(element).focus();
				});
			});
		}
	};
}]);

/**
 * Open a popup window pointing to the url in the href attribute
 */
module.directive('popup', function() {
	var opts = 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=800';
	return {
		link : function(scope, elm, attrs) {
			elm.bind('click', function(event) {
				window.open(this.href, '', opts);
				event.preventDefault();
			});
		}
	};
});

/**
 * Reusable favicon component
 */
module.directive('favicon', function() {
	var tpl = '';
	return {
		restrict : 'E',
		scope : {
			url : '='
		},
		replace : true,
		template : tpl
	};
});

/**
 * Support for the blur event
 */
module.directive('ngBlur', function() {
	return {
		restrict : 'A',
		link : function(scope, elm, attrs) {
			elm.bind('blur', function() {
				scope.$apply(attrs.ngBlur);
			});
		}
	};
});

/**
 * Fired when the top of the element is not visible anymore
 */
module.directive('onScrollMiddle', function() {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {

			var w = $(window);
			var e = $(element);
			var d = $(document);

			var offset = parseInt(attrs.onScrollMiddleOffset, 10);

			var down = function() {
				var docTop = w.scrollTop();
				var elemTop = e.offset().top;
				var threshold = docTop === 0 ? elemTop - 1 : docTop + offset;
				return (elemTop > threshold) ? 'below' : 'above';
			};
			var up = function() {
				var docTop = w.scrollTop();
				var elemTop = e.offset().top;
				var elemBottom = elemTop + e.height();
				var threshold = docTop === 0 ? elemBottom - 1 : docTop + offset;
				return (elemBottom > threshold) ? 'below' : 'above';
			};

			if (!w.data.scrollInit) {
				w.data.scrollPosition = d.scrollTop();
				w.data.scrollDirection = 'down';

				var onScroll = function(e) {
					var scroll = d.scrollTop();
					w.data.scrollDirection = (scroll - w.data.scrollPosition > 0) ? 'down' : 'up';
					w.data.scrollPosition = scroll;
					scope.$apply();
				};
				w.bind('scroll', _.throttle(onScroll, 500));
				w.data.scrollInit = true;
			}
			scope.$watch(down, function downCallback(value, oldValue) {
				if (value != oldValue && value == 'above')
					scope.$eval(attrs.onScrollMiddle);
			});
			scope.$watch(up, function upCallback(value, oldValue) {
				if (value != oldValue && value == 'below')
					scope.$eval(attrs.onScrollMiddle);
			});
		}
	};
});

/**
 * Scrolls to the element if the value is true and the attribute is not fully
 * visible, unless the attribute scroll-to-force is true
 */
module.directive('scrollTo', ['$timeout', function($timeout) {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {
			scope.$watch(attrs.scrollTo, function(value) {
				if (!value)
					return;
				var force = scope.$eval(attrs.scrollToForce);

				// timeout here to execute after dom update
				$timeout(function() {
					var docTop = $(window).scrollTop();
					var docBottom = docTop + $(window).height();

					var elemTop = $(element).offset().top;
					var elemBottom = elemTop + $(element).height();

					if (!force && (elemTop > docTop) && (elemBottom < docBottom)) {
						// element is entirely visible
						return;
					} else {
						var offset = parseInt(attrs.scrollToOffset, 10);
						var scrollTop = $(element).offset().top + offset;
						$('html, body').animate({
							scrollTop : scrollTop
						}, 0);
					}
				});
			});
		}
	};
}]);

/**
 * Prevent mousewheel scrolling from propagating to the parent when scrollbar
 * reaches top or bottom
 */
module.directive('mousewheelScrolling', function() {
	return {
		restrict : 'A',
		link : function(scope, elem, attr) {
			elem.bind('mousewheel', function(e, d) {
				var t = $(this);
				if (d > 0 && t.scrollTop() === 0) {
					e.preventDefault();
				} else {
					if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
						e.preventDefault();
					}
				}
			});
		}
	};
});

/**
 * Needed to use recursive directives. Wrap a recursive element with a
 * <recursive> tag
 */
module.directive('recursive', ['$compile', function($compile) {
	return {
		restrict : 'E',
		priority : 100000,
		compile : function(tElement, tAttr) {
			var contents = tElement.contents().remove();
			var compiledContents = null;
			return function(scope, iElement, iAttr) {
				if (!compiledContents) {
					compiledContents = $compile(contents);
				}
				iElement.append(compiledContents(scope, function(clone) {
					return clone;
				}));
			};
		}
	};
}]);

/**
 * Reusable category component
 */
module.directive('category', [function() {
	return {
		scope : {
			node : '=',
			level : '=',
			selectedType : '=',
			selectedId : '=',
			showLabel : '=',
			showChildren : '=',
			unreadCount : '&'
		},
		restrict : 'E',
		replace : true,
		templateUrl : 'templates/_category.html',
		controller : ['$scope', '$state', '$dialog', 'FeedService', 'CategoryService', 'SettingsService', 'MobileService',
				function($scope, $state, $dialog, FeedService, CategoryService, SettingsService, MobileService) {
					$scope.settingsService = SettingsService;

					$scope.getClass = function(level) {
						if ($scope.showLabel) {
							return 'indent' + level;
						}
					};

					$scope.categoryLabel = function(category) {
						return $scope.showLabel !== true ? $scope.showLabel : category.name;
					};

					$scope.categoryCountLabel = function(category) {
						var count = $scope.unreadCount({
							category : category
						});
						var label = '';
						if (count > 0) {
							label = '(' + count + ')';
						}
						return label;
					};

					$scope.feedCountLabel = function(feed) {
						var label = '';
						if (feed.unread > 0) {
							label = '(' + feed.unread + ')';
						}
						return label;
					};

					$scope.feedClicked = function(id, event) {
						// Could be called by a middle click
						if (!event || (!event.ctrlKey && event.which == 1)) {
							MobileService.toggleLeftMenu();
							if ($scope.selectedType == 'feed' && id == $scope.selectedId) {
								$scope.$emit('emitReload');
							} else {
								$state.transitionTo('feeds.view', {
									_type : 'feed',
									_id : id
								});
							}

							if (event) {
								event.preventDefault();
								event.stopPropagation();
							}
						}
					};

					$scope.categoryClicked = function(id) {
						MobileService.toggleLeftMenu();
						if ($scope.selectedType == 'category' && id == $scope.selectedId) {
							$scope.$emit('emitReload');
						} else {
							$state.transitionTo('feeds.view', {
								_type : 'category',
								_id : id
							});
						}
					};

					$scope.showFeedDetails = function(feed) {
						$state.transitionTo('feeds.feed_details', {
							_id : feed.id
						});
					};

					$scope.showCategoryDetails = function(category) {
						$state.transitionTo('feeds.category_details', {
							_id : category.id
						});
					};

					$scope.toggleCategory = function(category, event) {
						event.preventDefault();
						event.stopPropagation();
						category.expanded = !category.expanded;
						if (category.id == 'all') {
							return;
						}
						CategoryService.collapse({
							id : category.id,
							collapse : !category.expanded
						});
					};
				}]
	};
}]);

/**
 * Reusable spinner component
 */
module.directive('spinner', function() {
	return {
		scope : {
			shown : '='
		},
		restrict : 'A',
		link : function($scope, element) {
			element.addClass('spinner');
			var opts = {
				lines : 11, // The number of lines to draw
				length : 5, // The length of each line
				width : 3, // The line thickness
				radius : 8, // The radius of the inner circle
				corners : 1, // Corner roundness (0..1)
				rotate : 0, // The rotation offset
				color : '#000', // #rgb or #rrggbb
				speed : 1.3, // Rounds per second
				trail : 60, // Afterglow percentage
				shadow : false, // Whether to render a shadow
				hwaccel : true, // Whether to use hardware acceleration
				zIndex : 2e9, // The z-index (defaults to 2000000000)
				top : 'auto', // Top position relative to parent in px
				left : 'auto' // Left position relative to parent in px
			};
			var spinner = new Spinner(opts);
			$scope.$watch('shown', function(shown) {
				if (shown) {
					spinner.spin();
					element.append(spinner.el);
				} else {
					spinner.stop();
				}
			});
		}
	};
});

module.directive('draggable', function() {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {
			element.draggable({
				revert : 'invalid',
				helper : 'clone',
				distance : 10,
				axis : 'y'
			}).data('source', scope.$eval(attrs.draggable));
		}
	};
});

module.directive('droppable', ['CategoryService', 'FeedService', function(CategoryService, FeedService) {
	return {
		restrict : 'A',
		link : function(scope, element, attrs) {
			element.droppable({
				tolerance : 'pointer',
				over : function(event, ui) {

				},
				drop : function(event, ui) {
					var draggable = angular.element(ui.draggable);

					var source = draggable.data('source');
					var target = scope.$eval(attrs.droppable);

					if (angular.equals(source, target)) {
						return;
					}

					var data = {
						id : source.id,
						name : source.name
					};

					if (source.children) {
						// source is a category

					} else {
						// source is a feed

						if (target.children) {
							// target is a category
							data.categoryId = target.id;
							data.position = 0;
						} else {
							// target is a feed
							data.categoryId = target.categoryId;
							data.position = target.position;
						}

						FeedService.modify(data, function() {
							CategoryService.init();
						});
					}
					scope.$apply();
				}
			});
		}
	};
}]);

module.filter('highlight', function() {
	return function(html, keywords) {
		if (keywords) {
			var handleKeyword = function(token, html) {
				var expr = new RegExp(token, 'gi');
				var container = $('').html(html);
				var elements = container.find('*').addBack();
				var textNodes = elements.not('iframe').contents().not(elements);
				textNodes.each(function() {
					var replaced = this.nodeValue.replace(expr, '$&');
					$('').html(replaced).insertBefore(this);
					$(this).remove();
				});
				return container.html();
			};

			var tokens = keywords.split(' ');
			for ( var i = 0; i < tokens.length; i++) {
				html = handleKeyword(tokens[i], html);
			}
		}
		return html;
	};
});<|endoftext|>