<commit_before>// TODO: this is the new ViewHandler

const {ipcRenderer} = require('electron');
const shared = require('electron').remote.getGlobal('shared'); // Global shared object
const path = require('path');

const CSSStateManager = require('./CSSStateManager.js');
const FilesystemManager = require('./FilesystemManager.js');
const ViewState = require('./viewStateTemplate.js');
const pEncode = require('./PercentEncode.js');

let css = new CSSStateManager();
let fsm = new FilesystemManager();
let vs = ViewState;

class ViewHandler {
  constructor() {
    // create a new cssStateManager?
    this.HOME = true;
    this.ZOOMEDIN = false;
    this.CURRENT_ZOOM = 0;

    css.showHome();
  }

  setCurrentImage(filepath, callback) {
    try {
      if (fsm.isFileValid(filepath)) {
        this.HOME = false;

      } else {
        // quick error message
      }
    } catch (e) {
      // display generic error
    }
  }

  /** Show the next image in the viewer */
  showNext() {
    if (fsm.isReady() && !this.HOME) {
      try {

      } catch (e) {
        // display generic error
      }
    }
  }

  /** Show the previous image in the viewer */
  showPrev() {
    if (fsm.isReady() && !this.HOME) {
      try {

      } catch (e) {
        // display generic error
      }
    }
  }

  /**
   * Zoom in. Only zoom in if the image is currently not zoomed out,
   * the application is not at the home, or the image is already at
   * 100% size.
   * @method zoomIn
   */
  zoomIn() {
    if (!this.ZOOMEDIN && !this.HOME) {
      if (true) {
        this.ZOOMEDIN = true;

      } else {
        // message about image already at 100%?
      }
    }
  }

  /**
   * Zoom out. Only zoom out if the image is currently not zoomed in,
   * and the application is not at the home.
   * @method zoomOut
   */
  zoomOut() {
    if (this.ZOOMEDIN && !this.HOME) {
      this.ZOOMEDIN = false;
    }
  }

  /**
   * Is this image zoomed in?
   * @method isZoomed
   * @return {Boolean} True if zoomed in, false otherwise
   */
  isZoomed() {
    return this.ZOOMEDIN;
  }

  /** Sends an IPC message to minimize the window */
  minimize() {
    // can also call this.resetState()
  }

  infoToggle(state) {
    // state on/off
  }

  deleteToggle(state) {

  }

  settingsToggle(state) {
    // state = on/off
  }

  resetState() {
    this.HOME = true;
    this.ZOOMEDIN = false;
    this.CURRENT_ZOOM = 0;
  }
}

// Export the class
module.exports = ViewHandler;

/**
 * Set the window title, and use any new values passed in the
 * 'options' object
 * @method setTitle
 * @param  {Object} options [description]
 */
function setTitle(options) {
  let newTitle = '';

  updateDiff(vs.title.filename, options.filename);
  updateDiff(vs.title.fileindex, options.fileindex);
  updateDiff(vs.title.totalfiles, options.totalfiles);
  updateDiff(vs.title.percentdisplayed, options.percentdisplayed);

  if (vs.title.filename) {
    newTitle = vs.title.filename;
    if (vs.title.percentdisplayed) {
      newTitle += ' (' + vs.title.percentdisplayed + '%)';
    }
    if (vs.title.fileindex && vs.title.totalfiles) {
      newTitle += ' — ' + vs.title.fileindex + '/' + vs.title.totalfiles;
    }
    newTitle += ' — Appere';
  } else {
    newTitle = 'Appere';
  }

  document.title = newTitle;
}

// Use a ternerary operation to update a value only if
// 'n' exists
function updateDiff(c, n) { c = n ? n : c; }
<commit_msg>Zoom and resizeWindow<commit_after>// TODO: this is the new ViewHandler

const {ipcRenderer} = require('electron');
const shared = require('electron').remote.getGlobal('shared'); // Global shared object
const path = require('path');

const CSSStateManager = require('./CSSStateManager.js');
const FilesystemManager = require('./FilesystemManager.js');
const ViewState = require('./viewStateTemplate.js');
const pEncode = require('./PercentEncode.js');

let css = new CSSStateManager();
let fsm = new FilesystemManager();
let vs = ViewState;

class ViewHandler {
  constructor() {
    // create a new cssStateManager?
    this.HOME = true;
    this.ZOOMEDIN = false;
    this.CURRENT_ZOOM = 0;

    css.showHome();
  }

  setCurrentImage(filepath, callback) {
    try {
      if (fsm.isFileValid(filepath)) {
        this.HOME = false;

      } else {
        // quick error message
      }
    } catch (e) {
      // display generic error
    }
  }

  /** Show the next image in the viewer */
  showNext() {
    if (fsm.isReady() && !this.HOME) {
      try {

      } catch (e) {
        // display generic error
      }
    }
  }

  /** Show the previous image in the viewer */
  showPrev() {
    if (fsm.isReady() && !this.HOME) {
      try {

      } catch (e) {
        // display generic error
      }
    }
  }

  /**
   * Zoom in. Only zoom in if the image is currently not zoomed out,
   * the application is not at the home, or the image is not already at
   * 100% size.
   * @method zoomIn
   */
  zoomIn() {
    if (!this.ZOOMEDIN && !this.HOME) {
      if (vs.title.percentdisplayed < 100) {
        this.ZOOMEDIN = true;

        // Save the current zoom level
        this.CURRENT_ZOOM = vs.title.percentdisplayed;

        // Resize the window
        resizeWindow(vs.istate[vs.pointer.curr].dimensions, 'zoom-in');

        // Set the zoom amount to 100%
        setTitle({percentdisplayed: 100});

        // Zoom in the image
        css.zoomInImg(vs.istate[vs.pointer.curr].handle);
      } else {
        // image is already displayed at 100%
        // message about image already at 100%?
      }
    }
  }

  /**
   * Zoom out. Only zoom out if the image is currently not zoomed in,
   * and the application is not at the home.
   * @method zoomOut
   */
  zoomOut() {
    if (this.ZOOMEDIN && !this.HOME) {
      this.ZOOMEDIN = false;

      // Resize the window
      resizeWindow(vs.istate[vs.pointer.curr].dimensions, 'zoom-out');

      // Reset the original percent in the title
      setTitle({percentdisplayed: this.CURRENT_ZOOM});

      // Fit the image to the display
      css.zoomOutImg(vs.istate[vs.pointer.curr].handle);
    }
  }

  /**
   * Is this image zoomed in?
   * @method isZoomed
   * @return {Boolean} True if zoomed in, false otherwise
   */
  isZoomed() {
    return this.ZOOMEDIN;
  }

  /** Sends an IPC message to minimize the window */
  minimize() {
    ipcRenderer.send('minimize-window');
  }

  infoToggle(state) {
    // state on/off
  }

  deleteToggle(state) {
    // state on/off? or one-call?
  }

  settingsToggle(state) {
    // state = on/off
  }

  resetState() {
    this.HOME = true;
    this.ZOOMEDIN = false;
    this.CURRENT_ZOOM = 0;
  }
}

// Export the class
module.exports = ViewHandler;

/**
 * Function wrapper around the 'resize-window' ipc message
 * @method resizeWindow
 * @param  {Object}     dimensions Object containing 'width' and 'height'
 * @param  {String}     action     Currently 'resize', 'zoom-in', or 'zoom-out'
 */
function resizeWindow(dimensions, action) {
  switch (action) {
    case 'zoom-in':
      ipcRenderer.send('resize-window', 'fill', dimensions, false);
      break;

    case 'zoom-out':
      ipcRenderer.send('resize-window', 'resize', dimensions, false);
      break;

    case 'resize':
      ipcRenderer.send('resize-window', action, dimensions,
        shared.userConfig.get('RETURN_PERCENTAGE') // remove?
      );
      break;
  }
}

/**
 * Set the window title, and use any new values passed in the
 * 'options' object
 * @method setTitle
 * @param  {Object} options [description]
 */
function setTitle(options) {
  let newTitle = '';

  updateDiff(vs.title.filename, options.filename);
  updateDiff(vs.title.fileindex, options.fileindex);
  updateDiff(vs.title.totalfiles, options.totalfiles);
  updateDiff(vs.title.percentdisplayed, options.percentdisplayed);

  if (vs.title.filename) {
    newTitle = vs.title.filename;
    if (vs.title.percentdisplayed) {
      newTitle += ' (' + vs.title.percentdisplayed + '%)';
    }
    if (vs.title.fileindex && vs.title.totalfiles) {
      newTitle += ' — ' + vs.title.fileindex + '/' + vs.title.totalfiles;
    }
    newTitle += ' — Appere';
  } else {
    newTitle = 'Appere';
  }

  document.title = newTitle;
}

// Use a ternerary operation to update a value only if
// 'n' exists
function updateDiff(c, n) { c = n ? n : c; }
<|endoftext|>


<commit_before>(function () {

	var logEl,
		isInitialized = false;

	function createElement( tag, css ) {
		var element = document.createElement( tag );
		element.style.cssText = css;
		return element;
	}

	function createPanel(options) {
		checkInitialized();

		options = options || {};
		options.bgColor = options.bgColor || 'black';
		options.color = options.color || 'lightgreen';
		options.css = options.css || '';
		var div = createElement( 'div', 'font-family:Helvetica,Arial,sans-serif;font-size:10px;font-weight:bold;padding:5px;text-align:left;opacity:0.8;position:fixed;right:0;top:0;min-width:200px;max-height:50vh;overflow:auto;background:' + options.bgColor + ';color:' + options.color + ';' + options.css);
		return div;
	}

	function log() {
		var el = createElement( 'div', 'line-height:18px;background:' +
			(logEl.children.length % 2 ? 'rgba(255,255,255,0.1)' : '')); // zebra lines
		var val = [].slice.call(arguments).reduce(function(prev, arg) {
			return prev + ' ' + arg;
		}, '');
		el.textContent = val;

		logEl.appendChild(el);
		// Scroll to last element
		logEl.scrollTop = logEl.scrollHeight - logEl.clientHeight;
	}

	function clear() {
		logEl.innerHTML = '';
	}

	function init(options){
		if (isInitialized) {
			return;
		}

		isInitialized = true;
		options = options || {};
		logEl = createPanel(options);
		document.body.appendChild(logEl);

		if (!options.freeConsole) {
			console.log = log;
			console.clear = clear;
		}
	}

	/**
	 * Checking if isInitialized is set
	 */
	function checkInitialized(){
		if (!isInitialized){
			throw 'You need to call `screenLog.init()` first.';
		}
	}

	/**
	 */
	/**
	 * Decorator for checking if isInitialized is set
	 * @param  {Function} fn Fn to decorate
	 */
	function checkInitDecorator(fn){
		return function(){
			checkInitialized();
			return fn.apply(this, arguments);
		};
	}

	// Public API
	window.screenLog = {
		init: init,
		log: checkInitDecorator(log),
		clear: checkInitDecorator(clear)
	};
})();
<commit_msg>Preserve console functions<commit_after>(function () {

	var logEl,
		isInitialized = false,
		console_log_clone;

	function createElement( tag, css ) {
		var element = document.createElement( tag );
		element.style.cssText = css;
		return element;
	}

	function createPanel(options) {
		checkInitialized();

		options = options || {};
		options.bgColor = options.bgColor || 'black';
		options.color = options.color || 'lightgreen';
		options.css = options.css || '';
		var div = createElement( 'div', 'font-family:Helvetica,Arial,sans-serif;font-size:10px;font-weight:bold;padding:5px;text-align:left;opacity:0.8;position:fixed;right:0;top:0;min-width:200px;max-height:50vh;overflow:auto;background:' + options.bgColor + ';color:' + options.color + ';' + options.css);
		return div;
	}

	function log() {
		var el = createElement( 'div', 'line-height:18px;background:' +
			(logEl.children.length % 2 ? 'rgba(255,255,255,0.1)' : '')); // zebra lines
		var val = [].slice.call(arguments).reduce(function(prev, arg) {
			return prev + ' ' + arg;
		}, '');
		el.textContent = val;

		logEl.appendChild(el);
		// Scroll to last element
		logEl.scrollTop = logEl.scrollHeight - logEl.clientHeight;

		// Write the argument list to the console
		console_log_clone.apply(console, arguments);
	}

	function clear() {
		logEl.innerHTML = '';
	}

	function init(options){
		if (isInitialized) {
			return;
		}

		isInitialized = true;
		options = options || {};
		logEl = createPanel(options);
		document.body.appendChild(logEl);

		if (!options.freeConsole) {
			console_log_clone = console.log;
			console.log = log;
			console.clear = clear;
		}
	}

	/**
	 * Checking if isInitialized is set
	 */
	function checkInitialized(){
		if (!isInitialized){
			throw 'You need to call `screenLog.init()` first.';
		}
	}

	/**
	 */
	/**
	 * Decorator for checking if isInitialized is set
	 * @param  {Function} fn Fn to decorate
	 */
	function checkInitDecorator(fn){
		return function(){
			checkInitialized();
			return fn.apply(this, arguments);
		};
	}

	// Public API
	window.screenLog = {
		init: init,
		log: checkInitDecorator(log),
		clear: checkInitDecorator(clear)
	};
})();
<|endoftext|>


<commit_before>/* ==================================================================
	AngularJS Datatype Editor - Rich Text
	A directive to edit a large text blob in place.
	TODO: In the future it will allow rich text formatting

	Usage:

{{data}}

	Config:
	"class" will be added to the input box so you can style it.
	"id" will be used in messages broadcast to the app on state changes.

	Messages:
		name: ADE-start
		data: id from config

		name: ADE-finish
		data: {id from config, old value, new value, exit value}

------------------------------------------------------------------*/

angular.module('ADE').directive('adeRich', ['ADE', '$compile', function(ADE, $compile) {
	return {
		require: '?ngModel', //optional dependency for ngModel
		restrict: 'A', //Attribute declaration eg:

		//The link step (after compile)
		link: function(scope, element, attrs, controller) {
			var options = {};
			var editing = false;
			var txtArea = null;
			var input = null;
			var value = '';
			var oldValue = '';
			var exit = 0; //0=click, 1=tab, -1= shift tab, 2=return, -2=shift return, 3=esc. controls if you exited the field so you can focus the next field if appropriate
			var timeout = null; //the delay when mousing out of the ppopup

			//whenever the model changes, we get called so we can update our value
			if (controller !== null && controller !== undefined) {
				controller.$render = function() {
					oldValue = value = controller.$modelValue;
					if (value === undefined || value === null) value = '';
					return controller.$viewValue;
				};
			}

			//called once the edit is done, so we can save the new data	and remove edit mode
			var saveEdit = function(exited) {
				oldValue = value;
				exit = exited;

				if (exited != 3) { //don't save value on esc
					value = $('#tinyText_ifr').contents().find('#tinymce')[0].innerHTML;
					controller.$setViewValue(value);
				}

				input.remove();
				editing = false;

				ADE.done(options, oldValue, value, exit);

				if (exit == 1) {
					element.data('dontclick', true); //tells the focus handler not to click
					element.focus();
					//TODO: would prefer to advance the focus to the next logical element on the page
				} else if (exit == -1) {
					element.data('dontclick', true); //tells the focus handler not to click
					element.focus();
					//TODO: would prefer to advance the focus to the previous logical element on the page
				}

				scope.$digest();
			};

			//shows a popup with the full text in read mode
			//TODO: handle scrolling of very long text blobs
			var viewRichText = function() {
				scope.ADE_hidePopup();

				var elOffset = element.offset();
				var posLeft = elOffset.left;
				var posTop = elOffset.top + element[0].offsetHeight;
				var content = value.replace ? value.replace(/\n/g, '
') : value; //what is inside the popup

				if (!content) return; //dont show popup if there is nothing to show

				$compile('

' + content + '

')(scope).insertAfter(element);

				editing = false;

				input = element.next('.ade-rich');
				input.bind('mouseenter.ADE', mousein);
				input.bind('mouseleave.ADE', mouseout);
				input.bind('click.ADE', mouseclick);
			};

			//sets the height of the textarea based on the actual height of the contents.
			//min and max are set in css
			var textareaHeight = function(elem) {
				elem.style.height = '1px';
				elem.style.height = (elem.scrollHeight) + 'px';
			};

			//enters edit mode for the text
			var editRichText = function() {
				window.clearTimeout(timeout);
				if(input) input.unbind('.ADE');

				scope.ADE_hidePopup();

				var content = '<textarea id="tinyText" class="' + options.className + '" style="height:30px">' + value + '</textarea>';

				var elOffset = element.offset();
				var posLeft = elOffset.left;
				var posTop = elOffset.top + element[0].offsetHeight;
				var html = '
' + content + '
';
				$compile(html)(scope).insertAfter(element);

				// Initialize tinymce
				// Full example:
				//   http://www.tinymce.com/tryit/full.php

				tinymce.init({
					selector: "#tinyText",
					theme: "modern",
					menubar: "false",
					plugins: ["textcolor", "link"],
					toolbar: "styleselect | bold italic | bullist numlist outdent indent | hr | link | forecolor backcolor",
				});

				editing = true;

				input = element.next('.ade-rich');

				// Handle blur case
				// save when user blurs out of text editor

				// don't blur on initialization
				var ready = false;

				// listen to clicks on all elements in page
				// this will determine when to blur
				$(document).bind('click.ADE',
					function(e) {
						// check where click occurred
						//   1: inside ade popup
						//   0: outside ade popup
						var outerClick = $('.ade-popup').has(e.target).length === 0;

						// check if modal for link is shown
						var modalShown = $('.mce-floatpanel').css('display') === 'block';

						if (ready && !modalShown && outerClick) {
							// some elements are outside popup but belong to mce
							// these elements start with the text 'mce_' or have a parent/grandparent that starts with the text 'mce_'
							// the latter include texcolor color pickup background element, link ok and cancel buttons

							// check if id starts with 'mce_'
							//   0: true
							//  -1: false
							var parent = e.target;
							var startsMce = false;
							while (parent) {
								if (parent.id.search('mce_') === 0) {
									startsMce = true;
									break;
								}
								parent = parent.parentElement;
							}

							// blur and save changes
							if (!startsMce) {
								mouseout();
								saveEdit(0);

								// we're done, no need to listen to page clicks
								$(document).off('click.ADE');
							}
						} else {
							// set a timeout so it doesn't trigger during initialization
							setTimeout(function() { ready = true}, 500);
						}
					}
				);
			};

			//When the mouse enters, show the popup view of the note
			var mousein = function()  {
				window.clearTimeout(timeout);

				//if any other popup is open in edit mode, don't do this view
				if (angular.element('.ade-rich').hasClass('open') && angular.element('.ade-rich').find('textarea').length) return;

				var linkPopup = element.next('.ade-rich');
				if (!linkPopup.length) {
					viewRichText();
				}
			};

			//if the mouse leaves, hide the popup note view if in read mode
			var mouseout = function() {
				var linkPopup = element.next('.' + ADE.popupClass + '');
				if (linkPopup.length && !editing) { //checks for read/edit mode
					timeout = window.setTimeout(function() {
						scope.ADE_hidePopup(element);
					},400);
				}
			};

			//handles clicks on the read version of the data
			var mouseclick = function() {
				if(element) element.unbind('keypress.ADE');
				window.clearTimeout(timeout);
				if (editing) return;
				editing = true;
				exit = 0;

				ADE.begin(options);

				editRichText();
			};

			element.bind('mouseenter.ADE', mousein);
			element.bind('mouseleave.ADE', mouseout);
			element.bind('click.ADE', mouseclick);

			//handles focus events
			element.bind('focus.ADE', function(e) {

				//if this is an organic focus, then do a click to make the popup appear.
				//if this was a focus caused my myself then don't do the click
				if (!element.data('dontclick')) {
					element.click();
					return;
				}
				window.setTimeout(function() { //IE needs this delay because it fires 2 focus events in quick succession.
					element.data('dontclick',false);
				},100);

				//listen for keys pressed while the element is focused but not clicked
				element.bind('keypress.ADE', function(e) {
					if (e.keyCode == 13) { //return
						e.preventDefault();
						e.stopPropagation(); //to prevent return key from going into text box
						element.click();
					} else if (e.keyCode != 9) { //not tab
						//for a key other than tab we want it to go into the text box
						element.click();
					}
				});

			});

			//handles blur events
			element.bind('blur.ADE', function(e) {
				if(element) element.unbind('keypress.ADE');
			});

			// Watches for changes to the element
			// TODO: understand why I have to return the observer and why the observer returns element
			return attrs.$observe('adeRich', function(settings) { //settings is the contents of the ade-rich="" string
				options = ADE.parseSettings(settings, {className: 'input-xlarge'});
				return element;
			});
		}
	};
}]);
<commit_msg>Move bind function outside<commit_after>/* ==================================================================
	AngularJS Datatype Editor - Rich Text
	A directive to edit a large text blob in place.
	TODO: In the future it will allow rich text formatting

	Usage:

{{data}}

	Config:
	"class" will be added to the input box so you can style it.
	"id" will be used in messages broadcast to the app on state changes.

	Messages:
		name: ADE-start
		data: id from config

		name: ADE-finish
		data: {id from config, old value, new value, exit value}

------------------------------------------------------------------*/

angular.module('ADE').directive('adeRich', ['ADE', '$compile', function(ADE, $compile) {
	return {
		require: '?ngModel', //optional dependency for ngModel
		restrict: 'A', //Attribute declaration eg:

		//The link step (after compile)
		link: function(scope, element, attrs, controller) {
			var options = {};
			var editing = false;
			var txtArea = null;
			var input = null;
			var value = '';
			var oldValue = '';
			var exit = 0; //0=click, 1=tab, -1= shift tab, 2=return, -2=shift return, 3=esc. controls if you exited the field so you can focus the next field if appropriate
			var timeout = null; //the delay when mousing out of the ppopup

			//whenever the model changes, we get called so we can update our value
			if (controller !== null && controller !== undefined) {
				controller.$render = function() {
					oldValue = value = controller.$modelValue;
					if (value === undefined || value === null) value = '';
					return controller.$viewValue;
				};
			}

			//called once the edit is done, so we can save the new data	and remove edit mode
			var saveEdit = function(exited) {
				oldValue = value;
				exit = exited;

				if (exited != 3) { //don't save value on esc
					value = $('#tinyText_ifr').contents().find('#tinymce')[0].innerHTML;
					controller.$setViewValue(value);
				}

				input.remove();
				editing = false;

				ADE.done(options, oldValue, value, exit);

				if (exit == 1) {
					element.data('dontclick', true); //tells the focus handler not to click
					element.focus();
					//TODO: would prefer to advance the focus to the next logical element on the page
				} else if (exit == -1) {
					element.data('dontclick', true); //tells the focus handler not to click
					element.focus();
					//TODO: would prefer to advance the focus to the previous logical element on the page
				}

				scope.$digest();
			};

			//shows a popup with the full text in read mode
			//TODO: handle scrolling of very long text blobs
			var viewRichText = function() {
				scope.ADE_hidePopup();

				var elOffset = element.offset();
				var posLeft = elOffset.left;
				var posTop = elOffset.top + element[0].offsetHeight;
				var content = value.replace ? value.replace(/\n/g, '
') : value; //what is inside the popup

				if (!content) return; //dont show popup if there is nothing to show

				$compile('

' + content + '

')(scope).insertAfter(element);

				editing = false;

				input = element.next('.ade-rich');
				input.bind('mouseenter.ADE', mousein);
				input.bind('mouseleave.ADE', mouseout);
				input.bind('click.ADE', mouseclick);
			};

			//sets the height of the textarea based on the actual height of the contents.
			//min and max are set in css
			var textareaHeight = function(elem) {
				elem.style.height = '1px';
				elem.style.height = (elem.scrollHeight) + 'px';
			};

			// don't blur on initialization
			var ready = false;

			// detect clicks outside tinymce textarea
			var outerBlur = function(e) {
				// check where click occurred
				//   1: inside ade popup
				//   0: outside ade popup
				var outerClick = $('.ade-popup').has(e.target).length === 0;

				// check if modal for link is shown
				var modalShown = $('.mce-floatpanel').css('display') === 'block';

				if (ready && !modalShown && outerClick) {
					// some elements are outside popup but belong to mce
					// these elements start with the text 'mce_' or have a parent/grandparent that starts with the text 'mce_'
					// the latter include texcolor color pickup background element, link ok and cancel buttons

					// check if id starts with 'mce_'
					//   0: true
					//  -1: false
					var parent = e.target;
					var startsMce = false;
					while (parent) {
						if (parent.id.search('mce_') === 0) {
							startsMce = true;
							break;
						}
						parent = parent.parentElement;
					}

					// blur and save changes
					if (!startsMce) {
						mouseout();
						saveEdit(0);

						// we're done, no need to listen to page clicks
						$(document).off('click.ADE');

						// reset ready
						ready = false;
					}
				} else {
					// set a timeout so it doesn't trigger during initialization
					setTimeout(function() { ready = true}, 500);
				}
			};

			//enters edit mode for the text
			var editRichText = function() {
				window.clearTimeout(timeout);
				if(input) input.unbind('.ADE');

				scope.ADE_hidePopup();

				var content = '<textarea id="tinyText" class="' + options.className + '" style="height:30px">' + value + '</textarea>';

				var elOffset = element.offset();
				var posLeft = elOffset.left;
				var posTop = elOffset.top + element[0].offsetHeight;
				var html = '
' + content + '
';
				$compile(html)(scope).insertAfter(element);

				// Initialize tinymce
				// Full example:
				//   http://www.tinymce.com/tryit/full.php

				tinymce.init({
					selector: "#tinyText",
					theme: "modern",
					menubar: "false",
					plugins: ["textcolor", "link"],
					toolbar: "styleselect | bold italic | bullist numlist outdent indent | hr | link | forecolor backcolor",
				});

				editing = true;

				input = element.next('.ade-rich');

				// Handle blur case
				// save when user blurs out of text editor
				// listen to clicks on all elements in page
				// this will determine when to blur
				$(document).bind('click.ADE', outerBlur);
			};

			//When the mouse enters, show the popup view of the note
			var mousein = function()  {
				window.clearTimeout(timeout);

				//if any other popup is open in edit mode, don't do this view
				if (angular.element('.ade-rich').hasClass('open') && angular.element('.ade-rich').find('textarea').length) return;

				var linkPopup = element.next('.ade-rich');
				if (!linkPopup.length) {
					viewRichText();
				}
			};

			//if the mouse leaves, hide the popup note view if in read mode
			var mouseout = function() {
				var linkPopup = element.next('.' + ADE.popupClass + '');
				if (linkPopup.length && !editing) { //checks for read/edit mode
					timeout = window.setTimeout(function() {
						scope.ADE_hidePopup(element);
					},400);
				}
			};

			//handles clicks on the read version of the data
			var mouseclick = function() {
				if(element) element.unbind('keypress.ADE');
				window.clearTimeout(timeout);
				if (editing) return;
				editing = true;
				exit = 0;

				ADE.begin(options);

				editRichText();
			};

			element.bind('mouseenter.ADE', mousein);
			element.bind('mouseleave.ADE', mouseout);
			element.bind('click.ADE', mouseclick);

			//handles focus events
			element.bind('focus.ADE', function(e) {

				//if this is an organic focus, then do a click to make the popup appear.
				//if this was a focus caused my myself then don't do the click
				if (!element.data('dontclick')) {
					element.click();
					return;
				}
				window.setTimeout(function() { //IE needs this delay because it fires 2 focus events in quick succession.
					element.data('dontclick',false);
				},100);

				//listen for keys pressed while the element is focused but not clicked
				element.bind('keypress.ADE', function(e) {
					if (e.keyCode == 13) { //return
						e.preventDefault();
						e.stopPropagation(); //to prevent return key from going into text box
						element.click();
					} else if (e.keyCode != 9) { //not tab
						//for a key other than tab we want it to go into the text box
						element.click();
					}
				});

			});

			//handles blur events
			element.bind('blur.ADE', function(e) {
				if(element) element.unbind('keypress.ADE');
			});

			// Watches for changes to the element
			// TODO: understand why I have to return the observer and why the observer returns element
			return attrs.$observe('adeRich', function(settings) { //settings is the contents of the ade-rich="" string
				options = ADE.parseSettings(settings, {className: 'input-xlarge'});
				return element;
			});
		}
	};
}]);
<|endoftext|>


<commit_before>(function(){'use strict';angular.module('amJsTree').component('amJsTree',{bindings:{config:'<'},controller:['$element',function(){function _class(b){var _this=this;_classCallCheck(this,_class),b.jstree(this.config),this.config.hasOwnProperty('events')&&angular.forEach(this.config.events,function(c,d){b.on(d,function(){angular.isFunction(_this.config.events[d])&&_this.config.events[d](0>=arguments.length?void 0:arguments[0],1>=arguments.length?void 0:arguments[1])})})}return _class}()],controllerAs:'amJsTreeCtrl'})})();<commit_msg>fixed min.js file<commit_after>'use strict';function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError('Cannot call a class as a function')}(function(){'use strict';angular.module('amJsTree').component('amJsTree',{bindings:{config:'<'},controller:['$element',function(){function _class(b){var _this=this;_classCallCheck(this,_class),b.jstree(this.config),this.config.hasOwnProperty('events')&&angular.forEach(this.config.events,function(c,d){b.on(d,function(){angular.isFunction(_this.config.events[d])&&_this.config.events[d](0>=arguments.length?void 0:arguments[0],1>=arguments.length?void 0:arguments[1])})})}return _class}()],controllerAs:'amJsTreeCtrl'})})();<|endoftext|>


<commit_before>// Gimme a Github object! Please.
function github() {
  return new Github({
    token: $.cookie('oauth-token'),
    username: $.cookie('username'),
    auth: "oauth"
  });
}

var currentRepo = {
  user: null,
  repo: null,
  instance: null
};

// Smart caching (needed for managing subsequent updates)
// -------

function getRepo(user, repo) {
  if (currentRepo.user === user && currentRepo.repo === repo) {
    return currentRepo.instance; // Cached
  }

  currentRepo = {
    user: user,
    repo: repo,
    instance: github().getRepo(user, repo)
  };

  return currentRepo.instance;
}

// Authentication
// -------

function authenticate() {
  if ($.cookie("oauth-token")) return window.authenticated = true;
  var match = window.location.href.match(/\?code=([a-z0-9]*)/);

  // Handle Code
  if (match) {
    $.getJSON('{{site.gatekeeper_url}}/authenticate/'+match[1], function(data) {
      $.cookie('oauth-token', data.token);
      window.authenticated = true;
      // Adjust URL
      var regex = new RegExp("\\?code="+match[1]);
      window.location.href = window.location.href.replace(regex, '').replace('&state=', '');
    });
    return false;
  } else {
    return true;
  }
}

function logout() {
  window.authenticated = false;
  $.cookie("oauth-token", null);
}

// Load Application
// -------
//
// Load everything that's needed for the app + header

function loadApplication(cb) {

  if (window.authenticated) {
    $.ajax({
      type: "GET",
      url: 'https://api.github.com/user',
      dataType: 'json',
      contentType: 'application/x-www-form-urlencoded',
      headers : { Authorization : 'token ' + $.cookie('oauth-token') },
      success: function(res) {
        $.cookie("avatar", res.avatar_url);
        $.cookie("username", res.login);
        app.username = res.login;

        var user = github().getUser();
        var owners = {};

        user.repos(function(err, repos) {
          user.orgs(function(err, orgs) {
            _.each(repos, function(r) {
              owners[r.owner.login] = owners[r.owner.login] ? owners[r.owner.login].concat([r]) : [r];
            });

            cb(null, {
              "available_repos": repos,
              "organizations": orgs,
              "owners": owners
            });
          });
        });

      },
      error: function(err) {
        cb('error', { "available_repos": [], "owners": {} });
      }
    });

  } else {
    cb(null, { "available_repos": [], "owners": {} });
  }
}

// Load Repos
// -------
//
// List all available repositories for a certain user

function loadRepos(username, cb) {
  var user = github().getUser();

  user.show(username, function(err, u) {
    var owners = {};
    if (u.type.toLowerCase() === "user") {
      user.userRepos(username, function(err, repos) {
        cb(null, { "repos": repos, user: u });
      });
    } else {
      user.orgRepos(username, function(err, repos) {
        cb(null, { "repos": repos, user: u });
      });
    }
  });
}

// Load Branches
// -------
//
// List all available branches of a repository

function loadBranches(user, repo, cb) {
  var repo = getRepo(user, repo);

  repo.listBranches(function(err, branches) {
    cb(err, branches);
  });
}

// Get files from a tree based on a given path and searchstr
// -------

function getFiles(tree, path, searchstr) {
  var pathMatches = 0;
  function matchesPath(file) {
    if (file.path === path) return false; // skip current path
    var match = file.path.match(new RegExp("^"+path+"(.*)$"));
    if (match) {
      return !!searchstr || match[1].split('/').length <= 2;
      return true;
    }
    return false;
  }

  function matchesSearch(file) {
    if (!searchstr) return true;
    // Insert crazy search pattern match algorithm
    return file.path.search(searchstr) >= 0;
  }

  // Filter
  var files = _.filter(tree, function(file) {
    file.name = file.path;

    if (!matchesPath(file)) return false;
    pathMatches += 1;
    return matchesSearch(file);
  });

  // Sort by name
  files = _.sortBy(files, function(entry){
    return (entry.type === "tree" ? "A" : "B") + entry.path;
  });

  return {
    tree: tree,
    files: files,
    total: pathMatches
  }
}

// Load Posts
// -------
//
// List all postings for a given repo+branch+path plus load _config.yml

function loadPosts(user, reponame, branch, path, cb) {
  var repo = getRepo(user, reponame);

  function getDefaultBranch(cb) {
    repo.show(function(err, repo) {
      cb(null, repo.master_branch);
    });
  }

  function loadConfig(cb) {
    repo.read(branch, "_config.yml", function(err, data) {
      if (err) return cb(err);
      cb(null, jsyaml.load(data));
    });
  }

  function load() {
    loadConfig(function(err, config) {
      app.state.jekyll = !err;
      app.state.config = config;

      var root = config && config.prose && config.prose.rooturl ? config.prose.rooturl : "";
      if (!path) path = root;

      repo.getTree(branch+"?recursive=true", function(err, tree) {
        if (err) return cb("Not found");
        loadBranches(user, reponame, function(err, branches) {
          if (err) return cb("Branches couldn't be fetched");
          app.state.path = path ? path : "";
          app.state.branches = _.filter(branches, function(b) { return b !== branch });
          cb(null, getFiles(tree, path, ""));
        });
      });
    });
  }

  // Load ahead
  if (branch) return load();

  // Fallback to default branch
  getDefaultBranch(function(err, defaultBranch) {
    app.state.branch = branch = defaultBranch;
    load();
  });
}

// Save File
// -------
//
// List all postings for a given repository
// Looks into _posts/blog

function saveFile(user, repo, branch, path, metadata, content, message, cb) {

  var repo = getRepo(user, repo);
  function serialize() {
    if (app.state.jekyll && _.markdown(path)) {
      return ["---", metadata, "---"].join('\n')+'\n'+content;
    } else {
      return content;
    }
  }
  repo.write(branch, path, serialize(), message, cb);
}

// Delete Post
// -------

function deletePost(user, repo, branch, path, file, cb) {
  var repo = getRepo(user, repo);
  repo.remove(branch, _.filepath(path, file), cb);
}

// Move Post
// -------

function movePost(user, repo, branch, path, newPath, cb) {
  var repo = getRepo(user, repo);
  repo.move(branch, path, newPath, cb);
}

// New Post
// -------
//
// Prepare new empty post

function emptyPost(user, repo, branch, path, cb) {
  var rawMetadata = "layout: default\npublished: false";
  var metadata = {
    "layout": "default",
    "published": false,
  };

  var cfg = app.state.config
  if (cfg && cfg.prose && cfg.prose.metadata) {
    if (cfg.prose.metadata[path]) {
      rawMetadata = cfg.prose.metadata[path];
      try {
        metadata = jsyaml.load(rawMetadata);
      } catch(err) {
        console.log('ERROR encoding YAML');
        // No-op
      }
    }
  }

  cb(null, {
    "metadata": metadata,
    "raw_metadata": rawMetadata,
    "content": "# How does it work?\n\nEnter Text in Markdown format.",
    "repo": repo,
    "path": path,
    "persisted": false,
    "file": new Date().format("Y-m-d")+"-your-filename.md"
  });
}

// Load Post
// -------
//
// List all postings for a given repository
// Looks into _posts/blog

function loadPost(user, repo, branch, path, file, cb) {
  var repo = getRepo(user, repo);

  repo.read(branch, path ? path + "/" + file : file, function(err, data) {
    if (err) return cb(err);
    function parse(content) {
      var content = content.replace(/\r\n/g, "\n"); // normalize a little bit
      if (!_.jekyll(path, file)) return {
        metadata: {},
        raw_metadata: "",
        content: content
      };

      var res = {};
      res.content = content.replace(/(---\n)((.|\n)*)\n---\n/, function(match, dashes, frontmatter) {
        res.raw_metadata = frontmatter;
        res.metadata = jsyaml.load(frontmatter);
        return "";
      });
      return res;
    }

    // Extract metadata
    var post = parse(data);
    cb(err, _.extend(post, {
      "markdown": _.markdown(file),
      "jekyll": _.jekyll(path, file),
      "repo": repo,
      "path": path,
      "file": file,
      "persisted": true
    }));
  });
}<commit_msg>Case-insensitive search.<commit_after>// Gimme a Github object! Please.
function github() {
  return new Github({
    token: $.cookie('oauth-token'),
    username: $.cookie('username'),
    auth: "oauth"
  });
}

var currentRepo = {
  user: null,
  repo: null,
  instance: null
};

// Smart caching (needed for managing subsequent updates)
// -------

function getRepo(user, repo) {
  if (currentRepo.user === user && currentRepo.repo === repo) {
    return currentRepo.instance; // Cached
  }

  currentRepo = {
    user: user,
    repo: repo,
    instance: github().getRepo(user, repo)
  };

  return currentRepo.instance;
}

// Authentication
// -------

function authenticate() {
  if ($.cookie("oauth-token")) return window.authenticated = true;
  var match = window.location.href.match(/\?code=([a-z0-9]*)/);

  // Handle Code
  if (match) {
    $.getJSON('{{site.gatekeeper_url}}/authenticate/'+match[1], function(data) {
      $.cookie('oauth-token', data.token);
      window.authenticated = true;
      // Adjust URL
      var regex = new RegExp("\\?code="+match[1]);
      window.location.href = window.location.href.replace(regex, '').replace('&state=', '');
    });
    return false;
  } else {
    return true;
  }
}

function logout() {
  window.authenticated = false;
  $.cookie("oauth-token", null);
}

// Load Application
// -------
//
// Load everything that's needed for the app + header

function loadApplication(cb) {

  if (window.authenticated) {
    $.ajax({
      type: "GET",
      url: 'https://api.github.com/user',
      dataType: 'json',
      contentType: 'application/x-www-form-urlencoded',
      headers : { Authorization : 'token ' + $.cookie('oauth-token') },
      success: function(res) {
        $.cookie("avatar", res.avatar_url);
        $.cookie("username", res.login);
        app.username = res.login;

        var user = github().getUser();
        var owners = {};

        user.repos(function(err, repos) {
          user.orgs(function(err, orgs) {
            _.each(repos, function(r) {
              owners[r.owner.login] = owners[r.owner.login] ? owners[r.owner.login].concat([r]) : [r];
            });

            cb(null, {
              "available_repos": repos,
              "organizations": orgs,
              "owners": owners
            });
          });
        });

      },
      error: function(err) {
        cb('error', { "available_repos": [], "owners": {} });
      }
    });

  } else {
    cb(null, { "available_repos": [], "owners": {} });
  }
}

// Load Repos
// -------
//
// List all available repositories for a certain user

function loadRepos(username, cb) {
  var user = github().getUser();

  user.show(username, function(err, u) {
    var owners = {};
    if (u.type.toLowerCase() === "user") {
      user.userRepos(username, function(err, repos) {
        cb(null, { "repos": repos, user: u });
      });
    } else {
      user.orgRepos(username, function(err, repos) {
        cb(null, { "repos": repos, user: u });
      });
    }
  });
}

// Load Branches
// -------
//
// List all available branches of a repository

function loadBranches(user, repo, cb) {
  var repo = getRepo(user, repo);

  repo.listBranches(function(err, branches) {
    cb(err, branches);
  });
}

// Get files from a tree based on a given path and searchstr
// -------

function getFiles(tree, path, searchstr) {
  var pathMatches = 0;
  function matchesPath(file) {
    if (file.path === path) return false; // skip current path
    var match = file.path.match(new RegExp("^"+path+"(.*)$"));
    if (match) {
      return !!searchstr || match[1].split('/').length <= (path ? 2 : 1);
    }
    return false;
  }

  function matchesSearch(file) {
    if (!searchstr) return true;
    // Insert crazy search pattern match algorithm
    return file.path.toLowerCase().search(searchstr.toLowerCase()) >= 0;
  }

  // Filter
  var files = _.filter(tree, function(file) {
    file.name = file.path;
    if (!matchesPath(file)) return false;
    pathMatches += 1;
    return matchesSearch(file);
  });

  // Sort by name
  files = _.sortBy(files, function(entry){
    return (entry.type === "tree" ? "A" : "B") + entry.path;
  });

  return {
    tree: tree,
    files: files,
    total: pathMatches
  }
}

// Load Posts
// -------
//
// List all postings for a given repo+branch+path plus load _config.yml

function loadPosts(user, reponame, branch, path, cb) {
  var repo = getRepo(user, reponame);

  function getDefaultBranch(cb) {
    repo.show(function(err, repo) {
      cb(null, repo.master_branch);
    });
  }

  function loadConfig(cb) {
    repo.read(branch, "_config.yml", function(err, data) {
      if (err) return cb(err);
      cb(null, jsyaml.load(data));
    });
  }

  function load() {
    loadConfig(function(err, config) {
      app.state.jekyll = !err;
      app.state.config = config;

      var root = config && config.prose && config.prose.rooturl ? config.prose.rooturl : "";
      if (!path) path = root;

      repo.getTree(branch+"?recursive=true", function(err, tree) {
        if (err) return cb("Not found");
        loadBranches(user, reponame, function(err, branches) {
          if (err) return cb("Branches couldn't be fetched");
          app.state.path = path ? path : "";
          app.state.branches = _.filter(branches, function(b) { return b !== branch });
          cb(null, getFiles(tree, path, ""));
        });
      });
    });
  }

  // Load ahead
  if (branch) return load();

  // Fallback to default branch
  getDefaultBranch(function(err, defaultBranch) {
    app.state.branch = branch = defaultBranch;
    load();
  });
}

// Save File
// -------
//
// List all postings for a given repository
// Looks into _posts/blog

function saveFile(user, repo, branch, path, metadata, content, message, cb) {

  var repo = getRepo(user, repo);
  function serialize() {
    if (app.state.jekyll && _.markdown(path)) {
      return ["---", metadata, "---"].join('\n')+'\n'+content;
    } else {
      return content;
    }
  }
  repo.write(branch, path, serialize(), message, cb);
}

// Delete Post
// -------

function deletePost(user, repo, branch, path, file, cb) {
  var repo = getRepo(user, repo);
  repo.remove(branch, _.filepath(path, file), cb);
}

// Move Post
// -------

function movePost(user, repo, branch, path, newPath, cb) {
  var repo = getRepo(user, repo);
  repo.move(branch, path, newPath, cb);
}

// New Post
// -------
//
// Prepare new empty post

function emptyPost(user, repo, branch, path, cb) {
  var rawMetadata = "layout: default\npublished: false";
  var metadata = {
    "layout": "default",
    "published": false,
  };

  var cfg = app.state.config
  if (cfg && cfg.prose && cfg.prose.metadata) {
    if (cfg.prose.metadata[path]) {
      rawMetadata = cfg.prose.metadata[path];
      try {
        metadata = jsyaml.load(rawMetadata);
      } catch(err) {
        console.log('ERROR encoding YAML');
        // No-op
      }
    }
  }

  cb(null, {
    "metadata": metadata,
    "raw_metadata": rawMetadata,
    "content": "# How does it work?\n\nEnter Text in Markdown format.",
    "repo": repo,
    "path": path,
    "persisted": false,
    "file": new Date().format("Y-m-d")+"-your-filename.md"
  });
}

// Load Post
// -------
//
// List all postings for a given repository
// Looks into _posts/blog

function loadPost(user, repo, branch, path, file, cb) {
  var repo = getRepo(user, repo);

  repo.read(branch, path ? path + "/" + file : file, function(err, data) {
    if (err) return cb(err);
    function parse(content) {
      var content = content.replace(/\r\n/g, "\n"); // normalize a little bit
      if (!_.jekyll(path, file)) return {
        metadata: {},
        raw_metadata: "",
        content: content
      };

      var res = {};
      res.content = content.replace(/(---\n)((.|\n)*)\n---\n/, function(match, dashes, frontmatter) {
        res.raw_metadata = frontmatter;
        res.metadata = jsyaml.load(frontmatter);
        return "";
      });
      return res;
    }

    // Extract metadata
    var post = parse(data);
    cb(err, _.extend(post, {
      "markdown": _.markdown(file),
      "jekyll": _.jekyll(path, file),
      "repo": repo,
      "path": path,
      "file": file,
      "persisted": true
    }));
  });
}<|endoftext|>


<commit_before>// Looking at a single unit...

//// Constants /////////////////////////////////////////////////////////////////

const INIT_GUESS = 0.3
const INIT_SLIP = 0.1
const INIT_LEARNED = 0.4
const MAX_LEARNED = 0.99
const INIT_TRANSIT = 0 // TODO update to estimate, 0.05?

//// Formulas //////////////////////////////////////////////////////////////////

function calcCorrect(guess, slip, learned) {
  return learned * (1 - slip) + (1 - learned) * guess
}

function calcIncorrect(guess, slip, learned) {
  return learned * slip + (1 - learned) * (1 - guess)
}

function updateLearned({ score, learned, guess, slip, transit }) {
  const posterior =
    (score * calcCorrect(0, slip, learned)) /
      calcCorrect(guess, slip, learned) +
    ((1 - score) * calcIncorrect(0, slip, learned)) /
      calcIncorrect(guess, slip, learned)
  return posterior + (1 - posterior) * transit
}

function sum(arr) {
  return arr.reduce((sum, val) => sum + val, 0)
}

function mean(arr) {
  return sum(arr) / arr.length
}

function error(arr1, arr2) {
  return mean(
    [...Array(arr1.length).keys()].map(i => (arr1[i] - arr2[i]) ** 2)
  )
}

function correlation(arr1, arr2) {
  const sum1 = sum(arr1)
  const sum2 = sum(arr2)
  return error(arr1.map(a => a / sum1), arr2.map(a => a / sum2))
}

function around(value) {
  return value + Math.random() * value - 0.5 * value
}

function choose(arr) {
  return arr[Math.floor(Math.random() * arr.length)]
}

//// Mock //////////////////////////////////////////////////////////////////////

function createCards(numCards = 20) {
  return [...Array(numCards).keys()].map(i => ({
    name: i,
    realGuess: around(INIT_GUESS),
    guess: INIT_GUESS,
    realSlip: around(INIT_SLIP),
    slip: INIT_SLIP,
    realTransit: INIT_TRANSIT, // TODO around(INIT_TRANSIT)
    transit: INIT_TRANSIT,
    responses: [],
  }))
}

function createLearners(numLearners = 1000) {
  return [...Array(numLearners).keys()].map(i => ({
    name: i,
    learned: INIT_LEARNED,
  }))
}

//// Simluate //////////////////////////////////////////////////////////////////

function getScore(learner, card) {
  const correct = calcCorrect(card.realGuess, card.realSlip, learner.learned)
  return +(correct > Math.random())
}

function updateLearner(learner, params) {
  learner.learned = updateLearned(params)
}

function updateCard(card, params) {
  // THIS IS WHERE THE EXPERIMENT OCCURS!!!
  // card.guess = ???
  // card.slip = ???
  // card.transit = ???

  function calcGuess({ score, guess, slip, learned }) {
    return (score * 0.5 + 0.5) * (1 - learned)
  }

  function calcSlip({ score, guess, slip, learned }) {
    return (1 - (score * 0.5 + 0.5)) * learned
  }

  function smoothed(init, sum, count, C = 30) {
    return (C * init + sum) / (C + params.count + 1)
  }

  card.guess = smoothed(
    INIT_GUESS,
    sum(card.responses.map(calcGuess)),
    params.count
  )
  card.slip = smoothed(
    INIT_SLIP,
    sum(card.responses.map(calcSlip)),
    params.count
  )
}

function simulate(numCards, numLearners, rounds = 5000) {
  const cards = createCards(numCards)
  const learners = createLearners(numLearners)

  for (let i = 0; i < rounds; i++) {
    const card = choose(cards)
    const learner = choose(learners)
    if (learner.learned > MAX_LEARNED) continue
    const score = getScore(learner, card)
    const params = {
      score,
      learned: learner.learned,
      guess: card.guess,
      slip: card.slip,
      transit: card.transit,
      count: card.responses.length,
    }
    card.responses.push(params)
    updateLearner(learner, params)
    updateCard(card, params)
  }

  return { cards, learners }
}

//// Result ////////////////////////////////////////////////////////////////////

function results(cards, learners) {
  const meanResponses = mean(cards.map(({ responses }) => responses.length))
  const meanGuess = mean(cards.map(({ guess }) => guess))
  const guessPerf =
    error(
      cards.map(({ guess }) => guess),
      cards.map(({ realGuess }) => realGuess)
    ) /
    error(cards.map(({ realGuess }) => realGuess), cards.map(() => INIT_GUESS))
  const guessCorr =
    correlation(
      cards.map(({ guess }) => guess),
      cards.map(({ realGuess }) => realGuess)
    ) /
    correlation(
      cards.map(({ realGuess }) => realGuess),
      cards.map(() => INIT_GUESS)
    )
  const meanSlip = mean(cards.map(({ slip }) => slip))
  const slipPerf =
    error(
      cards.map(({ slip }) => slip),
      cards.map(({ realSlip }) => realSlip)
    ) /
    error(cards.map(({ realSlip }) => realSlip), cards.map(() => INIT_SLIP))
  const slipCorr =
    correlation(
      cards.map(({ slip }) => slip),
      cards.map(({ realSlip }) => realSlip)
    ) /
    correlation(
      cards.map(({ realSlip }) => realSlip),
      cards.map(() => INIT_SLIP)
    )
  return {
    meanResponses,
    meanGuess,
    guessPerf,
    guessCorr,
    meanSlip,
    slipPerf,
    slipCorr,
  }
}

//// Run ///////////////////////////////////////////////////////////////////////

if (require.main === module) {
  // The goal is to beat `1`.
  const { cards, learners } = simulate(160, 8000, 40000)
  console.log(results(cards, learners))
}

/*
  () => INIT_GUESS,
  () => INIT_SLIP,
  () => INIT_TRANSIT,
  () => 0,
  () => 1,
  ({ learned }) => learned,
  ({ learned }) => 1 - learned,
  ({ learned }) => 1 + learned,
  ({ learned }) => 1 / learned,
  ({ learned }) => 1 / (1 - learned),
  ({ learned }) => 1 / (1 + learned),
  ({ learned }) => learned / (1 - learned),
  ({ learned }) => learned / (1 + learned),
  ({ learned }) => (1 - learned) / learned,
  ({ learned }) => (1 - learned) / (1 + learned),
  ({ learned }) => (1 + learned) / learned,
  ({ learned }) => (1 + learned) / (1 - learned),
  ({ learned, guess, slip }) => calcCorrect(guess, slip, learned),
  ({ learned, guess, slip }) => calcIncorrect(guess, slip, learned),
  ({ learned, guess, slip }) => calcCorrect(0, slip, learned),
  ({ learned, guess, slip }) => calcIncorrect(0, slip, learned),
  ({ learned, guess, slip }) => calcCorrect(1, slip, learned),
  ({ learned, guess, slip }) => calcIncorrect(1, slip, learned),
  ({ learned, guess, slip }) => calcCorrect(guess, 0, learned),
  ({ learned, guess, slip }) => calcIncorrect(guess, 0, learned),
  ({ learned, guess, slip }) => calcCorrect(guess, 1, learned),
  ({ learned, guess, slip }) => calcIncorrect(guess, 1, learned),
  ({ learned, guess, slip }) => calcCorrect(guess, slip, 0),
  ({ learned, guess, slip }) => calcIncorrect(guess, slip, 0),
  ({ learned, guess, slip }) => calcCorrect(guess, slip, 1),
  ({ learned, guess, slip }) => calcIncorrect(guess, slip, 1),
  ({ learned, guess, slip }) => calcCorrect(0, 0, learned),
  ({ learned, guess, slip }) => calcIncorrect(0, 0, learned),
  ({ learned, guess, slip }) => calcCorrect(1, 1, learned),
  ({ learned, guess, slip }) => calcIncorrect(1, 1, learned),
  ({ learned, guess, slip }) => calcCorrect(0, 1, learned),
  ({ learned, guess, slip }) => calcIncorrect(0, 1, learned),
  ({ learned, guess, slip }) => calcCorrect(1, 0, learned),
  ({ learned, guess, slip }) => calcIncorrect(1, 0, learned),
  ({ learned, guess, slip }) => calcCorrect(0, slip, 0),
  ({ learned, guess, slip }) => calcIncorrect(0, slip, 0),
  ({ learned, guess, slip }) => calcCorrect(1, slip, 1),
  ({ learned, guess, slip }) => calcIncorrect(1, slip, 1),
  ({ learned, guess, slip }) => calcCorrect(0, slip, 1),
  ({ learned, guess, slip }) => calcIncorrect(0, slip, 1),
  ({ learned, guess, slip }) => calcCorrect(1, slip, 0),
  ({ learned, guess, slip }) => calcIncorrect(1, slip, 0),
  ({ learned, guess, slip }) => calcCorrect(guess, 0, 0),
  ({ learned, guess, slip }) => calcIncorrect(guess, 0, 0),
  ({ learned, guess, slip }) => calcCorrect(guess, 1, 1),
  ({ learned, guess, slip }) => calcIncorrect(guess, 1, 1),
  ({ learned, guess, slip }) => calcCorrect(guess, 1, 0),
  ({ learned, guess, slip }) => calcIncorrect(guess, 1, 0),
  ({ learned, guess, slip }) => calcCorrect(guess, 0, 1),
  ({ learned, guess, slip }) => calcIncorrect(guess, 0, 1),

  const totalOptions = values.length ** 6
  let option = 0

  const opt1 = []
  const opt2 = []
  const opt3 = []

  for (let a = 0; a < values.length; a++) {
    for (let b = 0; b < values.length; b++) {
      for (let c = 0; c < values.length; c++) {
        for (let d = 0; d < values.length; d++) {
          for (let e = 0; e < values.length; e++) {
            for (let f = 0; f < values.length; f++) {

  const path = [f, e, d, c, b, a]
  const { cards, learners } = simulate(20000, path)

  const foundCount = (guessPerf < 0.99) + (slipPerf < 0.99) + (transitPerf < 0.99)
  if (foundCount > 0) {
    const data = { path, guessPerf, slipPerf, transitPerf }
    if (foundCount === 1) opt1.push(data)
    if (foundCount === 2) opt2.push(data)
    if (foundCount === 3) opt3.push(data)
  }

  option++

  process.stdout.clearLine()
  process.stdout.cursorTo(0)
  process.stdout.write(`${option} of ${totalOptions}, found ${opt1.length} ${opt2.length} ${opt3.length} ... ${path.map(p => (''+p).padStart(2)).join(' ')}`)

            }
          }
        }
      }
    }
  }

*/
<commit_msg>Update simulation.js<commit_after>// Looking at a single unit...

//// Constants /////////////////////////////////////////////////////////////////

const INIT_GUESS = 0.3
const INIT_SLIP = 0.1
const INIT_LEARNED = 0.5
const MAX_LEARNED = 0.99
const INIT_TRANSIT = 0 // TODO update to estimate, 0.05?

//// Formulas //////////////////////////////////////////////////////////////////

function calcCorrect(guess, slip, learned) {
  return learned * (1 - slip) + (1 - learned) * guess
}

function calcIncorrect(guess, slip, learned) {
  return learned * slip + (1 - learned) * (1 - guess)
}

function updateLearned({ score, learned, guess, slip, transit }) {
  const posterior =
    (score * calcCorrect(0, slip, learned)) /
      calcCorrect(guess, slip, learned) +
    ((1 - score) * calcIncorrect(0, slip, learned)) /
      calcIncorrect(guess, slip, learned)
  return posterior + (1 - posterior) * transit
}

function sum(arr) {
  return arr.reduce((sum, val) => sum + val, 0)
}

function mean(arr) {
  return sum(arr) / arr.length
}

function error(arr1, arr2) {
  return mean(
    [...Array(arr1.length).keys()].map(i => Math.abs(arr1[i] - arr2[i]))
  )
}

function correlation(arr1, arr2) {
  const sum1 = sum(arr1)
  const sum2 = sum(arr2)
  return error(arr1.map(a => a / sum1), arr2.map(a => a / sum2))
}

function around(value) {
  return value + Math.random() * value - 0.5 * value
}

function choose(arr) {
  return arr[Math.floor(Math.random() * arr.length)]
}

//// Mock //////////////////////////////////////////////////////////////////////

function createCards(numCards = 20) {
  return [...Array(numCards).keys()].map(i => ({
    name: i,
    realGuess: around(INIT_GUESS),
    guess: INIT_GUESS,
    realSlip: around(INIT_SLIP),
    slip: INIT_SLIP,
    realTransit: INIT_TRANSIT, // TODO around(INIT_TRANSIT)
    transit: INIT_TRANSIT,
    responses: [],
  }))
}

function createLearners(numLearners = 1000) {
  return [...Array(numLearners).keys()].map(i => ({
    name: i,
    learned: INIT_LEARNED,
  }))
}

//// Simluate //////////////////////////////////////////////////////////////////

function getScore(learner, card) {
  const correct = calcCorrect(card.realGuess, card.realSlip, learner.learned)
  return +(correct > Math.random())
}

function updateLearner(learner, params) {
  learner.learned = updateLearned(params)
}

function updateCard(card, params) {
  // THIS IS WHERE THE EXPERIMENT OCCURS!!!
  // card.guess = ???
  // card.slip = ???
  // card.transit = ???

  function calcGuess({ score, guess, slip, learned }) {
    return (score * (1 - learned)) / learned
  }

  function calcSlip({ score, guess, slip, learned }) {
    return (1 - score) * learned * learned
  }

  function smoothed(init, sum, count, C = 30) {
    return (C * init + sum) / (C + params.count)
  }

  card.guess = smoothed(
    INIT_GUESS,
    sum(card.responses.map(calcGuess)),
    params.count
  )

  card.slip = smoothed(
    INIT_SLIP,
    sum(card.responses.map(calcSlip)),
    params.count
  )
}

function simulate(numCards, numLearners, rounds = 5000) {
  const cards = createCards(numCards)
  const learners = createLearners(numLearners)

  for (let i = 0; i < rounds; i++) {
    const learner = choose(learners)
    if (learner.learned > MAX_LEARNED) continue
    const card = choose(cards)
    const score = getScore(learner, card)
    const params = {
      score,
      learned: learner.learned,
      guess: card.guess,
      slip: card.slip,
      transit: card.transit,
      count: card.responses.length,
    }
    card.responses.push(params)
    updateLearner(learner, params)
    updateCard(card, params)
  }

  return { cards, learners }
}

//// Result ////////////////////////////////////////////////////////////////////

function results(cards, learners) {
  const meanResponses = mean(cards.map(({ responses }) => responses.length))
  const meanGuess = mean(cards.map(({ guess }) => guess))
  const guessPerf =
    error(
      cards.map(({ guess }) => guess),
      cards.map(({ realGuess }) => realGuess)
    ) /
    error(cards.map(({ realGuess }) => realGuess), cards.map(() => INIT_GUESS))
  const guessCorr =
    correlation(
      cards.map(({ guess }) => guess),
      cards.map(({ realGuess }) => realGuess)
    ) /
    correlation(
      cards.map(({ realGuess }) => realGuess),
      cards.map(() => INIT_GUESS)
    )
  const meanSlip = mean(cards.map(({ slip }) => slip))
  const slipPerf =
    error(
      cards.map(({ slip }) => slip),
      cards.map(({ realSlip }) => realSlip)
    ) /
    error(cards.map(({ realSlip }) => realSlip), cards.map(() => INIT_SLIP))
  const slipCorr =
    correlation(
      cards.map(({ slip }) => slip),
      cards.map(({ realSlip }) => realSlip)
    ) /
    correlation(
      cards.map(({ realSlip }) => realSlip),
      cards.map(() => INIT_SLIP)
    )
  return {
    meanResponses,
    meanGuess,
    guessPerf,
    guessCorr,
    meanSlip,
    slipPerf,
    slipCorr,
  }
}

//// Run ///////////////////////////////////////////////////////////////////////

if (require.main === module) {
  // The goal is to beat `1`.
  const { cards, learners } = simulate(30, 4000, 40000)
  console.log(results(cards, learners))
}

/*
  () => INIT_GUESS,
  () => INIT_SLIP,
  () => INIT_TRANSIT,
  () => 0,
  () => 1,
  ({ learned }) => learned,
  ({ learned }) => 1 - learned,
  ({ learned }) => 1 + learned,
  ({ learned }) => 1 / learned,
  ({ learned }) => 1 / (1 - learned),
  ({ learned }) => 1 / (1 + learned),
  ({ learned }) => learned / (1 - learned),
  ({ learned }) => learned / (1 + learned),
  ({ learned }) => (1 - learned) / learned,
  ({ learned }) => (1 - learned) / (1 + learned),
  ({ learned }) => (1 + learned) / learned,
  ({ learned }) => (1 + learned) / (1 - learned),
  ({ learned, guess, slip }) => calcCorrect(guess, slip, learned),
  ({ learned, guess, slip }) => calcIncorrect(guess, slip, learned),
  ({ learned, guess, slip }) => calcCorrect(0, slip, learned),
  ({ learned, guess, slip }) => calcIncorrect(0, slip, learned),
  ({ learned, guess, slip }) => calcCorrect(1, slip, learned),
  ({ learned, guess, slip }) => calcIncorrect(1, slip, learned),
  ({ learned, guess, slip }) => calcCorrect(guess, 0, learned),
  ({ learned, guess, slip }) => calcIncorrect(guess, 0, learned),
  ({ learned, guess, slip }) => calcCorrect(guess, 1, learned),
  ({ learned, guess, slip }) => calcIncorrect(guess, 1, learned),
  ({ learned, guess, slip }) => calcCorrect(guess, slip, 0),
  ({ learned, guess, slip }) => calcIncorrect(guess, slip, 0),
  ({ learned, guess, slip }) => calcCorrect(guess, slip, 1),
  ({ learned, guess, slip }) => calcIncorrect(guess, slip, 1),
  ({ learned, guess, slip }) => calcCorrect(0, 0, learned),
  ({ learned, guess, slip }) => calcIncorrect(0, 0, learned),
  ({ learned, guess, slip }) => calcCorrect(1, 1, learned),
  ({ learned, guess, slip }) => calcIncorrect(1, 1, learned),
  ({ learned, guess, slip }) => calcCorrect(0, 1, learned),
  ({ learned, guess, slip }) => calcIncorrect(0, 1, learned),
  ({ learned, guess, slip }) => calcCorrect(1, 0, learned),
  ({ learned, guess, slip }) => calcIncorrect(1, 0, learned),
  ({ learned, guess, slip }) => calcCorrect(0, slip, 0),
  ({ learned, guess, slip }) => calcIncorrect(0, slip, 0),
  ({ learned, guess, slip }) => calcCorrect(1, slip, 1),
  ({ learned, guess, slip }) => calcIncorrect(1, slip, 1),
  ({ learned, guess, slip }) => calcCorrect(0, slip, 1),
  ({ learned, guess, slip }) => calcIncorrect(0, slip, 1),
  ({ learned, guess, slip }) => calcCorrect(1, slip, 0),
  ({ learned, guess, slip }) => calcIncorrect(1, slip, 0),
  ({ learned, guess, slip }) => calcCorrect(guess, 0, 0),
  ({ learned, guess, slip }) => calcIncorrect(guess, 0, 0),
  ({ learned, guess, slip }) => calcCorrect(guess, 1, 1),
  ({ learned, guess, slip }) => calcIncorrect(guess, 1, 1),
  ({ learned, guess, slip }) => calcCorrect(guess, 1, 0),
  ({ learned, guess, slip }) => calcIncorrect(guess, 1, 0),
  ({ learned, guess, slip }) => calcCorrect(guess, 0, 1),
  ({ learned, guess, slip }) => calcIncorrect(guess, 0, 1),

  const totalOptions = values.length ** 6
  let option = 0

  const opt1 = []
  const opt2 = []
  const opt3 = []

  for (let a = 0; a < values.length; a++) {
    for (let b = 0; b < values.length; b++) {
      for (let c = 0; c < values.length; c++) {
        for (let d = 0; d < values.length; d++) {
          for (let e = 0; e < values.length; e++) {
            for (let f = 0; f < values.length; f++) {

  const path = [f, e, d, c, b, a]
  const { cards, learners } = simulate(20000, path)

  const foundCount = (guessPerf < 0.99) + (slipPerf < 0.99) + (transitPerf < 0.99)
  if (foundCount > 0) {
    const data = { path, guessPerf, slipPerf, transitPerf }
    if (foundCount === 1) opt1.push(data)
    if (foundCount === 2) opt2.push(data)
    if (foundCount === 3) opt3.push(data)
  }

  option++

  process.stdout.clearLine()
  process.stdout.cursorTo(0)
  process.stdout.write(`${option} of ${totalOptions}, found ${opt1.length} ${opt2.length} ${opt3.length} ... ${path.map(p => (''+p).padStart(2)).join(' ')}`)

            }
          }
        }
      }
    }
  }

*/
<|endoftext|>


<commit_before>const HTMLElement=(e=>{function t(){}return t.prototype=console.log(window.HTMLElement),window.HTMLElement.prototype,t})();class TokenList{constructor(e){const t=e=>{console.log("text content",e.textContent),/{(\w+|#)}/.test(e.textContent)&&(e.text=e.textContent).match(/[^{]+(?=})/g).map(t=>(this[t]||(this[t]=[])).push(e))},n=document.createNodeIterator(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,e=>e.attributes&&[].slice.call(e.attributes).map(t)||t(e),null);for(;n.nextNode(););}bind(e){const t=t=>n=>n.textContent=n.textContent.split("{"+t+"}").join(e[t]);for(let n in this)console.log("symbol",n),this[n].map(e=>(e.textContent=e.text)&&e);for(let n in this)this[n].map(t(n))}}(e=>{function t(e){let t=new XMLHttpRequest;t.link=e,t.onload=n,t.open("GET",e.href),t.responseType="document",t.send()}function n(){let e=this.link,t=this.response,n=e.nextChild,l=e.content=t.querySelector("template");for(let o of document.querySelectorAll(e.id))l&&s.call(o,l);for(let s of t.querySelectorAll("style,link,script"))!function(e,t,n){let s=t.getAttribute("as"),l=document.createElement("script"==s?s:t.localName);["id","rel","href","src","textContent","as","defer","crossOrigin"].map(e=>t[e]&&e in l&&(l[e]=t[e])),"style"==s&&(l.rel="stylesheet"),"script"==s&&(l.src=l.href),e.parentNode.insertBefore(l,n)}(e,s,n)}function s(e){e=document.importNode(e,!0);let t;[].slice.call(e.attributes).map(e=>!this.attributes[e.name]&&this.setAttribute(e.name,e.value));for(let n of this.querySelectorAll("[slot]"))(t=(e.content||e).querySelector("slot[name="+n.getAttribute("slot")+"]"))&&t.parentNode.replaceChild(n,t);return this.innerHTML=e.innerHTML}new MutationObserver(e=>{for(let n of e)for(let e of n.addedNodes)/^p/.test(e.rel)&&/\-/.test(e.id)&&t(e),/\-/.test(e.localName)&&(link=document.querySelector("#"+e.localName))&&link.content&&s.call(e,link.content)&&customElements.upgrade(e)}).observe(document.documentElement,{childList:!0,subtree:!0}),[].slice.call(document.querySelectorAll('[rel^=pre][id~="-"]')).map(t)})();const Template=e=>{e.length&&(e=document.querySelector("template[name="+e+"]"));let t=e.innerHTML,n=e.nextSibling;return e.innerHTML="",e.bind=function(e){const s=document.createElement("section");for(let t of this.dependents||[])t.parentNode.removeChild(t);s.innerHTML=[].concat(e).reduce((e,n,s)=>{let l=t;"object"!=typeof n&&(n={self:n}),n["#"]=s;for(let t in n)l=l.split("{"+t+"}").join(n[t]);return e+l},"");for(let t of this.dependents=[].slice.call(s.childNodes))this.parentNode.insertBefore(t,n)}.bind(e),e};window.customElements=window.customElements||{},new class{constructor(){customElements.define=this.define.bind(this),customElements.upgrade=this.upgrade.bind(this)}define(e,t){this[e]=t,[].slice.call(document.querySelectorAll(e)).map(this.upgrade,this)}upgrade(e){this[e.localName]&&Object.setPrototypeOf(e,this[e.localName].prototype).connectedCallback()}};const ParentNode=e=>class extends e{select(){return this.selectAll(...arguments)[0]}selectAll(e,...t){return e=[].concat(e),[].slice.call(this.querySelectorAll(t.reduce((t,n)=>t+n+e.shift(),e.shift())))}},EventTarget=e=>class extends e{on(e,t){this.addEventListener(e,this.renderable(t))}renderable(e){return t=>!1!==e.call(this,t)&&t.defaultPrevented||this.render()}},GlobalEventHandlers=e=>class extends e{onconnect(){this.templates=this.selectAll("template[name]").map(Template),this.tokens=new TokenList(this),super.onconnect&&super.onconnect()}reflect(e){/^on/.test(e)&&e in HTMLElement.prototype&&this.on(e.substr(2),this[e])}register(e,t,n){for(let s of[].slice.call(e.attributes))/^on/.test(n=s.name)&&(t=(/{\s*(\w+)/.exec(e[n])||[])[1])&&(e[n]=this.renderable(this[t]))}},Custom=e=>class extends(ParentNode(EventTarget(GlobalEventHandlers(e)))){connectedCallback(){this.context={},super.initialize&&super.initialize(),Object.getOwnPropertyNames(e.prototype).map(this.reflect,this),this.onconnect(),this.render()}render(){for(let e of this.templates)e.bind(this[e.getAttribute("name")]);this.tokens.bind(this),this.register(this),this.selectAll("*").map(this.register,this),super.onidle&&super.onidle()}},Element=e=>t=>customElements.define(e+"",Custom(t));<commit_msg>:muscle: bump ??? distribution dist/snuggsi.min.es.js<commit_after>const HTMLElement=(e=>{function t(){}return t.prototype=window.HTMLElement.prototype,t})();class TokenList{constructor(e){const t=e=>{console.log("text content",e.textContent),/{(\w+|#)}/.test(e.textContent)&&(e.text=e.textContent).match(/[^{]+(?=})/g).map(t=>(this[t]||(this[t]=[])).push(e))},n=document.createNodeIterator(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,e=>e.attributes&&[].slice.call(e.attributes).map(t)||t(e),null);for(;n.nextNode(););}bind(e){const t=t=>n=>n.textContent=n.textContent.split("{"+t+"}").join(e[t]);for(let n in this)console.log("symbol",n),this[n].map(e=>(e.textContent=e.text)&&e);for(let n in this)this[n].map(t(n))}}(e=>{function t(e){let t=new XMLHttpRequest;t.link=e,t.onload=n,t.open("GET",e.href),t.responseType="document",t.send()}function n(){let e=this.link,t=this.response,n=e.nextChild,l=e.content=t.querySelector("template");for(let o of document.querySelectorAll(e.id))l&&s.call(o,l);for(let s of t.querySelectorAll("style,link,script"))!function(e,t,n){let s=t.getAttribute("as"),l=document.createElement("script"==s?s:t.localName);["id","rel","href","src","textContent","as","defer","crossOrigin"].map(e=>t[e]&&e in l&&(l[e]=t[e])),"style"==s&&(l.rel="stylesheet"),"script"==s&&(l.src=l.href),e.parentNode.insertBefore(l,n)}(e,s,n)}function s(e){e=document.importNode(e,!0);let t;[].slice.call(e.attributes).map(e=>!this.attributes[e.name]&&this.setAttribute(e.name,e.value));for(let n of this.querySelectorAll("[slot]"))(t=(e.content||e).querySelector("slot[name="+n.getAttribute("slot")+"]"))&&t.parentNode.replaceChild(n,t);return this.innerHTML=e.innerHTML}new MutationObserver(e=>{for(let n of e)for(let e of n.addedNodes)/^p/.test(e.rel)&&/\-/.test(e.id)&&t(e),/\-/.test(e.localName)&&(link=document.querySelector("#"+e.localName))&&link.content&&s.call(e,link.content)&&customElements.upgrade(e)}).observe(document.documentElement,{childList:!0,subtree:!0}),[].slice.call(document.querySelectorAll('[rel^=pre][id~="-"]')).map(t)})();const Template=e=>{e.length&&(e=document.querySelector("template[name="+e+"]"));let t=e.innerHTML,n=e.nextSibling;return e.innerHTML="",e.bind=function(e){const s=document.createElement("section");for(let t of this.dependents||[])t.parentNode.removeChild(t);s.innerHTML=[].concat(e).reduce((e,n,s)=>{let l=t;"object"!=typeof n&&(n={self:n}),n["#"]=s;for(let t in n)l=l.split("{"+t+"}").join(n[t]);return e+l},"");for(let t of this.dependents=[].slice.call(s.childNodes))this.parentNode.insertBefore(t,n)}.bind(e),e};window.customElements=window.customElements||{},new class{constructor(){customElements.define=this.define.bind(this),customElements.upgrade=this.upgrade.bind(this)}define(e,t){this[e]=t,[].slice.call(document.querySelectorAll(e)).map(this.upgrade,this)}upgrade(e){this[e.localName]&&Object.setPrototypeOf(e,this[e.localName].prototype).connectedCallback()}};const ParentNode=e=>class extends e{select(){return this.selectAll(...arguments)[0]}selectAll(e,...t){return e=[].concat(e),[].slice.call(this.querySelectorAll(t.reduce((t,n)=>t+n+e.shift(),e.shift())))}},EventTarget=e=>class extends e{on(e,t){this.addEventListener(e,this.renderable(t))}renderable(e){return t=>!1!==e.call(this,t)&&t.defaultPrevented||this.render()}},GlobalEventHandlers=e=>class extends e{onconnect(){this.templates=this.selectAll("template[name]").map(Template),this.tokens=new TokenList(this),super.onconnect&&super.onconnect()}reflect(e){/^on/.test(e)&&e in HTMLElement.prototype&&this.on(e.substr(2),this[e])}register(e,t,n){for(let s of[].slice.call(e.attributes))/^on/.test(n=s.name)&&(t=(/{\s*(\w+)/.exec(e[n])||[])[1])&&(e[n]=this.renderable(this[t]))}},Custom=e=>class extends(ParentNode(EventTarget(GlobalEventHandlers(e)))){connectedCallback(){this.context={},super.initialize&&super.initialize(),Object.getOwnPropertyNames(e.prototype).map(this.reflect,this),this.onconnect(),this.render()}render(){for(let e of this.templates)e.bind(this[e.getAttribute("name")]);this.tokens.bind(this),this.register(this),this.selectAll("*").map(this.register,this),super.onidle&&super.onidle()}},Element=e=>t=>customElements.define(e+"",Custom(t));<|endoftext|>


<commit_before>Status.get(function(status) {

    var bagsPrefix = status.username;
    // This condition will only be met if hosted on tiddlyspace
    if(status.space) {
        bagsPrefix = status.space.name;
    }

    var public_bag = bagsPrefix + '_public';
    var $publicBagOutput = $('#public-bag-output');
    var $publicTiddlerOutput = $('#public-tiddler-output');
    var $searchOutput = $('#search-output');

    $('#fetch-public-bag').click(function () {

        var bag = new Bag(public_bag,
            function(tiddlers) {
                $.each(tiddlers, function(index, tiddler) {
                    $publicBagOutput.append('
' + tiddler.title + '
');
                });
            },
             null, isRadioButtonSelected('#render-bag'),
            isRadioButtonSelected('#fat-bag')
        );
        bag.get();
    });

    $('#fetch-public-tiddler').click(function () {
        var render = isRadioButtonSelected('#render-tiddler');

        var tiddler = new Tiddler(public_bag, $('#fetch-tiddler-title').val(),
            function(data) {
                if(render) {
                    $('#public-tiddler-output').html(data.render);
                } else {
                    $('#public-tiddler-output').html(data.text);
                }
            },
        null, render);
        tiddler.get();
    });

    $('#search').click(function() {
        Search.query($('#search-query').val(), function(results) {
            $.each(results, function(index, tiddler) {
                $searchOutput.append('
' + tiddler.title + '
');
            });
        }, null, isRadioButtonSelected('#search-render'), isRadioButtonSelected('#search-fat'));
    });

    $('#clear-public-bag').click(function() {
        $publicBagOutput.empty();
    });

    $('#clear-public-tiddler').click(function() {
        $publicTiddlerOutput.empty();
    });

    $('#clear-search').click(function() {
        $searchOutput.empty();
    });

    $('input[type="radio"]').change(function() {
        if($(this).hasClass('1')) {
            $('input.2').attr('checked', false);
        } else {
            $('input.1').attr('checked', false);
        }
    });
});

function isRadioButtonSelected(selector) {
    var optionSelected = false;
    if($(selector).is(':checked')) {
        optionSelected = true;
    }
    return optionSelected;
}<commit_msg>Basic tiddler creation/updating added<commit_after>Status.get(function(status) {

    var bagsPrefix = status.username;
    // This condition will only be met if hosted on tiddlyspace
    if(status.space) {
        bagsPrefix = status.space.name;
    }

    var public_bag = bagsPrefix + '_public';
    var $publicBagOutput = $('#public-bag-output');
    var $publicTiddlerOutput = $('#public-tiddler-output');
    var $searchOutput = $('#search-output');

    $('#fetch-public-bag').click(function () {

        var bag = new Bag(public_bag,
            function(tiddlers) {
                $.each(tiddlers, function(index, tiddler) {
                    $publicBagOutput.append('
' + tiddler.title + '
');
                });
            },
             null, isRadioButtonSelected('#render-bag'),
            isRadioButtonSelected('#fat-bag')
        );
        bag.get();
    });

    $('#fetch-public-tiddler').click(function () {
        var render = isRadioButtonSelected('#render-tiddler');

        var tiddler = new Tiddler(public_bag, $('#fetch-tiddler-title').val(),
            function(data) {
                if(render) {
                    $('#public-tiddler-output').html(data.render);
                } else {
                    $('#public-tiddler-output').html(data.text);
                }
            },
        null, render);
        tiddler.get();
    });

    $('#create-tiddler').click(function() {
        var tags = $('#new-tiddler-tags').val().split(' ');

        var tiddler = new Tiddler(public_bag, $('#new-tiddler-title').val(), function() {
            alert("tiddler created");
        });
        tiddler.put({ text: $('#new-tiddler-text').val(), tags: tags });
    });

    $('#search').click(function() {
        Search.query($('#search-query').val(), function(results) {
            $.each(results, function(index, tiddler) {
                $searchOutput.append('
' + tiddler.title + '
');
            });
        }, null, isRadioButtonSelected('#search-render'), isRadioButtonSelected('#search-fat'));
    });

    $('#clear-public-bag').click(function() {
        $publicBagOutput.empty();
    });

    $('#clear-public-tiddler').click(function() {
        $publicTiddlerOutput.empty();
    });

    $('#clear-search').click(function() {
        $searchOutput.empty();
    });

    $('input[type="radio"]').change(function() {
        if($(this).hasClass('1')) {
            $('input.2').attr('checked', false);
        } else {
            $('input.1').attr('checked', false);
        }
    });
});

function isRadioButtonSelected(selector) {
    var optionSelected = false;
    if($(selector).is(':checked')) {
        optionSelected = true;
    }
    return optionSelected;
}<|endoftext|>


<commit_before>var passport = require('koa-passport'),
    Router = require('koa-router'),
    DigestStrategy = require('passport-http').DigestStrategy,
    TwitterStrategy = require('passport-twitter').Strategy,
    GitHubStrategy = require('passport-github').Strategy,
    GoogleStrategy = require('passport-google-oauth').Strategy,
    OpenStreetMapStrategy = require('passport-openstreetmap').Strategy,
    FacebookStrategy = require('passport-facebook').Strategy,
    config = require('../config/config'),
    jwt = require('koa-jwt'),
    compose = require('koa-compose'),
    routes = {},
    tokenResponse;

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(id, done) {
  done(null, user);
});

/**
 * Middleware to build sign and return a JSON Web Token
 */
function *tokenResponse(next){
  var token;
  if (this.isAuthenticated()) {
    token = jwt.sign(this.req.user, config.jwt.secret);
    if (this.session && this.session.redirect) {
      return this.redirect(this.session.redirect + '?token=' + token);
    }
    this.status = 200;
    this.body = { token: token };
  } else {
    this.throw(401);
  }
}

/**
 * Middleware to store a redirectURL in the session for later use
 */
function *storeRedirect(next){
  if (this.request.query.redirect) {
    this.session.redirect = this.request.query.redirect;
  }
  yield next;
}

// Admin auth if local user and pass were provided
if (config.auth.local.username && config.auth.local.password) {
  passport.use(new DigestStrategy({ qop: 'auth' },
    function(username, done) {
      if (username === config.auth.local.username) {
        // If the correct username is supplied return the user and pass word for verification
        done(null, {name: config.auth.local.username}, config.auth.local.password);
      } else {
        done(null, false);
      }
    },
    function(params, done) {
      // asynchronous validation, for effect...
      process.nextTick(function () {
        // check nonces in params here, if desired
        return done(null, true);
      });
    }
  ));

  routes.admin = {
    handler: compose([
      storeRedirect,
      passport.authenticate('digest', { session: false }),
      tokenResponse
    ]),
    path: '/admin',
    method: 'get'
  };

}

// GitHub Auth if id and secret were provided
if (config.auth.github.client_id && config.auth.github.client_secret) {
  passport.use(new GitHubStrategy({
      clientID: config.auth.github.client_id,
      clientSecret: config.auth.github.client_secret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/github/callback'
    },
    function(accessToken, refreshToken, profile, done) {
      return done(null, {name: profile.displayName});
    }
  ));

  routes.github = {
    handler: compose([
      storeRedirect,
      passport.authenticate('github', {
        session: false,
        callbackURL:  config.app.baseUrl + config.auth.mount + '/github/callback'
      })
    ]),
    path: '/github',
    method: 'get'
  };

  routes.github_callback = {
    handler: compose([
      passport.authenticate('github', { session: false }),
      tokenResponse
    ]),
    path: '/github/callback',
    method: 'get'
  };
}

if (config.auth.twitter.consumerKey && config.auth.twitter.consumerSecret) {
  passport.use(new TwitterStrategy({
      consumerKey: config.auth.twitter.consumerKey,
      consumerSecret: config.auth.twitter.consumerSecret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/twitter/callback'
    },
    function(token, tokenSecret, profile, done){
      return done(null, {name: profile.displayName});
    }
  ));

  routes.twitter = {
    handler: compose([
      storeRedirect,
      passport.authenticate('twitter', {
        callbackURL:  config.app.baseUrl + config.auth.mount + '/twitter/callback'
      })
    ]),
    path: '/twitter',
    method: 'get'
  };

  routes.twitter_callback = {
    handler: compose([
      passport.authenticate('twitter'),
      tokenResponse
    ]),
    path: '/twitter/callback',
    method: 'get'
  };
}

if (config.auth.osm.consumerKey && config.auth.osm.consumerSecret) {
  passport.use(new OpenStreetMapStrategy({
      consumerKey: config.auth.osm.consumerKey,
      consumerSecret: config.auth.osm.consumerSecret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/openstreetmap/callback'
    },
    function(token, tokenSecret, profile, done){
      return done(null, {name: profile.displayName});
    }
  ));

  routes.openstreetmap = {
    handler: compose([
      storeRedirect,
      passport.authenticate('openstreetmap', {
        callbackURL:  config.app.baseUrl + config.auth.mount + '/openstreetmap/callback'
      })
    ]),
    path: '/openstreetmap',
    method: 'get'
  };

  routes.openstreetmap_callback = {
    handler: compose([
      passport.authenticate('openstreetmap'),
      tokenResponse
    ]),
    path: '/openstreetmap/callback',
    method: 'get'
  };
}

// Facebook Auth if id and secret were provided
if (config.auth.facebook.client_id && config.auth.facebook.client_secret) {
  passport.use(new FacebookStrategy({
      clientID: config.auth.facebook.client_id,
      clientSecret: config.auth.facebook.client_secret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/facebook/callback'
    },
    function(accessToken, refreshToken, profile, done) {
      return done(null, {name: profile.displayName});
    }
  ));

  routes.facebook = {
    handler: compose([
      storeRedirect,
      passport.authenticate('facebook', {
        session: false,
        callbackURL:  config.app.baseUrl + config.auth.mount + '/facebook/callback'
      })
    ]),
    path: '/facebook',
    method: 'get'
  };

  routes.facebook_callback = {
    handler: compose([
      passport.authenticate('facebook', { session: false }),
      tokenResponse
    ]),
    path: '/facebook/callback',
    method: 'get'
  };
}

// Google Auth if enabled
if (config.auth.google.consumerKey && config.auth.google.consumerSecret) {
  passport.use(new GoogleStrategy({
      clientID: config.auth.google.consumerKey,
      clientSecret: config.auth.google.consumerSecret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/google/callback'
    },
    function(accessToken, refreshToken, profile, done) {
      return done(null, {name: profile.displayName});
    }
  ));

  routes.google = {
    handler: compose([
      storeRedirect,
      passport.authenticate('google')
    ]),
    path: '/google',
    method: 'get'
  };

  routes.google_callback = {
    handler: compose([
      passport.authenticate('google'),
      tokenResponse
    ]),
    path: '/google/callback',
    method: 'get'
  };
}

module.exports.routes = routes;<commit_msg>Untangle secrets<commit_after>var passport = require('koa-passport'),
    Router = require('koa-router'),
    DigestStrategy = require('passport-http').DigestStrategy,
    TwitterStrategy = require('passport-twitter').Strategy,
    GitHubStrategy = require('passport-github').Strategy,
    GoogleStrategy = require('passport-google-oauth').Strategy,
    OpenStreetMapStrategy = require('passport-openstreetmap').Strategy,
    FacebookStrategy = require('passport-facebook').Strategy,
    config = require('../config/config'),
    jwt = require('koa-jwt'),
    compose = require('koa-compose'),
    routes = {},
    tokenResponse;

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(id, done) {
  done(null, user);
});

/**
 * Middleware to build sign and return a JSON Web Token
 */
function *tokenResponse(next){
  var token;
  if (this.isAuthenticated()) {
    token = jwt.sign(this.req.user, config.jwt.secret);
    if (this.session && this.session.redirect) {
      return this.redirect(this.session.redirect + '?token=' + token);
    }
    this.status = 200;
    this.body = { token: token };
  } else {
    this.throw(401);
  }
}

/**
 * Middleware to store a redirectURL in the session for later use
 */
function *storeRedirect(next){
  if (this.request.query.redirect) {
    this.session.redirect = this.request.query.redirect;
  }
  yield next;
}

// Admin auth if local user and pass were provided
if (config.auth.local.username && config.auth.local.password) {
  passport.use(new DigestStrategy({ qop: 'auth' },
    function(username, done) {
      if (username === config.auth.local.username) {
        // If the correct username is supplied return the user and pass word for verification
        done(null, {name: config.auth.local.username}, config.auth.local.password);
      } else {
        done(null, false);
      }
    },
    function(params, done) {
      // asynchronous validation, for effect...
      process.nextTick(function () {
        // check nonces in params here, if desired
        return done(null, true);
      });
    }
  ));

  routes.admin = {
    handler: compose([
      storeRedirect,
      passport.authenticate('digest', { session: false }),
      tokenResponse
    ]),
    path: '/admin',
    method: 'get'
  };

}

// GitHub Auth if id and secret were provided
if (config.auth.github.client_id && config.auth.github.client_secret) {
  passport.use(new GitHubStrategy({
      clientID: config.auth.github.client_id,
      clientSecret: config.auth.github.client_secret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/github/callback'
    },
    function(accessToken, refreshToken, profile, done) {
      return done(null, {name: profile.displayName});
    }
  ));

  routes.github = {
    handler: compose([
      storeRedirect,
      passport.authenticate('github', {
        session: false,
        callbackURL:  config.app.baseUrl + config.auth.mount + '/github/callback'
      })
    ]),
    path: '/github',
    method: 'get'
  };

  routes.github_callback = {
    handler: compose([
      passport.authenticate('github', { session: false }),
      tokenResponse
    ]),
    path: '/github/callback',
    method: 'get'
  };
}

if (config.auth.twitter.consumerKey && config.auth.twitter.consumerSecret) {
  passport.use(new TwitterStrategy({
      consumerKey: config.auth.twitter.consumerKey,
      consumerSecret: config.auth.twitter.consumerSecret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/twitter/callback'
    },
    function(token, tokenSecret, profile, done){
      return done(null, {name: profile.displayName});
    }
  ));

  routes.twitter = {
    handler: compose([
      storeRedirect,
      passport.authenticate('twitter', {
        callbackURL:  config.app.baseUrl + config.auth.mount + '/twitter/callback'
      })
    ]),
    path: '/twitter',
    method: 'get'
  };

  routes.twitter_callback = {
    handler: compose([
      passport.authenticate('twitter'),
      tokenResponse
    ]),
    path: '/twitter/callback',
    method: 'get'
  };
}

if (config.auth.osm.consumerKey && config.auth.osm.consumerSecret) {
  passport.use(new OpenStreetMapStrategy({
      consumerKey: config.auth.osm.consumerKey,
      consumerSecret: config.auth.osm.consumerSecret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/openstreetmap/callback'
    },
    function(token, tokenSecret, profile, done){
      return done(null, {name: profile.displayName});
    }
  ));

  routes.openstreetmap = {
    handler: compose([
      storeRedirect,
      passport.authenticate('openstreetmap', {
        callbackURL:  config.app.baseUrl + config.auth.mount + '/openstreetmap/callback'
      })
    ]),
    path: '/openstreetmap',
    method: 'get'
  };

  routes.openstreetmap_callback = {
    handler: compose([
      passport.authenticate('openstreetmap'),
      tokenResponse
    ]),
    path: '/openstreetmap/callback',
    method: 'get'
  };
}

// Facebook Auth if id and secret were provided
if (config.auth.facebook.client_id && config.auth.facebook.client_secret) {
  passport.use(new FacebookStrategy({
      clientID: config.auth.facebook.client_id,
      clientSecret: config.auth.facebook.client_secret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/facebook/callback'
    },
    function(accessToken, refreshToken, profile, done) {
      return done(null, {name: profile.displayName});
    }
  ));

  routes.facebook = {
    handler: compose([
      storeRedirect,
      passport.authenticate('facebook', {
        session: false,
        callbackURL:  config.app.baseUrl + config.auth.mount + '/facebook/callback'
      })
    ]),
    path: '/facebook',
    method: 'get'
  };

  routes.facebook_callback = {
    handler: compose([
      passport.authenticate('facebook', { session: false }),
      tokenResponse
    ]),
    path: '/facebook/callback',
    method: 'get'
  };
}

// Google Auth if enabled
if (config.auth.google.consumerKey && config.auth.google.consumerSecret) {
  passport.use(new GoogleStrategy({
      consumerKey: config.auth.google.consumerKey,
      consumerSecret: config.auth.google.consumerSecret,
      callbackURL: config.app.baseUrl + config.auth.mount + '/google/callback'
    },
    function(accessToken, refreshToken, profile, done) {
      return done(null, {name: profile.displayName});
    }
  ));

  routes.google = {
    handler: compose([
      storeRedirect,
      passport.authenticate('google')
    ]),
    path: '/google',
    method: 'get'
  };

  routes.google_callback = {
    handler: compose([
      passport.authenticate('google'),
      tokenResponse
    ]),
    path: '/google/callback',
    method: 'get'
  };
}

module.exports.routes = routes;<|endoftext|>


<commit_before>// 熊本地震の公共団体・自治体Webサイトモニタリングスクリプト
// https://docs.google.com/spreadsheets/d/1G-vKrvXxNPrwUY_rJXl2zTIxVp67vdgeb2TezCBxGdU/edit?usp=sharing のシート用
// これを変更した場合は、http://github.com/itdart/klgmonitor/blob/master/non-gh-pages/gas/monitoring.js を更新してください。

// var to = 'asai@thedott.io';
var to = 'takoratta@gmail.com';
var sub = 'ホームページ監視情報';
var body = "サーバの状態が正常ではありません。\n　---- \n 市町村名 :@name \n 対象url :@url \n 現在のサーバー状況 :@status \n ----";

var ss;

function myFunction()
{
  //URL格納用配列
  var urls = [];

  //シートデータ取得
  ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("自動モニタリング");
  //データを取得する
  var data = ss.getRange("C2:C").getValues();

  //スプレッドシートデータのレコード数を取得する（配列は0から始まる為）
  var length = ss.getLastRow() - 1;

  //A列の値を取得してwordと比較して一致したら抜ける
  for(var i = 0;i<length;i++){
    //セルの値を取得する
    urls[i] = data[i][0];
    //Logger.log("data: " + tempid);

    //空白行の場合、ループ抜ける
    if(data[i][0] == ""){
      break;
    }
  }
  writeCel(urls);
}

//
// 表示可否を判定
//
function writeCel(urls)
{
  var lastrow = ss.getLastRow();

  Logger.log(lastrow);
  for(var i = 2;i<lastrow+1;i++){
    //チェックした時間（日本時間）
    var date = new Date();
    var formattedDate = Utilities.formatDate(date, "JST", "yyyy-MM-dd HH:mm:ss");

    var serverInfo = check_server(urls[i-2]);

    ss.getRange(i, 4).setValue(formattedDate);
    ss.getRange(i, 5).setValue(serverInfo.txt);
//    ss.getRange(i, 6).setValue(pageSpeedScore(urls[i-2]));
    ss.getRange(i, 7).setValue(serverInfo.headers);
  }
}

//
// 指定URLが死んでたらメール送信
//
function mailSend(name, url)
{
  if(check_server(urls[i]) != 200){
    //MailApp.sendEmail(to, sub, body+urls[i]);
    MailApp.sendEmail(to, sub, body.replace("@name", name).replace("@url", url).replace("@status",code));
  }
}

//
// URLのステータスコードをチェック
//
function check_server(url)
{
  //Logger.log(url);
  try{
    var res = UrlFetchApp.fetch(url);
    return {"code": res.getResponseCode(), "txt":"◯", "headers": return_last_modified(res.getHeaders().toSource()) }
  } catch(e){
    // return {"code": res.getResponseCode(), "txt":"☓", "headers": return_last_modified(res.getHeaders().toSource()) }
    return {"code": "", "txt":"☓", "headers": "" }
  }
}

//
// URLのステータスコードをチェック
//
function return_check_server(url)
{
  try{
    var res = UrlFetchApp.fetch(url);
    return "◯";
  } catch(e){
    return "☓";
  }
}

//
// Last Modifiedを取得する
//
function return_last_modified(header) {
  var lastModifiedRegexp = /^.+'Last-Modified':"(.+?)"/;
  var last = lastModifiedRegexp.exec(header);
  if (!last) {
    // Last Modifiedが無い場合は空白に
    // return header;
    return "";
  }
  var date = new Date(last[1]);
  return Utilities.formatDate(date, "JST", "yyyy-MM-dd HH:mm:ss");
}
<commit_msg>Added Slack Alert<commit_after>// 熊本地震の公共団体・自治体Webサイトモニタリングスクリプト
// https://docs.google.com/spreadsheets/d/1G-vKrvXxNPrwUY_rJXl2zTIxVp67vdgeb2TezCBxGdU/edit?usp=sharing のシート用
// これを変更した場合は、http://github.com/itdart/klgmonitor/blob/master/non-gh-pages/gas/monitoring.js を更新してください。

// var to = 'asai@thedott.io';
var to = 'takoratta@gmail.com';
var sub = 'ホームページ監視情報';
var body = "サーバの状態が正常ではありません。\n　---- \n 市町村名 :@name \n 対象url :@url \n 現在のサーバー状況 :@status \n ----";

var ss;

function myFunction()
{
  //URL格納用配列
  var urls = [];

  //シートデータ取得
  ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("自動モニタリング");
  //データを取得する
  var data = ss.getRange("C2:C").getValues();

  //スプレッドシートデータのレコード数を取得する（配列は0から始まる為）
  var length = ss.getLastRow() - 1;

  //A列の値を取得してwordと比較して一致したら抜ける
  for(var i = 0;i<length;i++){
    //セルの値を取得する
    urls[i] = data[i][0];
    //Logger.log("data: " + tempid);

    //空白行の場合、ループ抜ける
    if(data[i][0] == ""){
      break;
    }
  }
  writeCel(urls);
}

//
// 表示可否を判定
//
function writeCel(urls)
{
  var lastrow = ss.getLastRow();

  Logger.log(lastrow);
  for(var i = 2;i<lastrow+1;i++){
    //チェックした時間（日本時間）
    var date = new Date();
    var formattedDate = Utilities.formatDate(date, "JST", "yyyy-MM-dd HH:mm:ss");

    var serverInfo = check_server(urls[i-2]);

    ss.getRange(i, 4).setValue(formattedDate);

    var oldValue = ss.getRange(i, 5).getValue(); // 表示可否の以前の値を保持
    ss.getRange(i, 5).setValue(serverInfo.txt);
    ss.getRange(i, 7).setValue(serverInfo.headers);

    if (oldValue != serverInfo.txt) { // 表示可否に変更があった場合（自治体サイトの状態が変更になった場合）- サーバーがダウンしたときなど の処理
      var cityCol = 2; // 自治体名列
      var urlCol = 3; // URL列
      var colNum = 5; // 表示可否の列
      var rowNum = i;

      var cityName = ss.getRange(rowNum, cityCol).getValue(); // 自治体名
      var urlVal = ss.getRange(rowNum, urlCol).getValue(); // URL

      var msg = cityName + '(' + urlVal + ')' + 'の状態が' + oldValue + 'から' + serverInfo.txt + 'に変わりました。';

      // Logger.log(msg);

      sendToSlack(msg, '#op_lgmonitor'); // Slackへの送信
    }
  }
}

//
// 指定URLが死んでたらメール送信
//
function mailSend(name, url)
{
  if(check_server(urls[i]) != 200){
    //MailApp.sendEmail(to, sub, body+urls[i]);
    MailApp.sendEmail(to, sub, body.replace("@name", name).replace("@url", url).replace("@status",code));
  }
}

//
// URLのステータスコードをチェック
//
function check_server(url)
{
  //Logger.log(url);
  try{
    var res = UrlFetchApp.fetch(url);
    return {"code": res.getResponseCode(), "txt":"◯", "headers": return_last_modified(res.getHeaders().toSource()) }
  } catch(e){
    // return {"code": res.getResponseCode(), "txt":"☓", "headers": return_last_modified(res.getHeaders().toSource()) }
    return {"code": "", "txt":"☓", "headers": "" }
  }
}

//
// URLのステータスコードをチェック
//
function return_check_server(url)
{
  try{
    var res = UrlFetchApp.fetch(url);
    return "◯";
  } catch(e){
    return "☓";
  }
}

//
// Last Modifiedを取得する
//
function return_last_modified(header) {
  var lastModifiedRegexp = /^.+'Last-Modified':"(.+?)"/;
  var last = lastModifiedRegexp.exec(header);
  if (!last) {
    // Last Modifiedが無い場合は空白に
    // return header;
    return "";
  }
  var date = new Date(last[1]);
  return Utilities.formatDate(date, "JST", "yyyy-MM-dd HH:mm:ss");
}
<|endoftext|>