<commit_before>'use strict';

var agentSchema = require('../schemata/agent'),
    https = require('https'),
    utils = require('../lib/utils'),
    q = require('q');

/**
 * Retrieve the conversation, if it exists
 *
 * @param Object - message
 * @param Object - agent
 *
 * @return conversationModel
 */
exports.loadConversation = function(message, agent) {

    var deferred = q.defer();

    // Is this an incoming, or outgoing message?
    var incoming = true;
    if (message.sender === agent.email) {
      incoming = false;
    }

    _getRole(message, incoming).
        then(function(role) {
            if (message.conversationId) {
              var agentDb = new agentSchema(agent.email);
              agentDb.conversationModel.findOne({ conversationId: message.conversationId },
                  function(err, conversation) {
                        if (err) {
                          deferred.reject(err);
                        }
                        else if (!conversation) {
                          _startNewConversation(message, agent, message.performative, role).
                            then(function(conversation) {
                                deferred.resolve(conversation);
                              }).
                            catch(function(err) {
                                deferred.reject(err);
                              });
                        }
                        else {
                          deferred.resolve(conversation);
                        }
                    });
            }
            else {
              _startNewConversation(message, agent, message.performative, role).
                then(function(conversation) {
                    deferred.resolve(conversation);
                  }).
                catch(function(err) {
                    deferred.reject(err);
                  });
            }
      }).
    catch(function(err) {
        deferred.reject(err);
      });

    return deferred.promise;
  };

/**
 * Start a new conversation
 *
 * @param Object
 * @param Object
 * @param string
 * @param string
 *
 * @return promise
 */
var _startNewConversation = function(message, agent, type, role) {
    var deferred = q.defer();

    if (message.performative === type) {
      var conversationId = message.conversationId;
      if (!conversationId) {
        conversationId = message.sender + ':' + Date.now().toString();
      }

      // Get default gebo if not set
      var gebo = message.gebo;
      if (!gebo) {
        gebo = utils.getDefaultDomain();
      }

      var agentDb = new agentSchema(agent.email);
      var conversation = new agentDb.conversationModel({
              type: type,
              role: role,
              conversationId: conversationId,
              gebo: gebo,
          });

      conversation.save(function(err) {
              if (err) {
                deferred.resolve(err);
              }
              else {
                deferred.resolve(conversation);
              }
            });
    }
    else {
      deferred.reject('There\'s no ' + type + ' conversation for this ' + message.performative);
    }
    return deferred.promise;
  };
exports.startNewConversation = _startNewConversation;

/**
 * getFirstUnfulfilledSocialCommitmentIndex
 *
 * @param array
 * @param string
 *
 * @return int
 */
exports.getFirstUnfulfilledSocialCommitmentIndex = function(socialCommitments, performative) {
    for (var i = 0; i < socialCommitments.length; i++) {
      if (socialCommitments[i].performative === performative &&
          socialCommitments[i].fulfilled === null) {
        return i;
      }
    }
    return -1;
  };

/**
 * Establish the role of the agent sending or
 * receiving the message
 *
 * @param Object
 * @param bool
 *
 * @promise
 */
var _getRole = function(message, incoming) {
    var deferred = q.defer();

    if (message.conversationId) {

      var email = message.sender;
      if (incoming) {
        email = message.receiver;
      }

      var agentDb = new agentSchema(email);
      agentDb.conversationModel.findOne({ conversationId: message.conversationId }, function(err, conversation) {

//              console.log('conversation',conversation);
            if (err) {
              deferred.reject(err);
            }
            if (conversation) {
              deferred.resolve(conversation.role);
            }
            else {
              switch(message.performative) {
                case 'request':
                    if (incoming) {
                      deferred.resolve('server');
                    }
                    else {
                      deferred.resolve('client');
                    }
                   break;
                case 'propose':
                    if (incoming) {
                      deferred.resolve('client');
                    }
                    else {
                      deferred.resolve('server');
                    }
                    break;
               }
            }
        });
    }
    else {
      switch(message.performative) {
        case 'request':
            if (incoming) {
              deferred.resolve('server');
            }
            else {
              deferred.resolve('client');
            }
            break;
        case 'propose':
            if (incoming) {
              deferred.resolve('client');
            }
            else {
              deferred.resolve('server');
            }
            break;
        }
    }

    return deferred.promise;
  };
exports.getRole = _getRole;

/**
 * Take a URI and extract options for making
 * an HTTPS request
 *
 * @param string
 * @param string
 * @param Object
 *
 * @return Object
 */
var _getOptions = function(uri, path, content) {
    var splitUri = uri.split('https://');
    uri = splitUri.pop();
    splitUri = uri.split(':');
    var port = '443';
    if (splitUri.length > 1) {
      port = splitUri.pop();
      uri = splitUri.pop();
    }

    return {
            host: uri,
            port: port,
            path: path,
            method: 'POST',
            rejectUnauthorized: false,
            requestCert: true,
            agent: false,
            headers: { 'Content-Type': 'application/json',
                       'Content-Length': Buffer.byteLength(JSON.stringify(content)) }
        };

  };
exports.getOptions = _getOptions;

/**
 * POST data to the server specified
 *
 * @param string
 * @param string
 * @param Object
 *
 * @return promise
 */
exports.postMessage = function(uri, path, content) {
    console.log('postMessage');
    console.log('uri', uri);
    console.log('path', path);
    var deferred = q.defer();

    var options = _getOptions(uri, path, content);
    content = JSON.stringify(content);

    console.log('options');
    console.log(options);

    var req = https.request(options, function(res) {
            var data = '';
            res.setEncoding('utf8');
            res.on('data', function(chunk) {
                    data += chunk;
                });
            res.on('end', function() {
                    data = JSON.parse(data);
                    if (data.error) {
                      deferred.reject(data.error_description);
                    }
                    else {
                      deferred.resolve(data);
                    }
                });
        }).
      on('error', function(err){
              console.log(err);
              deferred.reject(err);
        });

    req.write(content);
    req.end();

    return deferred.promise;
  };

<commit_msg>Remove email parameter when retrieving schema instances<commit_after>'use strict';

var agentSchema = require('../schemata/agent'),
    https = require('https'),
    utils = require('../lib/utils'),
    q = require('q');

/**
 * Retrieve the conversation, if it exists
 *
 * @param Object - message
 * @param Object - agent
 *
 * @return conversationModel
 */
exports.loadConversation = function(message, agent) {

    var deferred = q.defer();

    // Is this an incoming, or outgoing message?
    var incoming = true;
    if (message.sender === agent.email) {
      incoming = false;
    }

    _getRole(message, incoming).
        then(function(role) {
            if (message.conversationId) {
              var agentDb = new agentSchema();
              agentDb.conversationModel.findOne({ conversationId: message.conversationId },
                  function(err, conversation) {
                        if (err) {
                          deferred.reject(err);
                        }
                        else if (!conversation) {
                          _startNewConversation(message, agent, message.performative, role).
                            then(function(conversation) {
                                deferred.resolve(conversation);
                              }).
                            catch(function(err) {
                                deferred.reject(err);
                              });
                        }
                        else {
                          deferred.resolve(conversation);
                        }
                    });
            }
            else {
              _startNewConversation(message, agent, message.performative, role).
                then(function(conversation) {
                    deferred.resolve(conversation);
                  }).
                catch(function(err) {
                    deferred.reject(err);
                  });
            }
      }).
    catch(function(err) {
        deferred.reject(err);
      });

    return deferred.promise;
  };

/**
 * Start a new conversation
 *
 * @param Object
 * @param Object
 * @param string
 * @param string
 *
 * @return promise
 */
var _startNewConversation = function(message, agent, type, role) {
    var deferred = q.defer();

    if (message.performative === type) {
      var conversationId = message.conversationId;
      if (!conversationId) {
        conversationId = message.sender + ':' + Date.now().toString();
      }

      // Get default gebo if not set
      var gebo = message.gebo;
      if (!gebo) {
        gebo = utils.getDefaultDomain();
      }

      var agentDb = new agentSchema();
      var conversation = new agentDb.conversationModel({
              type: type,
              role: role,
              conversationId: conversationId,
              gebo: gebo,
          });

      conversation.save(function(err) {
              if (err) {
                deferred.resolve(err);
              }
              else {
                deferred.resolve(conversation);
              }
            });
    }
    else {
      deferred.reject('There\'s no ' + type + ' conversation for this ' + message.performative);
    }
    return deferred.promise;
  };
exports.startNewConversation = _startNewConversation;

/**
 * getFirstUnfulfilledSocialCommitmentIndex
 *
 * @param array
 * @param string
 *
 * @return int
 */
exports.getFirstUnfulfilledSocialCommitmentIndex = function(socialCommitments, performative) {
    for (var i = 0; i < socialCommitments.length; i++) {
      if (socialCommitments[i].performative === performative &&
          socialCommitments[i].fulfilled === null) {
        return i;
      }
    }
    return -1;
  };

/**
 * Establish the role of the agent sending or
 * receiving the message
 *
 * @param Object
 * @param bool
 *
 * @promise
 */
var _getRole = function(message, incoming) {
    var deferred = q.defer();

    if (message.conversationId) {

      var email = message.sender;
      if (incoming) {
        email = message.receiver;
      }

      var agentDb = new agentSchema();
      agentDb.conversationModel.findOne({ conversationId: message.conversationId }, function(err, conversation) {
            if (err) {
              deferred.reject(err);
            }
            if (conversation) {
              deferred.resolve(conversation.role);
            }
            else {
              switch(message.performative) {
                case 'request':
                    if (incoming) {
                      deferred.resolve('server');
                    }
                    else {
                      deferred.resolve('client');
                    }
                   break;
                case 'propose':
                    if (incoming) {
                      deferred.resolve('client');
                    }
                    else {
                      deferred.resolve('server');
                    }
                    break;
               }
            }
        });
    }
    else {
      switch(message.performative) {
        case 'request':
            if (incoming) {
              deferred.resolve('server');
            }
            else {
              deferred.resolve('client');
            }
            break;
        case 'propose':
            if (incoming) {
              deferred.resolve('client');
            }
            else {
              deferred.resolve('server');
            }
            break;
        }
    }

    return deferred.promise;
  };
exports.getRole = _getRole;

/**
 * Take a URI and extract options for making
 * an HTTPS request
 *
 * @param string
 * @param string
 * @param Object
 *
 * @return Object
 */
var _getOptions = function(uri, path, content) {
    var splitUri = uri.split('https://');
    uri = splitUri.pop();
    splitUri = uri.split(':');
    var port = '443';
    if (splitUri.length > 1) {
      port = splitUri.pop();
      uri = splitUri.pop();
    }

    return {
            host: uri,
            port: port,
            path: path,
            method: 'POST',
            rejectUnauthorized: false,
            requestCert: true,
            agent: false,
            headers: { 'Content-Type': 'application/json',
                       'Content-Length': Buffer.byteLength(JSON.stringify(content)) }
        };

  };
exports.getOptions = _getOptions;

/**
 * POST data to the server specified
 *
 * @param string
 * @param string
 * @param Object
 *
 * @return promise
 */
exports.postMessage = function(uri, path, content) {
    console.log('postMessage');
    console.log('uri', uri);
    console.log('path', path);
    var deferred = q.defer();

    var options = _getOptions(uri, path, content);
    content = JSON.stringify(content);

    console.log('options');
    console.log(options);

    var req = https.request(options, function(res) {
            var data = '';
            res.setEncoding('utf8');
            res.on('data', function(chunk) {
                    data += chunk;
                });
            res.on('end', function() {
                    data = JSON.parse(data);
                    if (data.error) {
                      deferred.reject(data.error_description);
                    }
                    else {
                      deferred.resolve(data);
                    }
                });
        }).
      on('error', function(err){
              console.log(err);
              deferred.reject(err);
        });

    req.write(content);
    req.end();

    return deferred.promise;
  };

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const TIMER_DEFAULT_STYLE =
	"position: fixed; z-index: 2147483647; " +
	"top: 0px; left: 0px; margin: 2px; padding: 4px 4px 2px 4px; " +
	"background-color: #fff; color: #000; " +
	"border: solid 1px #000; border-radius: 4px; " +
	"font: normal 12px \"Lucida Console\", Monaco, monospace; " +
	"user-select: none; -moz-user-select: none;";

const TIMER_SIZES = ["10px", "12px", "14px", "16px"];

const TIMER_LOCATIONS = [
	["0px", "", "0px", ""],
	["0px", "", "", "0px"],
	["", "0px", "", "0px"],
	["", "0px", "0px", ""]
];

const ALERT_CONTAINER_STYLE =
	"position: fixed; z-index: 2147483647; display: none; " +
	"top: 0px; left: 0px; width: 100%; height: 100%; " +
	"margin: auto; padding: 0px; " +
	"background-color: transparent;";

const ALERT_BOX_STYLE =
	"margin: auto; padding: 12px; " +
	"line-height: normal; text-align: center; " +
	"background-color: #fff; color: #000; " +
	"border: solid 1px #000; border-radius: 4px; " +
	"font: normal 16px Helvetica, Arial, sans-serif; " +
	"box-shadow: 2px 2px 2px #888; " +
	"user-select: none; -moz-user-select: none;";

const ALERT_ICON_STYLE = "padding: 0px;"

const ALERT_TEXT_STYLE = "padding: 8px 0px 0px 0px;"

const ALERT_ICON = "data:image/png;base64,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";

var gTimer;
var gAlert;

// Update timer
//
function updateTimer(text, size, location) {
	if (!text) {
		if (gTimer) {
			// Hide timer
			gTimer.hidden = true;
		}
	} else {
		if (!gTimer) {
			// Create timer
			gTimer = document.createElement("div");
			gTimer.setAttribute("style", TIMER_DEFAULT_STYLE);
			document.body.appendChild(gTimer);
		}

		// Set text
		gTimer.innerText = text;

		// Set size
		if (size >= 0 && size < TIMER_SIZES.length) {
			gTimer.style.fontSize = TIMER_SIZES[size];
		}

		// Set location
		if (location >= 0 && location < TIMER_LOCATIONS.length) {
			gTimer.style.top = TIMER_LOCATIONS[location][0];
			gTimer.style.bottom = TIMER_LOCATIONS[location][1];
			gTimer.style.left = TIMER_LOCATIONS[location][2];
			gTimer.style.right = TIMER_LOCATIONS[location][3];
		}

		// Show timer
		gTimer.hidden = false;
	}
}

// Show alert message
//
function showAlert(text) {
	let alertBox, alertIcon, alertText;

	if (!gAlert) {
		// Create container
		gAlert = document.createElement("div");
		gAlert.setAttribute("style", ALERT_CONTAINER_STYLE);
		document.body.appendChild(gAlert);

		// Create message box
		alertBox = document.createElement("div");
		alertBox.setAttribute("style", ALERT_BOX_STYLE);
		alertBox.addEventListener("click", hideAlert);
		alertIcon = document.createElement("img");
		alertIcon.setAttribute("style", ALERT_ICON_STYLE);
		alertIcon.setAttribute("src", ALERT_ICON);
		alertBox.appendChild(alertIcon);
		alertText = document.createElement("div");
		alertText.setAttribute("style", ALERT_TEXT_STYLE);
		alertBox.appendChild(alertText);
		gAlert.appendChild(alertBox);
	}

	// Set text
	alertText.innerText = text;

	// Show timer
	gAlert.style.display = "flex";
}

// Hide alert message
//
function hideAlert() {
	if (gAlert) {
		gAlert.style.display = "none";
	}
}

// Check page for keyword(s)
//
function checkKeyword(keywordRE) {
	// Create regular expression (case insensitive)
	let regexp = new RegExp(keywordRE, "i");

	// Get all text nodes in document
	let textNodes = document.evaluate(
		"//text()", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

	//console.log("Checking " + textNodes.snapshotLength + " text node(s) for keyword(s)...");

	for (let i = 0; i < textNodes.snapshotLength; i++) {
		if (regexp.test(textNodes.snapshotItem(i).data)) {
			return true; // keyword(s) found
		}
	}

	return false; // keyword(s) not found
}

/*** EVENT HANDLERS BEGIN HERE ***/

function handleMessage(message, sender, sendResponse) {
	if (message.type == "timer") {
		updateTimer(message.text, message.size, message.location);
	} else if (message.type == "alert") {
		showAlert(message.text);
	} else if (message.type == "keyword") {
		let keyword = checkKeyword(message.keywordRE);
		sendResponse(keyword);
	}
}

browser.runtime.onMessage.addListener(handleMessage);
<commit_msg>Fix issue: timer disappears on some sites<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const TIMER_DEFAULT_STYLE =
	"position: fixed; z-index: 2147483647; " +
	"top: 0px; left: 0px; margin: 2px; padding: 4px 4px 2px 4px; " +
	"background-color: #fff; color: #000; " +
	"border: solid 1px #000; border-radius: 4px; " +
	"font: normal 12px \"Lucida Console\", Monaco, monospace; " +
	"user-select: none; -moz-user-select: none;";

const TIMER_SIZES = ["10px", "12px", "14px", "16px"];

const TIMER_LOCATIONS = [
	["0px", "", "0px", ""],
	["0px", "", "", "0px"],
	["", "0px", "", "0px"],
	["", "0px", "0px", ""]
];

const ALERT_CONTAINER_STYLE =
	"position: fixed; z-index: 2147483647; display: none; " +
	"top: 0px; left: 0px; width: 100%; height: 100%; " +
	"margin: auto; padding: 0px; " +
	"background-color: transparent;";

const ALERT_BOX_STYLE =
	"margin: auto; padding: 12px; " +
	"line-height: normal; text-align: center; " +
	"background-color: #fff; color: #000; " +
	"border: solid 1px #000; border-radius: 4px; " +
	"font: normal 16px Helvetica, Arial, sans-serif; " +
	"box-shadow: 2px 2px 2px #888; " +
	"user-select: none; -moz-user-select: none;";

const ALERT_ICON_STYLE = "padding: 0px;"

const ALERT_TEXT_STYLE = "padding: 8px 0px 0px 0px;"

const ALERT_ICON = "data:image/png;base64,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";

var gTimer;
var gAlert;

// Update timer
//
function updateTimer(text, size, location) {
	if (!text) {
		if (gTimer) {
			// Hide timer
			gTimer.hidden = true;
		}
	} else {
		if (!gTimer) {
			// Create timer
			gTimer = document.createElement("div");
			gTimer.setAttribute("style", TIMER_DEFAULT_STYLE);
		}

		if (!document.body.contains(gTimer)) {
			// Insert timer at end of document body
			document.body.appendChild(gTimer);
		}

		// Set text
		gTimer.innerText = text;

		// Set size
		if (size >= 0 && size < TIMER_SIZES.length) {
			gTimer.style.fontSize = TIMER_SIZES[size];
		}

		// Set location
		if (location >= 0 && location < TIMER_LOCATIONS.length) {
			gTimer.style.top = TIMER_LOCATIONS[location][0];
			gTimer.style.bottom = TIMER_LOCATIONS[location][1];
			gTimer.style.left = TIMER_LOCATIONS[location][2];
			gTimer.style.right = TIMER_LOCATIONS[location][3];
		}

		// Show timer
		gTimer.hidden = false;
	}
}

// Show alert message
//
function showAlert(text) {
	let alertBox, alertIcon, alertText;

	if (!gAlert) {
		// Create container
		gAlert = document.createElement("div");
		gAlert.setAttribute("style", ALERT_CONTAINER_STYLE);
		document.body.appendChild(gAlert);

		// Create message box
		alertBox = document.createElement("div");
		alertBox.setAttribute("style", ALERT_BOX_STYLE);
		alertBox.addEventListener("click", hideAlert);
		alertIcon = document.createElement("img");
		alertIcon.setAttribute("style", ALERT_ICON_STYLE);
		alertIcon.setAttribute("src", ALERT_ICON);
		alertBox.appendChild(alertIcon);
		alertText = document.createElement("div");
		alertText.setAttribute("style", ALERT_TEXT_STYLE);
		alertBox.appendChild(alertText);
		gAlert.appendChild(alertBox);
	}

	// Set text
	alertText.innerText = text;

	// Show timer
	gAlert.style.display = "flex";
}

// Hide alert message
//
function hideAlert() {
	if (gAlert) {
		gAlert.style.display = "none";
	}
}

// Check page for keyword(s)
//
function checkKeyword(keywordRE) {
	// Create regular expression (case insensitive)
	let regexp = new RegExp(keywordRE, "i");

	// Get all text nodes in document
	let textNodes = document.evaluate(
		"//text()", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

	//console.log("Checking " + textNodes.snapshotLength + " text node(s) for keyword(s)...");

	for (let i = 0; i < textNodes.snapshotLength; i++) {
		if (regexp.test(textNodes.snapshotItem(i).data)) {
			return true; // keyword(s) found
		}
	}

	return false; // keyword(s) not found
}

/*** EVENT HANDLERS BEGIN HERE ***/

function handleMessage(message, sender, sendResponse) {
	if (message.type == "timer") {
		updateTimer(message.text, message.size, message.location);
	} else if (message.type == "alert") {
		showAlert(message.text);
	} else if (message.type == "keyword") {
		let keyword = checkKeyword(message.keywordRE);
		sendResponse(keyword);
	}
}

browser.runtime.onMessage.addListener(handleMessage);
<|endoftext|>


<commit_before>import expect from 'expect';
import filter from '../src/filter';

describe('filter', () => {
  it('should keep items the callback returns truthy for', () => {
    const array = [1, 2, 3, 4];
    const callback = (_, item) => item % 2;
    const result = filter(array, callback);

    expect(result).toContain(1);
    expect(result).toContain(3);
    expect(result).toNotContain(2);
    expect(result).toNotContain(4);
  });

  it('should pass argument to callback', () => {
    const array = [1];
    const callback = (argument) => {
      expect(argument).toEqual('foo');
    };

    filter(array, callback, 'foo');
  });
});
<commit_msg>Add test for filter change.<commit_after>import expect from 'expect';
import filter from '../src/filter';

describe('filter', () => {
  it('should keep items the callback returns truthy for', () => {
    const array = [1, 2, 3, 4];
    const callback = (_, item) => item % 2;
    const result = filter(array, callback);

    expect(result).toContain(1);
    expect(result).toContain(3);
    expect(result).toNotContain(2);
    expect(result).toNotContain(4);
  });

  it('should pass argument to callback', () => {
    const array = [1];
    const callback = (argument) => {
      expect(argument).toEqual('foo');
    };

    filter(array, callback, 'foo');
  });

  it('should iterate over items added while filtering', () => {
    const array = [1];
    let pushed = false;
    const callback = () => {
      if (!pushed) {
        array.push(2);
        pushed = true;
      }
      return true;
    };

    const result = filter(array, callback);

    expect(result).toEqual([1, 2]);
  });
});
<|endoftext|>


<commit_before>function makeUrl() {
 document.getElementById('fFile').innerHTML = '<input type="url" id="urlT">';
 document.getElementById('dRC').innerHTML = 'Download';
}

function mpCutI() {
 document.getElementById('display').innerHTML = 'Browser Not Supported

Mp3 Conversion By mp3cut.net

Not a fan of your website in my iFrames?
Email Me At darkheart527s@gmail.com with proof of ownership
';
}

function uUrl() {
 document.getElementById('urlA').href = document.getElementById('urlT').value;
}
<commit_msg>Update convertJava.js<commit_after>function makeUrl() {
 document.getElementById('fFile').innerHTML = '<input type="url" id="urlT">';
 document.getElementById('dRC').innerHTML = 'Download';
}

function mpCutI() {
 //document.getElementById('display').innerHTML = 'Browser Not Supported

Mp3 Conversion By mp3cut.net

Not a fan of your website in my iFrames?
Email Me At darkheart527s@gmail.com with proof of ownership
';
}

function uUrl() {
 document.getElementById('urlA').href = document.getElementById('urlT').value;
}
<|endoftext|>


<commit_before>var LookListenRead = (function() {

  if (!Array.prototype.last){
    Array.prototype.last = function(){
      return this[this.length - 1];
    };
  };

  var info = s => console.log("LookListenRead: " + s);

  var playing = false,
      chunkIx = null,
      blockIx = null,
      chunks = [],
      blocks = [],
      options, voice;

  var commands = {
    next: () => gotoChunk(chunkIx + 1),
    previous: () => gotoChunk(chunkIx - 1),
    nextBlock: () => gotoBlock(blockIx + 1),
    previousBlock: () => gotoBlock(blockIx - 1),
    pauseOrResume: pauseOrResume,
    slowdown: () => speedup(-10),
    speedup: () => speedup(10),
    exitMode: exitMode
  };

  function initVoice(callback) {
    var voices = speechSynthesis.getVoices();
    info("Found voices: " + voices.length);
    voice = voices.find(v => v.name == options.voice);
    if (voice) {
      callback();
    } else {
      info("Selected voice not found. Waiting for voices...");
      speechSynthesis.onvoiceschanged = () => initVoice(callback)
    }
  }

  function speakText(text, callback) {
    var msg = new SpeechSynthesisUtterance(text);
    msg.rate = options.rate;
    msg.voice = voice;
    msg.onend = e => playing && callback();
    msg.onerror = console.log;
    speechSynthesis.speak(msg);
  }

  /* Chunk is a set of blast nodes sharing common block-style parent. Nodes in chunk are
     played and highlighted together. */
  function initChunks() {

    function displayType(elem) {
      return (elem.currentStyle || window.getComputedStyle(elem, "")).display;
    }

    function closestBlock(elem) {
      while (displayType(elem) !== 'block' && elem.parentNode) { elem = elem.parentNode }
      return elem;
    }

    document.normalize();
    $("body").blast({ delimiter: options.delimiter });
    var regexFilter = new RegExp(options.regexFilter);
    var regexIgnore = new RegExp(options.regexIgnore);
    Array.from(document.getElementsByClassName("blast"))
      .filter(span => regexFilter.test(span.innerText) && !regexIgnore.test(span.innerText))
         .forEach(span => {
           var chunk = chunks.length > 0 ? chunks.last() : null;
           var block = closestBlock(span);
           if (chunk && chunk.block === block &&
               chunk.text.length + span.innerText.length <= options.maxLength
           ) {
             chunk.nodes.push(span);
             chunk.text += ' ' + span.innerText;
           } else {
             chunks.push({nodes:[span], text:span.innerText, block: block});
             var i = chunks.length - 1;
             blocks.length > 0 && blocks.last().node == block ?
                     blocks.last().chunkIxs.push(i) :
                     blocks.push({node: block, chunkIxs: [i]});
           }
         });
  }

  // Position where to start new playback: selected node or the last chunkIx or first chunk
  function startPosition() {
    try {
      var selNode = getSelection().getRangeAt(0).commonAncestorContainer;
      var blastNode = selNode.nodeType == Node.TEXT_NODE ? selNode.parentNode :
                 selNode.getElementsByClassName("blast")[0];
      return chunks.findIndex(chunk => chunk.nodes.includes(blastNode));
    } catch (err) {
      return chunkIx ? chunkIx : 0;
    }
  }

  function bringToRange(i, xs) {
    return i == null ? i : Math.max(0, Math.min(xs.length - 1, i));
  }

  // Set new chunkIx and update highlighted chunk.
  function setChunkIx(i) {
    if (chunkIx !== null)
      chunks[chunkIx].nodes.forEach(node => $(node).removeClass("llr-active"));
    chunkIx = i;
    blockIx = blocks.findIndex(block => block.chunkIxs.includes(chunkIx));
    if (chunkIx !== null)
      chunks[chunkIx].nodes.forEach(node => $(node).addClass("llr-active"));
  }

  function play() {
    chunkIx != null || setChunkIx(startPosition());
    playing = true;
    speakText(chunks[chunkIx].text, () => {
      if (chunkIx < chunks.length - 1) {
        setChunkIx(chunkIx + 1);
        play();
      } else {
        pause();
      }
    });
  }

  function pause() {
    playing = false;
    speechSynthesis.cancel();
  }

  function pauseOrResume() {
    playing ? pause() : play();
  }

  function pauseAndResume() {
    pause();
    setTimeout(play, 30);
  }

  function stop() {
    pause();
    setChunkIx(null);
  }

  function gotoBlock(i) {
    i = bringToRange(i, blocks);
    blockIx === i || gotoChunk(blocks[i].chunkIxs[0]);
  }

  function gotoChunk(i, startPlaying) {
    i = bringToRange(i, chunks);
    if (chunkIx !== i) {
      setChunkIx(i);
      (startPlaying || playing) && pauseAndResume();
    }
  }

  function speedup(percentage) {
    options.rate *= 1 + 0.01*percentage;
    playing && pauseAndResume();
  }

  function bindHotkey(hotkey, callback) {
    Mousetrap.bind(hotkey, e => { callback(); return false });
  }

  function enterMode() {
    initChunks();
    initVoice(() => {
      Mousetrap.unbind(options.hotkeys.enterMode);
      Object.keys(commands).forEach(cmd => bindHotkey(options.hotkeys[cmd], commands[cmd]));
      chunks.forEach((chunk, i) => chunk.nodes.forEach(node => node.ondblclick = e => {
          gotoChunk(i, true);
          e.stopPropagation();
      }));
      blocks.forEach(block => block.node.ondblclick = e => {
        gotoChunk(block.chunks[0], true);
        e.stopPropagation();
      });
      info("Enter speaking mode");
    });
  }

  function exitMode() {
    stop();
    Object.keys(commands).forEach(cmd => Mousetrap.unbind(options.hotkeys[cmd]));
    bindHotkey(options.hotkeys.exitMode, exitMode);
    bindHotkey(options.hotkeys.enterMode, enterMode);
    info("Exit speaking mode");
  }

  return opts => {
    options = opts;
    bindHotkey(options.hotkeys.enterMode, enterMode);
  }

})();

chrome.storage.sync.get(defaults, LookListenRead);
<commit_msg>Fix #6, scroll to active chunk if it's out of the view.<commit_after>var LookListenRead = (function() {

  if (!Array.prototype.last){
    Array.prototype.last = function(){
      return this[this.length - 1];
    };
  };

  var info = s => console.log("LookListenRead: " + s);

  var playing = false,
      chunkIx = null,
      blockIx = null,
      chunks = [],
      blocks = [],
      options, voice;

  var commands = {
    next: () => gotoChunk(chunkIx + 1) && reset(),
    previous: () => gotoChunk(chunkIx - 1) && reset(),
    nextBlock: () => gotoBlock(blockIx + 1),
    previousBlock: () => gotoBlock(blockIx - 1),
    pauseOrResume: pauseOrResume,
    slowdown: () => speedup(-10),
    speedup: () => speedup(10),
    exitMode: exitMode
  };

  function initVoice(callback) {
    var voices = speechSynthesis.getVoices();
    info("Found voices: " + voices.length);
    voice = voices.find(v => v.name == options.voice);
    if (voice) {
      callback();
    } else {
      info("Selected voice not found. Waiting for voices...");
      speechSynthesis.onvoiceschanged = () => initVoice(callback)
    }
  }

  function speakText(text, callback) {
    var msg = new SpeechSynthesisUtterance(text);
    msg.rate = options.rate;
    msg.voice = voice;
    msg.onend = e => playing && callback();
    msg.onerror = console.log;
    speechSynthesis.speak(msg);
  }

  /* Chunk is a set of blast nodes sharing common block-style parent. Nodes in chunk are
     played and highlighted together. */
  function initChunks() {

    function displayType(elem) {
      return (elem.currentStyle || window.getComputedStyle(elem, "")).display;
    }

    function closestBlock(elem) {
      while (displayType(elem) !== 'block' && elem.parentNode) { elem = elem.parentNode }
      return elem;
    }

    document.normalize();
    $("body").blast({ delimiter: options.delimiter });
    var regexFilter = new RegExp(options.regexFilter);
    var regexIgnore = new RegExp(options.regexIgnore);
    Array.from(document.getElementsByClassName("blast"))
      .filter(span => regexFilter.test(span.innerText) && !regexIgnore.test(span.innerText))
         .forEach(span => {
           var chunk = chunks.length > 0 ? chunks.last() : null;
           var block = closestBlock(span);
           if (chunk && chunk.block === block &&
               chunk.text.length + span.innerText.length <= options.maxLength
           ) {
             chunk.nodes.push(span);
             chunk.text += ' ' + span.innerText;
           } else {
             chunks.push({nodes:[span], text:span.innerText, block: block});
             var i = chunks.length - 1;
             blocks.length > 0 && blocks.last().node == block ?
                     blocks.last().chunkIxs.push(i) :
                     blocks.push({node: block, chunkIxs: [i]});
           }
         });
  }

  function bringToRange(i, xs) {
    return i == null ? i : Math.max(0, Math.min(xs.length - 1, i));
  }

  function play() {
    chunkIx != null || gotoChunk(0);
    playing = true;
    speakText(chunks[chunkIx].text, () =>
      chunkIx < chunks.length - 1 ? gotoChunk(chunkIx + 1) && play() : pause());
  }

  function pause() {
    playing = false;
    speechSynthesis.cancel();
  }

  function pauseOrResume() {
    playing ? pause() : play();
  }

  function reset() {
    if (playing) {
      pause();
      setTimeout(play, 30);
    }
  }

  function stop() {
    pause();
    gotoChunk(null);
  }

  function gotoBlock(i) {
    i = bringToRange(i, blocks);
    blockIx === i || gotoChunk(blocks[i].chunkIxs[0]) && reset();
  }

  // Go to new chunk index, highlight the chunk nodes and return true if new chunk index is
  // not null and is different from old.
  function gotoChunk(i) {
    i = bringToRange(i, chunks);
    var result = false;
    if (chunkIx !== i) {
      if (chunkIx !== null)
        chunks[chunkIx].nodes.forEach(node => $(node).removeClass("llr-active"));
      chunkIx = i;
      blockIx = blocks.findIndex(block => block.chunkIxs.includes(chunkIx));
      if (chunkIx !== null) {
        chunks[chunkIx].nodes.forEach(node => $(node).addClass("llr-active"));
        chunks[chunkIx].nodes[0].scrollIntoViewIfNeeded();
        result = true;
      }
    }
    return result;
  }

  function speedup(percentage) {
    options.rate *= 1 + 0.01*percentage;
    reset();
  }

  function bindHotkey(hotkey, callback) {
    Mousetrap.bind(hotkey, e => { callback(); return false });
  }

  function enterMode() {
    initChunks();
    initVoice(() => {
      Mousetrap.unbind(options.hotkeys.enterMode);
      Object.keys(commands).forEach(cmd => bindHotkey(options.hotkeys[cmd], commands[cmd]));
      chunks.forEach((chunk, i) => chunk.nodes.forEach(node => node.ondblclick = e => {
          gotoChunk(i) && reset();
          e.stopPropagation();
      }));
      blocks.forEach(block => block.node.ondblclick = e => {
        gotoChunk(block.chunks[0]) && reset();
        e.stopPropagation();
      });
      info("Enter speaking mode");
    });
  }

  function exitMode() {
    stop();
    Object.keys(commands).forEach(cmd => Mousetrap.unbind(options.hotkeys[cmd]));
    bindHotkey(options.hotkeys.exitMode, exitMode);
    bindHotkey(options.hotkeys.enterMode, enterMode);
    info("Exit speaking mode");
  }

  return opts => {
    options = opts;
    bindHotkey(options.hotkeys.enterMode, enterMode);
  }

})();

chrome.storage.sync.get(defaults, LookListenRead);
<|endoftext|>


<commit_before>/**
 * @fileoverview This Chrome extension allows the user to hide loans that they
 * are not interested in buying in the secondary market. Loan ids to be ignored
 * are stored in Chrome's sync storage system. This means the data is backed up
 * to Google and would be available to other installations of Chrome the user
 * may have.
 *
 * At the end of this file, the extension waits for the secondary market page to
 * finish loading. It then renders the ignore buttons, adds listeners to them
 * and then hides the loans currently in the extension's Chrome sync storage.
 *
 * @author steve@stevenwooding.com (Steven Wooding)
 * @license MIT
 */

// Define the maximum number of loans to store in each Chrome sync storage item
// and the maximum number of storage items. Times these two numbers together to
// get the total number of loans that can be stored.
var loansPerItem = 1000;
var totalNumItems = 100;

/**
 * Extracts the loan id from the loan details jQuery object extracted from the
 * web page.
 * @param {jQuery} loanDetails
 */
function extractLoanId($loanDetails) {
  return $loanDetails.find('p.company-info').text().replace(/\r?\n|\r/g, '');
}

/**
 * Function to hide loans based on the list stored in Chrome sync storage.
 */
function hideLoans() {
  var itemNames = [];
  for (i = 0; i < totalNumItems; i++) {
    itemNames.push('ignoredLoans' + i);
  }

  chrome.storage.sync.get(itemNames, function(result) {
    var loansToIgnore = [];
    if ($.isEmptyObject(result)) {
      return;
    } else {
      for (var loanItem in result) {
        if (result.hasOwnProperty(loanItem)) {
          loansToIgnore = loansToIgnore.concat(result[loanItem]);
        }
      }
    }

    $('.loan-details').each(function(i, loanDetails) {
      var $loanDetails = $(loanDetails),
        loanId = extractLoanId($loanDetails);
      if ($.inArray(parseInt(loanId), loansToIgnore) > -1) {
        $loanDetails.parent().hide();
      }
    });
  });
}

/**
 * Place an ignore button on each loan.
 */
function renderIgnoreButtons() {
  $('.loan-parts .loan-details').each(function(i, loanDetails) {
    var $loanDetails = $(loanDetails),
      loanId = extractLoanId($loanDetails);
    $loanDetails.append('<button class="ignore-btn" title="Ignore loan" id="' +
      loanId + '">🚫</button>');
  });
}

/**
 * This function will interface with local storage to store the loan id to be
 * ignored.
 * @param {int} loanId The ID of the loan to be set to ignore.
 * @param {int} itemNum The number of the storage item where the loan id will
 *     be stored.
 */
function ignoreLoan(loanId, itemNum) {
  // First get the currently stored ignored loans from storage. Then in the
  // callback function, update the array and write it back to sync storage.
  chrome.storage.sync.get('ignoredLoans' + itemNum, function(result) {
    var updatedIgnoredLoans = [];

    if ($.isEmptyObject(result)) {
      updatedIgnoredLoans = [parseInt(loanId)];
    } else if (result['ignoredLoans' + itemNum].length >= loansPerItem) {
      itemNum++;
      if (itemNum > (totalNumItems - 1)) {
        alert('Loan failed to be ignored. Maximum ignored number of loans ' +
          'reached!');
        return;
      }
      chrome.storage.sync.set({ 'currentItemNum': itemNum });
      updatedIgnoredLoans = [parseInt(loanId)];
    } else {
      updatedIgnoredLoans = result['ignoredLoans' + itemNum];
      updatedIgnoredLoans.push(parseInt(loanId));
    }

    var itemName = 'ignoredLoans' + itemNum;
    chrome.storage.sync.set({ [itemName]: updatedIgnoredLoans },
      function() {
        // Run the hide loans function again, so this latest loan is hidden too.
        hideLoans();
    });
  });
}

/**
 * Before calling the main ignore function, extract the loan id from the click
 * event and find out the current ignore loan item in Chrome sync storage.
 * @param {event} event The click event object.
 */
function ignoreLoanInit(event) {
  var loanId = event.data.loanId;

  // Get the current storage item number. If it doesn't exist, set it to zero.
  chrome.storage.sync.get('currentItemNum', function(result) {
    if ($.isEmptyObject(result)) {
      ignoreLoan(loanId, 0);
      chrome.storage.sync.set({ 'currentItemNum': 0 });
    } else {
      ignoreLoan(loanId, result.currentItemNum);
    }
  });

  // Stop the loan popup from also being displayed when ignoring a loan.
  event.stopPropagation();
}

/**
 * For each ignore button, add a click event listener to handle when a user
 * clicks on a ignore button.
 */
function addClickBtnListeners() {
  $('.ignore-btn').each(function(i, btnDetails) {
    var $btnDetails = $(btnDetails);
    var loanId = $btnDetails.attr('id');
    $btnDetails.click({ loanId: loanId }, ignoreLoanInit);
  });
}

/**
 * Once the page has loaded, render the ignore buttons, add a click event
 * handler to each one, then hide any loans that need hiding on the page.
 */
$(document).ready(function() {
  renderIgnoreButtons();
  addClickBtnListeners();
  hideLoans();
});
<commit_msg>fix: Make ignore feature survive filter change<commit_after>/**
 * @fileoverview This Chrome extension allows the user to hide loans that they
 * are not interested in buying in the secondary market. Loan ids to be ignored
 * are stored in Chrome's sync storage system. This means the data is backed up
 * to Google and would be available to other installations of Chrome the user
 * may have.
 *
 * At the end of this file, the extension waits for the secondary market page to
 * finish loading. It then renders the ignore buttons, adds listeners to them
 * and then hides the loans currently in the extension's Chrome sync storage.
 *
 * @author steve@stevenwooding.com (Steven Wooding)
 * @license MIT
 */

// Define the maximum number of loans to store in each Chrome sync storage item
// and the maximum number of storage items. Times these two numbers together to
// get the total number of loans that can be stored.
var loansPerItem = 1000;
var totalNumItems = 100;

/**
 * Extracts the loan id from the loan details jQuery object extracted from the
 * web page.
 * @param {jQuery} loanDetails
 */
function extractLoanId($loanDetails) {
  return $loanDetails.find('p.company-info').text().replace(/\r?\n|\r/g, '');
}

/**
 * Function to hide loans based on the list stored in Chrome sync storage.
 */
function hideLoans() {
  var itemNames = [];
  for (i = 0; i < totalNumItems; i++) {
    itemNames.push('ignoredLoans' + i);
  }

  chrome.storage.sync.get(itemNames, function(result) {
    var loansToIgnore = [];
    if ($.isEmptyObject(result)) {
      return;
    } else {
      for (var loanItem in result) {
        if (result.hasOwnProperty(loanItem)) {
          loansToIgnore = loansToIgnore.concat(result[loanItem]);
        }
      }
    }

    $('.loan-details').each(function(i, loanDetails) {
      var $loanDetails = $(loanDetails),
        loanId = extractLoanId($loanDetails);
      if ($.inArray(parseInt(loanId), loansToIgnore) > -1) {
        $loanDetails.parent().hide();
      }
    });
  });
}

/**
 * Place an ignore button on each loan.
 */
function renderIgnoreButtons() {
  $('.loan-parts .loan-details').each(function(i, loanDetails) {
    var $loanDetails = $(loanDetails),
      loanId = extractLoanId($loanDetails);
    $loanDetails.append('<button class="ignore-btn" title="Ignore loan" id="' +
      loanId + '">🚫</button>');
  });
}

/**
 * This function will interface with local storage to store the loan id to be
 * ignored.
 * @param {int} loanId The ID of the loan to be set to ignore.
 * @param {int} itemNum The number of the storage item where the loan id will
 *     be stored.
 */
function ignoreLoan(loanId, itemNum) {
  // First get the currently stored ignored loans from storage. Then in the
  // callback function, update the array and write it back to sync storage.
  chrome.storage.sync.get('ignoredLoans' + itemNum, function(result) {
    var updatedIgnoredLoans = [];

    if ($.isEmptyObject(result)) {
      updatedIgnoredLoans = [parseInt(loanId)];
    } else if (result['ignoredLoans' + itemNum].length >= loansPerItem) {
      itemNum++;
      if (itemNum > (totalNumItems - 1)) {
        alert('Loan failed to be ignored. Maximum ignored number of loans ' +
          'reached!');
        return;
      }
      chrome.storage.sync.set({ 'currentItemNum': itemNum });
      updatedIgnoredLoans = [parseInt(loanId)];
    } else {
      updatedIgnoredLoans = result['ignoredLoans' + itemNum];
      updatedIgnoredLoans.push(parseInt(loanId));
    }

    var itemName = 'ignoredLoans' + itemNum;
    chrome.storage.sync.set({ [itemName]: updatedIgnoredLoans },
      function() {
        // Run the hide loans function again, so this latest loan is hidden too.
        hideLoans();
    });
  });
}

/**
 * Before calling the main ignore function, extract the loan id from the click
 * event and find out the current ignore loan item in Chrome sync storage.
 * @param {event} event The click event object.
 */
function ignoreLoanInit(event) {
  var loanId = event.data.loanId;

  // Get the current storage item number. If it doesn't exist, set it to zero.
  chrome.storage.sync.get('currentItemNum', function(result) {
    if ($.isEmptyObject(result)) {
      ignoreLoan(loanId, 0);
      chrome.storage.sync.set({ 'currentItemNum': 0 });
    } else {
      ignoreLoan(loanId, result.currentItemNum);
    }
  });

  // Stop the loan popup from also being displayed when ignoring a loan.
  event.stopPropagation();
}

/**
 * For each ignore button, add a click event listener to handle when a user
 * clicks on a ignore button.
 */
function addClickBtnListeners() {
  $('.ignore-btn').each(function(i, btnDetails) {
    var $btnDetails = $(btnDetails);
    var loanId = $btnDetails.attr('id');
    $btnDetails.click({ loanId: loanId }, ignoreLoanInit);
  });
}

/**
 * Once the page has loaded, render the ignore buttons, add a click event
 * handler to each one, then hide any loans that need hiding on the page.
 */
$(document).ready(function() {
  renderIgnoreButtons();
  addClickBtnListeners();
  hideLoans();
});

/**
 * Set up a mutation observer to watch the page for when the data in the loan
 * table changes. When this happens, re-render the ignore buttons, add listeners
 * to buttons and hide any loans that need hiding.
 */
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if (mutation.addedNodes.length > 0 && mutation.addedNodes[0].className ===
      'loan-parts grouped fadeIn') {
        renderIgnoreButtons();
        addClickBtnListeners();
        hideLoans();
    }
  });
});

// configuration of the observer:
var config = {
  childList: true, subtree: true
};

// Start the observer on the whole web page.
observer.observe(document, config);

<|endoftext|>


<commit_before>var dictonary = [
			["break", "харэ"]
			, ["case", "лещ"]
			, ["catch", "гоп"]
			, ["continue", "двигай"]
			, ["debugger", "логопед"]
			, ["default", "понятие"]
			, ["delete", "ёбнуть"]
			, ["do", "крч"]
			, ["else", "иливжопураз"]
			, ["finally", "тюряжка"]
			, ["for", "го"]
			, ["function", "йопта"]
			, ["if", "вилкойвглаз"]
			, ["in", "в"]
			, ["instanceof", "внатуре"]
			, ["new", "гы"]
			, ["return", "отвечаю"]
			, ["switch", "пошерстим"]
			, ["this", "тырыпыры"]
			, ["throw", "пнх"]
			, ["try", "хапнуть"]
			, ["typeof", "чезажижан"]
			, ["var", "валио"]
			, ["void", "куку"]
			, ["while", "потрещим"]
			, ["with", "хзйопт"]
			, ["Abstract", "говнойбать"]
			, ["Boolean", "пацан"]
			, ["Byte", "семка"]
			, ["Char", "эээ"]
			, ["Class", "клёво"]
			, ["Const", "мусор"]
			, ["Double", "двойные"]
			, ["Enum", "еээ"]
			, ["export", "излабас"]
			, ["extends", "батя"]
			, ["final", "бачок"]
			, ["Float", "плавник"]
			, ["goto", "идинах"]
			, ["implements", "силикон"]
			, ["import", "спиздить"]
			, ["Int", "хуйня"]
			, ["interface", "хуёво"]
			, ["Long", "колонна"]
			, ["native", "братан"]
			, ["package", "клеёнка"]
			, ["private", "мой"]
			, ["protected", "подкрыша"]
			, ["public", "ебанное"]
			, ["Short", "пипин"]
			, ["static", "попонятия"]
			, ["super", "яга"]
			, ["synchronized", "вписон"]
			, ["throws", "плюнуть"]
			, ["transient", "ахз"]
			, ["volatile", "вписос"]
			, ["null", "нуллио"]
			, ["NaN", "гомункул"]
			, ["true", "трулио"]
			, ["false", "нетрулио"]
//            Операторы сравнения и логические операторы
			, [">", "пизже"]
			, ["<", "хуёвей"]
			, [">=", "поцик"]
			, ["<=", "поц"]
			, ["==", "эквалио"]
			, ["=", "сука"]
			, [";", " нах"]
//			["{", "типа"],
//			["}", "йоба"],
//			["(", "бля"],
//			[")", "йопт"],
			, ["!", "чобля"]
			, ["&&", "ичо"]
			, ["||", "иличо"]
            , ["alert", "шухер"]
//             Document methods
            , ["document", "ксива"]
            , ["captureEvents", "зафотатьШняги"]
            , ["caretPositionFromPoint", ""]
            , ["caretRangeFromPoint", ""]
            , ["createAttribute", ""]
            , ["createAttributeNS", ""]
            , ["reateCDATASection", ""]
            , ["createDocumentFragment", ""]
            , ["createElementNS", ""]
            , ["createEvent", "создатьШнягу"]
            , ["createNodeIterator", ""]
            , ["createProcessingInstruction", ""]
            , ["createRange", ""]
            , ["createTextNode", ""]
            , ["createTouch", ""]
            , ["createTreeWalker", ""]
            , ["elementFromPoint", ""]
            , ["elementsFromPoint", ""]
            , ["enableStyleSheetsForSet", ""]
            , ["exitPointerLock", ""]
            , ["getAnimations", ""]
            , ["getElementsByClassName", "вычислитьТерпилПоКлассу"]
            , ["getElementsByTagName", "вычислитьТерпилПоТегу"]
            , ["getElementsByTagNameNS", ""]
            , ["importNode", "влабазУзел"]
            , ["registerElement", ""]
            , ["releaseCapture", ""]
            , ["mozSetImageElement", ""]
            , ["getElementById", "вычислитьЛохаПоНомеру"]
            , ["querySelector", ""]
            , ["querySelectorAll", ""]
            , ["createExpression", ""]
            , ["createNSResolver", ""]
            , ["evaluate", ""]
//             Document Extension
            , ["clear", "урыть"]
            , ["close", "финиш"]
            , ["execCommand", ""]
            , ["getElementsByName", "вычислитьТерпилПоИмени"]
            , ["getSelection", ""]
            , ["hasFocus", ""]
            , ["open", ""]
            , ["queryCommandEnabled", ""]
            , ["queryCommandState", ""]
            , ["queryCommandSupported", ""]
            , ["queryCommandIndeterm", ""]
            , ["queryCommandValue", ""]
            , ["write", "малява"]
            , ["writeln", "малявагоп"]
];

var magicYoptaBtn = document.getElementById('magicYoptaBtn').addEventListener('click', converter, false);
var jsOnChange = document.getElementById('jstoyopta').addEventListener('keyup', converter, false);
String.prototype.replaceAll = function (search, replacement) {
    var target = this;
    return target.split(search).join(replacement);
};

function converter() {
    var jstoyopta = document.getElementById('jstoyopta').value;
    // var jsArray = jstoyopta.split (/\n| /);
    var i = 0;
    var yoptaNew = jstoyopta;
    for (i = 0; i < dictonary.length; i++) {
        yoptaNew = yoptaNew.replaceAll(dictonary[i][0], dictonary[i][1]);
    }
    document.getElementById('yopta').value = yoptaNew;
}<commit_msg>Добавил заготовку для document properties<commit_after>var dictonary = [
			["break", "харэ"]
			, ["case", "лещ"]
			, ["catch", "гоп"]
			, ["continue", "двигай"]
			, ["debugger", "логопед"]
			, ["default", "понятие"]
			, ["delete", "ёбнуть"]
			, ["do", "крч"]
			, ["else", "иливжопураз"]
			, ["finally", "тюряжка"]
			, ["for", "го"]
			, ["function", "йопта"]
			, ["if", "вилкойвглаз"]
			, ["in", "в"]
			, ["instanceof", "внатуре"]
			, ["new", "гы"]
			, ["return", "отвечаю"]
			, ["switch", "пошерстим"]
			, ["this", "тырыпыры"]
			, ["throw", "пнх"]
			, ["try", "хапнуть"]
			, ["typeof", "чезажижан"]
			, ["var", "валио"]
			, ["void", "куку"]
			, ["while", "потрещим"]
			, ["with", "хзйопт"]
			, ["Abstract", "говнойбать"]
			, ["Boolean", "пацан"]
			, ["Byte", "семка"]
			, ["Char", "эээ"]
			, ["Class", "клёво"]
			, ["Const", "мусор"]
			, ["Double", "двойные"]
			, ["Enum", "еээ"]
			, ["export", "излабас"]
			, ["extends", "батя"]
			, ["final", "бачок"]
			, ["Float", "плавник"]
			, ["goto", "идинах"]
			, ["implements", "силикон"]
			, ["import", "спиздить"]
			, ["Int", "хуйня"]
			, ["interface", "хуёво"]
			, ["Long", "колонна"]
			, ["native", "братан"]
			, ["package", "клеёнка"]
			, ["private", "мой"]
			, ["protected", "подкрыша"]
			, ["public", "ебанное"]
			, ["Short", "пипин"]
			, ["static", "попонятия"]
			, ["super", "яга"]
			, ["synchronized", "вписон"]
			, ["throws", "плюнуть"]
			, ["transient", "ахз"]
			, ["volatile", "вписос"]
			, ["null", "нуллио"]
			, ["NaN", "гомункул"]
			, ["true", "трулио"]
			, ["false", "нетрулио"]
//            Операторы сравнения и логические операторы
			, [">", "пизже"]
			, ["<", "хуёвей"]
			, [">=", "поцик"]
			, ["<=", "поц"]
			, ["==", "эквалио"]
			, ["=", "сука"]
			, [";", " нах"]
//			["{", "типа"],
//			["}", "йоба"],
//			["(", "бля"],
//			[")", "йопт"],
			, ["!", "чобля"]
			, ["&&", "ичо"]
			, ["||", "иличо"]
            , ["alert", "шухер"]
//             Document methods
            , ["document", "ксива"]
            , ["captureEvents", "зафотатьШняги"]
            , ["caretPositionFromPoint", ""]
            , ["caretRangeFromPoint", ""]
            , ["createAttribute", ""]
            , ["createAttributeNS", ""]
            , ["reateCDATASection", ""]
            , ["createDocumentFragment", ""]
            , ["createElementNS", ""]
            , ["createEvent", "создатьШнягу"]
            , ["createNodeIterator", ""]
            , ["createProcessingInstruction", ""]
            , ["createRange", ""]
            , ["createTextNode", ""]
            , ["createTouch", ""]
            , ["createTreeWalker", ""]
            , ["elementFromPoint", ""]
            , ["elementsFromPoint", ""]
            , ["enableStyleSheetsForSet", ""]
            , ["exitPointerLock", ""]
            , ["getAnimations", ""]
            , ["getElementsByClassName", "вычислитьТерпилПоКлассу"]
            , ["getElementsByTagName", "вычислитьТерпилПоТегу"]
            , ["getElementsByTagNameNS", ""]
            , ["importNode", "влабазУзел"]
            , ["registerElement", ""]
            , ["releaseCapture", ""]
            , ["mozSetImageElement", ""]
            , ["getElementById", "вычислитьЛохаПоНомеру"]
            , ["querySelector", ""]
            , ["querySelectorAll", ""]
            , ["createExpression", ""]
            , ["createNSResolver", ""]
            , ["evaluate", ""]
            , ["clear", "урыть"]
            , ["close", "финиш"]
            , ["execCommand", ""]
            , ["getElementsByName", "вычислитьТерпилПоИмени"]
            , ["getSelection", ""]
            , ["hasFocus", ""]
            , ["open", ""]
            , ["queryCommandEnabled", ""]
            , ["queryCommandState", ""]
            , ["queryCommandSupported", ""]
            , ["queryCommandIndeterm", ""]
            , ["queryCommandValue", ""]
            , ["write", "малява"]
            , ["writeln", "малявагоп"]
//            Document Properties
            , ["async", ""]
            , ["characterSet", ""]
            , ["charset", ""]
            , ["compatMode", ""]
            , ["contentType", ""]
            , ["doctype", ""]
            , ["documentElement", ""]
            , ["documentURI", ""]
            , ["domConfig", ""]
            , ["hidden", ""]
            , ["inputEncoding", ""]
            , ["lastStyleSheetSet", ""]
            , ["pointerLockElement", ""]
            , ["preferredStyleSheetSet", ""]
            , ["scrollingElement", ""]
            , ["selectedStyleSheetSet", ""]
            , ["scrollingElement", ""]
            , ["styleSheets", ""]
            , ["styleSheetSets", ""]
            , ["timeline", ""]
            , ["visibilityState", ""]
            , ["xmlEncoding", ""]
            , ["children", ""]
            , ["xmlVersion", ""]
            , ["firstElementChild", ""]
            , ["lastElementChild", ""]
            , ["childElementCount", ""]
            , ["activeElement", ""]
            , ["childElementCount", ""]
            , ["alinkColor", ""]
            , ["anchors", ""]
            , ["applets", ""]
            , ["bgColor", ""]
            , ["body", ""]
            , ["cookie", ""]
            , ["defaultView", ""]
            , ["designMode", ""]
            , ["dir", ""]
            , ["domain", ""]
            , ["embeds", ""]
            , ["fgColor", ""]
            , ["forms", ""]
            , ["head", ""]
            , ["height", ""]
            , ["images", ""]
            , ["lastModified", ""]
            , ["linkColor", ""]
            , ["links", ""]
            , ["location", ""]
            , ["plugins", ""]
            , ["readyState", ""]
            , ["referrer", ""]
            , ["scripts", ""]
            , ["title", ""]
            , ["URL", ""]
            , ["vlinkColor", ""]
            , ["width", ""]
];

var magicYoptaBtn = document.getElementById('magicYoptaBtn').addEventListener('click', converter, false);
var jsOnChange = document.getElementById('jstoyopta').addEventListener('keyup', converter, false);
String.prototype.replaceAll = function (search, replacement) {
    var target = this;
    return target.split(search).join(replacement);
};

function converter() {
    var jstoyopta = document.getElementById('jstoyopta').value;
    // var jsArray = jstoyopta.split (/\n| /);
    var i = 0;
    var yoptaNew = jstoyopta;
    for (i = 0; i < dictonary.length; i++) {
        yoptaNew = yoptaNew.replaceAll(dictonary[i][0], dictonary[i][1]);
    }
    document.getElementById('yopta').value = yoptaNew;
}<|endoftext|>


<commit_before>//
// Make Medium Readable Again
//

var makeReadable = function() {
	// Un-position:fixed the top nav bar
	var topNav = document.querySelector('.metabar.u-fixed');
	if (topNav) {
		topNav.classList.remove('u-fixed');
	}
	// Remove the footer
	var getUpdatesBar = document.querySelector('.js-stickyFooter');
	if (getUpdatesBar) {
		getUpdatesBar.style.display = 'none';
	}
};

var hideDickbar = function() {
	var dickbar = document.querySelector('.js-postShareWidget');
	if (dickbar) {
		dickbar.style.display = 'none';
	}
	var footerDickbar = document.querySelector('footer > .container:first-child');
	if (footerDickbar) {
		footerDickbar.style.display = 'none';
	}
};

var disableLazyLoading = function() {
	// Get all  tags accompanying dynamically-loading s
	var hiddenMedia = document.querySelectorAll('noscript.js-progressiveMedia-inner');
	if (hiddenMedia.length === 0) {
		return;
	}
	for (var i=0; i<hiddenMedia.length; i++) {
		// Create new  element from the one in  and add it in.
		// This is certainly a roundabout way of doing things, but I didn't want to
		// spend more time reverse-engineering Medium's lazy-loading code.
		var img = new Image();
		var srcMatch = hiddenMedia[i].textContent.match(/src="(https:\/\/[^"]+)"/);
		if (srcMatch != null) {
			img.src = srcMatch[1];
			img.className = hiddenMedia[i].textContent.match(/class="([^"]+)"/)[1];
			hiddenMedia[i].parentNode.appendChild(img);
		}
	}
};

var shrinkHeaderImages = function() {
	var ridiculousHeaderImage = document.querySelector('figure.graf--layoutFillWidth');
	if (ridiculousHeaderImage) {
		ridiculousHeaderImage.style.maxWidth = '700px';
		ridiculousHeaderImage.style.margin = '0 auto';
	}
}

var observer = new MutationObserver(function(mutations){
	mutations.forEach(function(){
		makeReadable();
		shrinkHeaderImages();
	});
});

var config = {attributes: true};

// Only run this on Medium sites.
if (document.querySelector('head meta[property="al:ios:app_name"][content="medium" i]')) {
	makeReadable();
	shrinkHeaderImages();

	chrome.storage.sync.get(null, function(items) {
		if (items.hideDickbar) {
			hideDickbar();
		}
		if (items.disableLazyImages) {
			disableLazyLoading();
		}
	});

	observer.observe(document.body, config);
}
<commit_msg>Comment more about the Medium site check<commit_after>//
// Make Medium Readable Again
//

var makeReadable = function() {
	// Un-position:fixed the top nav bar
	var topNav = document.querySelector('.metabar.u-fixed');
	if (topNav) {
		topNav.classList.remove('u-fixed');
	}
	// Remove the footer
	var getUpdatesBar = document.querySelector('.js-stickyFooter');
	if (getUpdatesBar) {
		getUpdatesBar.style.display = 'none';
	}
};

var hideDickbar = function() {
	var dickbar = document.querySelector('.js-postShareWidget');
	if (dickbar) {
		dickbar.style.display = 'none';
	}
	var footerDickbar = document.querySelector('footer > .container:first-child');
	if (footerDickbar) {
		footerDickbar.style.display = 'none';
	}
};

var disableLazyLoading = function() {
	// Get all  tags accompanying dynamically-loading s
	var hiddenMedia = document.querySelectorAll('noscript.js-progressiveMedia-inner');
	if (hiddenMedia.length === 0) {
		return;
	}
	for (var i=0; i<hiddenMedia.length; i++) {
		// Create new  element from the one in  and add it in.
		// This is certainly a roundabout way of doing things, but I didn't want to
		// spend more time reverse-engineering Medium's lazy-loading code.
		var img = new Image();
		var srcMatch = hiddenMedia[i].textContent.match(/src="(https:\/\/[^"]+)"/);
		if (srcMatch != null) {
			img.src = srcMatch[1];
			img.className = hiddenMedia[i].textContent.match(/class="([^"]+)"/)[1];
			hiddenMedia[i].parentNode.appendChild(img);
		}
	}
};

var shrinkHeaderImages = function() {
	var ridiculousHeaderImage = document.querySelector('figure.graf--layoutFillWidth');
	if (ridiculousHeaderImage) {
		ridiculousHeaderImage.style.maxWidth = '700px';
		ridiculousHeaderImage.style.margin = '0 auto';
	}
}

var observer = new MutationObserver(function(mutations){
	mutations.forEach(function(){
		makeReadable();
		shrinkHeaderImages();
	});
});

var config = {attributes: true};

// This extension runs on all domains so it can Make Medium Readable Again even for publications on custom domains.
// Here we make sure the code only runs on Medium sites.
if (document.querySelector('head meta[property="al:ios:app_name"][content="medium" i]')) {
	makeReadable();
	shrinkHeaderImages();

	chrome.storage.sync.get(null, function(items) {
		if (items.hideDickbar) {
			hideDickbar();
		}
		if (items.disableLazyImages) {
			disableLazyLoading();
		}
	});

	observer.observe(document.body, config);
}
<|endoftext|>


<commit_before>import _ from 'lodash'
import classNames from 'classnames'
import React, { Component, PropTypes } from 'react'
import numberToWord from '../../utils/numberToWord'
import META from '../../utils/Meta.js'

export default class GridColumn extends Component {
  static propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    style: PropTypes.object,
    width: PropTypes.oneOf(_.range(1, 17)),
  }

  static _meta = {
    library: META.library.semanticUI,
    name: 'GridColumn',
    type: META.type.collection,
    parent: 'Grid',
  }

  render() {
    const classes = classNames(
      'sd-grid-column',
      this.props.className,
      this.props.width && numberToWord(this.props.width) + ' wide',
      'column'
    )
    return (
      <div {...this.props} className={classes}>
        {this.props.children}
      </div>
    )
  }
}
<commit_msg>refactor(GridColumn): fix lint issues<commit_after>import _ from 'lodash'
import classNames from 'classnames'
import React, { Component, PropTypes } from 'react'
import numberToWord from '../../utils/numberToWord'
import META from '../../utils/Meta.js'

export default class GridColumn extends Component {
  static propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    style: PropTypes.object,
    width: PropTypes.oneOf(_.range(1, 17)),
  }

  static _meta = {
    library: META.library.semanticUI,
    name: 'GridColumn',
    type: META.type.collection,
    parent: 'Grid',
  }

  render() {
    const classes = classNames(
      'sd-grid-column',
      this.props.className,
      this.props.width && `${numberToWord(this.props.width)} wide`,
      'column'
    )
    return (
      <div {...this.props} className={classes}>
        {this.props.children}
      </div>
    )
  }
}
<|endoftext|>


<commit_before>var express = require('express'),
    bodyParser = require('body-parser'),
    app = express(),
    server;

/**
 * MODEL MOCKUP:
 * *************
 */
var model = {
  /**
   * PRIVATE DATA / METHODS:
   * ***********************
   */
  __data: [],
  __dataIndex: {},
  __getID: (function() {
    var i = 0;
    return function() {
      return (++i) + '';
    }
  })(),
  __refreshIndex: function() {
    this.__dataIndex = this.__data.reduce(function(index, row) {
      index[row.id] = row;
      return index;
    }, {});
  },

  /**
   * PUBLIC METHODS:
   * ***************
   */
  readData: function(id) {
    if (!arguments.length)
      return this.__data;

    if (typeof id !== 'string')
      throw new Error('Wrong type for model#readData argument');
    else
      return this.__dataIndex[id];
  },
  updateData: function(row) {
    if (!row || typeof row !== 'object')
      throw new Error('[0] Wrong type for model#updateData argument');
    if (typeof row.id !== 'string')
      throw new Error('[1] Row\'s ID must be a string');
    if (typeof row.data !== 'string')
      throw new Error('[2] Row\'s data must be a string');
    if (!this.__dataIndex[row.id])
      throw new Error('[3] Row "' + row.id + '" does not exist yet');

    this.__dataIndex[row.id].data = row.data;
    return this.__dataIndex[row.id];
  },
  createData: function(data) {
    if (typeof data !== 'string')
      throw new Error('Data must be a string');

    var row = { id: this.__getID(), data: data }
    this.__data.push(row);
    this.__refreshIndex();

    return row;
  },
  deleteData: function(id) {
    if (typeof id !== 'string')
      throw new Error('Wrong type for model#deleteData argument');

    if (this.__dataIndex[id]) {
      this.__data = this.__data.filter(function(row) {
        return row.id !== id;
      });
      this.__refreshIndex();
      return true
    }

    return false;
  }
};

/**
 * CONTROLLER MOCKUP:
 * ******************
 */
var controller = {
  getAll: function(req, res) {
    res.send(model.readData());
  },
  getRow: function(req, res) {
    var result;

    try {
      result = model.readData(req.param('id'));
    } catch (e) {
      return res.status(400).send('Bad request');
    }

    if (result)
      res.send({ result: result });
    else
      res.send({ result: null });
  },
  postRow: function(req, res) {
    var result,
        row = {
          id: req.param('id'),
          data: req.param('data')
        };

    try {
      result = model.updateData(row);
    } catch (e) {
      if (e.message.match(/^[3]/))
        return res.status(404).send('Data not found');
      else
        return res.status(400).send('Bad request');
    }

    res.send({ result: result });
  },
  putRow: function(req, res) {
    var result,
        data = req.param('data');

    try {
      result = model.createData(data);
    } catch (e) {
      return res.status(400).send('lol');
    }

    res.send({ result: result });
  },
  deleteRow: function(req, res) {
    var result,
        id = req.param('id');

    try {
      result = model.deleteData(id);
    } catch (e) {
      return res.status(400).send('Bad request');
    }

    if (result)
      res.send({ ok: 1 });
    else
      res.status(404).send('Data not found');
  }
};

/**
 * INITIALIZE THE DATABASE:
 * ************************
 */
model.createData('abcde');
model.createData('toBeDeleted');

/**
 * MIDDLEWARES:
 * ************
 */
app.use(bodyParser.json())

/**
 * ROUTES:
 * *******
 */
app.get('/data/', controller.getAll);
app.get('/data/:id', controller.getRow);
app.post('/data/:id', controller.postRow);
app.put('/data/', controller.putRow);
app.delete('/data/:id', controller.deleteRow);

// Ad-hoc route:
app.get('/adhoc/', function(req, res) {
  return res.status(req.param('status') || 200)
            .send(req.param('data'));
});

// Static files:
app.get('/*', express.static(__dirname));

/**
 * EXPORTING:
 * **********
 */
module.exports = app;
<commit_msg>Cleaning express deprecation notices<commit_after>var express = require('express'),
    bodyParser = require('body-parser'),
    app = express(),
    server;

/**
 * MODEL MOCKUP:
 * *************
 */
var model = {
  /**
   * PRIVATE DATA / METHODS:
   * ***********************
   */
  __data: [],
  __dataIndex: {},
  __getID: (function() {
    var i = 0;
    return function() {
      return (++i) + '';
    }
  })(),
  __refreshIndex: function() {
    this.__dataIndex = this.__data.reduce(function(index, row) {
      index[row.id] = row;
      return index;
    }, {});
  },

  /**
   * PUBLIC METHODS:
   * ***************
   */
  readData: function(id) {
    if (!arguments.length)
      return this.__data;

    if (typeof id !== 'string')
      throw new Error('Wrong type for model#readData argument');
    else
      return this.__dataIndex[id];
  },
  updateData: function(row) {
    if (!row || typeof row !== 'object')
      throw new Error('[0] Wrong type for model#updateData argument');
    if (typeof row.id !== 'string')
      throw new Error('[1] Row\'s ID must be a string');
    if (typeof row.data !== 'string')
      throw new Error('[2] Row\'s data must be a string');
    if (!this.__dataIndex[row.id])
      throw new Error('[3] Row "' + row.id + '" does not exist yet');

    this.__dataIndex[row.id].data = row.data;
    return this.__dataIndex[row.id];
  },
  createData: function(data) {
    if (typeof data !== 'string')
      throw new Error('Data must be a string');

    var row = { id: this.__getID(), data: data }
    this.__data.push(row);
    this.__refreshIndex();

    return row;
  },
  deleteData: function(id) {
    if (typeof id !== 'string')
      throw new Error('Wrong type for model#deleteData argument');

    if (this.__dataIndex[id]) {
      this.__data = this.__data.filter(function(row) {
        return row.id !== id;
      });
      this.__refreshIndex();
      return true
    }

    return false;
  }
};

/**
 * CONTROLLER MOCKUP:
 * ******************
 */
var controller = {
  getAll: function(req, res) {
    res.send(model.readData());
  },
  getRow: function(req, res) {
    var result;

    try {
      result = model.readData(req.params.id);
    } catch (e) {
      return res.status(400).send('Bad request');
    }

    if (result)
      res.send({ result: result });
    else
      res.send({ result: null });
  },
  postRow: function(req, res) {
    var result,
        row = {
          id: req.params.id,
          data: req.body.data
        };

    try {
      result = model.updateData(row);
    } catch (e) {
      if (e.message.match(/^[3]/))
        return res.status(404).send('Data not found');
      else
        return res.status(400).send('Bad request');
    }

    res.send({ result: result });
  },
  putRow: function(req, res) {
    var result,
        data = req.body.data;

    try {
      result = model.createData(data);
    } catch (e) {
      return res.status(400).send('lol');
    }

    res.send({ result: result });
  },
  deleteRow: function(req, res) {
    var result,
        id = req.params.id;

    try {
      result = model.deleteData(id);
    } catch (e) {
      return res.status(400).send('Bad request');
    }

    if (result)
      res.send({ ok: 1 });
    else
      res.status(404).send('Data not found');
  }
};

/**
 * INITIALIZE THE DATABASE:
 * ************************
 */
model.createData('abcde');
model.createData('toBeDeleted');

/**
 * MIDDLEWARES:
 * ************
 */
app.use(bodyParser.json())

/**
 * ROUTES:
 * *******
 */
app.get('/data/', controller.getAll);
app.get('/data/:id', controller.getRow);
app.post('/data/:id', controller.postRow);
app.put('/data/', controller.putRow);
app.delete('/data/:id', controller.deleteRow);

// Ad-hoc route:
app.get('/adhoc/', function(req, res) {
  return res.status(req.query.status || 200)
            .send(req.body.data);
});

// Static files:
app.get('/*', express.static(__dirname));

/**
 * EXPORTING:
 * **********
 */
module.exports = app;
<|endoftext|>


<commit_before>import hexRgb from 'hex-rgb';
import cssParser from 'css';

export default function colorDeclarations(file) {
  const data = new Map();
  const ast = cssParser.parse(file);

  ast.stylesheet.rules.forEach((rule) => {
    if (rule.declarations === undefined) return;

    rule.declarations.forEach((declaration) => {
      // if (!(declaration.property === 'color')) return;
      if (['background-color', 'background', 'border-color', 'border', 'color'].indexOf(declaration.property) === -1) {
        return;
      }

      let value = declaration.value;

      if (value.includes('important')) {
        value = value.replace('!important', '');
      }

      if (value.includes('px')) {
        value = value.split(' ')
        value = value[value.length-1]
      }

      if (value.includes('solid')) {
        value = value.split(' ')
        value = value[value.length-1]
      }

      value = value.trim();
      console.log('ADSFDSFD');
      console.log(declaration.property);

      if (!data.get(declaration.property)) {
        data.set(declaration.property, [])
      }

      data.get(declaration.property).push({
        value: value,
        rgb: hexRgb(value),
        selectors: (data.get('selectors') || []).concat(rule.selectors),
      })

      // data.set(declaration.property, {
      //   rgb: hexRgb(value),
      //   count: (data.get(value) || { count: 0 }).count + 1,
      //   selectors: (data.get('selectors') || []).concat(rule.selectors),
      // });
    });
  });

  return data;
}
<commit_msg>Move Lennon's stuff to new file<commit_after>import hexRgb from 'hex-rgb';
import cssParser from 'css';

export default function colorDeclarations(file) {
  const data = new Map();
  const ast = cssParser.parse(file);

  ast.stylesheet.rules.forEach((rule) => {
    if (rule.declarations === undefined) return;

    rule.declarations.forEach((declaration) => {
      // if (!(declaration.property === 'color')) return;
      if (['background-color', 'background', 'border-color', 'border', 'color'].indexOf(declaration.property) === -1) {
        return;
      }

      let value = declaration.value;

      if (value.includes('important')) {
        value = value.replace('!important', '');
      }

      if (value.includes('px')) {
        value = value.split(' ')
        value = value[value.length-1]
      }

      if (value.includes('solid')) {
        value = value.split(' ')
        value = value[value.length-1]
      }
      // if value is a hexa color
      if (value.includes('#')) {
        // if length of value is 4 (short form of hexa code)
        if (value.length == 4) {
          value = value.replace('#', '');
          value = value.split("").map((x) => x.repeat(2)).join('');
          value = '#' + value;
        }

      } else {
        // if value is not a hexa color
        console.log("not a hexa color");

        // color name

        // rgb

        // rgba

        // hsl
      }

      // inherit

      // transparent

      value = value.trim();
      console.log('ADSFDSFD');
      console.log(declaration.property);

      if (!data.get(declaration.property)) {
        data.set(declaration.property, [])
      }

      data.get(declaration.property).push({
        value: value,
        rgb: hexRgb(value),
        selectors: (data.get('selectors') || []).concat(rule.selectors),
      })

      // data.set(declaration.property, {
      //   rgb: hexRgb(value),
      //   count: (data.get(value) || { count: 0 }).count + 1,
      //   selectors: (data.get('selectors') || []).concat(rule.selectors),
      // });
    });
  });

  return data;
}
<|endoftext|>


<commit_before>var imageIsLoading = false;

// Used to pop a resizable image view
async function imageWasClicked(img) {
	img.classList.add("nnwClicked");

	const rect = img.getBoundingClientRect();

	var message = {
		x: rect.x,
		y: rect.y,
		width: rect.width,
		height: rect.height
	};

	try {
		showNetworkLoading(img);
		const response = await fetch(img.src);
		hideNetworkLoading(img);
		if (!response.ok) {
			throw new Error('Network response was not ok.');
		}

		const imgBlob = await response.blob();

		var reader = new FileReader();
		reader.readAsDataURL(imgBlob);

		reader.onloadend = function() {
			message.imageURL = reader.result;
			var jsonMessage = JSON.stringify(message);
			window.webkit.messageHandlers.imageWasClicked.postMessage(jsonMessage);
		}
	} catch (error) {
		console.log('There has been a problem with your fetch operation: ', error.message);
	}

}

function showNetworkLoading(img) {
	imageIsLoading = true;

	var wrapper = document.createElement("div");
	wrapper.classList.add("activityIndicatorWrap");
	img.parentNode.insertBefore(wrapper, img);
	wrapper.appendChild(img);

	var activityIndicatorImg = document.createElement("img");
	activityIndicatorImg.classList.add("activityIndicator");
	activityIndicatorImg.style.opacity = 0;
	activityIndicatorImg.src = activityIndicator;
	wrapper.appendChild(activityIndicatorImg);

	// Wait a half a second before showing the indicator
	function showActivityIndicator() {
		activityIndicatorImg.style.opacity = 1;
	}
	setTimeout(showActivityIndicator, 500);
}

function hideNetworkLoading(img) {
	var wrapper = img.parentNode;
	var wrapperParent = wrapper.parentNode;
	wrapperParent.insertBefore(img, wrapper);
	wrapperParent.removeChild(wrapper);

	imageIsLoading = false;
}

// Used to animate the transition to a fullscreen image
function hideClickedImage() {
	var img = document.querySelector('.nnwClicked')
	img.style.opacity = 0
}

// Used to animate the transition from a fullscreen image
function showClickedImage() {
	var img = document.querySelector('.nnwClicked')
	img.classList.remove("nnwClicked");
	img.style.opacity = 1
	window.webkit.messageHandlers.imageWasShown.postMessage("");
}

// Add the click listener for images
function imageClicks() {
	window.onclick = function(event) {
		if (event.target.matches('img') && !imageIsLoading) {
			imageWasClicked(event.target);
		}
	}
}

// Add the playsinline attribute to any HTML5 videos that don't have it.
// Without this attribute videos may autoplay and take over the whole screen
// on an iphone when viewing an article.
function inlineVideos() {
	document.querySelectorAll("video").forEach(element => {
		element.setAttribute("playsinline", true)
	});
}

function postRenderProcessing() {
	imageClicks()
	inlineVideos()
}

const activityIndicator = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZpZXdCb3g9IjAgMCAxMjggMTI4IiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMwMDAwMDAiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDMwIDY0IDY0KSIvPjxwYXRoIGQ9Ik01OS42IDBoOHY0MGgtOFYweiIgZmlsbD0iI2NjY2NjYyIgdHJhbnNmb3JtPSJyb3RhdGUoNjAgNjQgNjQpIi8+PHBhdGggZD0iTTU5LjYgMGg4djQwaC04VjB6IiBmaWxsPSIjY2NjY2NjIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNiMmIyYjIiIHRyYW5zZm9ybT0icm90YXRlKDE1MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM5OTk5OTkiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM3ZjdmN2YiIHRyYW5zZm9ybT0icm90YXRlKDIxMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM2NjY2NjYiIHRyYW5zZm9ybT0icm90YXRlKDI0MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM0YzRjNGMiIHRyYW5zZm9ybT0icm90YXRlKDI3MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMzMzMzMzMiIHRyYW5zZm9ybT0icm90YXRlKDMwMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMxOTE5MTkiIHRyYW5zZm9ybT0icm90YXRlKDMzMCA2NCA2NCkiLz48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgdmFsdWVzPSIwIDY0IDY0OzMwIDY0IDY0OzYwIDY0IDY0OzkwIDY0IDY0OzEyMCA2NCA2NDsxNTAgNjQgNjQ7MTgwIDY0IDY0OzIxMCA2NCA2NDsyNDAgNjQgNjQ7MjcwIDY0IDY0OzMwMCA2NCA2NDszMzAgNjQgNjQiIGNhbGNNb2RlPSJkaXNjcmV0ZSIgZHVyPSIxMDgwbXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIj48L2FuaW1hdGVUcmFuc2Zvcm0+PC9nPjwvc3ZnPg==";
<commit_msg>Make zoom image code more resilient on slow networks<commit_after>var imageIsLoading = false;

// Used to pop a resizable image view
async function imageWasClicked(img) {
	img.classList.add("nnwClicked");

	try {
		showNetworkLoading(img);
		const response = await fetch(img.src);
		if (!response.ok) {
			throw new Error('Network response was not ok.');
		}

		const imgBlob = await response.blob();
		hideNetworkLoading(img);

		var reader = new FileReader();
		reader.readAsDataURL(imgBlob);
		reader.onloadend = function() {

			const rect = img.getBoundingClientRect();
			var message = {
				x: rect.x,
				y: rect.y,
				width: rect.width,
				height: rect.height
			};
			message.imageURL = reader.result;

			var jsonMessage = JSON.stringify(message);
			window.webkit.messageHandlers.imageWasClicked.postMessage(jsonMessage);

		}
	} catch (error) {
		hideNetworkLoading(img);
		console.log('There has been a problem with your fetch operation: ', error.message);
	}

}

function showNetworkLoading(img) {
	imageIsLoading = true;

	var wrapper = document.createElement("div");
	wrapper.classList.add("activityIndicatorWrap");
	img.parentNode.insertBefore(wrapper, img);
	wrapper.appendChild(img);

	var activityIndicatorImg = document.createElement("img");
	activityIndicatorImg.classList.add("activityIndicator");
	activityIndicatorImg.style.opacity = 0;
	activityIndicatorImg.src = activityIndicator;
	wrapper.appendChild(activityIndicatorImg);

	// Wait a bit before showing the indicator
	function showActivityIndicator() {
		activityIndicatorImg.style.opacity = 1;
	}
	setTimeout(showActivityIndicator, 300);
}

function hideNetworkLoading(img) {
	var wrapper = img.parentNode;
	var wrapperParent = wrapper.parentNode;
	wrapperParent.insertBefore(img, wrapper);
	wrapperParent.removeChild(wrapper);

	imageIsLoading = false;
}

// Used to animate the transition to a fullscreen image
function hideClickedImage() {
	var img = document.querySelector('.nnwClicked')
	img.style.opacity = 0
}

// Used to animate the transition from a fullscreen image
function showClickedImage() {
	var img = document.querySelector('.nnwClicked')
	img.classList.remove("nnwClicked");
	img.style.opacity = 1
	window.webkit.messageHandlers.imageWasShown.postMessage("");
}

// Add the click listener for images
function imageClicks() {
	window.onclick = function(event) {
		if (event.target.matches('img') && !imageIsLoading) {
			imageWasClicked(event.target);
		}
	}
}

// Add the playsinline attribute to any HTML5 videos that don't have it.
// Without this attribute videos may autoplay and take over the whole screen
// on an iphone when viewing an article.
function inlineVideos() {
	document.querySelectorAll("video").forEach(element => {
		element.setAttribute("playsinline", true)
	});
}

function postRenderProcessing() {
	imageClicks()
	inlineVideos()
}

const activityIndicator = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZpZXdCb3g9IjAgMCAxMjggMTI4IiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMwMDAwMDAiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDMwIDY0IDY0KSIvPjxwYXRoIGQ9Ik01OS42IDBoOHY0MGgtOFYweiIgZmlsbD0iI2NjY2NjYyIgdHJhbnNmb3JtPSJyb3RhdGUoNjAgNjQgNjQpIi8+PHBhdGggZD0iTTU5LjYgMGg4djQwaC04VjB6IiBmaWxsPSIjY2NjY2NjIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNiMmIyYjIiIHRyYW5zZm9ybT0icm90YXRlKDE1MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM5OTk5OTkiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM3ZjdmN2YiIHRyYW5zZm9ybT0icm90YXRlKDIxMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM2NjY2NjYiIHRyYW5zZm9ybT0icm90YXRlKDI0MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM0YzRjNGMiIHRyYW5zZm9ybT0icm90YXRlKDI3MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMzMzMzMzMiIHRyYW5zZm9ybT0icm90YXRlKDMwMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMxOTE5MTkiIHRyYW5zZm9ybT0icm90YXRlKDMzMCA2NCA2NCkiLz48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgdmFsdWVzPSIwIDY0IDY0OzMwIDY0IDY0OzYwIDY0IDY0OzkwIDY0IDY0OzEyMCA2NCA2NDsxNTAgNjQgNjQ7MTgwIDY0IDY0OzIxMCA2NCA2NDsyNDAgNjQgNjQ7MjcwIDY0IDY0OzMwMCA2NCA2NDszMzAgNjQgNjQiIGNhbGNNb2RlPSJkaXNjcmV0ZSIgZHVyPSIxMDgwbXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIj48L2FuaW1hdGVUcmFuc2Zvcm0+PC9nPjwvc3ZnPg==";
<|endoftext|>