<commit_before>'use strict';

// Load modules
const i18n = require('../services/i18n');
const fs = require('fs');
var dateFormat = require('dateformat');
const Remarkable = require('remarkable');
const path = require('path');
const logger = require('../services/logger');

// Setup
const md = new Remarkable({
  html: true
});

exports.toId = (input) => {
  return input.replace(/ /g, '');
};

exports.clientConfig = () => {
  return JSON.stringify({
    baseUrl: process.env.REACT_BASEURL,
    clientSecret: process.env.REACT_CLIENT_SECRET,
    clientId: process.env.REACT_CLIENT_ID
  });
};

exports.stringify = (obj) => {

  if (!obj) {
    return false;
  }

  return JSON.stringify(obj);
};

/**
 * Concatenates first and second.
 * @param first
 * @param second
 */
exports.concat = (first, second) => first + second;

/**
 * Returns true if v1 == v2.
 * @param v1
 * @param v2
 * @param options
 * @returns {*}
 */
exports.ifCond = function (v1, v2, options) {

  if (v1 === v2) {
    return options.fn(this);
  }

  return options.inverse(this);
};

exports.weakIfCond = function (v1, v2, options) {

  if (v1 == v2) {
    return options.fn(this);
  }

  return options.inverse(this);
};

exports.isEven = function (context) {
  if ((context.data.index % 2) === 0) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};

exports.isOdd = function (context) {
  if ((context.data.index % 2) !== 0) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};

exports.isLast = function (context) {
  if (context.data.last) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};

/**
 * Render markdown from content/mdFileName to html
 * @param mdFileName
 * @returns rendered html
 */
exports.markdown = function renderMarkdown(mdFileName, context) {
  const rawMd = loadFileContent(mdFileName);
  const html = md.render(rawMd);
  return html;
};

exports.date = function makeDate(timestamp, context) {
  return new Date(timestamp);
};

exports.beautifuldate = function makeDate(timestamp, context) {
  if(timestamp === Number.MAX_VALUE) {
    return 'Kein Datum vorhanden';
  } else if (timestamp === 0) {
    return 'Kein Datum vorhanden';
  }

  let date = new Date(timestamp);
  let beautifuldate = dateFormat(date, 'dS mmmm, h:MM TT');
  return beautifuldate;
};

exports.md = function renderMarkdown(rawMd, context) {
  const html = md.render(rawMd);
  return html;
};

exports.contentfulImage = function (imageObject, clazz, id, context) {

  clazz = (clazz != null) ? clazz : '';
  id = (id != null) ? id : '';

  const url = imageObject.fields.file.url;
  const alt = (imageObject.fields.description != null) ? imageObject.fields.description : '';

  return `<img src="${url}" alt="${alt}" class="${clazz}" id="${id}"/>`;
};

function loadFileContent(mdFileName) {
  const path = getFilepath(mdFileName);
  return fs.readFileSync(path, 'utf-8');
}

function getFilepath(mdFileName) {
  const contentFolderPath = path.resolve('content/');
  return `${contentFolderPath}/${mdFileName}.md`;
}

/**
 * Tries to find the matching translation for the language the browser sent us.
 * @param text
 * @param options
 * @private
 */
exports.__ = (text, options) => {

  if (!options.data.root.language) {
    throw 'You did not pass the language to handlebars!';
  }

  const view = options.data.exphbs.view;
  let viewArr = [];

  if (view) {
    if (view.indexOf('\\') > -1) {
      viewArr = view.split('\\');
    } else {
      viewArr = view.split('/');
    }
  }

  if (text.indexOf('.') > -1) {
    viewArr = text.split('.');
    text = viewArr.pop();
  } else if (!view) {
    logger.error(`Could not parse view in ${options.data.exphbs}`);
  }

  return i18n.translate(viewArr[viewArr.length - 1].toUpperCase(), text.toUpperCase(), options.data.root.language);
};

exports.ifOr = function (v1, v2, options) {
  if (v1 || v2) {
    return options.fn(this);
  }

  return options.inverse(this);
};

exports.debug = function(optionalValue) {
  console.log("Current Context");
  console.log("====================");
  console.log(this);

  if (optionalValue) {
    console.log("Value");
    console.log("====================");
    console.log(optionalValue);
  }
};

exports.json = function (context) {
  return JSON.stringify(context);
};

exports.relativeTime = function (timestamp) {

  function leftPad(zahlen) {
    let string = '00' + zahlen;
    return string.substring(string.length - 2);
  }

  const MONTHS = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
  let dO = new Date((timestamp+(60*60*2)) * 1000);

  // TODO: Hack for timezone! Fix this in 2018
  let now = new Date(Date.now() + 60*60*2*1000);
  let difference = now - dO.getTime();
  if (difference < 60 * 1000) {
    return 'Gerade eben';
  } else if (difference < 60 * 60 * 1000) {
    return `vor ${Math.floor(difference / 60 / 1000)} Minuten`;
  } else if (difference < 60 * 60 * 24 * 1000) {
    return `vor ${Math.floor(difference / 60 / 60 / 1000)} Stunden`;
  } else {
    return `am ${dO.getDate()}. ${MONTHS[dO.getMonth()]} um ${dO.getHours()}:${leftPad(dO.getMinutes())}`;
  }
};

exports.length = function (array) {
  return array.length;
};

exports.strColor = (str) => {
  if(!str) {
    return 'gray';
  }

  var hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash);
  }
  var colour = '#';
  for (let i = 0; i < 3; i++) {
    var value = (hash >> (i * 8)) & 0xFF;
    colour += ('00' + value.toString(16)).substr(-2);
  }
  return colour;
};

exports.smallestImage = (sizes) => {

  if (Array.isArray(sizes) && sizes.length > 0) {
    var minSize = sizes[0].size;
    var url = sizes[0].url;

    sizes.forEach((thisSize) => {
      if (thisSize.size < minSize) {
        minSize = thisSize.size;
        url = thisSize.url;
      }
    });

    return url;
  }
};

exports.getImageByWidth = (width, sizes) => {

  width = parseFloat(width);

  if (Array.isArray(sizes)) {
    sizes = sizes.filter((size) => size.type === 'IMAGE');
    if (sizes.length > 0) {

      var minDiff = 100000000000;
      var bestFit = sizes[0].url;
      sizes.forEach(s => {
        let currDiff = s.width - width;
        if (currDiff < 0) currDiff = currDiff * -8;

        if (currDiff < minDiff) {
          minDiff = currDiff;
          bestFit = s.url;
        }
      });

      return bestFit;
    }
  }
};

exports.getH264VideoByWidth = (width, sizes) => {

  width = parseFloat(width);

  if (Array.isArray(sizes)) {
    sizes = sizes.filter((size) => size.type === 'VIDEO' && size.url.endsWith('.mp4'));

    if (sizes.length > 0) {

      var minDiff = 100000000000;
      var bestFit = sizes[0].url;
      sizes.forEach(s => {
        let currDiff = s.width - width;
        if (currDiff < 0) currDiff = currDiff * -8;

        if (currDiff < minDiff) {
          minDiff = currDiff;
          bestFit = s.url;
        }
      });

      return bestFit;
    }
  }
};

exports.getWebmVideoByWidth = (width, sizes) => {

  width = parseFloat(width);

  if (Array.isArray(sizes)) {
    sizes = sizes.filter((size) => size.type === 'VIDEO' && size.url.endsWith('.webm'));

    if (sizes.length > 0) {

      var minDiff = 100000000000;
      var bestFit = sizes[0].url;
      sizes.forEach(s => {
        let currDiff = s.width - width;
        if (currDiff < 0) currDiff = currDiff * -8;

        if (currDiff < minDiff) {
          minDiff = currDiff;
          bestFit = s.url;
        }
      });

      return bestFit;
    }
  }
};

exports.round = (amount) => {
  return Math.round(parseFloat(amount)).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
};

exports.roundWithoutFormat = (amount) => {
  return Math.round(parseFloat(amount));
};

exports.addOne = (amount) => {
  return (parseFloat(amount) + 1);
};

exports.prettyLocation = (location) => {
  //Check if it exists.
  if (!location) return '';

  var locString = '';

  //Check for best Level
  if (location.hasOwnProperty('LOCALITY')) {
    locString = location.LOCALITY;
  } else if (location.hasOwnProperty('ADMINISTRATIVE_AREA_LEVEL_3')) {
    locString = location.ADMINISTRATIVE_AREA_LEVEL_3;
  } else if (location.hasOwnProperty('ADMINISTRATIVE_AREA_LEVEL_2')) {
    locString = location.ADMINISTRATIVE_AREA_LEVEL_2;
  } else if (location.hasOwnProperty('ADMINISTRATIVE_AREA_LEVEL_1')) {
    locString = location.ADMINISTRATIVE_AREA_LEVEL_1;
  }

  if (location.hasOwnProperty('COUNTRY')) {
    if (locString !== '') {
      locString += ', ';
    }
    locString += location.COUNTRY;
  }
  if (locString !== '') {
    locString = ' in ' + locString;
  }
  return locString;
};

exports.getSmallestAudio = (sizes) => {
  if (Array.isArray(sizes) && sizes.length > 0) {

    var filteredsizes = sizes.filter((size) => size.type === 'AUDIO').filter((s) => s.url.endsWith('.mp3'));

    if (filteredsizes.length === 0) {
      filteredsizes = sizes;
    }

    var minsize = 100000000000;
    var url = filteredsizes[0].url;

    filteredsizes.forEach(s => {
      if (s.size < minsize) {
        minsize = s.size;
        url = s.url;
      }
    });

    return url;
  }
};

exports.challengeHasProof = (status) => {
  if (status === 'WITH_PROOF' || status === 'PROOF_ACCEPTED') {
    return true;
  }

  return false;
};

exports.isOlderTenMinutes = (date, context) => {
  if ((new Date(date * 1000 + 10 * 60 * 1000).getTime() < new Date().getTime())) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};

exports.isNewerTenMinutes = (date, context) => {
  if ((new Date(date * 1000 + 10 * 60 * 1000).getTime() > new Date().getTime())) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};<commit_msg>Fix eslint errors<commit_after>'use strict';

// Load modules
const i18n = require('../services/i18n');
const fs = require('fs');
var dateFormat = require('dateformat');
const Remarkable = require('remarkable');
const path = require('path');
const logger = require('../services/logger');

// Setup
const md = new Remarkable({
  html: true
});

exports.toId = (input) => {
  return input.replace(/ /g, '');
};

exports.clientConfig = () => {
  return JSON.stringify({
    baseUrl: process.env.REACT_BASEURL,
    clientSecret: process.env.REACT_CLIENT_SECRET,
    clientId: process.env.REACT_CLIENT_ID
  });
};

exports.stringify = (obj) => {

  if (!obj) {
    return false;
  }

  return JSON.stringify(obj);
};

/**
 * Concatenates first and second.
 * @param first
 * @param second
 */
exports.concat = (first, second) => first + second;

/**
 * Returns true if v1 == v2.
 * @param v1
 * @param v2
 * @param options
 * @returns {*}
 */
exports.ifCond = function (v1, v2, options) {

  if (v1 === v2) {
    return options.fn(this);
  }

  return options.inverse(this);
};

exports.weakIfCond = function (v1, v2, options) {

  if (v1 == v2) {
    return options.fn(this);
  }

  return options.inverse(this);
};

exports.isEven = function (context) {
  if ((context.data.index % 2) === 0) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};

exports.isOdd = function (context) {
  if ((context.data.index % 2) !== 0) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};

exports.isLast = function (context) {
  if (context.data.last) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};

/**
 * Render markdown from content/mdFileName to html
 * @param mdFileName
 * @returns rendered html
 */
exports.markdown = function renderMarkdown(mdFileName, context) {
  const rawMd = loadFileContent(mdFileName);
  const html = md.render(rawMd);
  return html;
};

exports.date = function makeDate(timestamp, context) {
  return new Date(timestamp);
};

exports.beautifuldate = function makeDate(timestamp, context) {
  if(timestamp === Number.MAX_VALUE) {
    return 'Kein Datum vorhanden';
  } else if (timestamp === 0) {
    return 'Kein Datum vorhanden';
  }

  let date = new Date(timestamp);
  let beautifuldate = dateFormat(date, 'dS mmmm, h:MM TT');
  return beautifuldate;
};

exports.md = function renderMarkdown(rawMd, context) {
  const html = md.render(rawMd);
  return html;
};

exports.contentfulImage = function (imageObject, clazz, id, context) {

  clazz = (clazz != null) ? clazz : '';
  id = (id != null) ? id : '';

  const url = imageObject.fields.file.url;
  const alt = (imageObject.fields.description != null) ? imageObject.fields.description : '';

  return `<img src="${url}" alt="${alt}" class="${clazz}" id="${id}"/>`;
};

function loadFileContent(mdFileName) {
  const path = getFilepath(mdFileName);
  return fs.readFileSync(path, 'utf-8');
}

function getFilepath(mdFileName) {
  const contentFolderPath = path.resolve('content/');
  return `${contentFolderPath}/${mdFileName}.md`;
}

/**
 * Tries to find the matching translation for the language the browser sent us.
 * @param text
 * @param options
 * @private
 */
exports.__ = (text, options) => {

  if (!options.data.root.language) {
    throw 'You did not pass the language to handlebars!';
  }

  const view = options.data.exphbs.view;
  let viewArr = [];

  if (view) {
    if (view.indexOf('\\') > -1) {
      viewArr = view.split('\\');
    } else {
      viewArr = view.split('/');
    }
  }

  if (text.indexOf('.') > -1) {
    viewArr = text.split('.');
    text = viewArr.pop();
  } else if (!view) {
    logger.error(`Could not parse view in ${options.data.exphbs}`);
  }

  return i18n.translate(viewArr[viewArr.length - 1].toUpperCase(), text.toUpperCase(), options.data.root.language);
};

exports.ifOr = function (v1, v2, options) {
  if (v1 || v2) {
    return options.fn(this);
  }

  return options.inverse(this);
};

/* eslint-disable no-console */
exports.debug = function(optionalValue) {
  console.log('Current Context');
  console.log('====================');
  console.log(this);

  if (optionalValue) {
    console.log('Value');
    console.log('====================');
    console.log(optionalValue);
  }
};
/*eslint-enable no-console */

exports.json = function (context) {
  return JSON.stringify(context);
};

exports.relativeTime = function (timestamp) {

  function leftPad(zahlen) {
    let string = '00' + zahlen;
    return string.substring(string.length - 2);
  }

  const MONTHS = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
  let dO = new Date((timestamp+(60*60*2)) * 1000);

  // TODO: Hack for timezone! Fix this in 2018
  let now = new Date(Date.now() + 60*60*2*1000);
  let difference = now - dO.getTime();
  if (difference < 60 * 1000) {
    return 'Gerade eben';
  } else if (difference < 60 * 60 * 1000) {
    return `vor ${Math.floor(difference / 60 / 1000)} Minuten`;
  } else if (difference < 60 * 60 * 24 * 1000) {
    return `vor ${Math.floor(difference / 60 / 60 / 1000)} Stunden`;
  } else {
    return `am ${dO.getDate()}. ${MONTHS[dO.getMonth()]} um ${dO.getHours()}:${leftPad(dO.getMinutes())}`;
  }
};

exports.length = function (array) {
  return array.length;
};

exports.strColor = (str) => {
  if(!str) {
    return 'gray';
  }

  var hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash);
  }
  var colour = '#';
  for (let i = 0; i < 3; i++) {
    var value = (hash >> (i * 8)) & 0xFF;
    colour += ('00' + value.toString(16)).substr(-2);
  }
  return colour;
};

exports.smallestImage = (sizes) => {

  if (Array.isArray(sizes) && sizes.length > 0) {
    var minSize = sizes[0].size;
    var url = sizes[0].url;

    sizes.forEach((thisSize) => {
      if (thisSize.size < minSize) {
        minSize = thisSize.size;
        url = thisSize.url;
      }
    });

    return url;
  }
};

exports.getImageByWidth = (width, sizes) => {

  width = parseFloat(width);

  if (Array.isArray(sizes)) {
    sizes = sizes.filter((size) => size.type === 'IMAGE');
    if (sizes.length > 0) {

      var minDiff = 100000000000;
      var bestFit = sizes[0].url;
      sizes.forEach(s => {
        let currDiff = s.width - width;
        if (currDiff < 0) currDiff = currDiff * -8;

        if (currDiff < minDiff) {
          minDiff = currDiff;
          bestFit = s.url;
        }
      });

      return bestFit;
    }
  }
};

exports.getH264VideoByWidth = (width, sizes) => {

  width = parseFloat(width);

  if (Array.isArray(sizes)) {
    sizes = sizes.filter((size) => size.type === 'VIDEO' && size.url.endsWith('.mp4'));

    if (sizes.length > 0) {

      var minDiff = 100000000000;
      var bestFit = sizes[0].url;
      sizes.forEach(s => {
        let currDiff = s.width - width;
        if (currDiff < 0) currDiff = currDiff * -8;

        if (currDiff < minDiff) {
          minDiff = currDiff;
          bestFit = s.url;
        }
      });

      return bestFit;
    }
  }
};

exports.getWebmVideoByWidth = (width, sizes) => {

  width = parseFloat(width);

  if (Array.isArray(sizes)) {
    sizes = sizes.filter((size) => size.type === 'VIDEO' && size.url.endsWith('.webm'));

    if (sizes.length > 0) {

      var minDiff = 100000000000;
      var bestFit = sizes[0].url;
      sizes.forEach(s => {
        let currDiff = s.width - width;
        if (currDiff < 0) currDiff = currDiff * -8;

        if (currDiff < minDiff) {
          minDiff = currDiff;
          bestFit = s.url;
        }
      });

      return bestFit;
    }
  }
};

exports.round = (amount) => {
  return Math.round(parseFloat(amount)).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
};

exports.roundWithoutFormat = (amount) => {
  return Math.round(parseFloat(amount));
};

exports.addOne = (amount) => {
  return (parseFloat(amount) + 1);
};

exports.prettyLocation = (location) => {
  //Check if it exists.
  if (!location) return '';

  var locString = '';

  //Check for best Level
  if (location.hasOwnProperty('LOCALITY')) {
    locString = location.LOCALITY;
  } else if (location.hasOwnProperty('ADMINISTRATIVE_AREA_LEVEL_3')) {
    locString = location.ADMINISTRATIVE_AREA_LEVEL_3;
  } else if (location.hasOwnProperty('ADMINISTRATIVE_AREA_LEVEL_2')) {
    locString = location.ADMINISTRATIVE_AREA_LEVEL_2;
  } else if (location.hasOwnProperty('ADMINISTRATIVE_AREA_LEVEL_1')) {
    locString = location.ADMINISTRATIVE_AREA_LEVEL_1;
  }

  if (location.hasOwnProperty('COUNTRY')) {
    if (locString !== '') {
      locString += ', ';
    }
    locString += location.COUNTRY;
  }
  if (locString !== '') {
    locString = ' in ' + locString;
  }
  return locString;
};

exports.getSmallestAudio = (sizes) => {
  if (Array.isArray(sizes) && sizes.length > 0) {

    var filteredsizes = sizes.filter((size) => size.type === 'AUDIO').filter((s) => s.url.endsWith('.mp3'));

    if (filteredsizes.length === 0) {
      filteredsizes = sizes;
    }

    var minsize = 100000000000;
    var url = filteredsizes[0].url;

    filteredsizes.forEach(s => {
      if (s.size < minsize) {
        minsize = s.size;
        url = s.url;
      }
    });

    return url;
  }
};

exports.challengeHasProof = (status) => {
  if (status === 'WITH_PROOF' || status === 'PROOF_ACCEPTED') {
    return true;
  }

  return false;
};

exports.isOlderTenMinutes = (date, context) => {
  if ((new Date(date * 1000 + 10 * 60 * 1000).getTime() < new Date().getTime())) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};

exports.isNewerTenMinutes = (date, context) => {
  if ((new Date(date * 1000 + 10 * 60 * 1000).getTime() > new Date().getTime())) {
    return context.fn(this);
  } else {
    return context.inverse(this);
  }
};<|endoftext|>


<commit_before>request = require('request');
url = require('url');
colors = require('./googleCalendarColorService')
cheerio = require('cheerio');

function scrapeEvents(studentYear, programmeCode, callback) {
	time = new Date();
	time.setDate(time.getDate + 4)
	feritUrl = 'https://www.ferit.unios.hr/studenti/raspored-nastave-i-ispita/' +
		time.getFullYear() + '-' + (time.getMonth() + 1) +
		'-' + time.getDate() + '/' + studentYear + '-' + programmeCode;

	request.get(feritUrl, (error, response, body) => {
		//TODO: handle error
		const html = cheerio.load(body);
		let anchors = html('[target=_blank]')

		if (anchors == null || anchors == undefined) {
			return 'error with site';
		}

		anchors = anchors.toArray();

		anchors = anchors.reduce(
			(accumulator, currentValue) => {
				var href = currentValue.attribs.href;
				if (href.includes('calendar')) {
					var url_parts = url.parse(href, true);
					var query = url_parts.query;
					query = renameProperties(query)
					query = splitDates(query)
					query = parseColor(query)
					delete query.action;
					accumulator.push(query)

					return accumulator;
				}
				return accumulator;
			}, []);

		return callback(anchors);
	});

}

function scrapeEventsAsync(studentYear, programmeCode) {
	return new Promise((resolve, reject) => {
		time = new Date();
		time.setDate(time.getDate() + 4)
		feritUrl = 'https://www.ferit.unios.hr/studenti/raspored-nastave-i-ispita/' +
			time.getFullYear() + '-' + (time.getMonth() + 1) +
			'-' + time.getDate() + '/' + studentYear + '-' + programmeCode;

		request.get(feritUrl, (error, response, body) => {

			if (error !== null) return reject(error);

			const html = cheerio.load(body);
			let anchors = html("[href*='calendar']");
			if (anchors == null || anchors == undefined || anchors.length == 0) {
				if(feritUrl.endsWith('2-52')){
					return reject('automobilsko jos nema drugu godinu :p')
				}
				return reject(new Error('Site is not loading properly or design has changed, url=' +feritUrl));
			}

			anchors = anchors.toArray();

			//use array map, not reduce,the if statement is no longer needed since we use a better query selector
			anchors = anchors.reduce(
				(accumulator, currentValue) => {
					var href = currentValue.attribs.href;
					if (href.includes('calendar')) {
						var url_parts = url.parse(href, true);
						var query = url_parts.query;
						query = renameProperties(query)
						query = splitDates(query)
						query = parseColor(query)
						delete query.action;
						accumulator.push(query)

						return accumulator;
					}
					return accumulator;
				}, []);

			return resolve(anchors);
		});

	});
}
// properties have wrong names, therefore we have to rename them
function renameProperties(query) {
	Object.defineProperty(query, 'summary',
		Object.getOwnPropertyDescriptor(query, 'text'));
	delete query['text'];

	Object.defineProperty(query, 'description',
		Object.getOwnPropertyDescriptor(query, 'details'));
	delete query['details'];

	return query;
}

function splitDates(query) {
	dates = query.dates.split("/")
	//console.log(dates[0].substring(4, 6))
	//console.log(dates[1].substring(4, 6))
	startDate = new Date(dates[0].substring(0, 4), parseInt(dates[0].substring(4, 6)) - 1, dates[0].substring(6, 8), parseInt(dates[0].substring(9, 11)) + 1, dates[0].substring(11, 13), dates[0].substring(13, 15));
	endDate   = new Date(dates[1].substring(0, 4), parseInt(dates[1].substring(4, 6)) - 1, dates[1].substring(6, 8), parseInt(dates[1].substring(9, 11)) + 1, dates[1].substring(11, 13), dates[1].substring(13, 15));

	//console.log(startDate);
	query.start = { 'dateTime': startDate }
	query.end = { 'dateTime': endDate }
	delete query.dates

	return query;
}

function parseColor(query) {
	if (query.description.includes('Kontrolna zadaća')) {
		query.colorId = colors.kontrolna_zadaca;
	}

	//handle potential wrong function call order
	if (Object.prototype.hasOwnProperty.call(query, 'text')) {
		query = renameProperties(query)
	}

	if (query.summary.includes('predavanja')) {
		query.colorId = colors.predavanja;
	} else if (query.summary.includes('auditorne')) {
		query.colorId = colors.auditorne;
	} else if (query.summary.includes('laboratorijske')) {
		query.colorId = colors.laboratorijske;
	} else if (query.summary.includes('konstrukcijske')) {
		query.colorId = colors.konstrukcijske;
	} else if (query.summary.includes('ispitni rok')) {
		query.colorId = colors.ispitni_rok;
	} else if (query.summary.includes('Tjelesna')) {
		query.colorId = colors.ispitni_rok;
	}
	return query;
}

module.exports.scrapeEvents = scrapeEvents;
module.exports.scrapeEventsAsync = scrapeEventsAsync;
<commit_msg>tjelesni color fix<commit_after>request = require('request');
url = require('url');
colors = require('./googleCalendarColorService')
cheerio = require('cheerio');

function scrapeEvents(studentYear, programmeCode, callback) {
	time = new Date();
	time.setDate(time.getDate + 4)
	feritUrl = 'https://www.ferit.unios.hr/studenti/raspored-nastave-i-ispita/' +
		time.getFullYear() + '-' + (time.getMonth() + 1) +
		'-' + time.getDate() + '/' + studentYear + '-' + programmeCode;

	request.get(feritUrl, (error, response, body) => {
		//TODO: handle error
		const html = cheerio.load(body);
		let anchors = html('[target=_blank]')

		if (anchors == null || anchors == undefined) {
			return 'error with site';
		}

		anchors = anchors.toArray();

		anchors = anchors.reduce(
			(accumulator, currentValue) => {
				var href = currentValue.attribs.href;
				if (href.includes('calendar')) {
					var url_parts = url.parse(href, true);
					var query = url_parts.query;
					query = renameProperties(query)
					query = splitDates(query)
					query = parseColor(query)
					delete query.action;
					accumulator.push(query)

					return accumulator;
				}
				return accumulator;
			}, []);

		return callback(anchors);
	});

}

function scrapeEventsAsync(studentYear, programmeCode) {
	return new Promise((resolve, reject) => {
		time = new Date();
		time.setDate(time.getDate() + 4)
		feritUrl = 'https://www.ferit.unios.hr/studenti/raspored-nastave-i-ispita/' +
			time.getFullYear() + '-' + (time.getMonth() + 1) +
			'-' + time.getDate() + '/' + studentYear + '-' + programmeCode;

		request.get(feritUrl, (error, response, body) => {

			if (error !== null) return reject(error);

			const html = cheerio.load(body);
			let anchors = html("[href*='calendar']");
			if (anchors == null || anchors == undefined || anchors.length == 0) {
				if(feritUrl.endsWith('2-52')){
					return reject('automobilsko jos nema drugu godinu :p')
				}
				return reject(new Error('Site is not loading properly or design has changed, url=' +feritUrl));
			}

			anchors = anchors.toArray();

			//use array map, not reduce,the if statement is no longer needed since we use a better query selector
			anchors = anchors.reduce(
				(accumulator, currentValue) => {
					var href = currentValue.attribs.href;
					if (href.includes('calendar')) {
						var url_parts = url.parse(href, true);
						var query = url_parts.query;
						query = renameProperties(query)
						query = splitDates(query)
						query = parseColor(query)
						delete query.action;
						accumulator.push(query)

						return accumulator;
					}
					return accumulator;
				}, []);

			return resolve(anchors);
		});

	});
}
// properties have wrong names, therefore we have to rename them
function renameProperties(query) {
	Object.defineProperty(query, 'summary',
		Object.getOwnPropertyDescriptor(query, 'text'));
	delete query['text'];

	Object.defineProperty(query, 'description',
		Object.getOwnPropertyDescriptor(query, 'details'));
	delete query['details'];

	return query;
}

function splitDates(query) {
	dates = query.dates.split("/")
	//console.log(dates[0].substring(4, 6))
	//console.log(dates[1].substring(4, 6))
	startDate = new Date(dates[0].substring(0, 4), parseInt(dates[0].substring(4, 6)) - 1, dates[0].substring(6, 8), parseInt(dates[0].substring(9, 11)) + 1, dates[0].substring(11, 13), dates[0].substring(13, 15));
	endDate   = new Date(dates[1].substring(0, 4), parseInt(dates[1].substring(4, 6)) - 1, dates[1].substring(6, 8), parseInt(dates[1].substring(9, 11)) + 1, dates[1].substring(11, 13), dates[1].substring(13, 15));

	//console.log(startDate);
	query.start = { 'dateTime': startDate }
	query.end = { 'dateTime': endDate }
	delete query.dates

	return query;
}

function parseColor(query) {
	if (query.description.includes('Kontrolna zadaća')) {
		query.colorId = colors.kontrolna_zadaca;
	}

	//handle potential wrong function call order
	if (Object.prototype.hasOwnProperty.call(query, 'text')) {
		query = renameProperties(query)
	}

	if (query.summary.includes('predavanja')) {
		query.colorId = colors.predavanja;
	} else if (query.summary.includes('auditorne')) {
		query.colorId = colors.auditorne;
	} else if (query.summary.includes('laboratorijske')) {
		query.colorId = colors.laboratorijske;
	} else if (query.summary.includes('konstrukcijske')) {
		query.colorId = colors.konstrukcijske;
	} else if (query.summary.includes('ispitni rok')) {
		query.colorId = colors.ispitni_rok;
	} else if (query.summary.includes('Tjelesna')) {
		query.colorId = colors.tjelesni;
	}
	return query;
}

module.exports.scrapeEvents = scrapeEvents;
module.exports.scrapeEventsAsync = scrapeEventsAsync;
<|endoftext|>


<commit_before>/**
 * Developer: Stepan Burguchev
 * Date: 8/4/2015
 * Copyright: 2009-2015 Comindware®
 *       All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Comindware
 *       The copyright notice above does not evidence any
 *       actual or intended publication of such source code.
 */

/* global define, require, Handlebars, Backbone, Marionette, $, _, Localizer */

//noinspection ThisExpressionReferencesGlobalObjectJS
(function (global) {
    'use strict';

    define([ 'module/lib', 'localizationMap' ], function (lib) {
        var defaultLangCode = 'en';
        var langCode = global.langCode;
        var localizationMap = global['LANGMAP' + langCode.toUpperCase()];

        global.Localizer = {
            langCode: langCode,

            get: function (locId) {
                var text = localizationMap[locId];
                if (text === undefined) {
                    throw new Error('Failed to find localization constant ' + locId);
                }
                return text;
            },

            resolveLocalizedText: function (localizedText) {
                if (!localizedText) {
                    return '';
                }

                return localizedText[langCode] || localizedText[defaultLangCode] || '';
            }
        };
        return global.Localizer;
    });
}(this));
<commit_msg>fixed a lot of localization bugs<commit_after>/**
 * Developer: Stepan Burguchev
 * Date: 8/4/2015
 * Copyright: 2009-2015 Comindware®
 *       All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Comindware
 *       The copyright notice above does not evidence any
 *       actual or intended publication of such source code.
 */

/* global define, require, Handlebars, Backbone, Marionette, $, _, Localizer */

//noinspection ThisExpressionReferencesGlobalObjectJS
(function (global) {
    'use strict';

    define([ 'module/lib', 'localizationMap' ], function (lib) {
        var defaultLangCode = 'en';
        var langCode = global.langCode;
        var localizationMap = global['LANGMAP' + langCode.toUpperCase()];

        global.Localizer = {
            langCode: langCode,

            get: function (locId) {
                if (!locId) {
                    throw new Error('Bad localization id: (locId = ' + locId + ')');
                }
                var text = localizationMap[locId];
                if (text === undefined) {
                    throw new Error('Failed to find localization constant ' + locId);
                }
                return text;
            },

            resolveLocalizedText: function (localizedText) {
                if (!localizedText) {
                    return '';
                }

                return localizedText[langCode] || localizedText[defaultLangCode] || '';
            }
        };
        return global.Localizer;
    });
}(this));
<|endoftext|>


<commit_before>const R = require('ramda');
const {
  ifNotAdmin,
  inClauseOr,
  knex,
  prepare,
  query,
  whereAnd,
  isPatchEmpty,
} = require('./utils');

// This contains the sql query generation logic
const Sql = {
  updateProject: ({ permissions: { projects } }, { id, patch }) =>
    knex('project')
      .where('id', '=', id)
      .whereIn('id', projects)
      .update(patch)
      .toString(),
  selectProject: id =>
    knex('project')
      .where('id', id)
      .toString(),
  selectProjectIdByName: name =>
    knex('project')
      .where('name', name)
      .select('id')
      .toString(),
  selectProjectIdsByCustomerIds: customerIds =>
    knex('project')
      .select('id')
      .whereIn('customer', customerIds)
      .toString(),
};

const Helpers = {
  getProjectById: async (sqlClient, id) => {
    const rows = await query(sqlClient, Sql.selectProject(id));
    return R.prop(0, rows);
  },
  getProjectIdByName: async (sqlClient, name) => {
    const pidResult = await query(sqlClient, Sql.selectProjectIdByName(name));

    const amount = R.length(pidResult);
    if (amount > 1) {
      throw new Error(
        `Multiple project candidates for '${name}' (${amount} found). Do nothing.`,
      );
    }

    if (amount === 0) {
      throw new Error(`Not found: '${name}'`);
    }

    const pid = R.path(['0', 'id'], pidResult);

    return pid;
  },
  getProjectIdsByCustomerIds: async (sqlClient, customerIds) =>
    query(sqlClient, Sql.selectProjectIdsByCustomerIds(customerIds)),
};

const getAllProjects = ({ sqlClient }) => async (cred, args) => {
  const { customers, projects } = cred.permissions;
  // We need one "WHERE" keyword, but we have multiple optional conditions
  const where = whereAnd([
    args.createdAfter ? 'created >= :created_after' : '',
    args.gitUrl ? 'git_url = :git_url' : '',
    ifNotAdmin(
      cred.role,
      `(${inClauseOr([['customer', customers], ['project.id', projects]])})`,
    ),
  ]);

  const prep = prepare(sqlClient, `SELECT * FROM project ${where}`);
  const rows = await query(sqlClient, prep(args));

  return rows;
};

const getProjectByEnvironmentId = ({ sqlClient }) => async (cred, eid) => {
  const { customers, projects } = cred.permissions;
  const prep = prepare(
    sqlClient,
    `SELECT
        p.*
      FROM environment e
      JOIN project p ON e.project = p.id
      WHERE e.id = :eid
      ${ifNotAdmin(
    cred.role,
    `AND (${inClauseOr([['p.customer', customers], ['p.id', projects]])})`,
  )}
      LIMIT 1
    `,
  );

  const rows = await query(sqlClient, prep({ eid }));

  return rows ? rows[0] : null;
};

const getProjectByGitUrl = ({ sqlClient }) => async (cred, args) => {
  const { customers, projects } = cred.permissions;
  const str = `
      SELECT
        *
      FROM project
      WHERE git_url = :git_url
      ${ifNotAdmin(
    cred.role,
    `AND (${inClauseOr([
      ['customer', customers],
      ['project.id', projects],
    ])})`,
  )}
      LIMIT 1
    `;

  const prep = prepare(sqlClient, str);
  const rows = await query(sqlClient, prep(args));

  return rows ? rows[0] : null;
};

const getProjectByName = ({ sqlClient }) => async (cred, args) => {
  const { customers, projects } = cred.permissions;
  const str = `
      SELECT
        *
      FROM project
      WHERE name = :name
      ${ifNotAdmin(
    cred.role,
    `AND (${inClauseOr([
      ['customer', customers],
      ['project.id', projects],
    ])})`,
  )}
    `;

  const prep = prepare(sqlClient, str);

  const rows = await query(sqlClient, prep(args));
  return rows[0];
};

const addProject = ({ sqlClient, keycloakClient }) => async (cred, input) => {
  const { customers } = cred.permissions;
  const cid = input.customer.toString();

  if (cred.role !== 'admin' && !R.contains(cid, customers)) {
    throw new Error('Project creation unauthorized.');
  }

  const prep = prepare(
    sqlClient,
    `CALL CreateProject(
        :id,
        :name,
        :customer,
        :git_url,
        ${input.subfolder ? ':subfolder' : 'NULL'},
        :openshift,
        ${
  input.openshiftProjectPattern ? ':openshift_project_pattern' : 'NULL'
},
        ${
  input.activeSystemsDeploy
    ? ':active_systems_deploy'
    : '"lagoon_openshiftBuildDeploy"'
},
        ${
  input.activeSystemsPromote
    ? ':active_systems_promote'
    : '"lagoon_openshiftBuildDeploy"'
},
        ${
  input.activeSystemsRemove
    ? ':active_systems_remove'
    : '"lagoon_openshiftRemove"'
},
        ${input.branches ? ':branches' : '"true"'},
        ${input.pullrequests ? ':pullrequests' : '"true"'},
        ${input.productionEnvironment ? ':production_environment' : 'NULL'},
        ${input.autoIdle ? ':auto_idle' : '1'},
        ${input.storageCalc ? ':storage_calc' : '1'}
      );
    `,
  );

  const rows = await query(sqlClient, prep(input));
  const project = R.path([0, 0], rows);

  // Create a group in Keycloak named the same as the project
  await keycloakClient.groups.create(R.pick(['name'], project));

  return project;
};

const deleteProject = ({ sqlClient }) => async (cred, input) => {
  const { projects } = cred.permissions;

  // Will throw on invalid conditions
  const pid = await Helpers.getProjectIdByName(sqlClient, input.project);

  if (cred.role !== 'admin') {
    if (!R.contains(pid, projects)) {
      throw new Error('Unauthorized.');
    }
  }

  const prep = prepare(sqlClient, 'CALL DeleteProject(:project)');
  await query(sqlClient, prep(input));

  // TODO: maybe check rows for changed result
  return 'success';
};

const updateProject = ({ sqlClient }) => async (cred, input) => {
  const { projects } = cred.permissions;
  const pid = input.id.toString();

  if (cred.role !== 'admin' && !R.contains(pid, projects)) {
    throw new Error('Unauthorized');
  }

  if (isPatchEmpty(input)) {
    throw new Error('input.patch requires at least 1 attribute');
  }

  await query(sqlClient, Sql.updateProject(cred, input));
  return Helpers.getProjectById(pid);
};

module.exports = {
  Sql,
  Queries: {
    deleteProject,
    addProject,
    getProjectByName,
    getProjectByGitUrl,
    getProjectByEnvironmentId,
    getAllProjects,
    updateProject,
  },
  Helpers,
};
<commit_msg>Log warning when Keycloak group already exists<commit_after>const R = require('ramda');
const logger = require('../logger');
const {
  ifNotAdmin,
  inClauseOr,
  knex,
  prepare,
  query,
  whereAnd,
  isPatchEmpty,
} = require('./utils');

// This contains the sql query generation logic
const Sql = {
  updateProject: ({ permissions: { projects } }, { id, patch }) =>
    knex('project')
      .where('id', '=', id)
      .whereIn('id', projects)
      .update(patch)
      .toString(),
  selectProject: id =>
    knex('project')
      .where('id', id)
      .toString(),
  selectProjectIdByName: name =>
    knex('project')
      .where('name', name)
      .select('id')
      .toString(),
  selectProjectIdsByCustomerIds: customerIds =>
    knex('project')
      .select('id')
      .whereIn('customer', customerIds)
      .toString(),
};

const Helpers = {
  getProjectById: async (sqlClient, id) => {
    const rows = await query(sqlClient, Sql.selectProject(id));
    return R.prop(0, rows);
  },
  getProjectIdByName: async (sqlClient, name) => {
    const pidResult = await query(sqlClient, Sql.selectProjectIdByName(name));

    const amount = R.length(pidResult);
    if (amount > 1) {
      throw new Error(
        `Multiple project candidates for '${name}' (${amount} found). Do nothing.`,
      );
    }

    if (amount === 0) {
      throw new Error(`Not found: '${name}'`);
    }

    const pid = R.path(['0', 'id'], pidResult);

    return pid;
  },
  getProjectIdsByCustomerIds: async (sqlClient, customerIds) =>
    query(sqlClient, Sql.selectProjectIdsByCustomerIds(customerIds)),
};

const getAllProjects = ({ sqlClient }) => async (cred, args) => {
  const { customers, projects } = cred.permissions;
  // We need one "WHERE" keyword, but we have multiple optional conditions
  const where = whereAnd([
    args.createdAfter ? 'created >= :created_after' : '',
    args.gitUrl ? 'git_url = :git_url' : '',
    ifNotAdmin(
      cred.role,
      `(${inClauseOr([['customer', customers], ['project.id', projects]])})`,
    ),
  ]);

  const prep = prepare(sqlClient, `SELECT * FROM project ${where}`);
  const rows = await query(sqlClient, prep(args));

  return rows;
};

const getProjectByEnvironmentId = ({ sqlClient }) => async (cred, eid) => {
  const { customers, projects } = cred.permissions;
  const prep = prepare(
    sqlClient,
    `SELECT
        p.*
      FROM environment e
      JOIN project p ON e.project = p.id
      WHERE e.id = :eid
      ${ifNotAdmin(
    cred.role,
    `AND (${inClauseOr([['p.customer', customers], ['p.id', projects]])})`,
  )}
      LIMIT 1
    `,
  );

  const rows = await query(sqlClient, prep({ eid }));

  return rows ? rows[0] : null;
};

const getProjectByGitUrl = ({ sqlClient }) => async (cred, args) => {
  const { customers, projects } = cred.permissions;
  const str = `
      SELECT
        *
      FROM project
      WHERE git_url = :git_url
      ${ifNotAdmin(
    cred.role,
    `AND (${inClauseOr([
      ['customer', customers],
      ['project.id', projects],
    ])})`,
  )}
      LIMIT 1
    `;

  const prep = prepare(sqlClient, str);
  const rows = await query(sqlClient, prep(args));

  return rows ? rows[0] : null;
};

const getProjectByName = ({ sqlClient }) => async (cred, args) => {
  const { customers, projects } = cred.permissions;
  const str = `
      SELECT
        *
      FROM project
      WHERE name = :name
      ${ifNotAdmin(
    cred.role,
    `AND (${inClauseOr([
      ['customer', customers],
      ['project.id', projects],
    ])})`,
  )}
    `;

  const prep = prepare(sqlClient, str);

  const rows = await query(sqlClient, prep(args));
  return rows[0];
};

const addProject = ({ sqlClient, keycloakClient }) => async (cred, input) => {
  const { customers } = cred.permissions;
  const cid = input.customer.toString();

  if (cred.role !== 'admin' && !R.contains(cid, customers)) {
    throw new Error('Project creation unauthorized.');
  }

  const prep = prepare(
    sqlClient,
    `CALL CreateProject(
        :id,
        :name,
        :customer,
        :git_url,
        ${input.subfolder ? ':subfolder' : 'NULL'},
        :openshift,
        ${
  input.openshiftProjectPattern ? ':openshift_project_pattern' : 'NULL'
},
        ${
  input.activeSystemsDeploy
    ? ':active_systems_deploy'
    : '"lagoon_openshiftBuildDeploy"'
},
        ${
  input.activeSystemsPromote
    ? ':active_systems_promote'
    : '"lagoon_openshiftBuildDeploy"'
},
        ${
  input.activeSystemsRemove
    ? ':active_systems_remove'
    : '"lagoon_openshiftRemove"'
},
        ${input.branches ? ':branches' : '"true"'},
        ${input.pullrequests ? ':pullrequests' : '"true"'},
        ${input.productionEnvironment ? ':production_environment' : 'NULL'},
        ${input.autoIdle ? ':auto_idle' : '1'},
        ${input.storageCalc ? ':storage_calc' : '1'}
      );
    `,
  );

  const rows = await query(sqlClient, prep(input));
  const project = R.path([0, 0], rows);

  try {
    // Create a group in Keycloak named the same as the project
    await keycloakClient.groups.create({
      // Create the group in the `lagoon` realm.
      // TODO: Switch out if the `keycloak-admin` PR to override config gets merged https://github.com/Canner/keycloak-admin/pull/4
      realm: 'lagoon',
      name: R.prop('name', project),
    });
  } catch (err) {
    if (err.response.status === 409) {
      logger.warn(
        `Failed to create already existing Keycloak group "${R.prop(
          'name',
          project,
        )}"`,
      );
    } else {
      throw err;
    }
  }

  return project;
};

const deleteProject = ({ sqlClient }) => async (cred, input) => {
  const { projects } = cred.permissions;

  // Will throw on invalid conditions
  const pid = await Helpers.getProjectIdByName(sqlClient, input.project);

  if (cred.role !== 'admin') {
    if (!R.contains(pid, projects)) {
      throw new Error('Unauthorized.');
    }
  }

  const prep = prepare(sqlClient, 'CALL DeleteProject(:project)');
  await query(sqlClient, prep(input));

  // TODO: maybe check rows for changed result
  return 'success';
};

const updateProject = ({ sqlClient }) => async (cred, input) => {
  const { projects } = cred.permissions;
  const pid = input.id.toString();

  if (cred.role !== 'admin' && !R.contains(pid, projects)) {
    throw new Error('Unauthorized');
  }

  if (isPatchEmpty(input)) {
    throw new Error('input.patch requires at least 1 attribute');
  }

  await query(sqlClient, Sql.updateProject(cred, input));
  return Helpers.getProjectById(pid);
};

module.exports = {
  Sql,
  Queries: {
    deleteProject,
    addProject,
    getProjectByName,
    getProjectByGitUrl,
    getProjectByEnvironmentId,
    getAllProjects,
    updateProject,
  },
  Helpers,
};
<|endoftext|>


<commit_before>const webpack = require('webpack');
const path = require('path');

// noinspection JSUnresolvedVariable
module.exports = Object.assign({}, require('./webpack.base.js'), {
  target: 'web',
  entry: {
    app: [
      './app/client.js',
    ],
  },
  output: {
    path: path.join(__dirname, '../public/build'),
    publicPath: '/build/',
    filename: '[name].js',
  },
  plugins: [
    new webpack.DefinePlugin({__CLIENT__: true, __SERVER__: false, __PRODUCTION__: true, __DEV__: false}),
    new webpack.DefinePlugin({'process.env': {NODE_ENV: '"development"'}}),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.LimitChunkCountPlugin({maxChunks: 20}),
  ],
});
<commit_msg>Minimize client js (uglify)<commit_after>const webpack = require('webpack');
const path = require('path');

// noinspection JSUnresolvedVariable
module.exports = Object.assign({}, require('./webpack.base.js'), {
  target: 'web',
  entry: {
    app: [
      './app/client.js',
    ],
  },
  output: {
    path: path.join(__dirname, '../public/build'),
    publicPath: '/build/',
    filename: '[name].js',
  },
  plugins: [
    new webpack.DefinePlugin({__CLIENT__: true, __SERVER__: false, __PRODUCTION__: true, __DEV__: false}),
    new webpack.DefinePlugin({'process.env': {NODE_ENV: '"development"'}}),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.LimitChunkCountPlugin({maxChunks: 20}),
    new webpack.optimize.UglifyJsPlugin({ minimize: true }),
  ],
});
<|endoftext|>


<commit_before>
var logger = require('../logging').getLogger(__LOGGER__);
var {PAGE_CSS_NODE_ID} = require('../constants');
var Q = require('q');
var PageUtil = require('./PageUtil')

var loadedCss = {};

module.exports = {

	registerPageLoad: function registerPageLoad() {
		if (SERVER_SIDE) {
			throw new Error("ClientCssHelper.registerPageLoad can't be called server-side");
		}
		const urlBase = location.protocol + "//" + location.host;

		// for each css node in the head that the react-server server wrote to the response, note it down in the cache, so that
		// we can remove it on a page to page transition.
		var serverWrittenLinkNodes = document.head.querySelectorAll(`link[${PAGE_CSS_NODE_ID}],style[${PAGE_CSS_NODE_ID}]`);
		for (var i = 0; i < serverWrittenLinkNodes.length; i++) {
			var key, styleNode = serverWrittenLinkNodes[i];
			if (styleNode.href) {
				key = styleNode.href;

				// The browser will give us a full URL even if we only put a
				// path in on the server.  So, if we're comparing against just
				// a path here we need to strip the base off to avoid a flash
				// of unstyled content.
				if (key.indexOf(urlBase) === 0) {
					key = key.substr(urlBase.length);
				}
			} else {
				key = styleNode.innerHTML;
			}
			loadedCss[key] = styleNode;
		}
	},

	ensureCss: function ensureCss(routeName, pageObject) {
		if (SERVER_SIDE) {
			throw new Error("ClientCssHelper.registerPageLoad can't be called server-side");
		}

		return Q.all(PageUtil.standardizeStyles(pageObject.getHeadStylesheets())).then(newCss => {
			var newCssByKey = {};
			newCss
				.filter(style => !!style)
				.forEach(style => {newCssByKey[this._keyFromStyleSheet(style)] = style});

			// first, remove the unneeded CSS link elements.
			Object.keys(loadedCss).forEach(loadedCssKey => {

				if (!newCssByKey[loadedCssKey]) {
					// remove the corresponding node from the DOM.
					logger.debug("Removing stylesheet: " + loadedCssKey);
					var node = loadedCss[loadedCssKey];
					node.parentNode.removeChild(node);
					delete loadedCss[loadedCssKey];
				}
			});

			// next add the style URLs that weren't already loaded.
			return Q.all(Object.keys(newCssByKey).map(newCssKey => {
				var retval;
				if (!loadedCss[newCssKey]) {
					// this means that the CSS is not currently present in the
					// document, so we need to add it.
					logger.debug("Adding stylesheet: " + newCssKey);

					var style = newCssByKey[newCssKey];
					var styleTag;

					if (style.href) {
						styleTag = document.createElement('link');
						styleTag.rel = 'stylesheet';
						styleTag.href = style.href;

						// If we _can_ wait for the CSS to be loaded before
						// proceeding, let's do so.
						if ('onload' in styleTag) {
							var dfd = Q.defer();
							styleTag.onload = dfd.resolve;
							retval = dfd.promise;
						}
					} else {
						styleTag = document.createElement('style');
						styleTag.innerHTML = style.text;
					}
					styleTag.type = style.type;
					styleTag.media = style.media;

					loadedCss[newCssKey] = styleTag;
					document.head.appendChild(styleTag);
				} else {
					logger.debug(`Stylesheet already loaded (no-op): ${newCssKey}`);
				}
				return retval;
			}));
		});
	},

	_keyFromStyleSheet: function(style) {
		return style.href || style.text;
	},
}
<commit_msg>Prevent another type of flash of unstyled content (#501)<commit_after>
var logger = require('../logging').getLogger(__LOGGER__);
var {PAGE_CSS_NODE_ID} = require('../constants');
var Q = require('q');
var PageUtil = require('./PageUtil')

var loadedCss = {};

module.exports = {

	registerPageLoad: function registerPageLoad() {
		if (SERVER_SIDE) {
			throw new Error("ClientCssHelper.registerPageLoad can't be called server-side");
		}

		// for each css node in the head that the react-server server wrote to the response, note it down in the cache, so that
		// we can remove it on a page to page transition.
		var serverWrittenLinkNodes = document.head.querySelectorAll(`link[${PAGE_CSS_NODE_ID}],style[${PAGE_CSS_NODE_ID}]`);
		for (var i = 0; i < serverWrittenLinkNodes.length; i++) {
			var key, styleNode = serverWrittenLinkNodes[i];
			if (styleNode.href) {
				key = normalizeLocalUrl(styleNode.href);
			} else {
				key = styleNode.innerHTML;
			}
			loadedCss[key] = styleNode;
		}
	},

	ensureCss: function ensureCss(routeName, pageObject) {
		if (SERVER_SIDE) {
			throw new Error("ClientCssHelper.registerPageLoad can't be called server-side");
		}

		return Q.all(PageUtil.standardizeStyles(pageObject.getHeadStylesheets())).then(newCss => {
			var newCssByKey = {};
			newCss
				.filter(style => !!style)
				.forEach(style => {newCssByKey[this._keyFromStyleSheet(style)] = style});

			// first, remove the unneeded CSS link elements.
			Object.keys(loadedCss).forEach(loadedCssKey => {

				if (!newCssByKey[loadedCssKey]) {
					// remove the corresponding node from the DOM.
					logger.debug("Removing stylesheet: " + loadedCssKey);
					var node = loadedCss[loadedCssKey];
					node.parentNode.removeChild(node);
					delete loadedCss[loadedCssKey];
				}
			});

			// next add the style URLs that weren't already loaded.
			return Q.all(Object.keys(newCssByKey).map(newCssKey => {
				var retval;
				if (!loadedCss[newCssKey]) {
					// this means that the CSS is not currently present in the
					// document, so we need to add it.
					logger.debug("Adding stylesheet: " + newCssKey);

					var style = newCssByKey[newCssKey];
					var styleTag;

					if (style.href) {
						styleTag = document.createElement('link');
						styleTag.rel = 'stylesheet';
						styleTag.href = style.href;

						// If we _can_ wait for the CSS to be loaded before
						// proceeding, let's do so.
						if ('onload' in styleTag) {
							var dfd = Q.defer();
							styleTag.onload = dfd.resolve;
							retval = dfd.promise;
						}
					} else {
						styleTag = document.createElement('style');
						styleTag.innerHTML = style.text;
					}
					styleTag.type = style.type;
					styleTag.media = style.media;

					loadedCss[newCssKey] = styleTag;
					document.head.appendChild(styleTag);
				} else {
					logger.debug(`Stylesheet already loaded (no-op): ${newCssKey}`);
				}
				return retval;
			}));
		});
	},

	_keyFromStyleSheet: function(style) {
		return normalizeLocalUrl(style.href) || style.text;
	},
}

function normalizeLocalUrl(url) {
	const urlBase = location.protocol + "//" + location.host;

	// The browser will give us a full URL even if we only put a
	// path in on the server.  So, if we're comparing against just
	// a path here we need to strip the base off to avoid a flash
	// of unstyled content.
	if (url && url.indexOf(urlBase) === 0) {
		url = url.substr(urlBase.length);
	}

	return url;
}
<|endoftext|>


<commit_before>import {React, Component, PropTypes} from '../imports';
import {CookieStorage} from './storage/cookie-storage';
import {LocalStorage} from './storage/local-storage';
import {Authorization} from './authorization';
import {WSDropdown} from '../ws-dropdown/ws-dropdown';

/**
 * This component renders a generic header which provides authentication and language management
 *
 * Optionally call WSHeader.setStorageType('cookie', 'zalando') If you want a to use cookies instead of localStorage
 * to persist the tokens. You can call WSHeader.getAccessToken().then(token => ...) to get the current access token.
 * It will resolve null when no access token is present and therefore the user isn't logged in.
 * @property {Object} props React properties object
 * @property {string} props.loginUrl property used to set the application login url
 * @property {string} props.businessPartnerId property used to set the application businessPartnerId
 * @property {string} props.clientId property used to set the application yourturn clientId
 * @property {Array<Object>} props.links property used to set the list of links with multiple levels
 * @property {string} props.appName property used to set the application name
 * @property {string} props.appLogo property used to set the application image logo
 * @property {string} props.rootUrl property used to set the root application url
 * @property {Boolean} props.showLocale Flag used to show the locale dropdown
 * @property {Boolean} props.showAuthorization Flag to show the login area
 * @property {Function} props.onLocaleChange Function used to propagate data
 * @property {Function} props.onAuthChange Function used to propagate data
 */
export class WSHeader extends Component {

  /**
   * @type {Authorization}
   */
  static authorization = undefined;

  /**
   * Default storage instance
   * @type {AbstractStorage}
   */
  static storage = new LocalStorage();

  static defaultProps = {
    loginUrl: 'https://identity.zalando.com/oauth2/authorize',
    businessPartnerId: null,
    clientId: null,
    links: [],
    appName: 'Zalando',
    appLogo: null,
    rootUrl: '#',
    showLocale: true,
    showAuthorization: true,
    onLocaleChange: () => {},
    onAuthChange: () => {}
  };

  static propTypes = {
    loginUrl: PropTypes.string,
    businessPartnerId: PropTypes.string,
    clientId: PropTypes.string,
    links: PropTypes.array,
    appName: PropTypes.string,
    appLogo: PropTypes.string,
    onLocaleChange: PropTypes.func,
    onAuthChange: PropTypes.func,
    rootUrl: PropTypes.string,
    showLocale: PropTypes.bool,
    showAuthorization: PropTypes.bool
  };

  /**
   * Initialize the storage
   * @param {string} type Can be either cookie or local
   * @param {string} name Storage name will be used as key prefix
   * @returns {void}
   */
  static setStorageType(type, name) {
    // Create storage for persisting keys
    if (type === 'cookie') {
      this.storage = new CookieStorage(name);
    } else {
      this.storage = new LocalStorage(name);
    }
  }

  /**
   * Tries to get the access token from authorization class
   * @param {string} queryString The current query string to parse the token from
   * @returns {string|null}
   */
  static getAccessToken(queryString = location.hash.substr(1)) {
    this.authorization = this.authorization || new Authorization(this.storage);
    if (!this.authorization.accessToken) {
      this.authorization.tryFetchToken(queryString);
    }
    return this.authorization.accessToken;
  }

  /**
   * Unauthorize will remove the access token from storage
   * @returns {void}
   */
  static removeAccessToken() {
    this.authorization = this.authorization || new Authorization(this.storage);
    this.authorization.unauthorize();
  }

  /**
   * Get abbreviation for the user the access token is issued for
   * @returns {string|null}
   */
  static getUserAbbreviation() {
    try {
      const token = this.getAccessToken();
      const parts = token.split('.');
      const json = JSON.parse(atob(parts[1]));
      // Find key which contains the name
      const nameKey = Object.keys(json).find(key => key.includes('managed-id'));
      return json[nameKey];
    } catch (e) {
      return null;
    }
  }

  /**
   * Retrieve the persisted locale
   * @returns {string}
   */
  static getLocale() {
    const locale = WSHeader.storage.get('locale') || window.navigator.language.replace(/([a-z]+)-.*/, '$1');
    if (['de', 'en'].indexOf(locale) === -1) {
      return 'en';
    }
    return locale;
  }

  /**
   * @param {Object} props React/Preact properties
   */
  constructor(props) {
    super(props);
    this.initState();
    this.initAuthorization(props);
    this.mounted = false;
    this.locales = [
      {label: 'German', value: 'de'},
      {label: 'English', value: 'en'}
    ];
    this.subMenus = [];
    this.menuItems = [];
    this.level2 = null;
  }

  /**
   * Detect mounted state to prevent calling setState to early
   * @returns {void}
   */
  componentDidMount() {
    this.mounted = true;
  }

  /**
   * Changes the locale to the given one
   * @param {string} newLocale The new locale
   * @returns {void}
   */
  setLocale(newLocale) {
    this.setState({locale: newLocale});
    WSHeader.storage.set('locale', newLocale);
    this.dispatchEvent('ws-locale-changed', newLocale);
    // Propagate value in React world
    if (typeof this.props.onLocaleChange === 'function') {
      this.props.onLocaleChange(newLocale);
    }
  }

  /**
   * Initialize the component state
   * @returns {void}
   */
  initState() {
    this.state = {
      isLoggedIn: !!(this.constructor.authorization && this.constructor.authorization.accessToken),
      locale: WSHeader.getLocale()
    };
  }

  /**
   * Initialize the OAuth2 authorization
   * @returns {void}
   */
  initAuthorization() {
    // Initialize authorization with implicit flow
    this.constructor.authorization = this.constructor.authorization || new Authorization(WSHeader.storage);
    // Listen to authorization changes
    this.constructor.authorization.onAccessTokenChange(accessToken => {
      if (this.mounted) {
        this.setState({isLoggedIn: !!accessToken});
      } else {
        this.state.isLoggedIn = !!accessToken;
      }

      this.dispatchEvent('ws-auth-changed', accessToken);
    });
    // Check if we was redirected from the auth page and an access token is available
    this.constructor.authorization.tryFetchToken(location.hash.substr(1));
    // Listen for authentication requests
    window.addEventListener('ws-authorize', () => this.login());
    // Listen for authentication removal requests
    window.addEventListener('ws-unauthorize', () => this.logout());
  }

  /**
   * Trigger login process
   * @returns {void}
   */
  login() {
    this.constructor.authorization.authorize(this.props.loginUrl, this.props.clientId, this.props.businessPartnerId);
  }

  /**
   * Delete tokens from storage to logout
   * @returns {void}
   */
  logout() {
    this.constructor.authorization.unauthorize();
  }

  /**
   * Get's called when the mouse enters a menu item
   * @param {number} index The index of the item in the link list
   * @returns {void}
   */
  enterMenuItem(index) {
    clearInterval(this.leaveTimeout);
    this.subMenus.forEach(subMenu => subMenu.classList.remove('is-active'));
    this.menuItems.forEach(item => item.classList.remove('is-hovered'));
    // Show sub menu or hide level 2 completely
    if (this.subMenus[index]) {
      this.level2.classList.add('is-active');
      const subMenu = this.subMenus[index];
      subMenu.classList.add('is-active');
      const item = this.menuItems[index];
      item.classList.add('is-hovered');
    } else {
      this.leaveLevel2();
    }
  }

  /**
   * Get's called when the mouse leaves a menu item
   * @param {number} index The index of the item in the link list
   * @returns {void}
   */
  leaveMenuItem(index) {
    this.leaveTimeout = setTimeout(() => {
      this.level2.classList.remove('is-active');
      if (this.subMenus[index]) {
        const subMenu = this.subMenus[index];
        subMenu.classList.remove('is-active');
        const item = this.menuItems[index];
        item.classList.remove('is-hovered');
      }
    }, 10);
  }

  /**
   * Get's called when the mouse enters into the level 2 (sub menu)
   * @returns {void}
   */
  enterLevel2() {
    clearInterval(this.leaveTimeout);
  }

  /**
   * Get's called when the mouse leaves the level 2 (sub menu)
   * @returns {void}
   */
  leaveLevel2() {
    this.subMenus.forEach(subMenu => subMenu.classList.remove('is-active'));
    this.menuItems.forEach(item => item.classList.remove('is-hovered'));
    this.level2.classList.remove('is-active');
  }

  /**
   * @returns {Object}
   */
  render() {
    return (
       { this.element = element; }}>

            {this.props.appLogo &&

            }
            {this.props.appName}

              {this.props.links.map((link, index) =>
                <li
                  key={`header-link${index}`}
                  onMouseEnter={() => this.enterMenuItem(index)}
                  onMouseLeave={() => this.leaveMenuItem(index)}
                  ref={element => { this.menuItems[index] = element; }}
                  className={(link.isCurrent) ? 'is-current' : null}
                >
                   { if (link.onClick) link.onClick(event); }}>
                    {link.label}

              )}

              {this.props.showLocale &&

                  <WSDropdown
                    className="locale"
                    icon="icon24 icon-sort-down"
                    items={this.locales}
                    text={this.state.locale}
                    onChange={item => this.setLocale(item.value)}
                    orientation="right"
                    type="anchor"
                  />

              }
              {this.props.showAuthorization && (!this.state.isLoggedIn ?

 this.login()}>
                  Login

              :

 this.logout()}>

              )}

 this.enterLevel2()}
          onMouseLeave={() => this.leaveLevel2()}
          onClick={() => this.leaveLevel2()}
          ref={element => { this.level2 = element; }}
        >
          {this.props.links.map((parent, index) =>
            parent.children && parent.children.length &&
            <ul className="main-sub-menu" key={`sub-menu${index}`} ref={element => { this.subMenus[index] = element; }}>
              {parent.children.map((child, childIndex) =>
                <li key={`sub-link-${index}-${childIndex}`} className={(child.isCurrent) ? 'is-current' : null}>
                   { if (child.onClick) child.onClick(event); }}>
                    {child.label}

              )}

          )}

    );
  }
}
<commit_msg>fix(ws-header): Ensure Authorization was initialized to fetch access token changes without DOMEvent (#147)<commit_after>import {React, Component, PropTypes} from '../imports';
import {CookieStorage} from './storage/cookie-storage';
import {LocalStorage} from './storage/local-storage';
import {Authorization} from './authorization';
import {WSDropdown} from '../ws-dropdown/ws-dropdown';

/**
 * This component renders a generic header which provides authentication and language management
 *
 * Optionally call WSHeader.setStorageType('cookie', 'zalando') If you want a to use cookies instead of localStorage
 * to persist the tokens. You can call WSHeader.getAccessToken().then(token => ...) to get the current access token.
 * It will resolve null when no access token is present and therefore the user isn't logged in.
 * @property {Object} props React properties object
 * @property {string} props.loginUrl property used to set the application login url
 * @property {string} props.businessPartnerId property used to set the application businessPartnerId
 * @property {string} props.clientId property used to set the application yourturn clientId
 * @property {Array<Object>} props.links property used to set the list of links with multiple levels
 * @property {string} props.appName property used to set the application name
 * @property {string} props.appLogo property used to set the application image logo
 * @property {string} props.rootUrl property used to set the root application url
 * @property {Boolean} props.showLocale Flag used to show the locale dropdown
 * @property {Boolean} props.showAuthorization Flag to show the login area
 * @property {Function} props.onLocaleChange Function used to propagate data
 * @property {Function} props.onAuthChange Function used to propagate data
 */
export class WSHeader extends Component {

  static defaultProps = {
    loginUrl: 'https://identity.zalando.com/oauth2/authorize',
    businessPartnerId: null,
    clientId: null,
    links: [],
    appName: 'Zalando',
    appLogo: null,
    rootUrl: '#',
    showLocale: true,
    showAuthorization: true,
    onLocaleChange: () => {},
    onAuthChange: () => {}
  };

  static propTypes = {
    loginUrl: PropTypes.string,
    businessPartnerId: PropTypes.string,
    clientId: PropTypes.string,
    links: PropTypes.array,
    appName: PropTypes.string,
    appLogo: PropTypes.string,
    onLocaleChange: PropTypes.func,
    onAuthChange: PropTypes.func,
    rootUrl: PropTypes.string,
    showLocale: PropTypes.bool,
    showAuthorization: PropTypes.bool
  };

  /**
   * @type {Authorization}
   */
  static authorization = new Authorization(this.storage);

  /**
   * Default storage instance
   * @type {AbstractStorage}
   */
  static storage = new LocalStorage('');

  /**
   * Initialize the storage
   * @param {string} type Can be either cookie or local
   * @param {string} name Storage name will be used as key prefix
   * @returns {void}
   */
  static setStorageType(type, name) {
    // Create storage for persisting keys
    if (type === 'cookie') {
      this.storage = new CookieStorage(name);
    } else {
      this.storage = new LocalStorage(name);
    }
    // Create authorization with new storage type
    this.authorization.storage = this.storage;
  }

  /**
   * Tries to get the access token from authorization class
   * @param {string} queryString The current query string to parse the token from
   * @returns {string|null}
   */
  static getAccessToken(queryString = location.hash.substr(1)) {
    if (!this.authorization.accessToken) {
      this.authorization.tryFetchToken(queryString);
    }
    return this.authorization.accessToken;
  }

  /**
   * Unauthorize will remove the access token from storage
   * @returns {void}
   */
  static removeAccessToken() {
    this.authorization.unauthorize();
  }

  /**
   * Get abbreviation for the user the access token is issued for
   * @returns {string|null}
   */
  static getUserAbbreviation() {
    try {
      const token = this.getAccessToken();
      const parts = token.split('.');
      const json = JSON.parse(atob(parts[1]));
      // Find key which contains the name
      const nameKey = Object.keys(json).find(key => key.includes('managed-id'));
      return json[nameKey];
    } catch (e) {
      return null;
    }
  }

  /**
   * Retrieve the persisted locale
   * @returns {string}
   */
  static getLocale() {
    const locale = WSHeader.storage.get('locale') || window.navigator.language.replace(/([a-z]+)-.*/, '$1');
    if (['de', 'en'].indexOf(locale) === -1) {
      return 'en';
    }
    return locale;
  }

  /**
   * @param {Object} props React/Preact properties
   */
  constructor(props) {
    super(props);
    this.initState();
    this.initAuthorization(props);
    this.mounted = false;
    this.locales = [
      {label: 'German', value: 'de'},
      {label: 'English', value: 'en'}
    ];
    this.subMenus = [];
    this.menuItems = [];
    this.level2 = null;
  }

  /**
   * Detect mounted state to prevent calling setState to early
   * @returns {void}
   */
  componentDidMount() {
    this.mounted = true;
  }

  /**
   * Changes the locale to the given one
   * @param {string} newLocale The new locale
   * @returns {void}
   */
  setLocale(newLocale) {
    this.setState({locale: newLocale});
    WSHeader.storage.set('locale', newLocale);
    this.dispatchEvent('ws-locale-changed', newLocale);
    // Propagate value in React world
    if (typeof this.props.onLocaleChange === 'function') {
      this.props.onLocaleChange(newLocale);
    }
  }

  /**
   * Initialize the component state
   * @returns {void}
   */
  initState() {
    this.state = {
      isLoggedIn: !!this.constructor.authorization.accessToken,
      locale: WSHeader.getLocale()
    };
  }

  /**
   * Initialize the OAuth2 authorization
   * @returns {void}
   */
  initAuthorization() {
    // Listen to authorization changes
    this.constructor.authorization.onAccessTokenChange(accessToken => {
      if (this.mounted) {
        this.setState({isLoggedIn: !!accessToken});
      } else {
        this.state.isLoggedIn = !!accessToken;
      }

      this.dispatchEvent('ws-auth-changed', accessToken);
    });
    // Check if we was redirected from the auth page and an access token is available
    this.constructor.authorization.tryFetchToken(location.hash.substr(1));
    // Listen for authentication requests
    window.addEventListener('ws-authorize', () => this.login());
    // Listen for authentication removal requests
    window.addEventListener('ws-unauthorize', () => this.logout());
  }

  /**
   * Trigger login process
   * @returns {void}
   */
  login() {
    this.constructor.authorization.authorize(this.props.loginUrl, this.props.clientId, this.props.businessPartnerId);
  }

  /**
   * Delete tokens from storage to logout
   * @returns {void}
   */
  logout() {
    this.constructor.authorization.unauthorize();
  }

  /**
   * Get's called when the mouse enters a menu item
   * @param {number} index The index of the item in the link list
   * @returns {void}
   */
  enterMenuItem(index) {
    clearInterval(this.leaveTimeout);
    this.subMenus.forEach(subMenu => subMenu.classList.remove('is-active'));
    this.menuItems.forEach(item => item.classList.remove('is-hovered'));
    // Show sub menu or hide level 2 completely
    if (this.subMenus[index]) {
      this.level2.classList.add('is-active');
      const subMenu = this.subMenus[index];
      subMenu.classList.add('is-active');
      const item = this.menuItems[index];
      item.classList.add('is-hovered');
    } else {
      this.leaveLevel2();
    }
  }

  /**
   * Get's called when the mouse leaves a menu item
   * @param {number} index The index of the item in the link list
   * @returns {void}
   */
  leaveMenuItem(index) {
    this.leaveTimeout = setTimeout(() => {
      this.level2.classList.remove('is-active');
      if (this.subMenus[index]) {
        const subMenu = this.subMenus[index];
        subMenu.classList.remove('is-active');
        const item = this.menuItems[index];
        item.classList.remove('is-hovered');
      }
    }, 10);
  }

  /**
   * Get's called when the mouse enters into the level 2 (sub menu)
   * @returns {void}
   */
  enterLevel2() {
    clearInterval(this.leaveTimeout);
  }

  /**
   * Get's called when the mouse leaves the level 2 (sub menu)
   * @returns {void}
   */
  leaveLevel2() {
    this.subMenus.forEach(subMenu => subMenu.classList.remove('is-active'));
    this.menuItems.forEach(item => item.classList.remove('is-hovered'));
    this.level2.classList.remove('is-active');
  }

  /**
   * @returns {Object}
   */
  render() {
    return (
       { this.element = element; }}>

            {this.props.appLogo &&

            }
            {this.props.appName}

              {this.props.links.map((link, index) =>
                <li
                  key={`header-link${index}`}
                  onMouseEnter={() => this.enterMenuItem(index)}
                  onMouseLeave={() => this.leaveMenuItem(index)}
                  ref={element => { this.menuItems[index] = element; }}
                  className={(link.isCurrent) ? 'is-current' : null}
                >
                   { if (link.onClick) link.onClick(event); }}>
                    {link.label}

              )}

              {this.props.showLocale &&

                  <WSDropdown
                    className="locale"
                    icon="icon24 icon-sort-down"
                    items={this.locales}
                    text={this.state.locale}
                    onChange={item => this.setLocale(item.value)}
                    orientation="right"
                    type="anchor"
                  />

              }
              {this.props.showAuthorization && (!this.state.isLoggedIn ?

 this.login()}>
                  Login

              :

 this.logout()}>

              )}

 this.enterLevel2()}
          onMouseLeave={() => this.leaveLevel2()}
          onClick={() => this.leaveLevel2()}
          ref={element => { this.level2 = element; }}
        >
          {this.props.links.map((parent, index) =>
            parent.children && parent.children.length &&
            <ul className="main-sub-menu" key={`sub-menu${index}`} ref={element => { this.subMenus[index] = element; }}>
              {parent.children.map((child, childIndex) =>
                <li key={`sub-link-${index}-${childIndex}`} className={(child.isCurrent) ? 'is-current' : null}>
                   { if (child.onClick) child.onClick(event); }}>
                    {child.label}

              )}

          )}

    );
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012-2013 Axelor. All Rights Reserved.
 *
 * The contents of this file are subject to the Common Public
 * Attribution License Version 1.0 (the “License”); you may not use
 * this file except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://license.axelor.com/.
 *
 * The License is based on the Mozilla Public License Version 1.1 but
 * Sections 14 and 15 have been added to cover use of software over a
 * computer network and provide for limited attribution for the
 * Original Developer. In addition, Exhibit A has been modified to be
 * consistent with Exhibit B.
 *
 * Software distributed under the License is distributed on an “AS IS”
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is part of "Axelor Business Suite", developed by
 * Axelor exclusively.
 *
 * The Original Developer is the Initial Developer. The Initial Developer of
 * the Original Code is Axelor.
 *
 * All portions of the code written by Axelor are
 * Copyright (c) 2012-2013 Axelor. All Rights Reserved.
 */
(function(){

var ui = angular.module('axelor.ui');
var widgets = {};
var registry = {};

/**
 * Perform common compile operations.
 *
 * example:
 *    ui.formCompile.call(this, element, attrs)
 */
ui.formCompile = function(element, attrs, linkerFn) {

	var showTitle = attrs.showTitle || this.showTitle,
		title = attrs.title || attrs.field;

	attrs.$set('show-title', showTitle, true, 'x-show-title');
	if (title) {
		attrs.$set('title', title, true, 'x-title');
	}
	if (this.cellCss) {
		attrs.$set('x-cell-css', this.cellCss);
	}

	function link(scope, element, attrs, controller) {

		element.addClass(this.css).parent().addClass(this.cellCss);
		element.data('$attrs', attrs); // store the attrs object for event handlers

		var getViewDef = this.getViewDef || scope.getViewDef || function() {return {}; };

		var field = getViewDef.call(scope, element);
		var props = _.pick(field, ['readonly', 'required', 'hidden']);
		var state = _.clone(props);

		if (field.css) {
			element.addClass(field.css);
		}

		scope.$events = {};
		scope.field = field || {};

		scope.attr = function(name) {
			if (arguments.length > 1) {
				state[name] = arguments[1];
			}
			return state[name];
		};

		scope.$on("on:edit", function(e, rec){
			if (_.isEmpty(rec)) {
				state = _.clone(props);
			}
		});

		scope.isRequired = function() {
			return this.attr("required") || false;
		};

		scope.isReadonly = function() {
			var parent = this.$parent;
			if (parent && parent.isReadonly && parent.isReadonly()) {
				return true;
			}
			if (scope.isEditable && !scope.isEditable()) {
				return true;
			}
			return this.attr("readonly") || false;
		};

		scope.isHidden = function() {
			return this.attr("hidden") || false;
		};

		scope.fireAction = function(name, success, error) {
			var handler = this.$events[name];
			if (handler) {
				return handler().then(success, error);
			}
		};

		if (angular.isFunction(this._link_internal)) {
			this._link_internal.call(this, scope, element, attrs, controller);
		}
		if (angular.isFunction(this.init)) {
			this.init.call(this, scope);
		}
		if (angular.isFunction(this.link)) {
			this.link.call(this, scope, element, attrs, controller);
		}

		function hideWidget(hidden) {
			var elem = element,
				parent = elem.parent('td'),
				label = elem.data('label') || $(),
				label_parent = label.parent('td');
			if (parent.size() == 0)
				parent = elem;
			if (label_parent.size())
				label = label_parent;
			return hidden ? parent.add(label).hide() : parent.add(label).show();
		}

		var hideFn = _.contains(this.handles, 'isHidden') ? angular.noop : hideWidget;

		scope.$watch("isHidden()", function(hidden, old) {
			if (hidden === old) return;
			return hideFn(hidden);
		});

		scope.$watch("isReadonly()", function(readonly, old) {
			return element.toggleClass("readonly", readonly);
		});

		this.prepare(scope, element, attrs, controller);

		scope.$evalAsync(function() {
			if (scope.isHidden()) {
				hideFn(true);
			}
		});
	}

	return angular.bind(this, link);
};

ui.formDirective = function(name, object) {

	if (object.compile === undefined) {
		object.compile = angular.bind(object, function(element, attrs){
			return ui.formCompile.apply(this, arguments);
		});
	}

	if (object.restrict === undefined) {
		object.restrict = 'EA';
	}

	if (object.template && object.replace == undefined) {
		object.replace = true;
	}

	if (object.cellCss === undefined) {
		object.cellCss = 'form-item';
	}

	if (object.scope === undefined) {
		object.scope = true;
	}

	if (object.require === undefined) {
		object.require = '?ngModel';
	}

	function prepare_templates($compile) {

		object.prepare = angular.bind(object, function(scope, element, attrs, model) {

			var self = this;

			if (this.template_editable == null && this.template_readonly == null) {
				return;
			}

			scope.$elem_editable = null;
			scope.$elem_readonly = null;

			function showEditable() {
				if (!self.template_editable) {
					return false;
				}
				if (scope.$elem_editable == null) {
					scope.$elem_editable = $compile(self.template_editable)(scope);
					if (self.link_editable) {
						self.link_editable.call(self, scope, scope.$elem_editable, attrs, model);
					}
					if (scope.validate) {
						model.$parsers.unshift(function(viewValue) {
							var valid = scope.validate(viewValue);
							model.$setValidity('valid', valid);
							return valid ? viewValue : undefined;
						});
					}
					// focus the first input field
					if (scope.$elem_editable.is('.input-append,.picker-input')) {
						scope.$elem_editable.on('click', '.btn, i', function(){
							scope.$elem_editable.find('input:first').focus();
						});
					}

					if (scope.$elem_editable.is(':input')) {
						scope.$elem_editable.attr('placeholder', scope.field.placeholder);
					}

					if (scope.$elem_editable.is('.picker-input')) {
						scope.$elem_editable.find(':input:first').attr('placeholder', scope.field.placeholder);
					}
				}
				if (scope.$elem_readonly) {
					scope.$elem_readonly.detach();
				}
				element.append(scope.$elem_editable);
				if (scope.$render_editable) scope.$render_editable();
				return true;
			}

			function showReadonly() {
				if (!self.template_readonly) {
					return false;
				}
				if (scope.$elem_readonly == null) {
					scope.$elem_readonly = $compile(self.template_readonly)(scope);
					if (self.link_readonly) {
						self.link_readonly.call(self, scope, scope.$elem_readonly, attrs, model);
					}
				}
				if (scope.$elem_editable) {
					scope.$elem_editable.detach();
				}
				element.append(scope.$elem_readonly);
				return true;
			}

			scope.$watch("isReadonly()", function(readonly) {
				if (readonly && showReadonly()) {
					return;
				}
				return showEditable();
			});
			scope.$watch("isRequired()", function(required, old) {
				if (required === old) return;
				var elem = element,
					label = elem.data('label') || $();
				if (label) {
					label.toggleClass('required', required);
				}
				attrs.$set('required', required);
			});

			if (scope.field && scope.field.validIf) {
				scope.$watch("attr('valid')", function(valid) {
					if (valid === undefined) return;
					model.$setValidity('invalid', valid);
				});
			}
		});

		return object;
	}

	return ui.directive(name, ['$compile', function($compile) {
		return prepare_templates($compile);
	}]);
};

var FormItem = {

	css: 'form-item',

	cellCss: 'form-item'
};

var FormInput = {

	_link_internal: function(scope, element, attrs, model) {

		scope.format = function(value) {
			return value;
		};

		scope.parse = function(value) {
			return value;
		};

		scope.validate = function(value) {
			return true;
		};

		scope.setValue = function(value, fireOnChange) {

			var val = this.parse(value);
			var txt = this.format(value);
			var onChange = this.$events.onChange;

			model.$setViewValue(val);
			this.text = txt;

			model.$render();
			if (onChange && fireOnChange) {
				onChange();
			}
		};

		scope.getValue = function() {
			if (model != null) {
				return model.$viewValue;
			}
			return null;
		};

		scope.getText = function() {
			return this.text;
		};

		scope.initValue = function(value) {
			this.text = this.format(value);
		};

		model.$render = function() {
			scope.initValue(scope.getValue());
			if (scope.$render_editable) {
				scope.$render_editable();
			}
			if (scope.$render_readonly) {
				scope.$render_readonly();
			}
		};

		// Clear invalid fields (use $setPrestine of angular.js 1.1)
		scope.$on('on:new', function(e, rec) {
			if (!model.$valid && model.$viewValue) {
				model.$viewValue = undefined;
				model.$render();
			}
		});
	},

	link: function(scope, element, attrs, model) {

	},

	link_editable: function(scope, element, attrs, model) {

		scope.$render_editable = function() {
			var value = this.format(this.getValue());
			element.val(value);
		};

		function bindListeners() {
			var onChange = scope.$events.onChange || angular.noop,
				onChangePending = false;

			function listener() {
				var value = _.str.trim(element.val()) || null;
				if (value !== model.$viewValue) {
					scope.$apply(function() {
						var val = scope.parse(value);
						var txt = scope.format(value);

						model.$setViewValue(val);
						scope.text = txt;
					});
					onChangePending = true;
				}
			}

			element.bind('input', listener);

			element.change(listener);

			element.blur(function(e){
				if (onChangePending) {
					onChangePending = false;
					setTimeout(onChange);
				}
			});
		}

		if (element.is(':input')) {
			setTimeout(bindListeners);
		}

		scope.$render_editable();
	},

	link_readonly: function(scope, element, attrs, model) {

	},

	template_editable: '<input type="text">',

	template_readonly: '{{text}}',

	template: ''
};

function inherit(array) {

	var args = _.chain(array).rest(1).flatten(true).value();
	var last = _.last(args);
	var base = null;
	var obj = {};

	_.chain(args).each(function(source, index) {
		if (_.isString(source)) {
			source = widgets[source];
		}
		if (index === args.length - 2) {
			base = source;
		}
		_.extend(obj, source);
	});

	if (base == null) {
		return obj;
	}

	function overridden(name) {
		return name !== "controller" &&
			_.isFunction(last[name]) && !last[name].$inject &&
			_.isFunction(base[name]);
	};

	for(var name in last) {
		if (overridden(name)) {
			obj[name] = (function(name, fn){
				return function() {
					var tmp = this._super;
					this._super = base[name];
					var ret = fn.apply(this, arguments);
					this._super = tmp;
					return ret;
				};
			})(name, obj[name]);
		}
	}

	return obj;
}

ui.formWidget = function(name, object) {
	var obj = inherit(arguments);
	var widget = _.str.titleize(name.replace(/^ui/, ''));
	var directive = "ui" + widget;

	registry[directive] = directive;
	_.each(obj.widgets, function(alias){
		registry[alias] = directive;
	});
	delete obj.widgets;

	widgets[widget] = _.clone(obj);

	ui.formDirective(directive, obj);

	return obj;
};

ui.formItem = function(name, object) {
	return ui.formWidget(name, FormItem, _.rest(arguments, 1));
};

ui.formInput = function(name, object) {
	return ui.formWidget(name, FormItem, FormInput, _.rest(arguments, 1));
};

ui.getWidget = function(type) {
	var name = _.chain(type).camelize().capitalize().value();
	var widget = registry["ui" + name] || registry[name];
	if (widget) {
		widget = widget.replace(/^ui/, '');
		return _.chain(widget).underscored().dasherize().value();
	}
	return null;
};

})(this);
<commit_msg>Optimize isHidden/isReadonly watch<commit_after>/*
 * Copyright (c) 2012-2013 Axelor. All Rights Reserved.
 *
 * The contents of this file are subject to the Common Public
 * Attribution License Version 1.0 (the “License”); you may not use
 * this file except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://license.axelor.com/.
 *
 * The License is based on the Mozilla Public License Version 1.1 but
 * Sections 14 and 15 have been added to cover use of software over a
 * computer network and provide for limited attribution for the
 * Original Developer. In addition, Exhibit A has been modified to be
 * consistent with Exhibit B.
 *
 * Software distributed under the License is distributed on an “AS IS”
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is part of "Axelor Business Suite", developed by
 * Axelor exclusively.
 *
 * The Original Developer is the Initial Developer. The Initial Developer of
 * the Original Code is Axelor.
 *
 * All portions of the code written by Axelor are
 * Copyright (c) 2012-2013 Axelor. All Rights Reserved.
 */
(function(){

var ui = angular.module('axelor.ui');
var widgets = {};
var registry = {};

/**
 * Perform common compile operations.
 *
 * example:
 *    ui.formCompile.call(this, element, attrs)
 */
ui.formCompile = function(element, attrs, linkerFn) {

	var showTitle = attrs.showTitle || this.showTitle,
		title = attrs.title || attrs.field;

	attrs.$set('show-title', showTitle, true, 'x-show-title');
	if (title) {
		attrs.$set('title', title, true, 'x-title');
	}
	if (this.cellCss) {
		attrs.$set('x-cell-css', this.cellCss);
	}

	function link(scope, element, attrs, controller) {

		element.addClass(this.css).parent().addClass(this.cellCss);
		element.data('$attrs', attrs); // store the attrs object for event handlers

		var getViewDef = this.getViewDef || scope.getViewDef || function() {return {}; };

		var field = getViewDef.call(scope, element);
		var props = _.pick(field, ['readonly', 'required', 'hidden']);
		var state = _.clone(props);

		if (field.css) {
			element.addClass(field.css);
		}

		scope.$events = {};
		scope.field = field || {};

		scope.attr = function(name) {
			if (arguments.length > 1) {
				state[name] = arguments[1];
			}
			return state[name];
		};

		scope.$on("on:edit", function(e, rec){
			if (_.isEmpty(rec)) {
				state = _.clone(props);
			}
		});

		scope.isRequired = function() {
			return this.attr("required") || false;
		};

		scope.isReadonly = function() {
			var parent = this.$parent;
			if (parent && parent.isReadonly && parent.isReadonly()) {
				return true;
			}
			if (scope.isEditable && !scope.isEditable()) {
				return true;
			}
			return this.attr("readonly") || false;
		};

		scope.isHidden = function() {
			return this.attr("hidden") || false;
		};

		scope.fireAction = function(name, success, error) {
			var handler = this.$events[name];
			if (handler) {
				return handler().then(success, error);
			}
		};

		if (angular.isFunction(this._link_internal)) {
			this._link_internal.call(this, scope, element, attrs, controller);
		}
		if (angular.isFunction(this.init)) {
			this.init.call(this, scope);
		}
		if (angular.isFunction(this.link)) {
			this.link.call(this, scope, element, attrs, controller);
		}

		function hideWidget(hidden) {
			var elem = element,
				parent = elem.parent('td'),
				label = elem.data('label') || $(),
				label_parent = label.parent('td');
			if (parent.size() == 0)
				parent = elem;
			if (label_parent.size())
				label = label_parent;
			return hidden ? parent.add(label).hide() : parent.add(label).show();
		}

		var hideFn = _.contains(this.handles, 'isHidden') ? angular.noop : hideWidget;

		var hiddenSet = false;
		scope.$watch("isHidden()", function(hidden, old) {
			if (hiddenSet && hidden === old) return;
			hiddenSet = true;
			return hideFn(hidden);
		});

		var readonlySet = false;
		scope.$watch("isReadonly()", function(readonly, old) {
			if (readonlySet && readonly === old) return;
			readonlySet = true;
			return element.toggleClass("readonly", readonly);
		});

		this.prepare(scope, element, attrs, controller);

		scope.$evalAsync(function() {
			if (scope.isHidden()) {
				hideFn(true);
			}
		});
	}

	return angular.bind(this, link);
};

ui.formDirective = function(name, object) {

	if (object.compile === undefined) {
		object.compile = angular.bind(object, function(element, attrs){
			return ui.formCompile.apply(this, arguments);
		});
	}

	if (object.restrict === undefined) {
		object.restrict = 'EA';
	}

	if (object.template && object.replace == undefined) {
		object.replace = true;
	}

	if (object.cellCss === undefined) {
		object.cellCss = 'form-item';
	}

	if (object.scope === undefined) {
		object.scope = true;
	}

	if (object.require === undefined) {
		object.require = '?ngModel';
	}

	function prepare_templates($compile) {

		object.prepare = angular.bind(object, function(scope, element, attrs, model) {

			var self = this;

			if (this.template_editable == null && this.template_readonly == null) {
				return;
			}

			scope.$elem_editable = null;
			scope.$elem_readonly = null;

			function showEditable() {
				if (!self.template_editable) {
					return false;
				}
				if (scope.$elem_editable == null) {
					scope.$elem_editable = $compile(self.template_editable)(scope);
					if (self.link_editable) {
						self.link_editable.call(self, scope, scope.$elem_editable, attrs, model);
					}
					if (scope.validate) {
						model.$parsers.unshift(function(viewValue) {
							var valid = scope.validate(viewValue);
							model.$setValidity('valid', valid);
							return valid ? viewValue : undefined;
						});
					}
					// focus the first input field
					if (scope.$elem_editable.is('.input-append,.picker-input')) {
						scope.$elem_editable.on('click', '.btn, i', function(){
							scope.$elem_editable.find('input:first').focus();
						});
					}

					if (scope.$elem_editable.is(':input')) {
						scope.$elem_editable.attr('placeholder', scope.field.placeholder);
					}

					if (scope.$elem_editable.is('.picker-input')) {
						scope.$elem_editable.find(':input:first').attr('placeholder', scope.field.placeholder);
					}
				}
				if (scope.$elem_readonly) {
					scope.$elem_readonly.detach();
				}
				element.append(scope.$elem_editable);
				if (scope.$render_editable) scope.$render_editable();
				return true;
			}

			function showReadonly() {
				if (!self.template_readonly) {
					return false;
				}
				if (scope.$elem_readonly == null) {
					scope.$elem_readonly = $compile(self.template_readonly)(scope);
					if (self.link_readonly) {
						self.link_readonly.call(self, scope, scope.$elem_readonly, attrs, model);
					}
				}
				if (scope.$elem_editable) {
					scope.$elem_editable.detach();
				}
				element.append(scope.$elem_readonly);
				return true;
			}

			scope.$watch("isReadonly()", function(readonly) {
				if (readonly && showReadonly()) {
					return;
				}
				return showEditable();
			});
			scope.$watch("isRequired()", function(required, old) {
				if (required === old) return;
				var elem = element,
					label = elem.data('label') || $();
				if (label) {
					label.toggleClass('required', required);
				}
				attrs.$set('required', required);
			});

			if (scope.field && scope.field.validIf) {
				scope.$watch("attr('valid')", function(valid) {
					if (valid === undefined) return;
					model.$setValidity('invalid', valid);
				});
			}
		});

		return object;
	}

	return ui.directive(name, ['$compile', function($compile) {
		return prepare_templates($compile);
	}]);
};

var FormItem = {

	css: 'form-item',

	cellCss: 'form-item'
};

var FormInput = {

	_link_internal: function(scope, element, attrs, model) {

		scope.format = function(value) {
			return value;
		};

		scope.parse = function(value) {
			return value;
		};

		scope.validate = function(value) {
			return true;
		};

		scope.setValue = function(value, fireOnChange) {

			var val = this.parse(value);
			var txt = this.format(value);
			var onChange = this.$events.onChange;

			model.$setViewValue(val);
			this.text = txt;

			model.$render();
			if (onChange && fireOnChange) {
				onChange();
			}
		};

		scope.getValue = function() {
			if (model != null) {
				return model.$viewValue;
			}
			return null;
		};

		scope.getText = function() {
			return this.text;
		};

		scope.initValue = function(value) {
			this.text = this.format(value);
		};

		model.$render = function() {
			scope.initValue(scope.getValue());
			if (scope.$render_editable) {
				scope.$render_editable();
			}
			if (scope.$render_readonly) {
				scope.$render_readonly();
			}
		};

		// Clear invalid fields (use $setPrestine of angular.js 1.1)
		scope.$on('on:new', function(e, rec) {
			if (!model.$valid && model.$viewValue) {
				model.$viewValue = undefined;
				model.$render();
			}
		});
	},

	link: function(scope, element, attrs, model) {

	},

	link_editable: function(scope, element, attrs, model) {

		scope.$render_editable = function() {
			var value = this.format(this.getValue());
			element.val(value);
		};

		function bindListeners() {
			var onChange = scope.$events.onChange || angular.noop,
				onChangePending = false;

			function listener() {
				var value = _.str.trim(element.val()) || null;
				if (value !== model.$viewValue) {
					scope.$apply(function() {
						var val = scope.parse(value);
						var txt = scope.format(value);

						model.$setViewValue(val);
						scope.text = txt;
					});
					onChangePending = true;
				}
			}

			element.bind('input', listener);

			element.change(listener);

			element.blur(function(e){
				if (onChangePending) {
					onChangePending = false;
					setTimeout(onChange);
				}
			});
		}

		if (element.is(':input')) {
			setTimeout(bindListeners);
		}

		scope.$render_editable();
	},

	link_readonly: function(scope, element, attrs, model) {

	},

	template_editable: '<input type="text">',

	template_readonly: '{{text}}',

	template: ''
};

function inherit(array) {

	var args = _.chain(array).rest(1).flatten(true).value();
	var last = _.last(args);
	var base = null;
	var obj = {};

	_.chain(args).each(function(source, index) {
		if (_.isString(source)) {
			source = widgets[source];
		}
		if (index === args.length - 2) {
			base = source;
		}
		_.extend(obj, source);
	});

	if (base == null) {
		return obj;
	}

	function overridden(name) {
		return name !== "controller" &&
			_.isFunction(last[name]) && !last[name].$inject &&
			_.isFunction(base[name]);
	};

	for(var name in last) {
		if (overridden(name)) {
			obj[name] = (function(name, fn){
				return function() {
					var tmp = this._super;
					this._super = base[name];
					var ret = fn.apply(this, arguments);
					this._super = tmp;
					return ret;
				};
			})(name, obj[name]);
		}
	}

	return obj;
}

ui.formWidget = function(name, object) {
	var obj = inherit(arguments);
	var widget = _.str.titleize(name.replace(/^ui/, ''));
	var directive = "ui" + widget;

	registry[directive] = directive;
	_.each(obj.widgets, function(alias){
		registry[alias] = directive;
	});
	delete obj.widgets;

	widgets[widget] = _.clone(obj);

	ui.formDirective(directive, obj);

	return obj;
};

ui.formItem = function(name, object) {
	return ui.formWidget(name, FormItem, _.rest(arguments, 1));
};

ui.formInput = function(name, object) {
	return ui.formWidget(name, FormItem, FormInput, _.rest(arguments, 1));
};

ui.getWidget = function(type) {
	var name = _.chain(type).camelize().capitalize().value();
	var widget = registry["ui" + name] || registry[name];
	if (widget) {
		widget = widget.replace(/^ui/, '');
		return _.chain(widget).underscored().dasherize().value();
	}
	return null;
};

})(this);
<|endoftext|>