<commit_before>Post.inject({
	getEditName: function(detailed) {
		if (this.title)
			return this.title.truncate(28, '...') + (detailed ? ' [' + this._id + ']' : '');
	},

	getDisplayName: function() {
		return this.getTitle();
	},

	getTitle: function() {
		// for the recent code in root, that deals with both nodes and posts
		return this.title;
	},

	getNode: function() {
		return this.getEditParent(this.node);
	},

	getEditForm: function() {
		var node = this.getNode();
		var form = new EditForm(this, {
			previewable: false, removable: true, showTitle: false, titles: { create: 'Post' }
		});
		var notifyItem = {
			type: 'boolean', name: 'notify', value: !!this.getNotification(),
			text: 'Notify on subsequent posts',
			onAfterApply: function(value) {
				node.setNotification(value, this.creator, this.getUserEmail(), this.getUserName());
			}
		};
		// If this post was anomyous and now edited by the same user who logged in,
		// do still show anomyous editor.
		if (!this.username && User.hasRole(User.POSTER)) {
			form.add([
				{
					suffix: 'Posting as: ' +
						session.user.renderLink({ attributes: { target: '_top' }})
				},
				notifyItem
			]);
		} else {
			form.add([
				{
					label: 'Name', type: 'string', name: 'username', length: 32, trim: true,
					requirements: {
						notNull: true,
						uniqueIn: {
							value: root.users, message:
							'\nThis user already exists.\nChoose a different name.'
						}
					}
				},
				{
					label: 'Website', type: 'string', name: 'website',
					requirements: {
						uri: true
					}
				}
			], [
				{
					label: 'Email', type: 'string', name: 'email', length: 255, trim: true,
					requirements: {
						notNull: {
							value: true, message: 'Needs to be specified.\nYour address will not be published.'
						},
						uniqueIn: {
							value: root.usersByEmail, message:
							'\nThis address is already in use.\nChoose a different address.'
						},
						email: true
					}
				},
				notifyItem
			]);
		}
		form.add({
			label: node.POST_TITLE, type: 'string', name: 'title', length: 64, trim: true,
			requirements: {
				notNull: { value: true, message: 'Please specify a title.' },
			}
		});
		form.add({
			label: 'Text', type: 'text', name: 'text', cols: '40', rows: '20',
			trim: true, hasLinks: true,
			requirements: {
				notNull: { value: true, message: 'Please write a text.' }
			}
		}/*, {
			label: 'Resources', type: 'multiselect', name: 'resources',
			showOptions: true, collection: this.allResources, value: this.resources,
			prototypes: 'Resource,Medium,Picture', movable: true,
			size: 6, autoRemove: true, sortable: true
		}*/);
		if (User.hasRole(User.ADMINISTRATOR) && !this.isCreating()) {
			form.add({
				type: 'ruler'
			}, {
				label: 'Date', type: 'date', name: 'creationDate', year: true,
				month: true, day: true, hours: true, minutes: true
			}, {
				label: 'Creator', type: 'select', name: 'creator',
				options: root.users, allowNull: true
			});
		}

		// Allow the parent node of the first post to add things to the post's
		// edit form, for scripts, gallery entires, etc, which are nothing more
		// than a stick first post.
		if (this.isFirst && node.populateFirstPostEditForm)
			node.populateFirstPostEditForm(form);

		form.add({
			label: 'Attachments', type: 'list', name: 'resources',
			collection: this.resources, prototypes: 'Resource',
			button: 'Attach', autoRemove: true, sortable: true,
			onCreate: function(values) {
				// Create a resource type based on file mime type
				var resource = values.file && Resource.create(values.file);
				if (resource)
					resource.visible = true;
				return resource;
			}
		}, {
			type: 'help', text: this.renderTemplate('help')
		});
		return form;
	},

	onAfterApply: function(changedItems) {
		var node = this.getNode();
		if (changedItems) {
			if (this.isFirst && node.onUpdateFirstPost)
				node.onUpdateFirstPost(this, changedItems);
			// If we're posting anonymously, store the values for next time
			if (!User.hasRole(User.POSTER)) {
				res.setCookie('post_username', this.username, 90);
				res.setCookie('post_email', this.email, 90);
				res.setCookie('post_website', this.website, 90);
			}
		}
	},

	initialize: function() {
		var node = this.getNode();
		if (node) {
			var count = node.posts.count();
			if (node.AUTO_POST_TITLE && count > 0 || !node.AUTO_POST_TITLE && count > 1) {
				var lastTitle = node.posts.get(count - 1).title;
				if (lastTitle)
					this.title = /^Re: /.test(lastTitle) ? lastTitle : 'Re: ' + lastTitle;
			}
		}
		if (!User.hasRole(User.POSTER)) {
			this.username = req.data.post_username;
			this.email = req.data.post_email;
			this.website = req.data.post_website;
		}
	},

	onCreate: function() {
		var node = this.getNode();
		this.isFirst = node.posts.count() == 0;
		// Store remote host
		this.host = Net.getHost();
		if (node.onAddPost)
			node.onAddPost(this);
	},

	onBeforeRemove: function() {
		// Check if this is a first post, and if so only allow removal
		// if there are no others
		var node = this.getNode();
		if (!User.hasRole(User.EDITOR) && this.isFirst && node.posts.count() > 1) {
			var other = node.posts.get(1);
			throw other.getUserName() + (node.posts.count() > 2 ? ' and others have' : ' has')
					+ ' already answered this post, therefore it cannot be removed.';
		}
	},

	onRemove: function() {
		var node = this.getNode();
		// If we're removing the first post, remove all.
		if (this.isFirst)
			node.remove();
		// If it's the last post, let the node know.
		if (node.onRemoveLastPost && !node.isRemoving() && node.posts.count() == 1)
			node.onRemoveLastPost();
	},

	isEditableBy: function(user, item) {
		return user && this.creator == user;
	},

	// Used by feed lib:
	renderSimple: function(out) {
		var resources = this.resources.list();
		return this.renderTemplate('simple', {
			text: Markup.render(this.text, {
				resources: resources, removeUsedResources: true,
				simple: true, inline: true, encoding: 'all'
			}),
			resources: resources,
		}, out);
	},

	renderUser: function(out) {
		var node = this.getNode();
		if (node.POST_USERS && !this.username && this.creator) {
			return this.creator.renderLink(null, out);
		} else if (node.POST_ANONYMOUS) {
			var name = encode(this.username);
			out.write(this.website ? '' + name + '' : name);
		}
	}.toRender(),

	render: function(withLink, asFirst, out) {
		var resources = this.resources.list();
		var title = encode(this.title);
		var param = {
			id: this.getEditId(),
			title: withLink ? this.node.renderLink(title) : title,
			resources: resources,
			postClass: this.node.POST_CLASS,
			styleClass: asFirst ? this.node.POST_CLASS_FIRST : this.node.POST_CLASS_OTHERS,
			// We need to process this before all the renderFields / Footer / Outer
			// methods since Markup modified param.resources already.
			// Therefore it canneot be moved to main.jstl until these render*
			// methods are converted to macros that can be called from there too.
			text: Markup.render(this.text, {
				resources: resources,
				removeUsedResources: true,
				encoding: 'all'
			})
		};
		// first posts in nodes also can show fields of the node (e.g. for scripts, gallery)
		if (this.isFirst) {
			if (this.node.renderFields)
				param.nodeFields = this.node.renderFields(param, resources);
			if (this.node.renderFooter)
				param.nodeFooter = this.node.renderFooter(param, resources);
			if (this.node.renderOuter)
				param.outer = this.node.renderOuter(param, resources);
		}
		return this.renderTemplate('main', param, out);
	},

	getNotification: function() {
		return this.getNode().getNotification(this.getUserEmail());
	},

	setNotification: function(notify, user, email, username) {
		this.getNode().setNotification(notify, user, email, username);
	},

	getUserEmail: function() {
		return this.creator != null ? this.creator.email : this.email;
	},

	getUserName: function() {
		return this.creator != null ? this.creator.name : this.username;
	},

	redirect: function() {
		// Calculate the pagination position from the index of the post within the node, to be used
		// in the redirect url bellow:
		var query = '';
		if (!this.isFirst || !this.node.POST_FIRST_STICKY) {
			var pos = this.node.indexOf(this);
			if (this.node.POST_FIRST_STICKY)
				pos--;
			pos = Math.floor(pos / this.node.POST_PER_PAGE);
			if (pos >= 0)
				query = '?pos=' + pos;
			query += '#' + this.getEditId();
		}
		res.redirect(this.node.href(this.node.POST_REDIRECT_ACTION) + query);
	}
});
<commit_msg>Cleaned up code and fixed rendering of user links.<commit_after>Post.inject({
	getEditName: function(detailed) {
		if (this.title)
			return this.title.truncate(28, '...') + (detailed ? ' [' + this._id + ']' : '');
	},

	getDisplayName: function() {
		return this.getTitle();
	},

	getTitle: function() {
		// for the recent code in root, that deals with both nodes and posts
		return this.title;
	},

	getNode: function() {
		return this.getEditParent(this.node);
	},

	getEditForm: function() {
		var node = this.getNode();
		var form = new EditForm(this, {
			previewable: false, removable: true, showTitle: false, titles: { create: 'Post' }
		});
		var notifyItem = {
			type: 'boolean', name: 'notify', value: !!this.getNotification(),
			text: 'Notify on subsequent posts',
			onAfterApply: function(value) {
				node.setNotification(value, this.creator, this.getUserEmail(), this.getUserName());
			}
		};
		// If this post was anomyous and now edited by the same user who logged in,
		// do still show anomyous editor.
		if (!this.username && User.hasRole(User.POSTER)) {
			form.add([
				{
					suffix: 'Posting as: ' +
						session.user.renderLink({ attributes: { target: '_top' }})
				},
				notifyItem
			]);
		} else {
			form.add([
				{
					label: 'Name', type: 'string', name: 'username', length: 32, trim: true,
					requirements: {
						notNull: true,
						uniqueIn: {
							value: root.users, message:
							'\nThis user already exists.\nChoose a different name.'
						}
					}
				},
				{
					label: 'Website', type: 'string', name: 'website',
					requirements: {
						uri: true
					}
				}
			], [
				{
					label: 'Email', type: 'string', name: 'email', length: 255, trim: true,
					requirements: {
						notNull: {
							value: true, message: 'Needs to be specified.\nYour address will not be published.'
						},
						uniqueIn: {
							value: root.usersByEmail, message:
							'\nThis address is already in use.\nChoose a different address.'
						},
						email: true
					}
				},
				notifyItem
			]);
		}
		form.add({
			label: node.POST_TITLE, type: 'string', name: 'title', length: 64, trim: true,
			requirements: {
				notNull: { value: true, message: 'Please specify a title.' },
			}
		});
		form.add({
			label: 'Text', type: 'text', name: 'text', cols: '40', rows: '20',
			trim: true, hasLinks: true,
			requirements: {
				notNull: { value: true, message: 'Please write a text.' }
			}
		});
		if (User.hasRole(User.ADMINISTRATOR) && !this.isCreating()) {
			form.add({
				type: 'ruler'
			}, {
				label: 'Date', type: 'date', name: 'creationDate', year: true,
				month: true, day: true, hours: true, minutes: true
			}, {
				label: 'Creator', type: 'select', name: 'creator',
				options: root.users, allowNull: true
			});
		}

		// Allow the parent node of the first post to add things to the post's
		// edit form, for scripts, gallery entires, etc, which are nothing more
		// than a stick first post.
		if (this.isFirst && node.populateFirstPostEditForm)
			node.populateFirstPostEditForm(form);

		form.add({
			label: 'Attachments', type: 'list', name: 'resources',
			collection: this.resources, prototypes: 'Resource',
			button: 'Attach', autoRemove: true, sortable: true,
			onCreate: function(values) {
				// Create a resource type based on file mime type
				var resource = values.file && Resource.create(values.file);
				if (resource)
					resource.visible = true;
				return resource;
			}
		}, {
			type: 'help', text: this.renderTemplate('help')
		});
		return form;
	},

	onAfterApply: function(changedItems) {
		var node = this.getNode();
		if (changedItems) {
			if (this.isFirst && node.onUpdateFirstPost)
				node.onUpdateFirstPost(this, changedItems);
			// If we're posting anonymously, store the values for next time
			if (!User.hasRole(User.POSTER)) {
				res.setCookie('post_username', this.username, 90);
				res.setCookie('post_email', this.email, 90);
				res.setCookie('post_website', this.website, 90);
			}
		}
	},

	initialize: function() {
		var node = this.getNode();
		if (node) {
			var count = node.posts.count();
			if (node.AUTO_POST_TITLE && count > 0 || !node.AUTO_POST_TITLE && count > 1) {
				var lastTitle = node.posts.get(count - 1).title;
				if (lastTitle)
					this.title = /^Re: /.test(lastTitle) ? lastTitle : 'Re: ' + lastTitle;
			}
		}
		if (!User.hasRole(User.POSTER)) {
			this.username = req.data.post_username;
			this.email = req.data.post_email;
			this.website = req.data.post_website;
		}
	},

	onCreate: function() {
		var node = this.getNode();
		this.isFirst = node.posts.count() == 0;
		// Store remote host
		this.host = Net.getHost();
		if (node.onAddPost)
			node.onAddPost(this);
	},

	onBeforeRemove: function() {
		// Check if this is a first post, and if so only allow removal
		// if there are no others
		var node = this.getNode();
		if (!User.hasRole(User.EDITOR) && this.isFirst && node.posts.count() > 1) {
			var other = node.posts.get(1);
			throw other.getUserName() + (node.posts.count() > 2 ? ' and others have' : ' has')
					+ ' already answered this post, therefore it cannot be removed.';
		}
	},

	onRemove: function() {
		var node = this.getNode();
		// If we're removing the first post, remove all.
		if (this.isFirst)
			node.remove();
		// If it's the last post, let the node know.
		if (node.onRemoveLastPost && !node.isRemoving() && node.posts.count() == 1)
			node.onRemoveLastPost();
	},

	isEditableBy: function(user, item) {
		return user && this.creator == user;
	},

	// Used by feed lib:
	renderSimple: function(out) {
		var resources = this.resources.list();
		return this.renderTemplate('simple', {
			text: Markup.render(this.text, {
				resources: resources, removeUsedResources: true,
				simple: true, inline: true, encoding: 'all'
			}),
			resources: resources,
		}, out);
	},

	renderUser: function(out) {
		if (this.username) {
			var name = encode(this.username);
			out.write(this.website ? '' + name + '' : name);
		} else if (this.creator) {
			this.creator.renderLink(null, out);
		}
	}.toRender(),

	render: function(withLink, asFirst, out) {
		var resources = this.resources.list();
		var title = encode(this.title);
		var param = {
			id: this.getEditId(),
			title: withLink ? this.node.renderLink(title) : title,
			resources: resources,
			postClass: this.node.POST_CLASS,
			styleClass: asFirst ? this.node.POST_CLASS_FIRST : this.node.POST_CLASS_OTHERS,
			// We need to process this before all the renderFields / Footer / Outer
			// methods since Markup modified param.resources already.
			// Therefore it canneot be moved to main.jstl until these render*
			// methods are converted to macros that can be called from there too.
			text: Markup.render(this.text, {
				resources: resources,
				removeUsedResources: true,
				encoding: 'all'
			})
		};
		// first posts in nodes also can show fields of the node (e.g. for scripts, gallery)
		if (this.isFirst) {
			if (this.node.renderFields)
				param.nodeFields = this.node.renderFields(param, resources);
			if (this.node.renderFooter)
				param.nodeFooter = this.node.renderFooter(param, resources);
			if (this.node.renderOuter)
				param.outer = this.node.renderOuter(param, resources);
		}
		return this.renderTemplate('main', param, out);
	},

	getNotification: function() {
		return this.getNode().getNotification(this.getUserEmail());
	},

	setNotification: function(notify, user, email, username) {
		this.getNode().setNotification(notify, user, email, username);
	},

	getUserEmail: function() {
		return this.creator != null ? this.creator.email : this.email;
	},

	getUserName: function() {
		return this.creator != null ? this.creator.name : this.username;
	},

	redirect: function() {
		// Calculate the pagination position from the index of the post within the node, to be used
		// in the redirect url bellow:
		var query = '';
		if (!this.isFirst || !this.node.POST_FIRST_STICKY) {
			var pos = this.node.indexOf(this);
			if (this.node.POST_FIRST_STICKY)
				pos--;
			pos = Math.floor(pos / this.node.POST_PER_PAGE);
			if (pos >= 0)
				query = '?pos=' + pos;
			query += '#' + this.getEditId();
		}
		res.redirect(this.node.href(this.node.POST_REDIRECT_ACTION) + query);
	}
});
<|endoftext|>


<commit_before>/***
 * Copyright (c) 2015, 2016 Alex Grant (@localnerve), LocalNerve LLC
 * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
 *
 * Entry point for the service worker.
 *
 * The 'precache' and 'data' modules are generated by the build.
 * @see grunt/tasks/service-worker.js
 */
'use strict';

var toolbox = require('sw-toolbox');
var data = require('./data');

toolbox.options.debug = data.debug;

// Construct cache name and save scope.
// Relies on sw-toolbox default name format for scope.
// CacheId must always start name.
var m = toolbox.options.cache.name.match(/([^\$]+)\${3}$/);
toolbox.options.scope = m && m[1];
toolbox.options.cache.name = data.cacheId + '-' + toolbox.options.cache.name;

// Setup debugging
var debugLib = require('./utils/debug');
if (data.debug) {
  debugLib.enable('*');
}
var debug = debugLib('index');

var init = require('./init');
var assets = require('./assets');

// Setup our activate handling
require('./activate');

// Setup push notification handling
require('./push');

// Setup message handling
require('./messages');

// Setup the sw-precache event handling and managed cache
require('./precache');

// Setup non-project static asset precaching (cdn requests)
assets.setupAssetRequests();

// If all init.data exists (and service-worker is starting), run the init command.
// The init message may never come if service-worker was restarted by the system.
init.data()
.then(function (payload) {
  payload.startup = true;
  init.command(payload, function (res) {
    if (res.error) {
      console.error('startup init command failed', res.error);
    }
  });
}).catch(function (error) {
  debug('startup not running init command', error);
});
<commit_msg>fix #43, add default fetch handler until sw-toolbox router setup<commit_after>/***
 * Copyright (c) 2015, 2016 Alex Grant (@localnerve), LocalNerve LLC
 * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
 *
 * Entry point for the service worker.
 *
 * The 'precache' and 'data' modules are generated by the build.
 * @see grunt/tasks/service-worker.js
 */
'use strict';

var toolbox = require('sw-toolbox');
var data = require('./data');

toolbox.options.debug = data.debug;

// Construct cache name and save scope.
// Relies on sw-toolbox default name format for scope.
// CacheId must always start name.
var m = toolbox.options.cache.name.match(/([^\$]+)\${3}$/);
toolbox.options.scope = m && m[1];
toolbox.options.cache.name = data.cacheId + '-' + toolbox.options.cache.name;

// Setup debugging
var debugLib = require('./utils/debug');
if (data.debug) {
  debugLib.enable('*');
}
var debug = debugLib('index');

var init = require('./init');
var assets = require('./assets');

// Setup our activate handling
require('./activate');

// Setup push notification handling
require('./push');

// Setup message handling
require('./messages');

// Setup the sw-precache event handling and managed cache
require('./precache');

// Setup non-project static asset precaching (cdn requests)
assets.setupAssetRequests();

// If all init.data exists (and service-worker is starting), run the init command.
// The init message may never come if service-worker was restarted by the system.
var initPromise = init.data()
.then(function (payload) {
  payload.startup = true;
  return init.command(payload, function (res) {
    if (res.error) {
      console.error('startup init command failed', res.error);
    }
  });
})
.catch(function (error) {
  debug('startup not running init command', error);
})
.then(function () {
  // Successful or not, we're done and don't want to impede other fetch
  // handlers.
  toolbox.router.default = null;
});

// #43, Setup a temporary default handler for async startup needs.
toolbox.router.default =
/**
 * The toolbox routes are not setup until the init command installs
 * the dynamic routes. Setup a default handler until init completes.
 *
 * @param {Request} request - The fetch event request object.
 * @returns {Promise} Resolves to a Response or a Network Error.
 */
function defaultHandler (request) {
  return initPromise.then(function () {
    debug('defaultHandler request ', request.url);

    // Since init complete, get the handler.
    var handler = toolbox.router.match(request);
    if (handler) {
      debug('defaultHandler successfully handled ', request.url);
      return handler(request);
    }

    debug('defaultHandler could not handle ', request);
    throw new Error('defaultHandler could not handle ' + request.url);
  });
};
<|endoftext|>


<commit_before>(() => {
  // Flash is a class that manages flash alerts and messages.
  class Flash {
    #container;
    #template;

    constructor(container, template) {
      this.#container = container;
      this.#template = template;
    }

    clear() {
      while (this.#container && this.#container.firstChild) {
        this.#container.removeChild(this.#container.firstChild);
      }
    }

    error(message) {
      this.flash('error', message);
    }

    warning(message) {
      this.flash('warning', message);
    }

    alert(message) {
      this.flash('alert', message);
    }

    flash(level, message) {
      const alert = this.#template.cloneNode(true);

      const body = alert.querySelector('.alert-message');
      body.textContent = message;

      const icon = alert.querySelector('.bi');

      switch (level) {
        case 'error':
          alert.classList.add('alert-danger');
          icon.classList.add('bi-exclamation-octagon-fill');
          break;
        case 'warning':
          alert.classList.add('alert-warning');
          icon.classList.add('bi-exclamation-square-fill');
          break;
        case 'alert':
          alert.classList.add('alert-success');
          icon.classList.add('bi-check-square-fill');
          break;
        default:
          throw `invalid flash level ${level}`;
      }

      this.#container.appendChild(alert).focus();
      alert.classList.remove('d-none');
    }
  }

  // Add proper classes to all links inside alerts that were rendered with the
  // page (server-side).
  window.addEventListener('DOMContentLoaded', () => {
    // alertsContainer is the container div for all alerts. alertTemplate is the
    // cloneable HTML fragement from which other alerts are generated.
    const alertsContainer = document.querySelector('body div#alerts-container');
    if (!alertsContainer) {
      return;
    }

    const alertTemplate = alertsContainer.querySelector('div#alert-template');
    if (!alertTemplate) {
      return;
    }

    // Make flash available everywhere.
    const flash = new Flash(alertsContainer, alertTemplate);
    window.flash = flash;
  });
})();
<commit_msg>Javascript is the best language and totally widely supported (#2165)<commit_after>(() => {
  // Flash is a class that manages flash alerts and messages.
  class Flash {
    _container;
    _template;

    constructor(container, template) {
      this._container = container;
      this._template = template;
    }

    clear() {
      while (this._container && this._container.firstChild) {
        this._container.removeChild(this._container.firstChild);
      }
    }

    error(message) {
      this.flash('error', message);
    }

    warning(message) {
      this.flash('warning', message);
    }

    alert(message) {
      this.flash('alert', message);
    }

    flash(level, message) {
      const alert = this._template.cloneNode(true);

      const body = alert.querySelector('.alert-message');
      body.textContent = message;

      const icon = alert.querySelector('.bi');

      switch (level) {
        case 'error':
          alert.classList.add('alert-danger');
          icon.classList.add('bi-exclamation-octagon-fill');
          break;
        case 'warning':
          alert.classList.add('alert-warning');
          icon.classList.add('bi-exclamation-square-fill');
          break;
        case 'alert':
          alert.classList.add('alert-success');
          icon.classList.add('bi-check-square-fill');
          break;
        default:
          throw `invalid flash level ${level}`;
      }

      this._container.appendChild(alert).focus();
      alert.classList.remove('d-none');
    }
  }

  // Add proper classes to all links inside alerts that were rendered with the
  // page (server-side).
  window.addEventListener('DOMContentLoaded', () => {
    // alertsContainer is the container div for all alerts. alertTemplate is the
    // cloneable HTML fragement from which other alerts are generated.
    const alertsContainer = document.querySelector('body div#alerts-container');
    if (!alertsContainer) {
      return;
    }

    const alertTemplate = alertsContainer.querySelector('div#alert-template');
    if (!alertTemplate) {
      return;
    }

    // Make flash available everywhere.
    const flash = new Flash(alertsContainer, alertTemplate);
    window.flash = flash;
  });
})();
<|endoftext|>


<commit_before>'use strict';

import { h, Component } from 'preact';
import Client from '../lib/client.js';
import { bind } from 'decko';

import * as d3 from 'd3';
import 'd3-transition';
d3.tip = require('d3-tip');

const formatDay = d3.timeFormat("%e"),
    formatMonth = d3.timeFormat("%b"),
    formatMonthDay = d3.timeFormat("%b %e"),
    formatYear = d3.timeFormat("%Y");

const t = d3.transition().duration(500).ease(d3.easeQuadOut);

// tooltip
const tip = d3.tip().attr('class', 'd3-tip').html((d) => (`

${d.Date.toLocaleDateString()}

${d.Pageviews}

Pageviews

${d.Visitors}

Visitors

`));

function padZero(s) {
  return s < 10 ? "0" + s : s;
}

function timeFormatPicker(n) {
  return function(d, i) {
    if(d.getDate() === 1) {
      return d.getMonth() === 0 ? formatYear(d) : formatMonth(d)
    }

    if(i === 0) {
      return formatMonthDay(d)
    } else if(n < 32) {
      return formatDay(d);
    }

    return '';
  }
}

function prepareData(startUnix, endUnix, data) {
  // add timezone offset back in to get local start date
  const timezoneOffset = (new Date()).getTimezoneOffset() * 60;
  let startDate = new Date((startUnix + timezoneOffset) * 1000);
  let endDate = new Date((endUnix+timezoneOffset) * 1000);
  let datamap = [];
  let newData = [];

   // create keyed array for quick date access
  let length = data.length;
  let d, dateParts, date, key;
  for(var i=0;i<length;i++) {
    d = data[i];
    // replace date with actual date object & store in datamap
    dateParts = d.Date.split('T')[0].split('-');
    date = new Date(dateParts[0], dateParts[1]-1, dateParts[2], 0, 0, 0)
    key = date.getFullYear() + "-" + padZero(date.getMonth() + 1) + "-" + padZero(date.getDate());
    d.Date = date;
    datamap[key] = d;
  }

  // make sure we have values for each date
  let currentDate = startDate;
  while(currentDate < endDate) {
    key = currentDate.getFullYear() + "-" + padZero(currentDate.getMonth() + 1) + "-" + padZero(currentDate.getDate());
    data = datamap[key] ? datamap[key] : {
        "Pageviews": 0,
        "Visitors": 0,
        "Date": new Date(currentDate),
     };

    newData.push(data);
    currentDate.setDate(currentDate.getDate() + 1);
  }

 return newData;
}

class Chart extends Component {
  constructor(props) {
    super(props)

    this.state = {
      loading: false,
      data: [],
    }
  }

  componentWillReceiveProps(newProps) {
    if(newProps.before == this.props.before && newProps.after == this.props.after) {
      return;
    }

    this.fetchData(newProps.before, newProps.after);
  }

  @bind
  prepareChart() {
    let padding = { top: 12, right: 12, bottom: 24, left: 40 };
    let height = 240;
    let width = this.base.clientWidth;

    this.innerWidth = width - padding.left - padding.right;
    this.innerHeight = height - padding.top - padding.bottom;

    this.ctx =  d3.select(this.base)
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .append('g')
      .attr('transform', 'translate(' + padding.left + ', '+padding.top+')')

    this.x = d3.scaleBand().range([0, this.innerWidth]).padding(0.1)
    this.y = d3.scaleLinear().range([this.innerHeight, 0])
    this.ctx.call(tip)
  }

  @bind
  redrawChart() {
    const data = this.state.data;
    this.base.parentNode.style.display = data.length <= 1 ? 'none' : '';
    if(data.length <= 1) {
      return;
    }

    if( ! this.ctx ) {
      this.prepareChart()
    }

    let graph = this.ctx;
    let innerWidth = this.innerWidth
    let innerHeight = this.innerHeight
    const max = d3.max(data, (d) => d.Pageviews);
    let x = this.x.domain(data.map((d) => d.Date))
    let y = this.y.domain([0, (max*1.1)])
    let yAxis = d3.axisLeft().scale(y).ticks(3).tickSize(-innerWidth)
    let xAxis = d3.axisBottom().scale(x).tickFormat(timeFormatPicker(data.length))

    window.xAxis = xAxis;
    window.data = data;

    // empty previous graph
    graph.selectAll('*').remove()

    // add axes
    let yTicks = graph.append("g")
      .attr("class", "y axis")
      .call(yAxis);
    let xTicks = graph.append("g")
      .attr("class", "x axis")
      .attr('transform', 'translate(0,' + innerHeight + ')')
      .call(xAxis)

    // hide all "day" ticks if we're watching more than 100 days of data
    xTicks.selectAll('g').filter(d => data.length > 100 && d.getDate() > 1).remove()

    // add data for each day
    let days = graph.selectAll('g.day').data(data).enter()
      .append('g')
      .attr('class', 'day')
      .attr('transform', function (d, i) { return "translate(" + x(d.Date) + ", 0)" })
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide)

    let pageviews = days.append('rect')
      .attr('class', 'bar-pageviews')
      .attr('width', x.bandwidth() * 0.5)
      .attr("y", innerHeight)
      .attr("height", 0)
      .transition(t)
      .attr('y', d => y(d.Pageviews))
      .attr('height', (d) => innerHeight - y(d.Pageviews))

    let visitors = days.append('rect')
      .attr('class', 'bar-visitors')
      .attr('width', x.bandwidth() * 0.5)
      .attr("y", innerHeight)
      .attr("height", 0)
      .attr('transform', function (d, i) { return "translate(" + ( 0.5 * x.bandwidth() ) + ", 0)" })
      .transition(t)
      .attr('height', (d) => (innerHeight - y(d.Visitors)) )
      .attr('y', (d) => y(d.Visitors))
  }

  @bind
  fetchData(before, after) {
    this.setState({ loading: true })

    Client.request(`/stats/site/groupby/day?before=${before}&after=${after}`)
      .then((d) => {
        // request finished; check if timestamp range is still the one user wants to see
        if( this.props.before != before || this.props.after != after ) {
          return;
        }

        this.setState({
          loading: false,
          data: prepareData(after, before, d),
        })
        this.redrawChart()
      })
  }

  render(props, state) {
    return (

    )
  }
}

export default Chart
<commit_msg>move data-length condition outside of filter (loop)<commit_after>'use strict';

import { h, Component } from 'preact';
import Client from '../lib/client.js';
import { bind } from 'decko';

import * as d3 from 'd3';
import 'd3-transition';
d3.tip = require('d3-tip');

const formatDay = d3.timeFormat("%e"),
    formatMonth = d3.timeFormat("%b"),
    formatMonthDay = d3.timeFormat("%b %e"),
    formatYear = d3.timeFormat("%Y");

const t = d3.transition().duration(500).ease(d3.easeQuadOut);

// tooltip
const tip = d3.tip().attr('class', 'd3-tip').html((d) => (`

${d.Date.toLocaleDateString()}

${d.Pageviews}

Pageviews

${d.Visitors}

Visitors

`));

function padZero(s) {
  return s < 10 ? "0" + s : s;
}

function timeFormatPicker(n) {
  return function(d, i) {
    if(d.getDate() === 1) {
      return d.getMonth() === 0 ? formatYear(d) : formatMonth(d)
    }

    if(i === 0) {
      return formatMonthDay(d)
    } else if(n < 32) {
      return formatDay(d);
    }

    return '';
  }
}

function prepareData(startUnix, endUnix, data) {
  // add timezone offset back in to get local start date
  const timezoneOffset = (new Date()).getTimezoneOffset() * 60;
  let startDate = new Date((startUnix + timezoneOffset) * 1000);
  let endDate = new Date((endUnix+timezoneOffset) * 1000);
  let datamap = [];
  let newData = [];

   // create keyed array for quick date access
  let length = data.length;
  let d, dateParts, date, key;
  for(var i=0;i<length;i++) {
    d = data[i];
    // replace date with actual date object & store in datamap
    dateParts = d.Date.split('T')[0].split('-');
    date = new Date(dateParts[0], dateParts[1]-1, dateParts[2], 0, 0, 0)
    key = date.getFullYear() + "-" + padZero(date.getMonth() + 1) + "-" + padZero(date.getDate());
    d.Date = date;
    datamap[key] = d;
  }

  // make sure we have values for each date
  let currentDate = startDate;
  while(currentDate < endDate) {
    key = currentDate.getFullYear() + "-" + padZero(currentDate.getMonth() + 1) + "-" + padZero(currentDate.getDate());
    data = datamap[key] ? datamap[key] : {
        "Pageviews": 0,
        "Visitors": 0,
        "Date": new Date(currentDate),
     };

    newData.push(data);
    currentDate.setDate(currentDate.getDate() + 1);
  }

 return newData;
}

class Chart extends Component {
  constructor(props) {
    super(props)

    this.state = {
      loading: false,
      data: [],
    }
  }

  componentWillReceiveProps(newProps) {
    if(newProps.before == this.props.before && newProps.after == this.props.after) {
      return;
    }

    this.fetchData(newProps.before, newProps.after);
  }

  @bind
  prepareChart() {
    let padding = { top: 12, right: 12, bottom: 24, left: 40 };
    let height = 240;
    let width = this.base.clientWidth;

    this.innerWidth = width - padding.left - padding.right;
    this.innerHeight = height - padding.top - padding.bottom;

    this.ctx =  d3.select(this.base)
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .append('g')
      .attr('transform', 'translate(' + padding.left + ', '+padding.top+')')

    this.x = d3.scaleBand().range([0, this.innerWidth]).padding(0.1)
    this.y = d3.scaleLinear().range([this.innerHeight, 0])
    this.ctx.call(tip)
  }

  @bind
  redrawChart() {
    const data = this.state.data;
    this.base.parentNode.style.display = data.length <= 1 ? 'none' : '';
    if(data.length <= 1) {
      return;
    }

    if( ! this.ctx ) {
      this.prepareChart()
    }

    let graph = this.ctx;
    let innerWidth = this.innerWidth
    let innerHeight = this.innerHeight
    const max = d3.max(data, (d) => d.Pageviews);
    let x = this.x.domain(data.map((d) => d.Date))
    let y = this.y.domain([0, (max*1.1)])
    let yAxis = d3.axisLeft().scale(y).ticks(3).tickSize(-innerWidth)
    let xAxis = d3.axisBottom().scale(x).tickFormat(timeFormatPicker(data.length))

    window.xAxis = xAxis;
    window.data = data;

    // empty previous graph
    graph.selectAll('*').remove()

    // add axes
    let yTicks = graph.append("g")
      .attr("class", "y axis")
      .call(yAxis);
    let xTicks = graph.append("g")
      .attr("class", "x axis")
      .attr('transform', 'translate(0,' + innerHeight + ')')
      .call(xAxis)

    // hide all "day" ticks if we're watching more than 100 days of data
    if(data.length > 100) {
      xTicks.selectAll('g').filter(d => d.getDate() > 1).remove()
    }

    // add data for each day
    let days = graph.selectAll('g.day').data(data).enter()
      .append('g')
      .attr('class', 'day')
      .attr('transform', function (d, i) { return "translate(" + x(d.Date) + ", 0)" })
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide)

    let pageviews = days.append('rect')
      .attr('class', 'bar-pageviews')
      .attr('width', x.bandwidth() * 0.5)
      .attr("y", innerHeight)
      .attr("height", 0)
      .transition(t)
      .attr('y', d => y(d.Pageviews))
      .attr('height', (d) => innerHeight - y(d.Pageviews))

    let visitors = days.append('rect')
      .attr('class', 'bar-visitors')
      .attr('width', x.bandwidth() * 0.5)
      .attr("y", innerHeight)
      .attr("height", 0)
      .attr('transform', function (d, i) { return "translate(" + ( 0.5 * x.bandwidth() ) + ", 0)" })
      .transition(t)
      .attr('height', (d) => (innerHeight - y(d.Visitors)) )
      .attr('y', (d) => y(d.Visitors))
  }

  @bind
  fetchData(before, after) {
    this.setState({ loading: true })

    Client.request(`/stats/site/groupby/day?before=${before}&after=${after}`)
      .then((d) => {
        // request finished; check if timestamp range is still the one user wants to see
        if( this.props.before != before || this.props.after != after ) {
          return;
        }

        this.setState({
          loading: false,
          data: prepareData(after, before, d),
        })
        this.redrawChart()
      })
  }

  render(props, state) {
    return (

    )
  }
}

export default Chart
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const path = require('path');
const glob = require('glob');
const camelCase = require('camelcase');
const ngc = require('@angular/compiler-cli/src/main').main;
const rollup = require('rollup');
const uglify = require('rollup-plugin-uglify');
const sourcemaps = require('rollup-plugin-sourcemaps');
const execa = require('execa');

const inlineResources = require('./inline-resources');

const libNameWithScope = require('./package.json').name;
const libName = libNameWithScope.slice(libNameWithScope.indexOf('/') + 1);
const rootFolder = path.join(__dirname);
const compilationFolder = path.join(rootFolder, 'out-tsc');
const srcFolder = path.join(rootFolder, 'src/lib');
const distFolder = path.join(rootFolder, 'dist');
const tempLibFolder = path.join(compilationFolder, 'lib');
const es5OutputFolder = path.join(compilationFolder, 'lib-es5');
const es2015OutputFolder = path.join(compilationFolder, 'lib-es2015');

return (
  Promise.resolve()
    // Copy library to temporary folder and inline html/css.
    .then(() =>
      _relativeCopy(`**/*`, srcFolder, tempLibFolder)
        .then(() => inlineResources(tempLibFolder))
        .then(() => console.log('Inlining succeeded.'))
    )
    // Compile to ES2015.
    .then(() => ngc(['--project', `${tempLibFolder}/tsconfig.lib.json`]))
    .then(exitCode => (exitCode === 0 ? Promise.resolve() : Promise.reject()))
    .then(() => console.log('ES2015 compilation succeeded.'))
    // Compile to ES5.
    .then(() => ngc(['--project', `${tempLibFolder}/tsconfig.es5.json`]))
    .then(exitCode => (exitCode === 0 ? Promise.resolve() : Promise.reject()))
    .then(() => console.log('ES5 compilation succeeded.'))
    // Copy typings and metadata to `dist/` folder.
    .then(() =>
      Promise.resolve()
        .then(() => _relativeCopy('**/*.d.ts', es2015OutputFolder, distFolder))
        .then(() => _relativeCopy('**/*.metadata.json', es2015OutputFolder, distFolder))
        .then(() => console.log('Typings and metadata copy succeeded.'))
    )
    // Bundle lib.
    .then(() => {
      // Base configuration.
      const es5Entry = path.join(es5OutputFolder, `${libName}.js`);
      const es2015Entry = path.join(es2015OutputFolder, `${libName}.js`);
      const rollupBaseConfig = {
        output: {
          name: camelCase(libName),
          globals: {
            // The key here is library name, and the value is the the name of the global variable name
            // the window object.
            // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals for more.
            tslib: 'tslib',
            openseadragon: 'ng.opensedragon',
            d3: 'd3',

            '@angular/animations': 'ng.animations',
            '@angular/core': 'ng.core',
            '@angular/common': 'ng.common',
            '@angular/forms': 'ng.forms',
            '@angular/common/http': 'ng.common.http',
            '@angular/router': 'ng.router',
            '@angular/platform-browser': 'ng.platformBrowser',
            '@angular/platform-server': 'ng.platformServer',
            '@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic',
            '@angular/platform-browser/animations': 'ng.platformBrowser.animations',
            '@angular/core/testing': 'ng.core.testing',
            '@angular/common/testing': 'ng.common.testing',
            '@angular/common/http/testing': 'ng.common.http.testing',
            '@angular/flex-layout': 'ng.flex-layout',

            // Some packages are not really needed for the UMD bundles, but for the missingRollupGlobals rule.
            '@angular/material': 'ng.material',
            '@angular/cdk': 'ng.cdk',

            // Include secondary entry-points of the cdk and material packages

            'rxjs/BehaviorSubject': 'Rx',
            'rxjs/ReplaySubject': 'Rx',
            'rxjs/Observable': 'Rx',
            'rxjs/Subject': 'Rx',
            'rxjs/Subscription': 'Rx',
            'rxjs/Observer': 'Rx',
            'rxjs/Subscriber': 'Rx',
            'rxjs/Scheduler': 'Rx',

            'rxjs/observable/combineLatest': 'Rx.Observable',
            'rxjs/observable/forkJoin': 'Rx.Observable',
            'rxjs/observable/fromEvent': 'Rx.Observable',
            'rxjs/observable/merge': 'Rx.Observable',
            'rxjs/observable/of': 'Rx.Observable',
            'rxjs/observable/throw': 'Rx.Observable',
            'rxjs/observable/defer': 'Rx.Observable',
            'rxjs/observable/fromEventPattern': 'Rx.Observable',
            'rxjs/observable/empty': 'Rx.Observable',

            'rxjs/operators': 'Rx.Observable',
            'rxjs/operators/debounceTime': 'Rx.Observable',
            'rxjs/operators/takeUntil': 'Rx.Observable',
            'rxjs/operators/first': 'Rx.Observable',
            'rxjs/operators/filter': 'Rx.Observable',
            'rxjs/operators/map': 'Rx.Observable',
            'rxjs/operators/tap': 'Rx.Observable',
            'rxjs/operators/startWith': 'Rx.Observable',
            'rxjs/operators/auditTime': 'Rx.Observable',
            'rxjs/operators/switchMap': 'Rx.Observable',
            'rxjs/operators/finalize': 'Rx.Observable',
            'rxjs/operators/catchError': 'Rx.Observable',
            'rxjs/operators/share': 'Rx.Observable',
            'rxjs/operators/delay': 'Rx.Observable',
            'rxjs/operators/combineLatest': 'Rx.Observable',
            'rxjs/operators/throttle': 'Rx.Observable',
            'rxjs/operators/interval': 'Rx.Observable',
            'rxjs/operators/val': 'Rx.Observable',
            'rxjs/operators/take': 'Rx.Observable',
            'rxjs/operators/sample': 'Rx.Observable',
            'rxjs/operators/distinctUntilChanged': 'Rx.Observable',
            'rxjs/operators/finally': 'Rx.Observable',
            'rxjs/observable/interval': 'Rx.Observable'
          },
          sourcemap: true
        },
        // ATTENTION:
        // Add any dependency or peer dependency your library to `globals` and `external`.
        // This is required for UMD bundle users.
        external: [
          // List of dependencies
          // See https://github.com/rollup/rollup/wiki/JavaScript-API#external for more.
          'd3',
          '@angular/core',
          '@angular/animations',
          '@angular/common',
          '@angular/common/http',
          '@angular/forms',
          '@angular/material',
          '@angular/flex-layout',
          'openseadragon',
          'rxjs/Observable',
          'rxjs/BehaviorSubject',
          'rxjs/Subject',
          'rxjs/ReplaySubject',
          'rxjs/Subscription',
          'rxjs/observable/interval',
          'rxjs/operators/throttle',
          'rxjs/operators/filter',
          'rxjs/operators/distinctUntilChanged',
          'rxjs/operators/finalize',
          'rxjs/operators/take',
          'rxjs/operators/takeUntil',
          'rxjs/operators/sample',
          'rxjs/operators'
        ],
        plugins: [sourcemaps()],
        onwarn: function(warning) {
          // Skip certain warnings

          // should intercept ... but doesn't in some rollup versions
          if (warning.code === 'THIS_IS_UNDEFINED') {
            return;
          } else if (warning.message.indexOf('QueryList') !== -1) {
            return;
          }
          // console.warn everything else
          console.warn(warning.message);
        }
      };

      // UMD bundle.
      const umdConfig = Object.assign({}, rollupBaseConfig, {
        input: es5Entry,
        output: {
          ...rollupBaseConfig.output,
          file: path.join(distFolder, `bundles`, `${libName}.umd.js`),
          format: 'umd'
        }
      });

      // Minified UMD bundle.
      const minifiedUmdConfig = Object.assign({}, rollupBaseConfig, {
        input: es5Entry,
        output: {
          ...rollupBaseConfig.output,
          file: path.join(distFolder, `bundles`, `${libName}.umd.min.js`),
          format: 'umd'
        },
        plugins: rollupBaseConfig.plugins.concat([uglify({})])
      });

      // ESM+ES5 flat module bundle.
      const fesm5config = Object.assign({}, rollupBaseConfig, {
        input: es5Entry,
        output: {
          ...rollupBaseConfig.output,
          file: path.join(distFolder, `${libName}.es5.js`),
          format: 'es'
        }
      });

      // ESM+ES2015 flat module bundle.
      const fesm2015config = Object.assign({}, rollupBaseConfig, {
        input: es2015Entry,
        output: {
          ...rollupBaseConfig.output,
          file: path.join(distFolder, `${libName}.js`),
          format: 'es'
        }
      });

      const allBundles = [umdConfig, minifiedUmdConfig, fesm5config, fesm2015config].map(cfg => {
        return rollup.rollup(cfg).then(bundle => {
          bundle.write(cfg.output);
        });
      });

      return Promise.all(allBundles).then(() => console.log('All bundles generated successfully.'));
    })
    // Copy package files
    .then(() =>
      Promise.resolve()
        .then(() => _relativeCopy('LICENSE', rootFolder, distFolder))
        .then(() => _relativeCopy('package.json', rootFolder, distFolder))
        .then(() => _relativeCopy('README.md', rootFolder, distFolder))
        .then(() => console.log('Package files copy succeeded.'))
    )
    .catch(e => {
      console.error('Build failed. See below for errors.\n');
      console.error(e);
      process.exit(1);
    })
);

// Copy files maintaining relative paths.
function _relativeCopy(fileGlob, from, to) {
  return new Promise((resolve, reject) => {
    glob(fileGlob, { cwd: from, nodir: true }, (err, files) => {
      if (err) {
        reject(err);
      }
      files.forEach(file => {
        const origin = path.join(from, file);
        const dest = path.join(to, file);
        const data = fs.readFileSync(origin, 'utf-8');
        _recursiveMkDir(path.dirname(dest));
        fs.writeFileSync(dest, data);
        resolve();
      });
    });
  });
}

// Recursively create a dir.
function _recursiveMkDir(dir) {
  if (!fs.existsSync(dir)) {
    _recursiveMkDir(path.dirname(dir));
    fs.mkdirSync(dir);
  }
}

function compileSassFiles() {
  return execa('node-sass', [
    tempLibFolder,
    '-o',
    tempLibFolder,
    '--output-style',
    'compressed',
    '--source-map',
    true,
    '--source-map-contents'
  ]);
}
<commit_msg>fix: replace spread operator<commit_after>'use strict';

const fs = require('fs');
const path = require('path');
const glob = require('glob');
const camelCase = require('camelcase');
const ngc = require('@angular/compiler-cli/src/main').main;
const rollup = require('rollup');
const uglify = require('rollup-plugin-uglify');
const sourcemaps = require('rollup-plugin-sourcemaps');
const execa = require('execa');

const inlineResources = require('./inline-resources');

const libNameWithScope = require('./package.json').name;
const libName = libNameWithScope.slice(libNameWithScope.indexOf('/') + 1);
const rootFolder = path.join(__dirname);
const compilationFolder = path.join(rootFolder, 'out-tsc');
const srcFolder = path.join(rootFolder, 'src/lib');
const distFolder = path.join(rootFolder, 'dist');
const tempLibFolder = path.join(compilationFolder, 'lib');
const es5OutputFolder = path.join(compilationFolder, 'lib-es5');
const es2015OutputFolder = path.join(compilationFolder, 'lib-es2015');

return (
  Promise.resolve()
    // Copy library to temporary folder and inline html/css.
    .then(() =>
      _relativeCopy(`**/*`, srcFolder, tempLibFolder)
        .then(() => inlineResources(tempLibFolder))
        .then(() => console.log('Inlining succeeded.'))
    )
    // Compile to ES2015.
    .then(() => ngc(['--project', `${tempLibFolder}/tsconfig.lib.json`]))
    .then(exitCode => (exitCode === 0 ? Promise.resolve() : Promise.reject()))
    .then(() => console.log('ES2015 compilation succeeded.'))
    // Compile to ES5.
    .then(() => ngc(['--project', `${tempLibFolder}/tsconfig.es5.json`]))
    .then(exitCode => (exitCode === 0 ? Promise.resolve() : Promise.reject()))
    .then(() => console.log('ES5 compilation succeeded.'))
    // Copy typings and metadata to `dist/` folder.
    .then(() =>
      Promise.resolve()
        .then(() => _relativeCopy('**/*.d.ts', es2015OutputFolder, distFolder))
        .then(() => _relativeCopy('**/*.metadata.json', es2015OutputFolder, distFolder))
        .then(() => console.log('Typings and metadata copy succeeded.'))
    )
    // Bundle lib.
    .then(() => {
      // Base configuration.
      const es5Entry = path.join(es5OutputFolder, `${libName}.js`);
      const es2015Entry = path.join(es2015OutputFolder, `${libName}.js`);
      const rollupBaseConfig = {
        output: {
          name: camelCase(libName),
          globals: {
            // The key here is library name, and the value is the the name of the global variable name
            // the window object.
            // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals for more.
            tslib: 'tslib',
            openseadragon: 'ng.opensedragon',
            d3: 'd3',

            '@angular/animations': 'ng.animations',
            '@angular/core': 'ng.core',
            '@angular/common': 'ng.common',
            '@angular/forms': 'ng.forms',
            '@angular/common/http': 'ng.common.http',
            '@angular/router': 'ng.router',
            '@angular/platform-browser': 'ng.platformBrowser',
            '@angular/platform-server': 'ng.platformServer',
            '@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic',
            '@angular/platform-browser/animations': 'ng.platformBrowser.animations',
            '@angular/core/testing': 'ng.core.testing',
            '@angular/common/testing': 'ng.common.testing',
            '@angular/common/http/testing': 'ng.common.http.testing',
            '@angular/flex-layout': 'ng.flex-layout',

            // Some packages are not really needed for the UMD bundles, but for the missingRollupGlobals rule.
            '@angular/material': 'ng.material',
            '@angular/cdk': 'ng.cdk',

            // Include secondary entry-points of the cdk and material packages

            'rxjs/BehaviorSubject': 'Rx',
            'rxjs/ReplaySubject': 'Rx',
            'rxjs/Observable': 'Rx',
            'rxjs/Subject': 'Rx',
            'rxjs/Subscription': 'Rx',
            'rxjs/Observer': 'Rx',
            'rxjs/Subscriber': 'Rx',
            'rxjs/Scheduler': 'Rx',

            'rxjs/observable/combineLatest': 'Rx.Observable',
            'rxjs/observable/forkJoin': 'Rx.Observable',
            'rxjs/observable/fromEvent': 'Rx.Observable',
            'rxjs/observable/merge': 'Rx.Observable',
            'rxjs/observable/of': 'Rx.Observable',
            'rxjs/observable/throw': 'Rx.Observable',
            'rxjs/observable/defer': 'Rx.Observable',
            'rxjs/observable/fromEventPattern': 'Rx.Observable',
            'rxjs/observable/empty': 'Rx.Observable',

            'rxjs/operators': 'Rx.Observable',
            'rxjs/operators/debounceTime': 'Rx.Observable',
            'rxjs/operators/takeUntil': 'Rx.Observable',
            'rxjs/operators/first': 'Rx.Observable',
            'rxjs/operators/filter': 'Rx.Observable',
            'rxjs/operators/map': 'Rx.Observable',
            'rxjs/operators/tap': 'Rx.Observable',
            'rxjs/operators/startWith': 'Rx.Observable',
            'rxjs/operators/auditTime': 'Rx.Observable',
            'rxjs/operators/switchMap': 'Rx.Observable',
            'rxjs/operators/finalize': 'Rx.Observable',
            'rxjs/operators/catchError': 'Rx.Observable',
            'rxjs/operators/share': 'Rx.Observable',
            'rxjs/operators/delay': 'Rx.Observable',
            'rxjs/operators/combineLatest': 'Rx.Observable',
            'rxjs/operators/throttle': 'Rx.Observable',
            'rxjs/operators/interval': 'Rx.Observable',
            'rxjs/operators/val': 'Rx.Observable',
            'rxjs/operators/take': 'Rx.Observable',
            'rxjs/operators/sample': 'Rx.Observable',
            'rxjs/operators/distinctUntilChanged': 'Rx.Observable',
            'rxjs/operators/finally': 'Rx.Observable',
            'rxjs/observable/interval': 'Rx.Observable'
          },
          sourcemap: true
        },
        // ATTENTION:
        // Add any dependency or peer dependency your library to `globals` and `external`.
        // This is required for UMD bundle users.
        external: [
          // List of dependencies
          // See https://github.com/rollup/rollup/wiki/JavaScript-API#external for more.
          'd3',
          '@angular/core',
          '@angular/animations',
          '@angular/common',
          '@angular/common/http',
          '@angular/forms',
          '@angular/material',
          '@angular/flex-layout',
          'openseadragon',
          'rxjs/Observable',
          'rxjs/BehaviorSubject',
          'rxjs/Subject',
          'rxjs/ReplaySubject',
          'rxjs/Subscription',
          'rxjs/observable/interval',
          'rxjs/operators/throttle',
          'rxjs/operators/filter',
          'rxjs/operators/distinctUntilChanged',
          'rxjs/operators/finalize',
          'rxjs/operators/take',
          'rxjs/operators/takeUntil',
          'rxjs/operators/sample',
          'rxjs/operators'
        ],
        plugins: [sourcemaps()],
        onwarn: function(warning) {
          // Skip certain warnings

          // should intercept ... but doesn't in some rollup versions
          if (warning.code === 'THIS_IS_UNDEFINED') {
            return;
          } else if (warning.message.indexOf('QueryList') !== -1) {
            return;
          }
          // console.warn everything else
          console.warn(warning.message);
        }
      };

      // UMD bundle.
      const umdConfig = Object.assign({}, rollupBaseConfig, {
        input: es5Entry,
        output: Object.assign({}, rollupBaseConfig.output, {
          file: path.join(distFolder, `bundles`, `${libName}.umd.js`),
          format: 'umd'
        })
      });

      // Minified UMD bundle.
      const minifiedUmdConfig = Object.assign({}, rollupBaseConfig, {
        input: es5Entry,
        output: Object.assign({}, rollupBaseConfig.output, {
          file: path.join(distFolder, `bundles`, `${libName}.umd.min.js`),
          format: 'umd'
        }),
        plugins: rollupBaseConfig.plugins.concat([uglify({})])
      });

      // ESM+ES5 flat module bundle.
      const fesm5config = Object.assign({}, rollupBaseConfig, {
        input: es5Entry,
        output: Object.assign({}, rollupBaseConfig.output, {
          file: path.join(distFolder, `${libName}.es5.js`),
          format: 'es'
        })
      });

      // ESM+ES2015 flat module bundle.
      const fesm2015config = Object.assign({}, rollupBaseConfig, {
        input: es2015Entry,
        output: Object.assign({}, rollupBaseConfig.output, {
          file: path.join(distFolder, `${libName}.js`),
          format: 'es'
        })
      });

      const allBundles = [umdConfig, minifiedUmdConfig, fesm5config, fesm2015config].map(cfg => {
        return rollup.rollup(cfg).then(bundle => {
          bundle.write(cfg.output);
        });
      });

      return Promise.all(allBundles).then(() => console.log('All bundles generated successfully.'));
    })
    // Copy package files
    .then(() =>
      Promise.resolve()
        .then(() => _relativeCopy('LICENSE', rootFolder, distFolder))
        .then(() => _relativeCopy('package.json', rootFolder, distFolder))
        .then(() => _relativeCopy('README.md', rootFolder, distFolder))
        .then(() => console.log('Package files copy succeeded.'))
    )
    .catch(e => {
      console.error('Build failed. See below for errors.\n');
      console.error(e);
      process.exit(1);
    })
);

// Copy files maintaining relative paths.
function _relativeCopy(fileGlob, from, to) {
  return new Promise((resolve, reject) => {
    glob(fileGlob, { cwd: from, nodir: true }, (err, files) => {
      if (err) {
        reject(err);
      }
      files.forEach(file => {
        const origin = path.join(from, file);
        const dest = path.join(to, file);
        const data = fs.readFileSync(origin, 'utf-8');
        _recursiveMkDir(path.dirname(dest));
        fs.writeFileSync(dest, data);
        resolve();
      });
    });
  });
}

// Recursively create a dir.
function _recursiveMkDir(dir) {
  if (!fs.existsSync(dir)) {
    _recursiveMkDir(path.dirname(dir));
    fs.mkdirSync(dir);
  }
}

function compileSassFiles() {
  return execa('node-sass', [
    tempLibFolder,
    '-o',
    tempLibFolder,
    '--output-style',
    'compressed',
    '--source-map',
    true,
    '--source-map-contents'
  ]);
}
<|endoftext|>


<commit_before>#! /usr/bin/env node

'use strict'

// BUILD.JS: This file is responsible for building static HTML pages

const fs = require('fs')
const gracefulFs = require('graceful-fs')
// This is needed at least on Windows to prevent the `EMFILE: too many open files` error
// https://github.com/isaacs/node-graceful-fs#global-patching
gracefulFs.gracefulify(fs)

const path = require('path')
const Metalsmith = require('metalsmith')
const collections = require('metalsmith-collections')
const feed = require('metalsmith-feed')
const discoverHelpers = require('metalsmith-discover-helpers')
const discoverPartials = require('metalsmith-discover-partials')
const layouts = require('metalsmith-layouts')
const markdown = require('metalsmith-markdown')
const prism = require('metalsmith-prism')
const permalinks = require('metalsmith-permalinks')
const pagination = require('metalsmith-yearly-pagination')
const defaultsDeep = require('lodash.defaultsdeep')
const autoprefixer = require('autoprefixer')
const marked = require('marked')
const postcss = require('postcss')
const sass = require('sass')
const ncp = require('ncp')
const junk = require('junk')
const semver = require('semver')

const githubLinks = require('./scripts/plugins/githubLinks')
const navigation = require('./scripts/plugins/navigation')
const anchorMarkdownHeadings = require('./scripts/plugins/anchor-markdown-headings')
const loadVersions = require('./scripts/load-versions')
const latestVersion = require('./scripts/helpers/latestversion')

// Set the default language, also functions as a fallback for properties which
// are not defined in the given language.
const DEFAULT_LANG = 'en'

// Set up the Markdown renderer that we'll use for our Metalsmith build process,
// with the necessary adjustments that we need to make in order to have Prism
// work.
const renderer = new marked.Renderer()
renderer.heading = anchorMarkdownHeadings
const markedOptions = {
  langPrefix: 'language-',
  renderer
}

// This function imports a given language file and uses the default language set
// in DEFAULT_LANG as a fallback to prevent any strings that aren't filled out
// from appearing as blank.
function i18nJSON (lang) {
  const defaultJSON = require(`./locale/${DEFAULT_LANG}/site.json`)
  const templateJSON = require(`./locale/${lang}/site.json`)

  return defaultsDeep({}, templateJSON, defaultJSON)
}

// This function imports language file for each given locale in array 'localesList'
// and based on it generating locales data, which includes full language name, english language name, locale and link
function generateLocalesData (localesList) {
  return localesList.map(localeEl => {
    const { language, languageEnglishVersion, locale, url } = require(`./locale/${localeEl}/site.json`)
    return { language, locale, url, languageEnglishVersion }
  })
}

// This is the function where the actual magic happens. This contains the main
// Metalsmith build cycle used for building a locale subsite, such as the
// english one.
function buildLocale (source, locale, opts) {
  console.log(`[metalsmith] build/${locale} started`)
  const labelForBuild = `[metalsmith] build/${locale} finished`
  console.time(labelForBuild)
  const metalsmith = Metalsmith(__dirname)

  metalsmith
  // Sets global metadata imported from the locale's respective site.json.
    .metadata({
      site: i18nJSON(locale),
      project: source.project,
      locales: opts.localesData
    })
  // Sets the build source as the locale folder.
    .source(path.join(__dirname, 'locale', locale))
    // site.json files aren't needed in the output dir
    .ignore('site.json')
    .use(withPreserveLocale(opts && opts.preserveLocale))
    // Extracts the main menu and sub-menu links form locale's site.json and
    // adds them to the metadata. This data is used in the navigation template
    .use(navigation(source.project.latestVersions))
    // Defines the blog post/guide collections used to internally group them for
    // easier future handling and feed generation.
    .use(collections({
      blog: {
        pattern: 'blog/**/*.md',
        sortBy: 'date',
        reverse: true,
        refer: false
      },
      blogReleases: {
        pattern: 'blog/release/*.md',
        sortBy: 'date',
        reverse: true,
        refer: false
      },
      blogVulnerability: {
        pattern: 'blog/vulnerability/*.md',
        sortBy: 'date',
        reverse: true,
        refer: false
      },
      knowledgeBase: {
        pattern: 'knowledge/**/*.md',
        refer: false
      },
      guides: {
        pattern: 'docs/guides/!(index).md'
      }
    }))
    .use(pagination({
      path: 'blog/year',
      iteratee: (post, idx) => ({
        post,
        displaySummary: idx < 10
      })
    }))
    .use(markdown(markedOptions))
    .use(githubLinks({ locale, site: i18nJSON(locale) }))
    .use(prism())
    // Set pretty permalinks, we don't want .html suffixes everywhere.
    .use(permalinks({
      relative: false
    }))
    // Generates the feed XML files from their respective collections which were
    // defined earlier on.
    .use(feed({
      collection: 'blog',
      destination: 'feed/blog.xml',
      title: 'Node.js Blog'
    }))
    .use(feed({
      collection: 'blogReleases',
      destination: 'feed/releases.xml',
      title: 'Node.js Blog: Releases'
    }))
    .use(feed({
      collection: 'blogVulnerability',
      destination: 'feed/vulnerability.xml',
      title: 'Node.js Blog: Vulnerability Reports'
    }))
    // Finally, this compiles the rest of the layouts present in ./layouts.
    // They're language-agnostic, but have to be regenerated for every locale
    // anyways.
    .use(discoverPartials({
      directory: 'layouts/partials',
      pattern: /\.hbs$/
    }))
    .use(discoverHelpers({
      directory: 'scripts/helpers',
      pattern: /\.js$/
    }))
    .use(layouts())
    // Pipes the generated files into their respective subdirectory in the build
    // directory.
    .destination(path.join(__dirname, 'build', locale))

  // This actually executes the build and stops the internal timer after
  // completion.
  metalsmith.build((err) => {
    if (err) { throw err }
    console.timeEnd(labelForBuild)
  })
}

// This plugin reads the files present in the english locale that are missing
// in the current locale being built (requires preserveLocale flag)
function withPreserveLocale (preserveLocale) {
  return (files, m, next) => {
    if (preserveLocale) {
      const path = m.path('locale/en')
      m.read(path, (err, newfiles) => {
        if (err) {
          console.error(err)
          return next(err)
        }

        Object.keys(newfiles).forEach((key) => {
          if (!files[key]) {
            files[key] = newfiles[key]
          }
        })
        next()
      })
    } else {
      next()
    }
  }
}

// This function builds the static/css folder for all the Sass files.
function buildCSS () {
  console.log('[sass] static/css started')
  const labelForBuild = '[sass] static/css finished'
  console.time(labelForBuild)

  const src = path.join(__dirname, 'layouts/css/styles.scss')
  const dest = path.join(__dirname, 'build/static/css/styles.css')

  const sassOpts = {
    file: src,
    outFile: dest,
    outputStyle: process.env.NODE_ENV !== 'development' ? 'compressed' : 'expanded'
  }

  gracefulFs.mkdir(path.join(__dirname, 'build/static/css'), { recursive: true }, (err) => {
    if (err) {
      throw err
    }

    sass.render(sassOpts, (error, result) => {
      if (error) {
        throw error
      }

      postcss([autoprefixer]).process(result.css, { from: src }).then(res => {
        res.warnings().forEach(warn => {
          console.warn(warn.toString())
        })

        gracefulFs.writeFile(dest, res.css, (err) => {
          if (err) {
            throw err
          }

          console.timeEnd(labelForBuild)
        })
      })
    })
  })
}

// This function copies the rest of the static assets to their subfolder in the
// build directory.
function copyStatic () {
  console.log('[ncp] build/static started')
  const labelForBuild = '[ncp] build/static finished'
  console.time(labelForBuild)
  gracefulFs.mkdir(path.join(__dirname, 'build/static'), { recursive: true }, (err) => {
    if (err) {
      throw err
    }

    ncp(path.join(__dirname, 'static'), path.join(__dirname, 'build/static'), (error) => {
      if (error) {
        return console.error(error)
      }
      console.timeEnd(labelForBuild)
    })
  })
}

function getSource (callback) {
  // Loads all node/io.js versions.
  loadVersions((err, versions) => {
    const source = {
      project: {
        versions,
        latestVersions: {
          current: latestVersion.current(versions),
          lts: latestVersion.lts(versions)
        },
        blacklivesmatter: {
          visible: false,
          text: '#BlackLivesMatter',
          link: '/en/black-lives-matter/'
        },
        banner: {
          visible: true,
          text: 'Join us at OpenJS World, a free virtual event on June 2-3, 2021',
          link: 'https://openjs-world.com'
        }
      }
    }
    if (semver.gt(source.project.latestVersions.lts.node, source.project.latestVersions.current.node)) {
      // If LTS is higher than Current hide it from the main page
      source.project.latestVersions.hideCurrent = true
    }

    callback(err, source)
  })
}

// This is where the build is orchestrated from, as indicated by the function
// name. It brings together all build steps and dependencies and executes them.
function fullBuild (opts) {
  const { selectedLocales, preserveLocale } = opts
  getSource((err, source) => {
    if (err) { throw err }

    // Executes the build cycle for every locale.
    gracefulFs.readdir(path.join(__dirname, 'locale'), (e, locales) => {
      if (e) {
        throw e
      }
      const filteredLocales = locales.filter(file => junk.not(file) && (selectedLocales ? selectedLocales.includes(file) : true))
      const localesData = generateLocalesData(filteredLocales)
      filteredLocales.forEach((locale) => {
        buildLocale(source, locale, { preserveLocale, localesData })
      })
    })
  })
}

// Starts the build if the file was executed from the command line
if (require.main === module) {
  const preserveLocale = process.argv.includes('--preserveLocale')
  const selectedLocales = process.env.DEFAULT_LOCALE ? process.env.DEFAULT_LOCALE.toLowerCase().split(',') : process.env.DEFAULT_LOCALE
  // Copy static files
  copyStatic()
  // Build CSS
  buildCSS()
  fullBuild({ selectedLocales, preserveLocale })
}

exports.getSource = getSource
exports.fullBuild = fullBuild
exports.buildCSS = buildCSS
exports.buildLocale = buildLocale
exports.copyStatic = copyStatic
exports.generateLocalesData = generateLocalesData
<commit_msg>chore: remove banner for OPenJS World<commit_after>#! /usr/bin/env node

'use strict'

// BUILD.JS: This file is responsible for building static HTML pages

const fs = require('fs')
const gracefulFs = require('graceful-fs')
// This is needed at least on Windows to prevent the `EMFILE: too many open files` error
// https://github.com/isaacs/node-graceful-fs#global-patching
gracefulFs.gracefulify(fs)

const path = require('path')
const Metalsmith = require('metalsmith')
const collections = require('metalsmith-collections')
const feed = require('metalsmith-feed')
const discoverHelpers = require('metalsmith-discover-helpers')
const discoverPartials = require('metalsmith-discover-partials')
const layouts = require('metalsmith-layouts')
const markdown = require('metalsmith-markdown')
const prism = require('metalsmith-prism')
const permalinks = require('metalsmith-permalinks')
const pagination = require('metalsmith-yearly-pagination')
const defaultsDeep = require('lodash.defaultsdeep')
const autoprefixer = require('autoprefixer')
const marked = require('marked')
const postcss = require('postcss')
const sass = require('sass')
const ncp = require('ncp')
const junk = require('junk')
const semver = require('semver')

const githubLinks = require('./scripts/plugins/githubLinks')
const navigation = require('./scripts/plugins/navigation')
const anchorMarkdownHeadings = require('./scripts/plugins/anchor-markdown-headings')
const loadVersions = require('./scripts/load-versions')
const latestVersion = require('./scripts/helpers/latestversion')

// Set the default language, also functions as a fallback for properties which
// are not defined in the given language.
const DEFAULT_LANG = 'en'

// Set up the Markdown renderer that we'll use for our Metalsmith build process,
// with the necessary adjustments that we need to make in order to have Prism
// work.
const renderer = new marked.Renderer()
renderer.heading = anchorMarkdownHeadings
const markedOptions = {
  langPrefix: 'language-',
  renderer
}

// This function imports a given language file and uses the default language set
// in DEFAULT_LANG as a fallback to prevent any strings that aren't filled out
// from appearing as blank.
function i18nJSON (lang) {
  const defaultJSON = require(`./locale/${DEFAULT_LANG}/site.json`)
  const templateJSON = require(`./locale/${lang}/site.json`)

  return defaultsDeep({}, templateJSON, defaultJSON)
}

// This function imports language file for each given locale in array 'localesList'
// and based on it generating locales data, which includes full language name, english language name, locale and link
function generateLocalesData (localesList) {
  return localesList.map(localeEl => {
    const { language, languageEnglishVersion, locale, url } = require(`./locale/${localeEl}/site.json`)
    return { language, locale, url, languageEnglishVersion }
  })
}

// This is the function where the actual magic happens. This contains the main
// Metalsmith build cycle used for building a locale subsite, such as the
// english one.
function buildLocale (source, locale, opts) {
  console.log(`[metalsmith] build/${locale} started`)
  const labelForBuild = `[metalsmith] build/${locale} finished`
  console.time(labelForBuild)
  const metalsmith = Metalsmith(__dirname)

  metalsmith
  // Sets global metadata imported from the locale's respective site.json.
    .metadata({
      site: i18nJSON(locale),
      project: source.project,
      locales: opts.localesData
    })
  // Sets the build source as the locale folder.
    .source(path.join(__dirname, 'locale', locale))
    // site.json files aren't needed in the output dir
    .ignore('site.json')
    .use(withPreserveLocale(opts && opts.preserveLocale))
    // Extracts the main menu and sub-menu links form locale's site.json and
    // adds them to the metadata. This data is used in the navigation template
    .use(navigation(source.project.latestVersions))
    // Defines the blog post/guide collections used to internally group them for
    // easier future handling and feed generation.
    .use(collections({
      blog: {
        pattern: 'blog/**/*.md',
        sortBy: 'date',
        reverse: true,
        refer: false
      },
      blogReleases: {
        pattern: 'blog/release/*.md',
        sortBy: 'date',
        reverse: true,
        refer: false
      },
      blogVulnerability: {
        pattern: 'blog/vulnerability/*.md',
        sortBy: 'date',
        reverse: true,
        refer: false
      },
      knowledgeBase: {
        pattern: 'knowledge/**/*.md',
        refer: false
      },
      guides: {
        pattern: 'docs/guides/!(index).md'
      }
    }))
    .use(pagination({
      path: 'blog/year',
      iteratee: (post, idx) => ({
        post,
        displaySummary: idx < 10
      })
    }))
    .use(markdown(markedOptions))
    .use(githubLinks({ locale, site: i18nJSON(locale) }))
    .use(prism())
    // Set pretty permalinks, we don't want .html suffixes everywhere.
    .use(permalinks({
      relative: false
    }))
    // Generates the feed XML files from their respective collections which were
    // defined earlier on.
    .use(feed({
      collection: 'blog',
      destination: 'feed/blog.xml',
      title: 'Node.js Blog'
    }))
    .use(feed({
      collection: 'blogReleases',
      destination: 'feed/releases.xml',
      title: 'Node.js Blog: Releases'
    }))
    .use(feed({
      collection: 'blogVulnerability',
      destination: 'feed/vulnerability.xml',
      title: 'Node.js Blog: Vulnerability Reports'
    }))
    // Finally, this compiles the rest of the layouts present in ./layouts.
    // They're language-agnostic, but have to be regenerated for every locale
    // anyways.
    .use(discoverPartials({
      directory: 'layouts/partials',
      pattern: /\.hbs$/
    }))
    .use(discoverHelpers({
      directory: 'scripts/helpers',
      pattern: /\.js$/
    }))
    .use(layouts())
    // Pipes the generated files into their respective subdirectory in the build
    // directory.
    .destination(path.join(__dirname, 'build', locale))

  // This actually executes the build and stops the internal timer after
  // completion.
  metalsmith.build((err) => {
    if (err) { throw err }
    console.timeEnd(labelForBuild)
  })
}

// This plugin reads the files present in the english locale that are missing
// in the current locale being built (requires preserveLocale flag)
function withPreserveLocale (preserveLocale) {
  return (files, m, next) => {
    if (preserveLocale) {
      const path = m.path('locale/en')
      m.read(path, (err, newfiles) => {
        if (err) {
          console.error(err)
          return next(err)
        }

        Object.keys(newfiles).forEach((key) => {
          if (!files[key]) {
            files[key] = newfiles[key]
          }
        })
        next()
      })
    } else {
      next()
    }
  }
}

// This function builds the static/css folder for all the Sass files.
function buildCSS () {
  console.log('[sass] static/css started')
  const labelForBuild = '[sass] static/css finished'
  console.time(labelForBuild)

  const src = path.join(__dirname, 'layouts/css/styles.scss')
  const dest = path.join(__dirname, 'build/static/css/styles.css')

  const sassOpts = {
    file: src,
    outFile: dest,
    outputStyle: process.env.NODE_ENV !== 'development' ? 'compressed' : 'expanded'
  }

  gracefulFs.mkdir(path.join(__dirname, 'build/static/css'), { recursive: true }, (err) => {
    if (err) {
      throw err
    }

    sass.render(sassOpts, (error, result) => {
      if (error) {
        throw error
      }

      postcss([autoprefixer]).process(result.css, { from: src }).then(res => {
        res.warnings().forEach(warn => {
          console.warn(warn.toString())
        })

        gracefulFs.writeFile(dest, res.css, (err) => {
          if (err) {
            throw err
          }

          console.timeEnd(labelForBuild)
        })
      })
    })
  })
}

// This function copies the rest of the static assets to their subfolder in the
// build directory.
function copyStatic () {
  console.log('[ncp] build/static started')
  const labelForBuild = '[ncp] build/static finished'
  console.time(labelForBuild)
  gracefulFs.mkdir(path.join(__dirname, 'build/static'), { recursive: true }, (err) => {
    if (err) {
      throw err
    }

    ncp(path.join(__dirname, 'static'), path.join(__dirname, 'build/static'), (error) => {
      if (error) {
        return console.error(error)
      }
      console.timeEnd(labelForBuild)
    })
  })
}

function getSource (callback) {
  // Loads all node/io.js versions.
  loadVersions((err, versions) => {
    const source = {
      project: {
        versions,
        latestVersions: {
          current: latestVersion.current(versions),
          lts: latestVersion.lts(versions)
        },
        blacklivesmatter: {
          visible: false,
          text: '#BlackLivesMatter',
          link: '/en/black-lives-matter/'
        },
        banner: {
          visible: false,
          text: 'Join us at OpenJS World, a free virtual event on June 2-3, 2021',
          link: 'https://openjs-world.com'
        }
      }
    }
    if (semver.gt(source.project.latestVersions.lts.node, source.project.latestVersions.current.node)) {
      // If LTS is higher than Current hide it from the main page
      source.project.latestVersions.hideCurrent = true
    }

    callback(err, source)
  })
}

// This is where the build is orchestrated from, as indicated by the function
// name. It brings together all build steps and dependencies and executes them.
function fullBuild (opts) {
  const { selectedLocales, preserveLocale } = opts
  getSource((err, source) => {
    if (err) { throw err }

    // Executes the build cycle for every locale.
    gracefulFs.readdir(path.join(__dirname, 'locale'), (e, locales) => {
      if (e) {
        throw e
      }
      const filteredLocales = locales.filter(file => junk.not(file) && (selectedLocales ? selectedLocales.includes(file) : true))
      const localesData = generateLocalesData(filteredLocales)
      filteredLocales.forEach((locale) => {
        buildLocale(source, locale, { preserveLocale, localesData })
      })
    })
  })
}

// Starts the build if the file was executed from the command line
if (require.main === module) {
  const preserveLocale = process.argv.includes('--preserveLocale')
  const selectedLocales = process.env.DEFAULT_LOCALE ? process.env.DEFAULT_LOCALE.toLowerCase().split(',') : process.env.DEFAULT_LOCALE
  // Copy static files
  copyStatic()
  // Build CSS
  buildCSS()
  fullBuild({ selectedLocales, preserveLocale })
}

exports.getSource = getSource
exports.fullBuild = fullBuild
exports.buildCSS = buildCSS
exports.buildLocale = buildLocale
exports.copyStatic = copyStatic
exports.generateLocalesData = generateLocalesData
<|endoftext|>


<commit_before>import React, { Component } from "react";

import styled from "styled-components";
import FontAwesome from "react-fontawesome";
import ScrollArea from "react-scrollbar";

import SubmitButton from "../SubmitButton.js";
import AdminToolBar from "./AdminToolBar.js";
import AdminFooter from "./AdminFooter.js";

import {
	ListContainer,
	ListHeader,
	ListTitle,
	SubListTitle,
	HR
} from "../SharedComponents.js";

import store from "../store.js";
import {
	requestAddUserToList,
	requestRemoveUserFromList,
	requestDeleteList,
	requestPopList,
	requestSetDiscussionStatus,
	requestListAdminAddUser
} from "../actions.js";

const createSpeakerRow = (user, index) => {
	return (
		<SpeakerRow key={user.id}>
			{index === 0
				? <CurrentSpeakerArrow name="angle-right" />
				: <PlaceHolder name="angle-right" />}
			<Speaker key={user.id}>
				{user.nick}
			</Speaker>
		</SpeakerRow>
	);
};

class List extends Component {
	constructor(props) {
		super(props);
		this.state = {
			adminAdduser: false,
			adminAddUserNick: ""
		};
	}

	registerTalkRequest = () => {
		store.dispatch(requestAddUserToList(this.props.list.id));
	};

	unregisterTalkRequest = () => {
		store.dispatch(requestRemoveUserFromList(this.props.list.id));
	};

	setDiscussionStatus = status => {
		if (this.props.inactive) {
			return;
		}

		store.dispatch(requestSetDiscussionStatus(this.props.list.id, status));
	};

	deleteList = () => {
		if (this.props.inactive) {
			return;
		}

		const { list: { id } } = this.props;
		store.dispatch(requestDeleteList(id));
	};

	nextSpeaker = () => {
		if (this.props.inactive) {
			return;
		}

		store.dispatch(requestPopList(this.props.list.id));
	};

	adminAddUserToggle = () => {
		if (this.props.inactive) {
			return;
		}

		this.setState({
			adminAddingUser: !this.state.adminAddingUser
		});
	};

	renderAdminTools = (discussionIsOpen, listId, inactive) => {
		return (
			<AdminToolBar
				discussionIsOpen={discussionIsOpen}
				setDiscussionStatus={this.setDiscussionStatus}
				listId={listId}
				onNextClick={this.nextSpeaker}
				onAddUser={this.adminAddUserToggle}
				inactive={inactive}
			/>
		);
	};

	renderAdminAddUserInputBox = () => {
		const { adminAddUserNick } = this.state;
		return (
			<AdminAddUserContainer>
				Nick:
				<AdminUserNickInput
					type="text"
					value={adminAddUserNick}
					onChange={this.adminAddingUserInputChange}
					onKeyPress={this.onAdminAddingUserKeyPress}
					autoFocus
				/>
				<AdminAddUserButton
					disabled={adminAddUserNick === ""}
					onClick={this.adminAddUser}
				/>
			</AdminAddUserContainer>
		);
	};

	adminAddingUserInputChange = e => {
		this.setState({
			adminAddUserNick: e.target.value
		});
	};

	onAdminAddingUserKeyPress = e => {
		if (e.key === "Enter") {
			this.adminAddUser();
		}
	};

	adminAddUser = () => {
		if (this.props.inactive) {
			return;
		}

		const { adminAddUserNick } = this.state;
		const { list: { id } } = this.props;
		if (adminAddUserNick === "") {
			return;
		}

		this.setState({
			adminAddingUser: false,
			adminAddUserNick: ""
		});
		store.dispatch(requestListAdminAddUser(id, adminAddUserNick));
	};

	renderAdminFooter = (listId, inactive) => {
		return <AdminFooter onClick={this.deleteList} inactive={inactive} />;
	};

	render() {
		const { adminAddingUser } = this.state;
		const { list, user, inactive } = this.props;

		const userIsPresent = list.speakersQueue
			.concat(list.secondSpeakersQueue)
			.some(u => u.id === user.id);

		return (
			<ListContainer key={list.id} inactive={inactive}>
				<ListHeader>
					<ListTitle>
						{list.title}
					</ListTitle>
					{list.status === "open" || user.isAdmin
						? <SubmitButton
								disabled={list.updating || inactive}
								isShowingPositive={!userIsPresent}
								onNegativeClick={this.unregisterTalkRequest}
								onPositiveClick={this.registerTalkRequest}
								positiveText="Add me"
								negativeText="Remove me"
							/>
						: <DiscussionClosedLabel>
								Debate closed
							</DiscussionClosedLabel>}
					{user.isAdmin &&
						this.renderAdminTools(list.status === "open", list.id, inactive)}
				</ListHeader>

				{adminAddingUser && this.renderAdminAddUserInputBox()}

				<Scroll speed={0.8} horizontal={false} minScrollSize={1}>
					<SubListTitle>
						First speakers list ({list.speakersQueue.length})
					</SubListTitle>
					{list.speakersQueue
						.map((user, index) => createSpeakerRow(user, index))
						.reduce(
							(acc, item, i) =>
								acc.concat(i === 0 ? [item] : [
, item]),
							[]
						)}

					<SubListTitle>
						Second speakers list ({list.secondSpeakersQueue.length})
					</SubListTitle>
					{list.secondSpeakersQueue
						.map((user, index) => createSpeakerRow(user, index))
						.reduce(
							(acc, item, i) =>
								acc.concat(i === 0 ? [item] : [
, item]),
							[]
						)}
				</Scroll>
				{user.isAdmin && this.renderAdminFooter(list.Id, inactive)}
			</ListContainer>
		);
	}
}

const AdminAddUserButton = props => {
	return (
		<AdminAddUserButtonStyle {...props}>
			<FontAwesome name="user-plus" />
		</AdminAddUserButtonStyle>
	);
};

const AdminAddUserButtonStyle = styled.button`
	border: none;
	outline: none;
	background-color: transparent;
	cursor: pointer;
	color: #84c043;
	font-size: 1.5em;
	:hover {
		color: #a4e063;
	}
	:active {
		color: #94d053;
	}
	:disabled {
		color: #aeaeae;
		cursor: default;
	}
`;

const AdminAddUserContainer = styled.div`
	display: flex;
	align-items: center;
	padding: 1em;
	background-color: #efeeee;
	font-size: 1em;
`;

const AdminUserNickInput = styled.input`
	border: none;
	outline: none;
	flex: 1;
	font-size: 1em;
	margin: 0 1em;
	background-color: #efeeee;
	border-bottom: 1px solid black;
`;

const DiscussionClosedLabel = styled.div`
	background-color: #f4512e;
	color: white;
	border: none;
	outline: none;
	padding: 15px 45px;
	font-size: 1.1em;
	text-align: center;
`;

const Scroll = styled(ScrollArea)`
	flex: 1;
	max-height: 50em;
	min-height: 20em;
	margin-bottom: 1em;
`;

const Speaker = styled.div`
	font-size: 1em;
	padding: 0.5em 0;
`;

const SpeakerRow = styled.div`display: flex;`;

const CurrentSpeakerArrow = styled(FontAwesome)`
  color: green;
  padding 0.2em;
  padding-left: 1.5em;
  font-size: 1.5em;
`;

const PlaceHolder = styled(CurrentSpeakerArrow)`
  opacity: 0;
`;

export default List;
<commit_msg>Remove scroll area from lists<commit_after>import React, { Component } from "react";

import styled from "styled-components";
import FontAwesome from "react-fontawesome";

import SubmitButton from "../SubmitButton.js";
import AdminToolBar from "./AdminToolBar.js";
import AdminFooter from "./AdminFooter.js";

import {
	ListContainer,
	ListHeader,
	ListTitle,
	SubListTitle,
	HR
} from "../SharedComponents.js";

import store from "../store.js";
import {
	requestAddUserToList,
	requestRemoveUserFromList,
	requestDeleteList,
	requestPopList,
	requestSetDiscussionStatus,
	requestListAdminAddUser
} from "../actions.js";

const createSpeakerRow = (user, index) => {
	return (
		<SpeakerRow key={user.id}>
			{index === 0
				? <CurrentSpeakerArrow name="angle-right" />
				: <PlaceHolder name="angle-right" />}
			<Speaker key={user.id}>
				{user.nick}
			</Speaker>
		</SpeakerRow>
	);
};

class List extends Component {
	constructor(props) {
		super(props);
		this.state = {
			adminAdduser: false,
			adminAddUserNick: ""
		};
	}

	registerTalkRequest = () => {
		store.dispatch(requestAddUserToList(this.props.list.id));
	};

	unregisterTalkRequest = () => {
		store.dispatch(requestRemoveUserFromList(this.props.list.id));
	};

	setDiscussionStatus = status => {
		if (this.props.inactive) {
			return;
		}

		store.dispatch(requestSetDiscussionStatus(this.props.list.id, status));
	};

	deleteList = () => {
		if (this.props.inactive) {
			return;
		}

		const { list: { id } } = this.props;
		store.dispatch(requestDeleteList(id));
	};

	nextSpeaker = () => {
		if (this.props.inactive) {
			return;
		}

		store.dispatch(requestPopList(this.props.list.id));
	};

	adminAddUserToggle = () => {
		if (this.props.inactive) {
			return;
		}

		this.setState({
			adminAddingUser: !this.state.adminAddingUser
		});
	};

	renderAdminTools = (discussionIsOpen, listId, inactive) => {
		return (
			<AdminToolBar
				discussionIsOpen={discussionIsOpen}
				setDiscussionStatus={this.setDiscussionStatus}
				listId={listId}
				onNextClick={this.nextSpeaker}
				onAddUser={this.adminAddUserToggle}
				inactive={inactive}
			/>
		);
	};

	renderAdminAddUserInputBox = () => {
		const { adminAddUserNick } = this.state;
		return (
			<AdminAddUserContainer>
				Nick:
				<AdminUserNickInput
					type="text"
					value={adminAddUserNick}
					onChange={this.adminAddingUserInputChange}
					onKeyPress={this.onAdminAddingUserKeyPress}
					autoFocus
				/>
				<AdminAddUserButton
					disabled={adminAddUserNick === ""}
					onClick={this.adminAddUser}
				/>
			</AdminAddUserContainer>
		);
	};

	adminAddingUserInputChange = e => {
		this.setState({
			adminAddUserNick: e.target.value
		});
	};

	onAdminAddingUserKeyPress = e => {
		if (e.key === "Enter") {
			this.adminAddUser();
		}
	};

	adminAddUser = () => {
		if (this.props.inactive) {
			return;
		}

		const { adminAddUserNick } = this.state;
		const { list: { id } } = this.props;
		if (adminAddUserNick === "") {
			return;
		}

		this.setState({
			adminAddingUser: false,
			adminAddUserNick: ""
		});
		store.dispatch(requestListAdminAddUser(id, adminAddUserNick));
	};

	renderAdminFooter = (listId, inactive) => {
		return <AdminFooter onClick={this.deleteList} inactive={inactive} />;
	};

	render() {
		const { adminAddingUser } = this.state;
		const { list, user, inactive } = this.props;

		const userIsPresent = list.speakersQueue
			.concat(list.secondSpeakersQueue)
			.some(u => u.id === user.id);

		return (
			<ListContainer key={list.id} inactive={inactive}>
				<ListHeader>
					<ListTitle>
						{list.title}
					</ListTitle>
					{list.status === "open" || user.isAdmin
						? <SubmitButton
								disabled={list.updating || inactive}
								isShowingPositive={!userIsPresent}
								onNegativeClick={this.unregisterTalkRequest}
								onPositiveClick={this.registerTalkRequest}
								positiveText="Add me"
								negativeText="Remove me"
							/>
						: <DiscussionClosedLabel>
								Debate closed
							</DiscussionClosedLabel>}
					{user.isAdmin &&
						this.renderAdminTools(list.status === "open", list.id, inactive)}
				</ListHeader>

				{adminAddingUser && this.renderAdminAddUserInputBox()}

				<SpeakersArea>
					<SubListTitle>
						First speakers list ({list.speakersQueue.length})
					</SubListTitle>
					{list.speakersQueue
						.map((user, index) => createSpeakerRow(user, index))
						.reduce(
							(acc, item, i) =>
								acc.concat(i === 0 ? [item] : [
, item]),
							[]
						)}

					<SubListTitle>
						Second speakers list ({list.secondSpeakersQueue.length})
					</SubListTitle>
					{list.secondSpeakersQueue
						.map((user, index) => createSpeakerRow(user, index))
						.reduce(
							(acc, item, i) =>
								acc.concat(i === 0 ? [item] : [
, item]),
							[]
						)}
				</SpeakersArea>
				{user.isAdmin && this.renderAdminFooter(list.Id, inactive)}
			</ListContainer>
		);
	}
}

const AdminAddUserButton = props => {
	return (
		<AdminAddUserButtonStyle {...props}>
			<FontAwesome name="user-plus" />
		</AdminAddUserButtonStyle>
	);
};

const AdminAddUserButtonStyle = styled.button`
	border: none;
	outline: none;
	background-color: transparent;
	cursor: pointer;
	color: #84c043;
	font-size: 1.5em;
	:hover {
		color: #a4e063;
	}
	:active {
		color: #94d053;
	}
	:disabled {
		color: #aeaeae;
		cursor: default;
	}
`;

const AdminAddUserContainer = styled.div`
	display: flex;
	align-items: center;
	padding: 1em;
	background-color: #efeeee;
	font-size: 1em;
`;

const AdminUserNickInput = styled.input`
	border: none;
	outline: none;
	flex: 1;
	font-size: 1em;
	margin: 0 1em;
	background-color: #efeeee;
	border-bottom: 1px solid black;
`;

const DiscussionClosedLabel = styled.div`
	background-color: #f4512e;
	color: white;
	border: none;
	outline: none;
	padding: 15px 45px;
	font-size: 1.1em;
	text-align: center;
`;

const SpeakersArea = styled.div`
	flex: 1;
	min-height: 20em;
	margin-bottom: 1em;
`;

const Speaker = styled.div`
	font-size: 1em;
	padding: 0.5em 0;
`;

const SpeakerRow = styled.div`display: flex;`;

const CurrentSpeakerArrow = styled(FontAwesome)`
  color: green;
  padding 0.2em;
  padding-left: 1.5em;
  font-size: 1.5em;
`;

const PlaceHolder = styled(CurrentSpeakerArrow)`
  opacity: 0;
`;

export default List;
<|endoftext|>


<commit_before>/* @flow */

import React from 'react';
import _ from 'lodash';
import {
    Input
  , ListGroup, ListGroupItem
  , Glyphicon
} from 'react-bootstrap';

const createAction = (name, optionalArgs) => {
  const args = optionalArgs || {};

  return _.merge({ actionType: name }, args);
}

export const MyMeetings = ({meetings, dispatchFactory}) => {
  const meetingsWithActions = _.map(
    meetings,
    m => {
      return {
        data:    m,
        actions: {
          meetingClicked: dispatchFactory(
            createAction('meeting_clicked', {meeting:m})
          )
        }
      };
    }
  );

  return (

      <MeetingFilter />
      <MeetingList meetings={meetingsWithActions} />

  );
};

const MeetingFilter = (props) => {
  const filterGlyphicon = <Glyphicon glyph="search" />;

  return (
    <Input type="text"
           placeholder="Filter meetings by user ..."
           addonBefore={filterGlyphicon} />
  );
};

const MeetingList = (props) => {
  const meetings = _.map(
    props.meetings,
    m => (<MeetingListItem key={m.data.id} meeting={m} />)
  );

  return (
    <ListGroup>
      {meetings}
    </ListGroup>
  );
};

const MeetingListItem = ({meeting}) => {
  return (
    <ListGroupItem onClick={meeting.actions.meetingClicked}>
      {meeting.data.name}
    </ListGroupItem>
  );
};
<commit_msg>Create specific MeetingFilter component<commit_after>/* @flow */

import React from 'react';
import _ from 'lodash';
import {
    Input
  , ListGroup, ListGroupItem
  , Glyphicon
} from 'react-bootstrap';

const createAction = (name, optionalArgs) => {
  const args = optionalArgs || {};

  return _.merge({ actionType: name }, args);
}

export const MyMeetings = ({meetings, dispatchFactory}) => {
  const meetingsWithActions = _.map(
    meetings,
    m => {
      return {
        data:    m,
        actions: {
          meetingClicked: dispatchFactory(
            createAction('meeting_clicked', {meeting:m})
          )
        }
      };
    }
  );

  return (

      <MeetingFilter dispatchFactory={dispatchFactory} />
      <MeetingList meetings={meetingsWithActions} />

  );
};

const MeetingFilter = ({dispatchFactory}) => {
  const filterGlyphicon = <Glyphicon glyph="search" />;

  const rawFilterTextAction = (raw) => {
    dispatchFactory(createAction('raw_filter', {raw: raw.target.value}))();
  };

  return (
    <Input type="text"
           placeholder="Filter meetings by user ..."
           addonBefore={filterGlyphicon}
           onChange={rawFilterTextAction} />
  );
};

const MeetingList = (props) => {
  const meetings = _.map(
    props.meetings,
    m => (<MeetingListItem key={m.data.id} meeting={m} />)
  );

  return (
    <ListGroup>
      {meetings}
    </ListGroup>
  );
};

const MeetingListItem = ({meeting}) => {
  return (
    <ListGroupItem onClick={meeting.actions.meetingClicked}>
      {meeting.data.name}
    </ListGroupItem>
  );
};
<|endoftext|>