<commit_before>// ==UserScript==
// @name        Stack Exchange - Add link to network and chat profiles
// @namespace   https://github.com/Glorfindel83/
// @description Adds a link to the network profile for all user profile pages (even if they've hidden the current community) and a link to the chat profile
// @author      Glorfindel
// @updateURL   https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/add-network-profile-link/add-network-profile-link.user.js
// @downloadURL https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/add-network-profile-link/add-network-profile-link.user.js
// @version     0.4
// @match       *://*.stackexchange.com/users/*
// @match       *://*.stackoverflow.com/users/*
// @match       *://*.superuser.com/users/*
// @match       *://*.serverfault.com/users/*
// @match       *://*.askubuntu.com/users/*
// @match       *://*.stackapps.com/users/*
// @match       *://*.mathoverflow.net/users/*
// @exclude     *://stackexchange.com/users/*
// @grant       none
// ==/UserScript==
(function () {
  'use strict';

  // Some pages (e.g. https://*.stackexchange.com/users/message/create/*) need to be skipped
  if (typeof(StackExchange.user) == 'undefined')
    return;

  StackExchange.ready(function() {
    // Find user IDs
    let userID = StackExchange.user.options.userId;
    let accountID = StackExchange.user.options.accountId;

    // Check if link already present
    let user = $(".user-show-new");
    var existingButton = user.find("button[aria-controls='profiles-menu']");
    if (existingButton.length == 0) {
      existingButton = user.find("a.s-btn[href^='https://stackexchange.com/users/']").parents("ul");
    }
    if (existingButton.length == 0) {
      // Add link to network profile
      let profile = user[0].children[0];
      if (profile.classList.contains("system-alert")) {
        // Suspension message, take the next one
        profile = user[0].children[1];
      }
      let container = profile.children[profile.children.length - 1];
      let networkProfileButton = '\n' +
        '<svg aria-hidden="true" class="native mln2 mr2 svg-icon iconLogoSEXxs" width="18" height="18" viewBox="0 0 18 18"><path d="M3 4c0-1.1.9-2 2-2h8a2 2 0 012 2H3Z" fill="#8FD8F7"></path><path d="M15 11H3c0 1.1.9 2 2 2h5v3l3-3a2 2 0 002-2Z" fill="#155397"></path><path fill="#46A2D9" d="M3 5h12v2H3z"></path><path fill="#2D6DB5" d="M3 8h12v2H3z"></path></svg>\n' +
        'Network profile';
      // Main/meta profile switcher?
      existingButton = user.find("a.s-btn[href*='/users/']").parents("ul");
      if (existingButton.length == 0) {
        container.innerHTML = networkProfileButton;
        existingButton = $(container).children();
      } else {
        existingButton.after(networkProfileButton);
      }
    }

    // Add link to chat profile
    // NICETOHAVE: add as part of dropdown menu
    var chatProfile;
    switch (location.host) {
      case 'meta.stackexchange.com':
        chatProfile = 'https://chat.meta.stackexchange.com/users/' + userID;
        break;
      case 'stackoverflow.com':
      case 'meta.stackoverflow.com':
        chatProfile = 'https://chat.stackoverflow.com/users/' + userID;
        break;
      default:
        chatProfile = 'https://chat.stackexchange.com/account/' + accountID;
        break;
    }
    existingButton.before('\n' +
        'Chat profile');
  });
})();<commit_msg>Add link to network & chat profile v0.5<commit_after>// ==UserScript==
// @name        Stack Exchange - Add link to network and chat profiles
// @namespace   https://github.com/Glorfindel83/
// @description Adds a link to the network profile for all user profile pages (even if they've hidden the current community) and a link to the chat profile
// @author      Glorfindel
// @updateURL   https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/add-network-profile-link/add-network-profile-link.user.js
// @downloadURL https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/add-network-profile-link/add-network-profile-link.user.js
// @version     0.5
// @match       *://*.stackexchange.com/users/*
// @match       *://*.stackoverflow.com/users/*
// @match       *://*.superuser.com/users/*
// @match       *://*.serverfault.com/users/*
// @match       *://*.askubuntu.com/users/*
// @match       *://*.stackapps.com/users/*
// @match       *://*.mathoverflow.net/users/*
// @exclude     *://stackexchange.com/users/*
// @grant       none
// ==/UserScript==
(function () {
  'use strict';

  // Some pages (e.g. https://*.stackexchange.com/users/message/create/*) need to be skipped
  if (typeof(StackExchange.user) == 'undefined' ||
      typeof(StackExchange.user.options) == 'undefined') // unregistered users
    return;

  StackExchange.ready(function() {
    // Find user IDs
    let userID = StackExchange.user.options.userId;
    let accountID = StackExchange.user.options.accountId;

    // Check if link already present
    let user = $(".user-show-new");
    var existingButton = user.find("button[aria-controls='profiles-menu']");
    if (existingButton.length == 0) {
      existingButton = user.find("a.s-btn[href^='https://stackexchange.com/users/']").parents("ul");
    }
    if (existingButton.length == 0) {
      // Add link to network profile
      let profile = user[0].children[0];
      if (profile.classList.contains("system-alert")) {
        // Suspension message, take the next one
        profile = user[0].children[1];
      }
      let container = profile.children[profile.children.length - 1];
      let networkProfileButton = '\n' +
        '<svg aria-hidden="true" class="native mln2 mr2 svg-icon iconLogoSEXxs" width="18" height="18" viewBox="0 0 18 18"><path d="M3 4c0-1.1.9-2 2-2h8a2 2 0 012 2H3Z" fill="#8FD8F7"></path><path d="M15 11H3c0 1.1.9 2 2 2h5v3l3-3a2 2 0 002-2Z" fill="#155397"></path><path fill="#46A2D9" d="M3 5h12v2H3z"></path><path fill="#2D6DB5" d="M3 8h12v2H3z"></path></svg>\n' +
        'Network profile';
      // Main/meta profile switcher?
      existingButton = user.find("a.s-btn[href*='/users/']").parents("ul");
      if (existingButton.length == 0) {
        container.innerHTML = networkProfileButton;
        existingButton = $(container).children();
      } else {
        existingButton.after(networkProfileButton);
      }
    }

    // Add link to chat profile
    // NICETOHAVE: add as part of dropdown menu
    var chatProfile;
    switch (location.host) {
      case 'meta.stackexchange.com':
        chatProfile = 'https://chat.meta.stackexchange.com/users/' + userID;
        break;
      case 'stackoverflow.com':
      case 'meta.stackoverflow.com':
        chatProfile = 'https://chat.stackoverflow.com/users/' + userID;
        break;
      default:
        chatProfile = 'https://chat.stackexchange.com/account/' + accountID;
        break;
    }
    existingButton.before('\n' +
        'Chat profile');
  });
})();
<|endoftext|>


<commit_before>// The app object is defined in a closure to make it easier to
// change its visible name, if that would be needed.
var app = (function()
{
	// Application object.
	var app = {};

	// Touch event state.
	var touchIsActive = false
	var touchStartX = 0
	var touchStartY = 0

	// Application data.
	app.currentScreenId = null;
	app.beaconColorStyles = [
		'style-color-unknown style-color-unknown-text',
		'style-color-mint style-color-mint-text',
		'style-color-ice style-color-ice-text',
		'style-color-blueberry-dark style-color-blueberry-dark-text',
		'style-color-white style-color-white-text',
		'style-color-transparent style-color-transparent-text'];
	app.proximityNames = [
		'unknown',
		'immediate',
		'near',
		'far'];

	// ------------- Private helper functions ------------- //

	function onDeviceReady()
	{
		// TODO: Add functionality if needed.
	}

	function checkTouchActive()
	{
		var active = touchIsActive;
		touchIsActive = false;
		return active;
	}

	function formatDistance(meters)
	{
		if (!meters) { return 'Unknown'; }

		if (meters > 1)
		{
			return meters.toFixed(3) + ' m';
		}
		else
		{
			return (meters * 100).toFixed(3) + ' cm';
		}
	}

	function formatProximity(proximity)
	{
		if (!proximity) { return 'Unknown'; }

		// Eliminate bad values (just in case).
		proximity = Math.max(0, proximity);
		proximity = Math.min(3, proximity);

		// Return name for proximity.
		return app.proximityNames[proximity];
	}

	function beaconColorStyle(color)
	{
		if (!color)
		{
			color = 0;
		}

		// Eliminate bad values (just in case).
		color = Math.max(0, color);
		color = Math.min(5, color);

		// Return style class for color.
		return app.beaconColorStyles[color];
	}

	// ------------- Public application functions ------------- //

	app.showScreen = function(screenId)
	{
		// Hide current screen if set.
		if (app.currentScreenId != null)
		{
			$('#' + app.currentScreenId).hide();
		}

		// Show new screen.
		app.currentScreenId = screenId;
		$('#' + app.currentScreenId).show();
		document.body.scrollTop = 0;
	};

	app.showHomeScreen = function()
	{
		app.showScreen('id-screen-home');
	};

	app.showScanScreen = function()
	{
		app.showScreen('id-screen-scan');
	};

	app.showRangeScreen = function()
	{
		app.showScreen('id-screen-range');
	};

	app.showMonitorScreen = function()
	{
		app.showScreen('id-screen-monitor');
	};

	app.startScanning = function()
	{
		function onScan(beaconInfo)
		{
			//console.log('onScan');
			displayBeconInfo(beaconInfo);
		}

		function onError(errorMessage)
		{
			console.log('Scan error: ' + errorMessage);
		}

		function displayBeconInfo(beaconInfo)
		{
			//console.log('displayBeconInfo');
			// Clear beacon HTML items.
			$('#id-screen-scan .style-beacon-list').empty();

			// Sort beacons by signal strength.
			beaconInfo.beacons.sort(function(beacon1, beacon2) {
				return beacon1.rssi > beacon2.rssi; });

			// Generate HTML for beacons.
			var html = '';
			$.each(beaconInfo.beacons, function(key, beacon)
			{
				// jQuery doesn't work.
				var element = $(createBeaconHTML(beacon));
				$('#id-screen-scan .style-beacon-list').append(element);
			});
		};

		function createBeaconHTML(beacon)
		{
			console.log('beacon: '+beacon.major+' '+beacon.minor+', '+beacon.rssi+' ('+beacon.measuredPower+')');
			var colorClasses = beaconColorStyle(beacon.color);
			htm = '
'
				+ '

Major	' + beacon.major
				+ '

Minor	' + beacon.minor
				+ '

RSSI	' + beacon.rssi
				+ '

Measured power	' + beacon.measuredPower
				+ '

MAC address	' + beacon.macAddress
				+ '

';
			return htm;
		};

		app.showScanScreen();

		console.log("startEstimoteBeaconsDiscoveryForRegion")

		EstimoteBeacons.startEstimoteBeaconsDiscoveryForRegion(
			{}, // Empty region matches all beacons.
			onScan,
			onError);
	};

	app.stopScanning = function()
	{
		console.log("stopEstimoteBeaconDiscovery")
		EstimoteBeacons.stopEstimoteBeaconDiscovery();
		app.showHomeScreen();
	};

	app.startRanging = function()
	{
		function onRange(beaconInfo)
		{
			console.log('onRange');
			displayBeconInfo(beaconInfo);
		}

		function onError(errorMessage)
		{
			console.log('Range error: ' + errorMessage);
		}

		function displayBeconInfo(beaconInfo)
		{
			// Clear beacon HTML items.
			$('#id-screen-range .style-beacon-list').empty();

			// Sort beacons by distance.
			beaconInfo.beacons.sort(function(beacon1, beacon2) {
				return beacon1.distance > beacon2.distance; });

			// Generate HTML for beacons.
			$.each(beaconInfo.beacons, function(key, beacon)
			{
				var element = $(createBeaconHTML(beacon));
				$('#id-screen-range .style-beacon-list').append(element);
			});
		};

		function createBeaconHTML(beacon)
		{
			var colorClasses = beaconColorStyle(beacon.color);
			htm = '
'
				+ '

Major	' + beacon.major
				+ '

Minor	' + beacon.minor
				+ '

RSSI	' + beacon.rssi
			if (beacon.proximity)
			{
				htm += '

Proximity	'
					+ formatProximity(beacon.proximity)
			}
			if (beacon.distance)
			{
				htm += '

Distance	'
					+ formatDistance(beacon.distance)
			}
			htm += '

';
			return htm;
		};

		app.showRangeScreen();

		EstimoteBeacons.requestWhenInUseAuthorization();

		EstimoteBeacons.startRangingBeaconsInRegion(
			{}, // Empty region matches all beacons.
			onRange,
			onError);
	};

	app.stopRanging = function()
	{
		EstimoteBeacons.stopRangingBeaconsInRegion({});
		app.showHomeScreen();
	};

	app.startMonitoring = function()
	{
		function onMonitor(regionState)
		{
			displayRegionInfo(regionState);
		}

		function onError(errorMessage)
		{
			console.log('Monitor error: ' + errorMessage);
		}

		// TODO: Handle multiple regions. Currently only
		// one region will be displayed.
		function displayRegionInfo(regionState)
		{
			$('#id-screen-monitor .style-beacon-list').empty();

			var element = $(createRegionStateHTML(regionState));
			$('#id-screen-monitor .style-beacon-list').append(element);
		};

		function createRegionStateHTML(regionState)
		{
			var color = 'style-color-pink';
			htm = '
'
				+ '

State	' + regionState.state
				+ '

Major	' + regionState.major
				+ '

Minor	' + regionState.minor
				+ '

Identifier	' + regionState.identifier
				+ '

UUID	' + regionState.uuid
				+ '

';
			return htm;
		};

		app.showMonitorScreen();

		EstimoteBeacons.requestWhenInUseAuthorization();

		EstimoteBeacons.startMonitoringForRegion(
			{}, // Empty region matches all beacons.
			onMonitor,
			onError);
	};

	app.stopMonitoring = function()
	{
		EstimoteBeacons.stopMonitoringForRegion({});
		app.showHomeScreen();
	};

	// ------------- Public touch event functions ------------- //

	app.onStartScanning = function()
	{
		console.log('app.onStartScanning1');
		if (!checkTouchActive()) { return }
		console.log('app.onStartScanning2');
		app.startScanning();
	};

	app.onStopScanning = function()
	{
		if (!checkTouchActive()) { return }
		app.stopScanning();
	};

	app.onStartRanging = function()
	{
		if (!checkTouchActive()) { return }
		app.startRanging();
	};

	app.onStopRanging = function()
	{
		if (!checkTouchActive()) { return }
		app.stopRanging();
	};

	app.onStartMonitoring = function()
	{
		if (!checkTouchActive()) { return }
		app.startMonitoring();
	};

	app.onStopMonitoring = function()
	{
		if (!checkTouchActive()) { return }
		app.stopMonitoring();
	};

	app.onNavigateBack = function()
	{
		if (!checkTouchActive()) { return }
		history.back();
	};

	app.onTouchStart = function(event)
	{
		var changedTouch = event.changedTouches[0];
		touchStartX = changedTouch.screenX
		touchStartY = changedTouch.screenY;
		touchIsActive = true;
	};

	app.onTouchMove = function(event)
	{
		if (touchIsActive)
		{
			var changedTouch = event.changedTouches[0];
			var touchDeltaX = Math.abs(changedTouch.screenX - touchStartX);
			var touchDeltaY = Math.abs(changedTouch.screenY - touchStartY);
			if (touchDeltaX > 5 || touchDeltaY > 5)
			{
				touchIsActive = false;
			}
		}
	};

	app.onTouchCancel = function(event)
	{
		touchIsActive = false;
	};

	// ------------- Initialisation ------------- //

	document.addEventListener('deviceready', onDeviceReady, false);

	app.showHomeScreen();

	// ------------- Return application object ------------- //

	return app;

})();
<commit_msg>Using requestAlwaysAuthorization in example app to make monitoring work on iOS 8.<commit_after>// The app object is defined in a closure to make it easier to
// change its visible name, if that would be needed.
var app = (function()
{
	// Application object.
	var app = {};

	// Touch event state.
	var touchIsActive = false
	var touchStartX = 0
	var touchStartY = 0

	// Application data.
	app.currentScreenId = null;
	app.beaconColorStyles = [
		'style-color-unknown style-color-unknown-text',
		'style-color-mint style-color-mint-text',
		'style-color-ice style-color-ice-text',
		'style-color-blueberry-dark style-color-blueberry-dark-text',
		'style-color-white style-color-white-text',
		'style-color-transparent style-color-transparent-text'];
	app.proximityNames = [
		'unknown',
		'immediate',
		'near',
		'far'];

	// ------------- Private helper functions ------------- //

	function onDeviceReady()
	{
		// TODO: Add functionality if needed.
	}

	function checkTouchActive()
	{
		var active = touchIsActive;
		touchIsActive = false;
		return active;
	}

	function formatDistance(meters)
	{
		if (!meters) { return 'Unknown'; }

		if (meters > 1)
		{
			return meters.toFixed(3) + ' m';
		}
		else
		{
			return (meters * 100).toFixed(3) + ' cm';
		}
	}

	function formatProximity(proximity)
	{
		if (!proximity) { return 'Unknown'; }

		// Eliminate bad values (just in case).
		proximity = Math.max(0, proximity);
		proximity = Math.min(3, proximity);

		// Return name for proximity.
		return app.proximityNames[proximity];
	}

	function beaconColorStyle(color)
	{
		if (!color)
		{
			color = 0;
		}

		// Eliminate bad values (just in case).
		color = Math.max(0, color);
		color = Math.min(5, color);

		// Return style class for color.
		return app.beaconColorStyles[color];
	}

	// ------------- Public application functions ------------- //

	app.showScreen = function(screenId)
	{
		// Hide current screen if set.
		if (app.currentScreenId != null)
		{
			$('#' + app.currentScreenId).hide();
		}

		// Show new screen.
		app.currentScreenId = screenId;
		$('#' + app.currentScreenId).show();
		document.body.scrollTop = 0;
	};

	app.showHomeScreen = function()
	{
		app.showScreen('id-screen-home');
	};

	app.showScanScreen = function()
	{
		app.showScreen('id-screen-scan');
	};

	app.showRangeScreen = function()
	{
		app.showScreen('id-screen-range');
	};

	app.showMonitorScreen = function()
	{
		app.showScreen('id-screen-monitor');
	};

	app.startScanning = function()
	{
		function onScan(beaconInfo)
		{
			//console.log('onScan');
			displayBeconInfo(beaconInfo);
		}

		function onError(errorMessage)
		{
			console.log('Scan error: ' + errorMessage);
		}

		function displayBeconInfo(beaconInfo)
		{
			//console.log('displayBeconInfo');
			// Clear beacon HTML items.
			$('#id-screen-scan .style-beacon-list').empty();

			// Sort beacons by signal strength.
			beaconInfo.beacons.sort(function(beacon1, beacon2) {
				return beacon1.rssi > beacon2.rssi; });

			// Generate HTML for beacons.
			var html = '';
			$.each(beaconInfo.beacons, function(key, beacon)
			{
				// jQuery doesn't work.
				var element = $(createBeaconHTML(beacon));
				$('#id-screen-scan .style-beacon-list').append(element);
			});
		};

		function createBeaconHTML(beacon)
		{
			console.log('beacon: '+beacon.major+' '+beacon.minor+', '+beacon.rssi+' ('+beacon.measuredPower+')');
			var colorClasses = beaconColorStyle(beacon.color);
			htm = '
'
				+ '

Major	' + beacon.major
				+ '

Minor	' + beacon.minor
				+ '

RSSI	' + beacon.rssi
				+ '

Measured power	' + beacon.measuredPower
				+ '

MAC address	' + beacon.macAddress
				+ '

';
			return htm;
		};

		app.showScanScreen();

		console.log("startEstimoteBeaconsDiscoveryForRegion")

		EstimoteBeacons.startEstimoteBeaconsDiscoveryForRegion(
			{}, // Empty region matches all beacons.
			onScan,
			onError);
	};

	app.stopScanning = function()
	{
		console.log("stopEstimoteBeaconDiscovery")
		EstimoteBeacons.stopEstimoteBeaconDiscovery();
		app.showHomeScreen();
	};

	app.startRanging = function()
	{
		function onRange(beaconInfo)
		{
			console.log('onRange');
			displayBeconInfo(beaconInfo);
		}

		function onError(errorMessage)
		{
			console.log('Range error: ' + errorMessage);
		}

		function displayBeconInfo(beaconInfo)
		{
			// Clear beacon HTML items.
			$('#id-screen-range .style-beacon-list').empty();

			// Sort beacons by distance.
			beaconInfo.beacons.sort(function(beacon1, beacon2) {
				return beacon1.distance > beacon2.distance; });

			// Generate HTML for beacons.
			$.each(beaconInfo.beacons, function(key, beacon)
			{
				var element = $(createBeaconHTML(beacon));
				$('#id-screen-range .style-beacon-list').append(element);
			});
		};

		function createBeaconHTML(beacon)
		{
			var colorClasses = beaconColorStyle(beacon.color);
			htm = '
'
				+ '

Major	' + beacon.major
				+ '

Minor	' + beacon.minor
				+ '

RSSI	' + beacon.rssi
			if (beacon.proximity)
			{
				htm += '

Proximity	'
					+ formatProximity(beacon.proximity)
			}
			if (beacon.distance)
			{
				htm += '

Distance	'
					+ formatDistance(beacon.distance)
			}
			htm += '

';
			return htm;
		};

		app.showRangeScreen();

		EstimoteBeacons.requestAlwaysAuthorization();

		EstimoteBeacons.startRangingBeaconsInRegion(
			{}, // Empty region matches all beacons.
			onRange,
			onError);
	};

	app.stopRanging = function()
	{
		EstimoteBeacons.stopRangingBeaconsInRegion({});
		app.showHomeScreen();
	};

	app.startMonitoring = function()
	{
		function onMonitor(regionState)
		{
			displayRegionInfo(regionState);
		}

		function onError(errorMessage)
		{
			console.log('Monitor error: ' + errorMessage);
		}

		// TODO: Handle multiple regions. Currently only
		// one region will be displayed.
		function displayRegionInfo(regionState)
		{
			$('#id-screen-monitor .style-beacon-list').empty();

			var element = $(createRegionStateHTML(regionState));
			$('#id-screen-monitor .style-beacon-list').append(element);
		};

		function createRegionStateHTML(regionState)
		{
			var color = 'style-color-pink';
			htm = '
'
				+ '

State	' + regionState.state
				+ '

Major	' + regionState.major
				+ '

Minor	' + regionState.minor
				+ '

Identifier	' + regionState.identifier
				+ '

UUID	' + regionState.uuid
				+ '

';
			return htm;
		};

		app.showMonitorScreen();

		EstimoteBeacons.requestAlwaysAuthorization();

		EstimoteBeacons.startMonitoringForRegion(
			{}, // Empty region matches all beacons.
			onMonitor,
			onError);
	};

	app.stopMonitoring = function()
	{
		EstimoteBeacons.stopMonitoringForRegion({});
		app.showHomeScreen();
	};

	// ------------- Public touch event functions ------------- //

	app.onStartScanning = function()
	{
		console.log('app.onStartScanning1');
		if (!checkTouchActive()) { return }
		console.log('app.onStartScanning2');
		app.startScanning();
	};

	app.onStopScanning = function()
	{
		if (!checkTouchActive()) { return }
		app.stopScanning();
	};

	app.onStartRanging = function()
	{
		if (!checkTouchActive()) { return }
		app.startRanging();
	};

	app.onStopRanging = function()
	{
		if (!checkTouchActive()) { return }
		app.stopRanging();
	};

	app.onStartMonitoring = function()
	{
		if (!checkTouchActive()) { return }
		app.startMonitoring();
	};

	app.onStopMonitoring = function()
	{
		if (!checkTouchActive()) { return }
		app.stopMonitoring();
	};

	app.onNavigateBack = function()
	{
		if (!checkTouchActive()) { return }
		history.back();
	};

	app.onTouchStart = function(event)
	{
		var changedTouch = event.changedTouches[0];
		touchStartX = changedTouch.screenX
		touchStartY = changedTouch.screenY;
		touchIsActive = true;
	};

	app.onTouchMove = function(event)
	{
		if (touchIsActive)
		{
			var changedTouch = event.changedTouches[0];
			var touchDeltaX = Math.abs(changedTouch.screenX - touchStartX);
			var touchDeltaY = Math.abs(changedTouch.screenY - touchStartY);
			if (touchDeltaX > 5 || touchDeltaY > 5)
			{
				touchIsActive = false;
			}
		}
	};

	app.onTouchCancel = function(event)
	{
		touchIsActive = false;
	};

	// ------------- Initialisation ------------- //

	document.addEventListener('deviceready', onDeviceReady, false);

	app.showHomeScreen();

	// ------------- Return application object ------------- //

	return app;

})();
<|endoftext|>


<commit_before>'use strict';

const path = require('path');

const feplet = require('feplet');
const fs = require('fs-extra');
const JSON5 = require('json5');

const lineageHunt = require('./lineage-hunt');
const listItemsBuilder = require('./list-items-builder');
const Pattern = require('./object-factory').Pattern;
const plutils = require('./utilities');
const pseudopatternHunt = require('./pseudopattern-hunt');

// PRIVATE FUNCTIONS

function addPattern(pattern, patternlab) {
  // add the link to the global object
  patternlab.data.link[pattern.patternPartial] = '/patterns/' + pattern.patternLink;

  // only push to array if the array doesn't contain this pattern
  let isNew = true;

  for (let i = 0, l = patternlab.patterns.length; i < l; i++) {
    // so we need the identifier to be unique, which patterns[i].relPath is
    if (pattern.relPath === patternlab.patterns[i].relPath) {
      // if relPath already exists, overwrite that element
      patternlab.patterns[i] = pattern;
      isNew = false;

      break;
    }
  }

  // if the pattern is new, we must register it with various data structures!
  if (isNew) {
    if (patternlab.config.debug) {
      console.log('Found new pattern ' + pattern.patternPartial);
    }

    patternlab.patterns.push(pattern);
  }
}

function addSubTypePattern(subTypePattern, patternlab) {
  patternlab.subTypePatterns[subTypePattern.patternPartial] = subTypePattern;
}

function isPatternFile(filename, patternlab) {
  const extension = path.extname(filename);

  // skip hidden patterns and data files
  if (filename.charAt(0) === '.' || (extension === '.json' && !isPseudopatternJson(filename))) {
    return false;
  }

  // return boolean condition for everything else
  return (extension === patternlab.config.patternExtension || isPseudopatternJson(filename));
}

function isPseudopatternJson(filename) {
  const extension = path.extname(filename);

  return (extension === '.json' && filename.indexOf('~') > -1);
}

function preprocessPartials(fepletPartials, patternlab) {
  for (let i in fepletPartials) {
    if (!fepletPartials.hasOwnProperty(i)) {
      continue;
    }

    const partial = fepletPartials[i];

    if (typeof patternlab.partials[partial.name] === 'undefined') {
      const pattern = patternlab.getPattern(partial.name);

      // try seeing if the partial has no param and the name matches one of an already defined pattern
      if (pattern) {
        patternlab.partials[partial.name] = pattern.template;
      }
      // else, create a placeholder in the partials object to get populated later
      else {
        patternlab.partials[partial.name] = '';
      }
    }
  }
}

function setState(pattern, patternlab) {
  if (patternlab.config.patternStates && patternlab.config.patternStates[pattern.patternPartial]) {
    pattern.patternState = patternlab.config.patternStates[pattern.patternPartial];
  }
}

// EXPORTED FUNCTIONS

exports.preprocessPattern = function (relPath, patternlab) {
  const fileObject = path.parse(relPath);

  // extract some information
  const filename = fileObject.base;
  const ext = fileObject.ext;
  const patternsPath = patternlab.config.paths.source.patterns;

  // skip non-pattern files
  if (!isPatternFile(filename, patternlab)) {
    return null;
  }

  // make a new Pattern Object
  const pattern = new Pattern(relPath);

  // create subTypePattern if it doesn't already exist
  const subTypeKey = 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup;

  if (pattern.patternSubGroup && !patternlab.subTypePatterns[subTypeKey]) {
    const subTypePattern = {
      subdir: pattern.subdir,
      patternName: pattern.patternSubGroup,
      patternLink: pattern.flatPatternPath + '/index.html',
      patternGroup: pattern.patternGroup,
      patternSubGroup: pattern.patternSubGroup,
      flatPatternPath: pattern.flatPatternPath,
      patternPartial: subTypeKey,
      patternSectionSubType: true
    };

    addSubTypePattern(subTypePattern, patternlab);
  }

  // if file is named in the syntax for variants
  if (isPseudopatternJson(filename)) {
    addPattern(pattern, patternlab);

    return pattern;
  }

  // can ignore all non-supported files at this point
  if (ext !== patternlab.config.patternExtension) {
    return pattern;
  }

  // see if this file has a state
  setState(pattern, patternlab);

  // look for a json file for this template
  let jsonFilename = '';
  let jsonFilenameStats;
  let jsonFileStr = '';

  try {
    jsonFilename = path.resolve(patternsPath, pattern.subdir, pattern.fileName + '.json');
    jsonFilenameStats = fs.statSync(jsonFilename);
  }
  catch (err) {
    // not a file
  }

  if (jsonFilenameStats && jsonFilenameStats.isFile()) {
    try {
      jsonFileStr = fs.readFileSync(jsonFilename, patternlab.enc);
      pattern.jsonFileData = JSON5.parse(jsonFileStr);

      if (patternlab.config.debug) {
        console.log('Found pattern-specific data.json for ' + pattern.patternPartial);
      }
    }
    catch (err) {
      console.log('There was an error parsing sibling JSON for ' + pattern.relPath);
      console.log(err.message || err);
    }
  }

  // look for a listitems.json file for this template
  try {
    const listJsonFileName = path.resolve(patternsPath, pattern.subdir, pattern.fileName + '.listitems.json');

    let listJsonFileStats;

    try {
      listJsonFileStats = fs.statSync(listJsonFileName);
    }
    catch (err) {
      // not a file
    }

    if (listJsonFileStats && listJsonFileStats.isFile()) {
      jsonFileStr = fs.readFileSync(listJsonFileName, patternlab.enc);
      pattern.listItems = JSON5.parse(jsonFileStr);

      if (patternlab.config.debug) {
        console.log('Found pattern-specific listitems.json for ' + pattern.patternPartial);
      }

      listItemsBuilder.listItemsBuild(pattern);
      plutils.extendButNotOverride(pattern.jsonFileData.listItems, patternlab.data.listItems);
    }
  }
  catch (err) {
    console.log('There was an error parsing sibling listitem JSON for ' + pattern.relPath);
    console.log(err.message || err);
  }

  pattern.template = fs.readFileSync(path.resolve(patternsPath, relPath), patternlab.enc);

  const scan = feplet.scan(pattern.template);
  const parseArr = feplet.parse(scan);

  // check if the template uses listItems
  patternlab.useListItems = listItemsBuilder.listItemsScan(parseArr, patternlab);

  pattern.fepletParse = parseArr;
  pattern.fepletComp = feplet.generate(parseArr, pattern.template, {});

  preprocessPartials(pattern.fepletComp.partials, patternlab);

  // add pattern to patternlab.patterns array
  addPattern(pattern, patternlab);

  return pattern;
}

exports.preprocessPartialParams = function (patternlab) {
  let partials = patternlab.partials;

  for (let partialName in partials) {
    if (!partials.hasOwnProperty(partialName)) {
      continue;
    }

    const partialTemplate = partials[partialName];

    let pattern = patternlab.getPattern(partialName);

    if (pattern) {
      if (partialTemplate) {
        continue;
      }
      // if the partialName exactly matches a pattern name, just copy the template
      else {
        partials[partialName] = pattern.template;
      }
    }

    // if no exact match, must have param
    // make sure there is an entry for the non-param partial
    else {
      for (let i = 0, l = patternlab.patterns.length; i < l; i++) {
        const pattern = patternlab.patterns[i];

        let nonParamPartialName;

        if (partialName.indexOf(pattern.patternPartialPhp) === 0) {
          nonParamPartialName = pattern.patternPartialPhp;
        }
        else if (partialName.indexOf(pattern.patternPartial) === 0) {
          nonParamPartialName = pattern.patternPartial;
        }
        else if (partialName.indexOf(pattern.relPathTrunc) === 0) {
          nonParamPartialName = pattern.relPathTrunc;
        }
        else if (partialName.indexOf(pattern.relPath) === 0) {
          nonParamPartialName = pattern.relPath;
        }

        if (nonParamPartialName) {
          partials[nonParamPartialName] = pattern.template;
        }
      }
    }
  }

  for (let i = 0, l = patternlab.patterns.length; i < l; i++) {
    const pattern = patternlab.patterns[i];

    feplet.preprocessPartialParams(pattern.template, partials);

    // find and set lineages
    lineageHunt(pattern, patternlab);
    pattern.lineageExists = pattern.lineage.length > 0;
    pattern.lineageRExists = pattern.lineageR.length > 0;
  }
};

exports.processPattern = function (pattern, patternlab) {
  let patternVariants = [];
  let preRenderObj;
  let tmpPartial;

  // the tilde suffix will sort pseudopatterns after basePatterns
  // so first, check if this is not a pseudopattern (therefore a basePattern) and look for its pseudopattern variants
  if (!isPseudopatternJson(pattern.relPath)) {
    if (pattern.jsonFileData) {
      pattern.allData = plutils.extendButNotOverride(pattern.jsonFileData, patternlab.data);
    }
    else {
      pattern.jsonFileData = {};
      // if no local data, create reference to patternlab.allData
      pattern.allData = patternlab.data;
    }

    // set cacheBuster property if not already set
    if (!pattern.allData.cacheBuster) {
      pattern.allData.cacheBuster = patternlab.cacheBuster;
    }

    pattern.extendedTemplate = pattern.fepletComp.render(pattern.allData, patternlab.partials);
    patternVariants = pseudopatternHunt(pattern, patternlab);
  }

  // identified a pseudopattern by checking if this is a file containing same name, with ~ in it, ending in .json
  // copy its basePattern.extendedTemplate to extendedTemplate and return
  else {
    // set cacheBuster property if not already set
    if (!pattern.allData.cacheBuster) {
      pattern.allData.cacheBuster = patternlab.cacheBuster;
    }

    pattern.extendedTemplate = pattern.fepletComp.render(pattern.allData, patternlab.partials);
  }

  // stringify these data for individual pattern rendering and use on the styleguide
  pattern.allData.patternData = JSON.stringify({
    cssEnabled: false,
    lineage: pattern.lineage,
    lineageExists: pattern.lineageExists,
    lineageR: pattern.lineageR,
    lineageRExists: pattern.lineageRExists,
    patternDesc: pattern.patternDescExists ? pattern.patternDesc : '',
    patternExtension: pattern.fileExtension,
    patternName: pattern.patternName,
    patternPartial: pattern.patternPartial,
    patternState: pattern.patternState
  });

  let head;

  if (patternlab.userHead) {
    head = patternlab.userHead.replace('{{{ patternLabHead }}}', patternlab.header);
  }
  else {
    head = patternlab.header;
  }

  let headHtml = feplet.render(head, pattern.allData);

  // set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer
  const footerPartial = feplet.render(patternlab.footer, {
    cacheBuster: patternlab.cacheBuster,
    isPattern: pattern.isPattern,
    lineage: JSON.stringify(pattern.lineage),
    lineageR: JSON.stringify(pattern.lineageR),
    patternData: pattern.allData.patternData,
    patternPartial: pattern.patternPartial,
    patternState: pattern.patternState,
    portReloader: global.conf.livereload_port,
    portServer: global.conf.express_port
  });

  let footerHtml = patternlab.userFoot.replace('{{{ patternLabFoot }}}', footerPartial);
  footerHtml = feplet.render(footerHtml, pattern.allData);

  pattern.header = headHtml;
  pattern.footer = footerHtml;

  // write the built template to the public patterns directory
  const paths = patternlab.config.paths;
  const patternPage = pattern.header + pattern.extendedTemplate + pattern.footer;

  fs.outputFileSync(
    path.resolve(
      paths.public.patterns,
      pattern.patternLink
    ),
    patternPage
  );

  // write the mustache file too
  fs.outputFileSync(
    path.resolve(
      paths.public.patterns,
      pattern.patternLink.slice(0, -(pattern.outfileExtension.length))  + pattern.fileExtension
    ),
    pattern.template
  );

  // write the markup-only version too
  fs.outputFileSync(
    path.resolve(
      paths.public.patterns,
      pattern.patternLink.slice(0, -(pattern.outfileExtension.length)) + '.markup-only' + pattern.outfileExtension
    ),
    pattern.extendedTemplate
  );

  // free memory
  for (let key in pattern) {
    if (!pattern.hasOwnProperty(key)) {
      continue;
    }

    switch (key) {
      case 'footer':
      case 'extendedTemplate':
      case 'flatPatternPath':
      case 'isPattern':
      case 'name':
      case 'patternBaseName':
      case 'patternGroup':
      case 'patternLink':
      case 'patternName':
      case 'patternPartial':
      case 'patternPartialPhp':
      case 'patternState':
      case 'patternSubGroup':
      case 'relPath':
      case 'relPathTrunc':
      case 'subdir':
      case 'lineageR':
      case 'lineageRIndex':
        continue;
    }

    pattern[key] = null;
    delete pattern[key];
  }
};
<commit_msg>ensuring last pattern in iteration order for shorthand notation gets saved as partial template for that name<commit_after>'use strict';

const path = require('path');

const feplet = require('feplet');
const fs = require('fs-extra');
const JSON5 = require('json5');

const lineageHunt = require('./lineage-hunt');
const listItemsBuilder = require('./list-items-builder');
const Pattern = require('./object-factory').Pattern;
const plutils = require('./utilities');
const pseudopatternHunt = require('./pseudopattern-hunt');

// PRIVATE FUNCTIONS

function addPattern(pattern, patternlab) {
  // add the link to the global object
  patternlab.data.link[pattern.patternPartial] = '/patterns/' + pattern.patternLink;

  // only push to array if the array doesn't contain this pattern
  let isNew = true;

  for (let i = 0, l = patternlab.patterns.length; i < l; i++) {
    // so we need the identifier to be unique, which patterns[i].relPath is
    if (pattern.relPath === patternlab.patterns[i].relPath) {
      // if relPath already exists, overwrite that element
      patternlab.patterns[i] = pattern;
      isNew = false;

      break;
    }
  }

  // if the pattern is new, we must register it with various data structures!
  if (isNew) {
    if (patternlab.config.debug) {
      console.log('Found new pattern ' + pattern.patternPartial);
    }

    patternlab.patterns.push(pattern);
  }
}

function addSubTypePattern(subTypePattern, patternlab) {
  patternlab.subTypePatterns[subTypePattern.patternPartial] = subTypePattern;
}

function isPatternFile(filename, patternlab) {
  const extension = path.extname(filename);

  // skip hidden patterns and data files
  if (filename.charAt(0) === '.' || (extension === '.json' && !isPseudopatternJson(filename))) {
    return false;
  }

  // return boolean condition for everything else
  return (extension === patternlab.config.patternExtension || isPseudopatternJson(filename));
}

function isPseudopatternJson(filename) {
  const extension = path.extname(filename);

  return (extension === '.json' && filename.indexOf('~') > -1);
}

function preprocessPartials(fepletPartials, patternlab) {
  for (let i in fepletPartials) {
    if (!fepletPartials.hasOwnProperty(i)) {
      continue;
    }

    const partial = fepletPartials[i];

    // if undefined, create a placeholder in the partials object to get populated later
    // possible performance improvement by skipping unnecessary assignment
    if (typeof patternlab.partials[partial.name] === 'undefined') {
      patternlab.partials[partial.name] = '';
    }
  }
}

function setState(pattern, patternlab) {
  if (patternlab.config.patternStates && patternlab.config.patternStates[pattern.patternPartial]) {
    pattern.patternState = patternlab.config.patternStates[pattern.patternPartial];
  }
}

// EXPORTED FUNCTIONS

exports.preprocessPattern = function (relPath, patternlab) {
  const fileObject = path.parse(relPath);

  // extract some information
  const filename = fileObject.base;
  const ext = fileObject.ext;
  const patternsPath = patternlab.config.paths.source.patterns;

  // skip non-pattern files
  if (!isPatternFile(filename, patternlab)) {
    return null;
  }

  // make a new Pattern Object
  const pattern = new Pattern(relPath);

  // create subTypePattern if it doesn't already exist
  const subTypeKey = 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup;

  if (pattern.patternSubGroup && !patternlab.subTypePatterns[subTypeKey]) {
    const subTypePattern = {
      subdir: pattern.subdir,
      patternName: pattern.patternSubGroup,
      patternLink: pattern.flatPatternPath + '/index.html',
      patternGroup: pattern.patternGroup,
      patternSubGroup: pattern.patternSubGroup,
      flatPatternPath: pattern.flatPatternPath,
      patternPartial: subTypeKey,
      patternSectionSubType: true
    };

    addSubTypePattern(subTypePattern, patternlab);
  }

  // if file is named in the syntax for variants
  if (isPseudopatternJson(filename)) {
    addPattern(pattern, patternlab);

    return pattern;
  }

  // can ignore all non-supported files at this point
  if (ext !== patternlab.config.patternExtension) {
    return pattern;
  }

  // see if this file has a state
  setState(pattern, patternlab);

  // look for a json file for this template
  let jsonFilename = '';
  let jsonFilenameStats;
  let jsonFileStr = '';

  try {
    jsonFilename = path.resolve(patternsPath, pattern.subdir, pattern.fileName + '.json');
    jsonFilenameStats = fs.statSync(jsonFilename);
  }
  catch (err) {
    // not a file
  }

  if (jsonFilenameStats && jsonFilenameStats.isFile()) {
    try {
      jsonFileStr = fs.readFileSync(jsonFilename, patternlab.enc);
      pattern.jsonFileData = JSON5.parse(jsonFileStr);

      if (patternlab.config.debug) {
        console.log('Found pattern-specific data.json for ' + pattern.patternPartial);
      }
    }
    catch (err) {
      console.log('There was an error parsing sibling JSON for ' + pattern.relPath);
      console.log(err.message || err);
    }
  }

  // look for a listitems.json file for this template
  try {
    const listJsonFileName = path.resolve(patternsPath, pattern.subdir, pattern.fileName + '.listitems.json');

    let listJsonFileStats;

    try {
      listJsonFileStats = fs.statSync(listJsonFileName);
    }
    catch (err) {
      // not a file
    }

    if (listJsonFileStats && listJsonFileStats.isFile()) {
      jsonFileStr = fs.readFileSync(listJsonFileName, patternlab.enc);
      pattern.listItems = JSON5.parse(jsonFileStr);

      if (patternlab.config.debug) {
        console.log('Found pattern-specific listitems.json for ' + pattern.patternPartial);
      }

      listItemsBuilder.listItemsBuild(pattern);
      plutils.extendButNotOverride(pattern.jsonFileData.listItems, patternlab.data.listItems);
    }
  }
  catch (err) {
    console.log('There was an error parsing sibling listitem JSON for ' + pattern.relPath);
    console.log(err.message || err);
  }

  pattern.template = fs.readFileSync(path.resolve(patternsPath, relPath), patternlab.enc);

  const scan = feplet.scan(pattern.template);
  const parseArr = feplet.parse(scan);

  // check if the template uses listItems
  patternlab.useListItems = listItemsBuilder.listItemsScan(parseArr, patternlab);

  pattern.fepletParse = parseArr;
  pattern.fepletComp = feplet.generate(parseArr, pattern.template, {});

  preprocessPartials(pattern.fepletComp.partials, patternlab);

  // add pattern to patternlab.patterns array
  addPattern(pattern, patternlab);

  return pattern;
}

exports.preprocessPartialParams = function (patternlab) {
  let partials = patternlab.partials;

  for (let partialName in partials) {
    if (!partials.hasOwnProperty(partialName)) {
      continue;
    }

    const partialTemplate = partials[partialName];

    let pattern = patternlab.getPattern(partialName);

    if (pattern) {
      partials[partialName] = pattern.template;
    }

    // if no exact match, must have param
    // make sure there is an entry for the non-param partial
    else {
      for (let i = 0, l = patternlab.patterns.length; i < l; i++) {
        const pattern = patternlab.patterns[i];

        let nonParamPartialName;

        if (partialName.indexOf(pattern.patternPartialPhp) === 0) {
          nonParamPartialName = pattern.patternPartialPhp;
        }
        else if (partialName.indexOf(pattern.patternPartial) === 0) {
          nonParamPartialName = pattern.patternPartial;
        }
        else if (partialName.indexOf(pattern.relPathTrunc) === 0) {
          nonParamPartialName = pattern.relPathTrunc;
        }
        else if (partialName.indexOf(pattern.relPath) === 0) {
          nonParamPartialName = pattern.relPath;
        }

        if (nonParamPartialName) {
          partials[nonParamPartialName] = pattern.template;
        }
      }
    }
  }

  for (let i = 0, l = patternlab.patterns.length; i < l; i++) {
    const pattern = patternlab.patterns[i];

    feplet.preprocessPartialParams(pattern.template, partials);

    // find and set lineages
    lineageHunt(pattern, patternlab);
    pattern.lineageExists = pattern.lineage.length > 0;
    pattern.lineageRExists = pattern.lineageR.length > 0;
  }
};

exports.processPattern = function (pattern, patternlab) {
  let patternVariants = [];
  let preRenderObj;
  let tmpPartial;

  // the tilde suffix will sort pseudopatterns after basePatterns
  // so first, check if this is not a pseudopattern (therefore a basePattern) and look for its pseudopattern variants
  if (!isPseudopatternJson(pattern.relPath)) {
    if (pattern.jsonFileData) {
      pattern.allData = plutils.extendButNotOverride(pattern.jsonFileData, patternlab.data);
    }
    else {
      pattern.jsonFileData = {};
      // if no local data, create reference to patternlab.allData
      pattern.allData = patternlab.data;
    }

    // set cacheBuster property if not already set
    if (!pattern.allData.cacheBuster) {
      pattern.allData.cacheBuster = patternlab.cacheBuster;
    }

    pattern.extendedTemplate = pattern.fepletComp.render(pattern.allData, patternlab.partials);
    patternVariants = pseudopatternHunt(pattern, patternlab);
  }

  // identified a pseudopattern by checking if this is a file containing same name, with ~ in it, ending in .json
  // copy its basePattern.extendedTemplate to extendedTemplate and return
  else {
    // set cacheBuster property if not already set
    if (!pattern.allData.cacheBuster) {
      pattern.allData.cacheBuster = patternlab.cacheBuster;
    }

    pattern.extendedTemplate = pattern.fepletComp.render(pattern.allData, patternlab.partials);
  }

  // stringify these data for individual pattern rendering and use on the styleguide
  pattern.allData.patternData = JSON.stringify({
    cssEnabled: false,
    lineage: pattern.lineage,
    lineageExists: pattern.lineageExists,
    lineageR: pattern.lineageR,
    lineageRExists: pattern.lineageRExists,
    patternDesc: pattern.patternDescExists ? pattern.patternDesc : '',
    patternExtension: pattern.fileExtension,
    patternName: pattern.patternName,
    patternPartial: pattern.patternPartial,
    patternState: pattern.patternState
  });

  let head;

  if (patternlab.userHead) {
    head = patternlab.userHead.replace('{{{ patternLabHead }}}', patternlab.header);
  }
  else {
    head = patternlab.header;
  }

  let headHtml = feplet.render(head, pattern.allData);

  // set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer
  const footerPartial = feplet.render(patternlab.footer, {
    cacheBuster: patternlab.cacheBuster,
    isPattern: pattern.isPattern,
    lineage: JSON.stringify(pattern.lineage),
    lineageR: JSON.stringify(pattern.lineageR),
    patternData: pattern.allData.patternData,
    patternPartial: pattern.patternPartial,
    patternState: pattern.patternState,
    portReloader: global.conf.livereload_port,
    portServer: global.conf.express_port
  });

  let footerHtml = patternlab.userFoot.replace('{{{ patternLabFoot }}}', footerPartial);
  footerHtml = feplet.render(footerHtml, pattern.allData);

  pattern.header = headHtml;
  pattern.footer = footerHtml;

  // write the built template to the public patterns directory
  const paths = patternlab.config.paths;
  const patternPage = pattern.header + pattern.extendedTemplate + pattern.footer;

  fs.outputFileSync(
    path.resolve(
      paths.public.patterns,
      pattern.patternLink
    ),
    patternPage
  );

  // write the mustache file too
  fs.outputFileSync(
    path.resolve(
      paths.public.patterns,
      pattern.patternLink.slice(0, -(pattern.outfileExtension.length))  + pattern.fileExtension
    ),
    pattern.template
  );

  // write the markup-only version too
  fs.outputFileSync(
    path.resolve(
      paths.public.patterns,
      pattern.patternLink.slice(0, -(pattern.outfileExtension.length)) + '.markup-only' + pattern.outfileExtension
    ),
    pattern.extendedTemplate
  );

  // free memory
  for (let key in pattern) {
    if (!pattern.hasOwnProperty(key)) {
      continue;
    }

    switch (key) {
      case 'footer':
      case 'extendedTemplate':
      case 'flatPatternPath':
      case 'isPattern':
      case 'name':
      case 'patternBaseName':
      case 'patternGroup':
      case 'patternLink':
      case 'patternName':
      case 'patternPartial':
      case 'patternPartialPhp':
      case 'patternState':
      case 'patternSubGroup':
      case 'relPath':
      case 'relPathTrunc':
      case 'subdir':
      case 'lineageR':
      case 'lineageRIndex':
        continue;
    }

    pattern[key] = null;
    delete pattern[key];
  }
};
<|endoftext|>


<commit_before>const debug = require('debug')('pongdome:chat')
const Table = require('cli-table2')
const fs = require('fs')
const numeral = require('numeral')
const io = require('socket.io-client')
const Stdbot = require('stdbot')
const uuid = require('uuid')

const makeConfig = require('../config')
const defaults = require('./config')
const actions = require('./actions')

function getAdapterConfig (config) {
  if (config.ADAPTER_CONFIG) return JSON.parse(config.ADAPTER_CONFIG)

  if (config.ADAPTER === 'stdbot-flowdock') {
    const adapterConfig = { token: config.FLOWDOCK_TOKEN }
    if (config.FLOWDOCK_FLOWS) adapterConfig.flows = config.FLOWDOCK_FLOWS.split(',')
    if (config.FLOWDOCK_USER === 'true') adapterConfig.streamConfig = { user: true }
    return adapterConfig
  }
}

function makeBot (config) {
  const adapterConfig = getAdapterConfig(config)
  const adapter = require(config.ADAPTER)(adapterConfig)
  return Stdbot(adapter)
}

exports.run = function chat (config) {
  config = makeConfig(defaults, config)

  const bot = makeBot(config)
  const api = io(config.API_URL)

  const admins = config.ADMINS ? config.ADMINS.split(',') : []
  const challenges = {}
  const matchesById = {}
  const matchesByThread = {}

  function saveState () {
    const state = Object.keys(matchesById).map(key => matchesById[key])
    fs.writeFileSync(`${__dirname}/state.json`, JSON.stringify(state, null, 2))
  }

  try {
    require('./state')
      .map(request => Object.assign({}, request, { message: bot.formatMessage(request.message) }))
      .forEach(addRequest)
  } catch (e) {}

  function findRequestThread (message) {
    const request = matchesByThread[message.thread]

    if (!request) {
      message.send('Could not find a challenge here.')
      throw new Error('Could not find a challenge here.')
    }

    return request
  }

  function findRequestUser (message) {
    const user = message.mentions()[0]

    const requests = Object.keys(matchesById)
      .map(id => matchesById[id])
      .filter(request => {
        const isAuthor = request.challenger.id === message.author.id ||
          (request.challengee && request.challengee.id === message.author.id)

        if (!user) return isAuthor

        return isAuthor ||
            request.challenger.id === user.id ||
            (request.challengee && request.challengee.id === user.id)
      })

    if (!requests || !requests.length) {
      message.send('Could not find a challenge here.')
      throw new Error('Could not find a challenge here.')
    }

    if (user) {
      return requests.find(request => {
        return (request.challenger.id === user.id && (!request.challenge || request.challengee.id === message.author.id)) ||
          ((!request.challengee || request.challengee.id === user.id) && request.challenger.id === message.author.id)
      })
    }

    if (requests.length > 1) {
      message.send('Multiple possible challenges, please mention your partner.')
      throw new Error('Multiple possible challenges, please mention your partner.')
    }

    return requests.pop()
  }

  function findRequest (message) {
    if (message.thread) return findRequestThread(message)
    else return findRequestUser(message)
  }

  function addRequest (request) {
    if (!request.id) request = Object.assign({ id: uuid.v4() }, request)

    if (matchesByThread[request.message.thread]) {
      request.message.send('There\'s already a challenge here.')
      throw new Error('There\'s already a challenge here.')
    }

    challenges[request.challenger.id] = challenges[request.challenger.id] || []
    challenges[request.challenger.id].push(request)

    if (request.challengee) {
      challenges[request.challengee.id] = challenges[request.challengee.id] || []
      challenges[request.challengee.id].push(request)
    }

    matchesById[request.id] = request

    if (request.message.thread) {
      matchesByThread[request.message.thread] = request
    }

    saveState()

    return request
  }

  function removeRequest ({ id, challenger, challengee }) {
    if (challenges[challenger.id]) {
      challenges[challenger.id] = challenges[challenger.id]
        .filter(request => request.id !== id)
    }

    if (challenges[challengee.id]) {
      challenges[challengee.id] = challenges[challengee.id]
        .filter(request => request.id !== id)
    }

    const request = matchesById[id]

    delete matchesById[id]
    delete matchesByThread[request.message.thread]

    saveState()
  }

  function matchAll (regex, string) {
    let match
    const results = []
    while ((match = regex.exec(string)) != null) results.push(match)
    return results
  }

  const wordBoundary = /[ \n\r\t.,'"+!?-]/

  let botState

  bot.on('load', state => {
    botState = state
  })

  bot.on('message', message => {
    let results = matchAll(/(?:^|[^\w])#(\w+)/g, message.text)

    // Flowdock specific code: in a flow.
    if (message.raw.thread_id) {
      // Keep only tags that are actual Flowdock tags (will not match tags
      // inside code blocks and alike). Do it only in a flow because those
      // are never present in private messages.
      results = results.filter(match => message.raw.tags.includes(match[1]))

      // In a flow, other users are properly tagged so use the native way.
      message.mentions = function mentions () {
        return bot.mentions(message)
      }
    }

    // Flowdock specific code: in a private conversation.
    if (message.raw.to) {
      // In a private conversation, we can't mention persons, so we need
      // an alternative way to find users.
      message.mentions = function mentions () {
        const matches = message.text.split(wordBoundary)
          .filter(word => word.startsWith('@'))
          .map(name => name.substr(1))

        if (!matches.length) return []

        const usersByName = {}

        Object.keys(botState.usersById)
          .map(id => botState.usersById[id])
          .forEach(user => {
            usersByName[user.name.toLowerCase()] = user
          })

        return matches
          .map(name => usersByName[name.toLowerCase()])
          .filter(user => user)
      }
    }

    if (!results.length) return

    const action = results[0][1].toLowerCase()

    if (!actions[action]) return

    const flags = results.slice(1).map(match => match[1])

    const flagsObject = flags.reduce((object, flag) => {
      object[flag] = true
      return object
    }, {})

    const isAdmin = admins
      .map(name => name.toLowerCase())
      .find(name => name === message.author.name.toLowerCase())

    debug(`@${message.author.name} #${action} ${message.thread}${flags.map(x => ` #${x}`).join('')}`)

    try {
      actions[action]({ api, bot, saveState, findRequest, addRequest, removeRequest, challenges, matchesById, matchesByThread, message, flags: flagsObject, isAdmin })
    } catch (err) {
      debug(err)
    }
  })

  bot.on('error', debug)

  api.on('match', ({ match }) => {
    if (!matchesById[match.id]) return
    const { challenger, challengee, message } = matchesById[match.id]
    message.send(`${bot.mention(challenger)} ${bot.mention(challengee)} Game on!`)
  })

  api.on('queue', ({ match, position }) => {
    if (!matchesById[match.id]) return
    const { challenger, challengee, message } = matchesById[match.id]
    message.send(`${bot.mention(challenger)} ${bot.mention(challengee)} Queued Up! You're ${numeral(position).format('0o')} in the queue.`)
  })

  api.on('progress', match => {
    const request = matchesById[match.id]

    if (!request) return
    if (!request.message.edit) return
    if (match.unranked) return

    const table = new Table({
      style: { head: [], border: [] }
    })

    const p1 = match.playerOne
    const p2 = match.playerTwo

    table.push(
      [p1.name, ...p1.games, p1.current],
      [p2.name, ...p2.games, p2.current]
    )

    const liveScore = '```\n' + table.toString() + '\n```'

    if (request.progress) {
      request.progress = request.progress.then(message => message.edit(liveScore))
    } else {
      request.progress = request.message.send(liveScore)
    }
  })

  api.on('end', ({ match, winner, loser }) => {
    if (!matchesById[match.id]) return

    const request = matchesById[match.id]
    const winnerTotal = winner.games.reduce((a, b) => a + b, 0)
    const loserTotal = loser.games.reduce((a, b) => a + b, 0)

    request.message.send(`${bot.mention(winner.meta)} beat ${bot.mention(loser.meta)}! ${winnerTotal} points to ${loserTotal} points.`)
    removeRequest(request)
  })

  api.on('cancel', ({ match }) => {
    if (!matchesById[match.id]) return

    const request = matchesById[match.id]
    request.message.send('Game cancelled.')
    removeRequest(request)
  })
}

if (require.main === module) {
  exports.run()
}
<commit_msg>Flag chat log as direct message if no thread<commit_after>const debug = require('debug')('pongdome:chat')
const Table = require('cli-table2')
const fs = require('fs')
const numeral = require('numeral')
const io = require('socket.io-client')
const Stdbot = require('stdbot')
const uuid = require('uuid')

const makeConfig = require('../config')
const defaults = require('./config')
const actions = require('./actions')

function getAdapterConfig (config) {
  if (config.ADAPTER_CONFIG) return JSON.parse(config.ADAPTER_CONFIG)

  if (config.ADAPTER === 'stdbot-flowdock') {
    const adapterConfig = { token: config.FLOWDOCK_TOKEN }
    if (config.FLOWDOCK_FLOWS) adapterConfig.flows = config.FLOWDOCK_FLOWS.split(',')
    if (config.FLOWDOCK_USER === 'true') adapterConfig.streamConfig = { user: true }
    return adapterConfig
  }
}

function makeBot (config) {
  const adapterConfig = getAdapterConfig(config)
  const adapter = require(config.ADAPTER)(adapterConfig)
  return Stdbot(adapter)
}

exports.run = function chat (config) {
  config = makeConfig(defaults, config)

  const bot = makeBot(config)
  const api = io(config.API_URL)

  const admins = config.ADMINS ? config.ADMINS.split(',') : []
  const challenges = {}
  const matchesById = {}
  const matchesByThread = {}

  function saveState () {
    const state = Object.keys(matchesById).map(key => matchesById[key])
    fs.writeFileSync(`${__dirname}/state.json`, JSON.stringify(state, null, 2))
  }

  try {
    require('./state')
      .map(request => Object.assign({}, request, { message: bot.formatMessage(request.message) }))
      .forEach(addRequest)
  } catch (e) {}

  function findRequestThread (message) {
    const request = matchesByThread[message.thread]

    if (!request) {
      message.send('Could not find a challenge here.')
      throw new Error('Could not find a challenge here.')
    }

    return request
  }

  function findRequestUser (message) {
    const user = message.mentions()[0]

    const requests = Object.keys(matchesById)
      .map(id => matchesById[id])
      .filter(request => {
        const isAuthor = request.challenger.id === message.author.id ||
          (request.challengee && request.challengee.id === message.author.id)

        if (!user) return isAuthor

        return isAuthor ||
            request.challenger.id === user.id ||
            (request.challengee && request.challengee.id === user.id)
      })

    if (!requests || !requests.length) {
      message.send('Could not find a challenge here.')
      throw new Error('Could not find a challenge here.')
    }

    if (user) {
      return requests.find(request => {
        return (request.challenger.id === user.id && (!request.challenge || request.challengee.id === message.author.id)) ||
          ((!request.challengee || request.challengee.id === user.id) && request.challenger.id === message.author.id)
      })
    }

    if (requests.length > 1) {
      message.send('Multiple possible challenges, please mention your partner.')
      throw new Error('Multiple possible challenges, please mention your partner.')
    }

    return requests.pop()
  }

  function findRequest (message) {
    if (message.thread) return findRequestThread(message)
    else return findRequestUser(message)
  }

  function addRequest (request) {
    if (!request.id) request = Object.assign({ id: uuid.v4() }, request)

    if (matchesByThread[request.message.thread]) {
      request.message.send('There\'s already a challenge here.')
      throw new Error('There\'s already a challenge here.')
    }

    challenges[request.challenger.id] = challenges[request.challenger.id] || []
    challenges[request.challenger.id].push(request)

    if (request.challengee) {
      challenges[request.challengee.id] = challenges[request.challengee.id] || []
      challenges[request.challengee.id].push(request)
    }

    matchesById[request.id] = request

    if (request.message.thread) {
      matchesByThread[request.message.thread] = request
    }

    saveState()

    return request
  }

  function removeRequest ({ id, challenger, challengee }) {
    if (challenges[challenger.id]) {
      challenges[challenger.id] = challenges[challenger.id]
        .filter(request => request.id !== id)
    }

    if (challenges[challengee.id]) {
      challenges[challengee.id] = challenges[challengee.id]
        .filter(request => request.id !== id)
    }

    const request = matchesById[id]

    delete matchesById[id]
    delete matchesByThread[request.message.thread]

    saveState()
  }

  function matchAll (regex, string) {
    let match
    const results = []
    while ((match = regex.exec(string)) != null) results.push(match)
    return results
  }

  const wordBoundary = /[ \n\r\t.,'"+!?-]/

  let botState

  bot.on('load', state => {
    botState = state
  })

  bot.on('message', message => {
    let results = matchAll(/(?:^|[^\w])#(\w+)/g, message.text)

    // Flowdock specific code: in a flow.
    if (message.raw.thread_id) {
      // Keep only tags that are actual Flowdock tags (will not match tags
      // inside code blocks and alike). Do it only in a flow because those
      // are never present in private messages.
      results = results.filter(match => message.raw.tags.includes(match[1]))

      // In a flow, other users are properly tagged so use the native way.
      message.mentions = function mentions () {
        return bot.mentions(message)
      }
    }

    // Flowdock specific code: in a private conversation.
    if (message.raw.to) {
      // In a private conversation, we can't mention persons, so we need
      // an alternative way to find users.
      message.mentions = function mentions () {
        const matches = message.text.split(wordBoundary)
          .filter(word => word.startsWith('@'))
          .map(name => name.substr(1))

        if (!matches.length) return []

        const usersByName = {}

        Object.keys(botState.usersById)
          .map(id => botState.usersById[id])
          .forEach(user => {
            usersByName[user.name.toLowerCase()] = user
          })

        return matches
          .map(name => usersByName[name.toLowerCase()])
          .filter(user => user)
      }
    }

    if (!results.length) return

    const action = results[0][1].toLowerCase()

    if (!actions[action]) return

    const flags = results.slice(1).map(match => match[1])

    const flagsObject = flags.reduce((object, flag) => {
      object[flag] = true
      return object
    }, {})

    const isAdmin = admins
      .map(name => name.toLowerCase())
      .find(name => name === message.author.name.toLowerCase())

    debug(`@${message.author.name} #${action} ${message.thread || '[dm]'}${flags.map(x => ` #${x}`).join('')}`)

    try {
      actions[action]({ api, bot, saveState, findRequest, addRequest, removeRequest, challenges, matchesById, matchesByThread, message, flags: flagsObject, isAdmin })
    } catch (err) {
      debug(err)
    }
  })

  bot.on('error', debug)

  api.on('match', ({ match }) => {
    if (!matchesById[match.id]) return
    const { challenger, challengee, message } = matchesById[match.id]
    message.send(`${bot.mention(challenger)} ${bot.mention(challengee)} Game on!`)
  })

  api.on('queue', ({ match, position }) => {
    if (!matchesById[match.id]) return
    const { challenger, challengee, message } = matchesById[match.id]
    message.send(`${bot.mention(challenger)} ${bot.mention(challengee)} Queued Up! You're ${numeral(position).format('0o')} in the queue.`)
  })

  api.on('progress', match => {
    const request = matchesById[match.id]

    if (!request) return
    if (!request.message.edit) return
    if (match.unranked) return

    const table = new Table({
      style: { head: [], border: [] }
    })

    const p1 = match.playerOne
    const p2 = match.playerTwo

    table.push(
      [p1.name, ...p1.games, p1.current],
      [p2.name, ...p2.games, p2.current]
    )

    const liveScore = '```\n' + table.toString() + '\n```'

    if (request.progress) {
      request.progress = request.progress.then(message => message.edit(liveScore))
    } else {
      request.progress = request.message.send(liveScore)
    }
  })

  api.on('end', ({ match, winner, loser }) => {
    if (!matchesById[match.id]) return

    const request = matchesById[match.id]
    const winnerTotal = winner.games.reduce((a, b) => a + b, 0)
    const loserTotal = loser.games.reduce((a, b) => a + b, 0)

    request.message.send(`${bot.mention(winner.meta)} beat ${bot.mention(loser.meta)}! ${winnerTotal} points to ${loserTotal} points.`)
    removeRequest(request)
  })

  api.on('cancel', ({ match }) => {
    if (!matchesById[match.id]) return

    const request = matchesById[match.id]
    request.message.send('Game cancelled.')
    removeRequest(request)
  })
}

if (require.main === module) {
  exports.run()
}
<|endoftext|>


<commit_before>/*jslint white: true, vars: true, sloppy: true, devel: true, plusplus: true, browser: true */
/*global CoreView, CoreClass*/

(function (a) {

    var cordova = a.cordova;
    var $ = a.$;
    var TmplFactory = a.TemplateFactory;

    function noop() { return; }

    function CoreApplication()   {
        this.views = {};
        this.viewReg = {};
        this.models = {};
        this.modelReg = {};
        this.sourceView = null;
        this.sourceTrace = [];
        this.viewId = "";
    }

/**
 * @static @method start(controller)
 *
 * This function links CoreApplication to the app's controller logic, so the controller
 * can use all CoreApplication functions and properties as usual.
 *
 * For getting the application going one needs to call
 *
 *    CoreApplication.start(MyController);
 *
 * after all required modules are loaded.
 */
CoreApplication.start = function (controller) {
    function CoreApplicationHelper() {
        CoreApplication.call(this);
        controller.call(this);
    }

    CoreClass.extend(controller, CoreApplication);
    CoreClass.extend(CoreApplicationHelper, controller);

    function initApplication() {
        a.app = new CoreApplicationHelper();
        a.app.bindEvents();
        a.app.ready();
    }

    if (cordova) {
        // init for cordova applications
        document.addEventListener('deviceready', initApplication, false);
    }
    else {
        // init for web applications
        $(document).ready(initApplication);
    }
};

CoreApplication.prototype.setReadOnly = function (name, value) {
    if (!this.hasOwnProperty(name) && !this[name]) {
        var tmpVal = value;
        Object.defineProperty(this, name, {get: function () {return tmpVal;}});
    }
};

CoreApplication.prototype.ready = function () {
    this.initTemplates();
    this.initModels();
    this.initViews();

    this.initialize();
    this.openFirstView();
};

CoreApplication.prototype.registerView = function (fn) {
    var fname = fn.name;
    this.viewReg[fname] = fn;
};

CoreApplication.prototype.registerModel = function (fn){
    var fname = fn.name;
    this.modelReg[fname] = fn;
};

CoreApplication.prototype.initTemplates = function () {
    this.templates = new TmplFactory();
    if (!this.templates) {
        console.log('templates failed');
    }
};

CoreApplication.prototype.initModels = function () {
    var i, ml, meta, rs, models = [];

    // new style
    $('[data-model]').each(function () {
         ml = this.dataset.model;
        if (ml && ml.length) {
            ml.split(" ").forEach(function (mName) {
                if (mName && mName.length && models.indexOf(mName) < 0) {
                    models.push(mName);
                }
            });
        }
    });

    if (!models.length) {
        meta = document.getElementsByTagName('META');
        for (i = 0; i < meta.length; i++) {
            if (meta[i].getAttribute('name') === 'app:models') {
                rs = meta[i].content;
                rs = rs.replace(/\s+/g, '');
                models = rs.split(/\,/);
            }
        }
    }
    this.modelReg = {};
    if (models.length) {
        models.forEach(function(m) {
            if (typeof window[m] === 'function') {
                this.modelReg[m.replace('Model', '').toLowerCase()] = window[m];
            }
        }, this);
    }

    var pn = Object.getOwnPropertyNames(this.modelReg);
    for (i = 0; i < pn.length; i++) {
        this.models[pn[i]] = new this.modelReg[pn[i]](this);
    }
};

CoreApplication.prototype.initViews = function () {
    var self = this;
    $('[data-view]').each(function () {
        var tagid = this.id;
        var className = this.dataset.view;

        if (className && typeof window[className] === 'function') {
            self.views[tagid] = new CoreView(self, tagid, window[className]);
        }
    }); // end each()
};

CoreApplication.prototype.isActiveView = function (viewObject) {
    return (this.views[this.viewId] === viewObject);
};

CoreApplication.prototype.changeView = function chView(viewname, eventname) {
    var self = this;

    function defer() {
        if (viewname &&
            typeof viewname === 'string' &&
            self.views[viewname] &&
            self.viewId !== viewname) {
            if (viewname === self.sourceView) {
                self.sourceView = self.sourceTrace.pop();
            }
            else {
                self.sourceTrace.push(this.sourceView);
                self.sourceView = this.viewId; // this is used for back operations in CoreView
            }
            if (self.viewId && self.views[self.viewId]) {
                self.views[self.viewId].close();
            }
            self.viewId = viewname;
            self.views[self.viewId].open();
        }
        // won't do any harm
        $(document).unbind(eventname, defer);
    }

    if (!eventname) {
        defer();
    }
    else {
        $(document).bind(eventname, defer);
    }

};

CoreApplication.prototype.deferredChangeView = function dchView(eventname, viewname, viewData) {
    var self = this;
    function defer() {
        self.changeView(viewname, viewData);
        $(document).unbind(eventname, defer);
    }

    $(document).bind(eventname, defer);
};

CoreApplication.prototype.reopenView = function (viewData) {
    this.views[this.viewId].close();
    this.views[this.viewId].open(viewData);
};

CoreApplication.prototype.openFirstView = function() {
    if (this.viewId && this.viewId.length) {
        this.views[this.viewId].open();
    }
};

CoreApplication.prototype.initialize = noop;
CoreApplication.prototype.bindEvents = noop;

    // register the class to the target object
    if (typeof a.CoreApplication !== 'function') {
        a.CoreApplication = CoreApplication;
    }
}(window));
<commit_msg>drop deferredChangeView and add viewData<commit_after>/*jslint white: true, vars: true, sloppy: true, devel: true, plusplus: true, browser: true */
/*global CoreView, CoreClass*/

(function (a) {

    var cordova = a.cordova;
    var $ = a.$;
    var TmplFactory = a.TemplateFactory;

    function noop() { return; }

    function CoreApplication()   {
        this.views = {};
        this.viewReg = {};
        this.models = {};
        this.modelReg = {};
        this.sourceView = null;
        this.sourceTrace = [];
        this.viewId = "";
    }

/**
 * @static @method start(controller)
 *
 * This function links CoreApplication to the app's controller logic, so the controller
 * can use all CoreApplication functions and properties as usual.
 *
 * For getting the application going one needs to call
 *
 *    CoreApplication.start(MyController);
 *
 * after all required modules are loaded.
 */
CoreApplication.start = function (controller) {
    function CoreApplicationHelper() {
        CoreApplication.call(this);
        controller.call(this);
    }

    CoreClass.extend(controller, CoreApplication);
    CoreClass.extend(CoreApplicationHelper, controller);

    function initApplication() {
        a.app = new CoreApplicationHelper();
        a.app.bindEvents();
        a.app.ready();
    }

    if (cordova) {
        // init for cordova applications
        document.addEventListener('deviceready', initApplication, false);
    }
    else {
        // init for web applications
        $(document).ready(initApplication);
    }
};

CoreApplication.prototype.setReadOnly = function (name, value) {
    if (!this.hasOwnProperty(name) && !this[name]) {
        var tmpVal = value;
        Object.defineProperty(this, name, {get: function () {return tmpVal;}});
    }
};

CoreApplication.prototype.ready = function () {
    this.initTemplates();
    this.initModels();
    this.initViews();

    this.initialize();
    this.openFirstView();
};

CoreApplication.prototype.registerView = function (fn) {
    var fname = fn.name;
    this.viewReg[fname] = fn;
};

CoreApplication.prototype.registerModel = function (fn){
    var fname = fn.name;
    this.modelReg[fname] = fn;
};

CoreApplication.prototype.initTemplates = function () {
    this.templates = new TmplFactory();
    if (!this.templates) {
        console.log('templates failed');
    }
};

CoreApplication.prototype.initModels = function () {
    var i, ml, meta, rs, models = [];

    // new style
    $('[data-model]').each(function () {
         ml = this.dataset.model;
        if (ml && ml.length) {
            ml.split(" ").forEach(function (mName) {
                if (mName && mName.length && models.indexOf(mName) < 0) {
                    models.push(mName);
                }
            });
        }
    });

    if (!models.length) {
        meta = document.getElementsByTagName('META');
        for (i = 0; i < meta.length; i++) {
            if (meta[i].getAttribute('name') === 'app:models') {
                rs = meta[i].content;
                rs = rs.replace(/\s+/g, '');
                models = rs.split(/\,/);
            }
        }
    }
    this.modelReg = {};
    if (models.length) {
        models.forEach(function(m) {
            if (typeof window[m] === 'function') {
                this.modelReg[m.replace('Model', '').toLowerCase()] = window[m];
            }
        }, this);
    }

    var pn = Object.getOwnPropertyNames(this.modelReg);
    for (i = 0; i < pn.length; i++) {
        this.models[pn[i]] = new this.modelReg[pn[i]](this);
    }
};

CoreApplication.prototype.initViews = function () {
    var self = this;
    $('[data-view]').each(function () {
        var tagid = this.id;
        var className = this.dataset.view;

        if (className && typeof window[className] === 'function') {
            self.views[tagid] = new CoreView(self, tagid, window[className]);
        }
    }); // end each()
};

CoreApplication.prototype.isActiveView = function (viewObject) {
    return (this.views[this.viewId] === viewObject);
};

CoreApplication.prototype.changeView = function chView(viewname, eventname, viewdata) {
    var self = this;

    function defer() {
        if (viewname &&
            typeof viewname === 'string' &&
            self.views[viewname] &&
            self.viewId !== viewname) {
            if (viewname === self.sourceView) {
                self.sourceView = self.sourceTrace.pop();
            }
            else {
                self.sourceTrace.push(this.sourceView);
                self.sourceView = this.viewId; // this is used for back operations in CoreView
            }
            if (self.viewId && self.views[self.viewId]) {
                self.views[self.viewId].close();
            }
            self.viewId = viewname;
            self.views[self.viewId].open(viewdata);
        }
        // won't do any harm
        $(document).unbind(eventname, defer);
    }

    if (!eventname) {
        defer();
    }
    else {
        $(document).bind(eventname, defer);
    }

};

CoreApplication.prototype.reopenView = function (viewData) {
    this.views[this.viewId].close();
    this.views[this.viewId].open(viewData);
};

CoreApplication.prototype.openFirstView = function() {
    if (this.viewId && this.viewId.length) {
        this.views[this.viewId].open();
    }
};

CoreApplication.prototype.initialize = noop;
CoreApplication.prototype.bindEvents = noop;

    // register the class to the target object
    if (typeof a.CoreApplication !== 'function') {
        a.CoreApplication = CoreApplication;
    }
}(window));
<|endoftext|>