<commit_before>import {CompositeDisposable, Disposable, File} from 'atom';

import path from 'path';

import React from 'react';
import ReactDom from 'react-dom';
import {autobind} from 'core-decorators';
import compareSets from 'compare-sets';

import Repository from './models/repository';
import FileSystemChangeObserver from './models/file-system-change-observer';
import WorkspaceChangeObserver from './models/workspace-change-observer';
import ResolutionProgress from './models/conflicts/resolution-progress';
import FilePatchController from './controllers/file-patch-controller';
import GitController from './controllers/git-controller';
import GitTimingsView from './views/git-timings-view';

const defaultState = {
  resolutionProgressByPath: {},
};

export default class GithubPackage {
  constructor(workspace, project, commandRegistry, notificationManager, config, confirm) {
    this.workspace = workspace;
    this.project = project;
    this.commandRegistry = commandRegistry;
    this.notificationManager = notificationManager;
    this.config = config;

    this.modelPromisesByProjectPath = new Map();
    this.modelsByProjectPath = new Map();
    this.confirm = confirm;
    this.activeRepository = null;
    this.activeResolutionProgress = null;

    this.subscriptions = new CompositeDisposable();
    this.savedState = {};
  }

  activate(state = {}) {
    this.savedState = {...defaultState, ...state};

    this.subscriptions = new CompositeDisposable();
    const projectPaths = this.project.getDirectories().map(dir => dir.getRealPathSync());
    const initPromises = this.cacheModelsForPaths(projectPaths);
    this.initWatchersStartedPromise = initPromises.changeObserversStartedPromise;
    this.initModelsPromise = initPromises.modelsPromise;

    this.subscriptions.add(
      this.project.onDidChangePaths(this.didChangeProjectPaths),
      this.workspace.onDidChangeActivePaneItem(this.didChangeActivePaneItem),
      this.config.onDidChange('github.githubEnabled', this.rerender),
      this.workspace.addOpener(uri => {
        if (uri === 'atom-github://debug/timings') {
          return this.createGitTimingsView();
        } else {
          return null;
        }
      }),
    );

    this.deserialize(this.savedState);

    this.rerender();
  }

  serialize() {
    const activeRepository = this.getActiveRepository();

    const resolutionProgressByPath = {};
    this.modelsByProjectPath.forEach((models, projectPath) => {
      const resolutionProgress = models.resolutionProgress;
      if (!resolutionProgress.isEmpty()) {
        resolutionProgressByPath[projectPath] = resolutionProgress.serialize();
      }
    });

    return {
      activeRepositoryPath: activeRepository ? activeRepository.getWorkingDirectoryPath() : null,
      gitController: this.controller.serialize(),
      resolutionProgressByPath,
    };
  }

  async deserialize(state) {
    // Ensure that Repository promises are available
    await this.initModelsPromise;
    let nextActiveRepository, nextActiveResolutionProgress;

    if (state.activeRepositoryPath) {
      const repository = await this.getRepositoryForWorkdirPath(state.activeRepositoryPath);
      if (repository) { nextActiveRepository = repository; }
    }

    if (nextActiveRepository) {
      const savedResolutionProgress = this.savedState.resolutionProgressByPath[state.activeRepositoryPath] || {};
      const commit = await nextActiveRepository.getLastCommit();
      nextActiveResolutionProgress = new ResolutionProgress(commit.sha, savedResolutionProgress);

      this.setActiveModels(nextActiveRepository, nextActiveResolutionProgress);
    } else {
      await this.updateActiveModels();
    }
  }

  @autobind
  rerender() {
    if (!this.element) {
      this.element = document.createElement('div');
      this.subscriptions.add(new Disposable(() => {
        ReactDom.unmountComponentAtNode(this.element);
        delete this.element;
      }));
    }

    ReactDom.render(
      <GitController
        ref={c => { this.controller = c; }}
        workspace={this.workspace}
        commandRegistry={this.commandRegistry}
        notificationManager={this.notificationManager}
        confirm={this.confirm}
        repository={this.getActiveRepository()}
        resolutionProgress={this.getActiveResolutionProgress()}
        statusBar={this.statusBar}
        savedState={this.savedState.gitController}
        githubEnabled={this.config.get('github.githubEnabled')}
      />, this.element,
    );
  }

  async deactivate() {
    this.subscriptions.dispose();
    if (this.destroyedRepositorySubscription) { this.destroyedRepositorySubscription.dispose(); }
    await this.destroyModelsForPaths(Array.from(this.modelPromisesByProjectPath.keys()));
  }

  consumeStatusBar(statusBar) {
    this.statusBar = statusBar;
    this.rerender();
  }

  createGitTimingsView() {
    return GitTimingsView.createPaneItem();
  }

  @autobind
  async didChangeProjectPaths(projectPaths) {
    this.updateActiveModels();
    const previousProjectPaths = Array.from(this.modelPromisesByProjectPath.keys());
    const {added, removed} = compareSets(new Set(previousProjectPaths), new Set(projectPaths));
    this.cacheModelsForPaths(Array.from(added));
    await this.destroyModelsForPaths(Array.from(removed));
  }

  @autobind
  didChangeActivePaneItem() {
    this.updateActiveModels();
  }

  getInitialWatchersStartedPromise() {
    return this.initWatchersStartedPromise;
  }

  getInitialModelsPromise() {
    return this.initModelsPromise;
  }

  getActiveRepository() {
    return this.activeRepository;
  }

  getActiveResolutionProgress() {
    return this.activeResolutionProgress;
  }

  async updateActiveModels() {
    await this.initModelsPromise;
    let nextActiveRepository, nextActiveResolutionProgress;

    const activeItem = this.workspace.getActivePaneItem();
    if (activeItem && typeof activeItem.getPath === 'function') {
      const projectPath = this.projectPathForItemPath(activeItem.getPath());
      if (projectPath) {
        await Promise.all([
          this.getRepositoryForWorkdirPath(projectPath).then(r => { nextActiveRepository = r; }),
          this.getResolutionProgressForWorkdirPath(projectPath).then(rp => { nextActiveResolutionProgress = rp; }),
        ]);
      }
    } else {
      nextActiveRepository = this.activeRepository;
      nextActiveResolutionProgress = this.activeResolutionProgress;
    }

    if (activeItem instanceof FilePatchController) {
      if (!activeItem.props.repository.isDestroyed()) {
        nextActiveRepository = activeItem.props.repository;
        nextActiveResolutionProgress = activeItem.props.resolutionProgress;
      }
    }

    if (!activeItem && this.project.getPaths().length === 1) {
      nextActiveRepository = await this.getRepositoryForWorkdirPath(this.project.getPaths()[0]);
    }

    if (nextActiveRepository && nextActiveRepository !== this.activeRepository) {
      this.setActiveModels(nextActiveRepository, nextActiveResolutionProgress);

      if (this.destroyedRepositorySubscription) { this.destroyedRepositorySubscription.dispose(); }
      this.destroyedRepositorySubscription = nextActiveRepository.onDidDestroy(() => {
        if (nextActiveRepository === this.activeRepository) {
          this.setActiveModels(null);
        }
        this.destroyedRepositorySubscription.dispose();
      });
    }

    if (!nextActiveRepository) {
      this.setActiveModels(null, null);
    }
  }

  setActiveModels(repository, resolutionProgress) {
    this.activeRepository = repository;
    this.activeResolutionProgress = resolutionProgress;
    this.rerender();
  }

  refreshAtomGitRepository(repository) {
    const repoPath = repository.getWorkingDirectoryPath();
    const atomGitRepo = this.project.getRepositories().find(repo => {
      return repo && path.normalize(repo.getWorkingDirectory()) === repoPath;
    });
    return atomGitRepo ? atomGitRepo.refreshStatus() : Promise.resolve();
  }

  projectPathForItemPath(filePath) {
    if (!filePath) { return null; }
    const realFilePath = new File(filePath).getRealPathSync();
    const directory = this.project.getDirectories().find(projectDir => {
      const fixedPath = projectDir.getRealPathSync() + path.sep;
      return realFilePath.startsWith(fixedPath);
    });
    return directory ? directory.getRealPathSync() : null;
  }

  getRepositoryForWorkdirPath(workdirPath) {
    const models = this.modelPromisesByProjectPath.get(workdirPath);
    return models ? models.repositoryPromise : Promise.resolve(null);
  }

  getResolutionProgressForWorkdirPath(workdirPath) {
    const models = this.modelPromisesByProjectPath.get(workdirPath);
    return models ? models.resolutionProgressPromise : Promise.resolve(null);
  }

  getChangeObserverForWorkdirPath(workdirPath) {
    const models = this.modelPromisesByProjectPath.get(workdirPath);
    return models ? models.changeObserverPromise : Promise.resolve(null);
  }

  cacheModelsForPaths(addedPaths) {
    const changeObserverPromises = [];
    const modelsPromises = [];

    const ready = Promise.all(addedPaths.map(async projectPath => {
      // TODO Since we need to await the repositoryPromise to see if a promise is available or not anyway, other
      // calls that are initialized with the promise aren't actually necessary anymore.
      const repositoryPromise = Repository.open(projectPath);
      const repository = await repositoryPromise;
      if (!repository) {
        return;
      }

      const changeObserver = process.platform === 'linux'
        ? new WorkspaceChangeObserver(window, this.workspace, repositoryPromise)
        : new FileSystemChangeObserver(repositoryPromise);
      this.subscriptions.add(
        changeObserver.onDidChange(() => repository.refresh()),
        changeObserver.onDidChangeWorkdirOrHead(() => this.refreshAtomGitRepository(repository)),
      );
      const changeObserverPromise = changeObserver.start();
      changeObserverPromises.push(changeObserverPromise);

      const savedResolutionProgress = this.savedState.resolutionProgressByPath[projectPath] || {};
      const resolutionProgressPromise = repository.getLastCommit()
        .then(commit => new ResolutionProgress(commit.sha, savedResolutionProgress));

      // Technically this should wait on repositoryPromise too, but we know for a fact that it's already
      // resolved.
      modelsPromises.push(resolutionProgressPromise);

      // Once they resolve, stash the models in modelsByProjectPath for synchronous access when it's needed.
      Promise.all([repositoryPromise, changeObserverPromise, resolutionProgressPromise]).then(models => {
        this.modelsByProjectPath.set(projectPath, {
          repository: models[0],
          changeObserver: models[1],
          resolutionProgress: models[2],
        });
      });

      this.modelPromisesByProjectPath.set(projectPath, {
        repositoryPromise,
        changeObserverPromise,
        resolutionProgressPromise,
      });
    }));

    return {
      changeObserversStartedPromise: ready.then(() => Promise.all(changeObserverPromises)),
      modelsPromise: ready.then(() => Promise.all(modelsPromises)),
    };
  }

  destroyModelsForPaths(removedPaths) {
    return Promise.all(
      removedPaths.map(async projectPath => {
        const repositoryPromise = this.getRepositoryForWorkdirPath(projectPath);
        const changeObserverPromise = this.getChangeObserverForWorkdirPath(projectPath);
        this.modelPromisesByProjectPath.delete(projectPath);

        const repository = await repositoryPromise;
        const changeObserver = await changeObserverPromise;
        // repository and changeObserver may be null if the associated workdir was not a Git repo
        repository && repository.destroy();
        changeObserver && await changeObserver.destroy();
      }),
    );
  }
}
<commit_msg>Populate `modelsByProjectPath` on a per-Promise basis rather than .all()<commit_after>import {CompositeDisposable, Disposable, File} from 'atom';

import path from 'path';

import React from 'react';
import ReactDom from 'react-dom';
import {autobind} from 'core-decorators';
import compareSets from 'compare-sets';

import Repository from './models/repository';
import FileSystemChangeObserver from './models/file-system-change-observer';
import WorkspaceChangeObserver from './models/workspace-change-observer';
import ResolutionProgress from './models/conflicts/resolution-progress';
import FilePatchController from './controllers/file-patch-controller';
import GitController from './controllers/git-controller';
import GitTimingsView from './views/git-timings-view';

const defaultState = {
  resolutionProgressByPath: {},
};

export default class GithubPackage {
  constructor(workspace, project, commandRegistry, notificationManager, config, confirm) {
    this.workspace = workspace;
    this.project = project;
    this.commandRegistry = commandRegistry;
    this.notificationManager = notificationManager;
    this.config = config;

    this.modelPromisesByProjectPath = new Map();
    this.modelsByProjectPath = new Map();
    this.confirm = confirm;
    this.activeRepository = null;
    this.activeResolutionProgress = null;

    this.subscriptions = new CompositeDisposable();
    this.savedState = {};
  }

  activate(state = {}) {
    this.savedState = {...defaultState, ...state};

    this.subscriptions = new CompositeDisposable();
    const projectPaths = this.project.getDirectories().map(dir => dir.getRealPathSync());
    const initPromises = this.cacheModelsForPaths(projectPaths);
    this.initWatchersStartedPromise = initPromises.changeObserversStartedPromise;
    this.initModelsPromise = initPromises.modelsPromise;

    this.subscriptions.add(
      this.project.onDidChangePaths(this.didChangeProjectPaths),
      this.workspace.onDidChangeActivePaneItem(this.didChangeActivePaneItem),
      this.config.onDidChange('github.githubEnabled', this.rerender),
      this.workspace.addOpener(uri => {
        if (uri === 'atom-github://debug/timings') {
          return this.createGitTimingsView();
        } else {
          return null;
        }
      }),
    );

    this.deserialize(this.savedState);

    this.rerender();
  }

  serialize() {
    const activeRepository = this.getActiveRepository();

    const resolutionProgressByPath = {};
    this.modelsByProjectPath.forEach((models, projectPath) => {
      const resolutionProgress = models.resolutionProgress;
      if (!resolutionProgress.isEmpty()) {
        resolutionProgressByPath[projectPath] = resolutionProgress.serialize();
      }
    });

    return {
      activeRepositoryPath: activeRepository ? activeRepository.getWorkingDirectoryPath() : null,
      gitController: this.controller.serialize(),
      resolutionProgressByPath,
    };
  }

  async deserialize(state) {
    // Ensure that Repository promises are available
    await this.initModelsPromise;
    let nextActiveRepository, nextActiveResolutionProgress;

    if (state.activeRepositoryPath) {
      const repository = await this.getRepositoryForWorkdirPath(state.activeRepositoryPath);
      if (repository) { nextActiveRepository = repository; }
    }

    if (nextActiveRepository) {
      const savedResolutionProgress = this.savedState.resolutionProgressByPath[state.activeRepositoryPath] || {};
      const commit = await nextActiveRepository.getLastCommit();
      nextActiveResolutionProgress = new ResolutionProgress(commit.sha, savedResolutionProgress);

      this.setActiveModels(nextActiveRepository, nextActiveResolutionProgress);
    } else {
      await this.updateActiveModels();
    }
  }

  @autobind
  rerender() {
    if (!this.element) {
      this.element = document.createElement('div');
      this.subscriptions.add(new Disposable(() => {
        ReactDom.unmountComponentAtNode(this.element);
        delete this.element;
      }));
    }

    ReactDom.render(
      <GitController
        ref={c => { this.controller = c; }}
        workspace={this.workspace}
        commandRegistry={this.commandRegistry}
        notificationManager={this.notificationManager}
        confirm={this.confirm}
        repository={this.getActiveRepository()}
        resolutionProgress={this.getActiveResolutionProgress()}
        statusBar={this.statusBar}
        savedState={this.savedState.gitController}
        githubEnabled={this.config.get('github.githubEnabled')}
      />, this.element,
    );
  }

  async deactivate() {
    this.subscriptions.dispose();
    if (this.destroyedRepositorySubscription) { this.destroyedRepositorySubscription.dispose(); }
    await this.destroyModelsForPaths(Array.from(this.modelPromisesByProjectPath.keys()));
  }

  consumeStatusBar(statusBar) {
    this.statusBar = statusBar;
    this.rerender();
  }

  createGitTimingsView() {
    return GitTimingsView.createPaneItem();
  }

  @autobind
  async didChangeProjectPaths(projectPaths) {
    this.updateActiveModels();
    const previousProjectPaths = Array.from(this.modelPromisesByProjectPath.keys());
    const {added, removed} = compareSets(new Set(previousProjectPaths), new Set(projectPaths));
    this.cacheModelsForPaths(Array.from(added));
    await this.destroyModelsForPaths(Array.from(removed));
  }

  @autobind
  didChangeActivePaneItem() {
    this.updateActiveModels();
  }

  getInitialWatchersStartedPromise() {
    return this.initWatchersStartedPromise;
  }

  getInitialModelsPromise() {
    return this.initModelsPromise;
  }

  getActiveRepository() {
    return this.activeRepository;
  }

  getActiveResolutionProgress() {
    return this.activeResolutionProgress;
  }

  async updateActiveModels() {
    await this.initModelsPromise;
    let nextActiveRepository, nextActiveResolutionProgress;

    const activeItem = this.workspace.getActivePaneItem();
    if (activeItem && typeof activeItem.getPath === 'function') {
      const projectPath = this.projectPathForItemPath(activeItem.getPath());
      if (projectPath) {
        await Promise.all([
          this.getRepositoryForWorkdirPath(projectPath).then(r => { nextActiveRepository = r; }),
          this.getResolutionProgressForWorkdirPath(projectPath).then(rp => { nextActiveResolutionProgress = rp; }),
        ]);
      }
    } else {
      nextActiveRepository = this.activeRepository;
      nextActiveResolutionProgress = this.activeResolutionProgress;
    }

    if (activeItem instanceof FilePatchController) {
      if (!activeItem.props.repository.isDestroyed()) {
        nextActiveRepository = activeItem.props.repository;
        nextActiveResolutionProgress = activeItem.props.resolutionProgress;
      }
    }

    if (!activeItem && this.project.getPaths().length === 1) {
      nextActiveRepository = await this.getRepositoryForWorkdirPath(this.project.getPaths()[0]);
    }

    if (nextActiveRepository && nextActiveRepository !== this.activeRepository) {
      this.setActiveModels(nextActiveRepository, nextActiveResolutionProgress);

      if (this.destroyedRepositorySubscription) { this.destroyedRepositorySubscription.dispose(); }
      this.destroyedRepositorySubscription = nextActiveRepository.onDidDestroy(() => {
        if (nextActiveRepository === this.activeRepository) {
          this.setActiveModels(null);
        }
        this.destroyedRepositorySubscription.dispose();
      });
    }

    if (!nextActiveRepository) {
      this.setActiveModels(null, null);
    }
  }

  setActiveModels(repository, resolutionProgress) {
    this.activeRepository = repository;
    this.activeResolutionProgress = resolutionProgress;
    this.rerender();
  }

  refreshAtomGitRepository(repository) {
    const repoPath = repository.getWorkingDirectoryPath();
    const atomGitRepo = this.project.getRepositories().find(repo => {
      return repo && path.normalize(repo.getWorkingDirectory()) === repoPath;
    });
    return atomGitRepo ? atomGitRepo.refreshStatus() : Promise.resolve();
  }

  projectPathForItemPath(filePath) {
    if (!filePath) { return null; }
    const realFilePath = new File(filePath).getRealPathSync();
    const directory = this.project.getDirectories().find(projectDir => {
      const fixedPath = projectDir.getRealPathSync() + path.sep;
      return realFilePath.startsWith(fixedPath);
    });
    return directory ? directory.getRealPathSync() : null;
  }

  getRepositoryForWorkdirPath(workdirPath) {
    const models = this.modelPromisesByProjectPath.get(workdirPath);
    return models ? models.repositoryPromise : Promise.resolve(null);
  }

  getResolutionProgressForWorkdirPath(workdirPath) {
    const models = this.modelPromisesByProjectPath.get(workdirPath);
    return models ? models.resolutionProgressPromise : Promise.resolve(null);
  }

  getChangeObserverForWorkdirPath(workdirPath) {
    const models = this.modelPromisesByProjectPath.get(workdirPath);
    return models ? models.changeObserverPromise : Promise.resolve(null);
  }

  cacheModelsForPaths(addedPaths) {
    const changeObserverPromises = [];
    const modelsPromises = [];

    const ready = Promise.all(addedPaths.map(async projectPath => {
      // TODO Since we need to await the repositoryPromise to see if a promise is available or not anyway, other
      // calls that are initialized with the promise aren't actually necessary anymore.
      const repositoryPromise = Repository.open(projectPath);
      const repository = await repositoryPromise;
      if (!repository) {
        return;
      }

      const changeObserver = process.platform === 'linux'
        ? new WorkspaceChangeObserver(window, this.workspace, repositoryPromise)
        : new FileSystemChangeObserver(repositoryPromise);
      this.subscriptions.add(
        changeObserver.onDidChange(() => repository.refresh()),
        changeObserver.onDidChangeWorkdirOrHead(() => this.refreshAtomGitRepository(repository)),
      );
      const changeObserverPromise = changeObserver.start();
      changeObserverPromises.push(changeObserverPromise);

      const savedResolutionProgress = this.savedState.resolutionProgressByPath[projectPath] || {};
      const resolutionProgressPromise = repository.getLastCommit()
        .then(commit => new ResolutionProgress(commit.sha, savedResolutionProgress));

      // Technically this should wait on repositoryPromise too, but we know for a fact that it's already
      // resolved.
      modelsPromises.push(resolutionProgressPromise);

      // Once they resolve, stash the models in modelsByProjectPath for synchronous access when they're needed.
      const models = {
        repository,
        changeObserver: null,
        resolutionProgress: null,
      };
      this.modelsByProjectPath.set(projectPath, models);
      changeObserverPromise.then(() => {
        models.changeObserver = changeObserver;
      });
      resolutionProgressPromise.then(rp => {
        models.resolutionProgress = rp;
      });

      this.modelPromisesByProjectPath.set(projectPath, {
        repositoryPromise,
        changeObserverPromise,
        resolutionProgressPromise,
      });
    }));

    return {
      changeObserversStartedPromise: ready.then(() => Promise.all(changeObserverPromises)),
      modelsPromise: ready.then(() => Promise.all(modelsPromises)),
    };
  }

  destroyModelsForPaths(removedPaths) {
    return Promise.all(
      removedPaths.map(async projectPath => {
        const repositoryPromise = this.getRepositoryForWorkdirPath(projectPath);
        const changeObserverPromise = this.getChangeObserverForWorkdirPath(projectPath);
        this.modelPromisesByProjectPath.delete(projectPath);

        const repository = await repositoryPromise;
        const changeObserver = await changeObserverPromise;
        // repository and changeObserver may be null if the associated workdir was not a Git repo
        repository && repository.destroy();
        changeObserver && await changeObserver.destroy();
      }),
    );
  }
}
<|endoftext|>


<commit_before>/*
 *	Glossy Producer - Generate valid syslog messages
 *
 *	Copyright Squeeks <privacymyass@gmail.com>.
 * 	This is free software licensed under the MIT License -
 * 	see the LICENSE file that should be included with this package.
 */

/*
 *	These values replace the integers in message that define the facility.
 */
Facility = {
	'kern':   0,  // kernel messages
	'user':   1,  // user-level messages
	'mail':   2,  // mail system
	'daemon': 3,  // system daemons
	'auth':   4,  // security/authorization messages
	'syslog': 5,  // messages generated internally by syslogd
	'lpr':    6,  // line printer subsystem
	'news':   7,  // network news subsystem
	'uucp':   8,  // UUCP subsystem
	'clock':  9,  // clock daemon
	'sec':    10, // security/authorization messages
	'ftp':    11, // FTP daemon
	'ntp':    12, // NTP subsystem
	'audit':  13, // log audit
	'alert':  14, // log alert
	'clock':  15, // clock daemon (note 2)
	'local0': 16, // local use 0  (local0)
	'local1': 17, // local use 1  (local1)
	'local2': 18, // local use 2  (local2)
	'local3': 19, // local use 3  (local3)
	'local4': 20, // local use 4  (local4)
	'local5': 21, // local use 5  (local5)
	'local6': 22, // local use 6  (local6)
	'local7': 23  // local use 7  (local7)
};

// Note 1 - Various operating systems have been found to utilize
//           Facilities 4, 10, 13 and 14 for security/authorization,
//           audit, and alert messages which seem to be similar.

// Note 2 - Various operating systems have been found to utilize
//           both Facilities 9 and 15 for clock (cron/at) messages.

/*
 *	These values replace the integers in message that define the severity.
 */
SeverityIndex = {
	'emerg': 0,                 // Emergency: system is unusable
	'emergency': 0,

	'alert': 1,                 // Alert: action must be taken immediately

	'crit': 2,                  // Critical: critical conditions
	'critical': 2,

	'err': 3,                   // Error: error conditions
	'error': 3,

	'warn': 4,                  // Warning: warning conditions
	'warning': 4,

	'notice': 5,                // Notice: normal but significant condition

	'info': 6  ,                // Informational: informational messages
	'information': 6,
	'informational': 6,

	'debug':  7                 // Debug: debug-level messages
};

/*
 *	Defines the range matching BSD style months to integers.
 */
BSDDateIndex = {
	'Jan': 1,
	'Feb': 2,
	'Mar': 3,
	'Apr': 4,
	'May': 5,
	'Jun': 6,
	'Jul': 7,
	'Aug': 8,
	'Sep': 9,
	'Oct': 10,
	'Nov': 11,
	'Dec': 12
}

/*
 *
 */
var SyslogProduce = function(messageFormat) {

	this.messageFormat = messageFormat;
};

/*
 * 	@param {Object} options object containing details of the message:
 * 		facility: The facility index
 *		severity: Severity index
 *		app_id: Application ID
 *		date: Timestamp to be applied, uses current GMT by default
 *		message: The message to be sent
 *
 *	@param {Function} callback a callback run once the message is built
 */
SyslogProduce.prototype.produce = function(options, callback) {

	if(!this.messageFormat)	return false;

	//TODO: Designate between BSD and 5424 styles, handle them as such

	if(callback) {
		return callback();
	} else {

	}

};

/*
 *	Prepend a zero to a number less than 10
 *	@param {Number} n
 *	@return {String}
 *
 *	Where's sprintf when you need it?
 */
function leadZero(n) {
	if(typeof n != 'number') return n;
	n = n < 10 ? '0' + n : n ;
	return n;
}

/*
 *	Get current date in RFC 3164 format. If no date is supplied, the default
 *	is the current time in GMT + 0.
 *	@param {Date} dateObject optional Date object
 *	@returns {String}
 *
 *	Features code taken from https://github.com/akaspin/ain
 */
function generateBSDDate(dateObject) {
	if(!dateObject) dateObject = new Date();
	var hours   = leadZero(dateObject.getHours());
	var minutes = leadZero(dateObject.getMinutes());
	var seconds = leadZero(dateObject.getSeconds());
	var month   = dateObject.getMonth();
	var day     = dateObject.getDate();
	(day < 10) && (day = ' ' + day);
	return BSDDateIndex[month] + " " + day + " " + hours + ":" + minutes
		+ ":" + seconds;
}

module.exports = SyslogProduce;

<commit_msg>Adding missing var declartions from produce.js<commit_after>/*
 *	Glossy Producer - Generate valid syslog messages
 *
 *	Copyright Squeeks <privacymyass@gmail.com>.
 * 	This is free software licensed under the MIT License -
 * 	see the LICENSE file that should be included with this package.
 */

/*
 *	These values replace the integers in message that define the facility.
 */
var Facility = {
	'kern':   0,  // kernel messages
	'user':   1,  // user-level messages
	'mail':   2,  // mail system
	'daemon': 3,  // system daemons
	'auth':   4,  // security/authorization messages
	'syslog': 5,  // messages generated internally by syslogd
	'lpr':    6,  // line printer subsystem
	'news':   7,  // network news subsystem
	'uucp':   8,  // UUCP subsystem
	'clock':  9,  // clock daemon
	'sec':    10, // security/authorization messages
	'ftp':    11, // FTP daemon
	'ntp':    12, // NTP subsystem
	'audit':  13, // log audit
	'alert':  14, // log alert
	'clock':  15, // clock daemon (note 2)
	'local0': 16, // local use 0  (local0)
	'local1': 17, // local use 1  (local1)
	'local2': 18, // local use 2  (local2)
	'local3': 19, // local use 3  (local3)
	'local4': 20, // local use 4  (local4)
	'local5': 21, // local use 5  (local5)
	'local6': 22, // local use 6  (local6)
	'local7': 23  // local use 7  (local7)
};

// Note 1 - Various operating systems have been found to utilize
//           Facilities 4, 10, 13 and 14 for security/authorization,
//           audit, and alert messages which seem to be similar.

// Note 2 - Various operating systems have been found to utilize
//           both Facilities 9 and 15 for clock (cron/at) messages.

/*
 *	These values replace the integers in message that define the severity.
 */
var SeverityIndex = {
	'emerg': 0,                 // Emergency: system is unusable
	'emergency': 0,

	'alert': 1,                 // Alert: action must be taken immediately

	'crit': 2,                  // Critical: critical conditions
	'critical': 2,

	'err': 3,                   // Error: error conditions
	'error': 3,

	'warn': 4,                  // Warning: warning conditions
	'warning': 4,

	'notice': 5,                // Notice: normal but significant condition

	'info': 6  ,                // Informational: informational messages
	'information': 6,
	'informational': 6,

	'debug':  7                 // Debug: debug-level messages
};

/*
 *	Defines the range matching BSD style months to integers.
 */
var BSDDateIndex = {
	'Jan': 1,
	'Feb': 2,
	'Mar': 3,
	'Apr': 4,
	'May': 5,
	'Jun': 6,
	'Jul': 7,
	'Aug': 8,
	'Sep': 9,
	'Oct': 10,
	'Nov': 11,
	'Dec': 12
}

/*
 *
 */
var SyslogProduce = function(messageFormat) {

	this.messageFormat = messageFormat;
};

/*
 * 	@param {Object} options object containing details of the message:
 * 		facility: The facility index
 *		severity: Severity index
 *		app_id: Application ID
 *		date: Timestamp to be applied, uses current GMT by default
 *		message: The message to be sent
 *
 *	@param {Function} callback a callback run once the message is built
 */
SyslogProduce.prototype.produce = function(options, callback) {

	if(!this.messageFormat)	return false;

	//TODO: Designate between BSD and 5424 styles, handle them as such

	if(callback) {
		return callback();
	} else {

	}

};

/*
 *	Prepend a zero to a number less than 10
 *	@param {Number} n
 *	@return {String}
 *
 *	Where's sprintf when you need it?
 */
function leadZero(n) {
	if(typeof n != 'number') return n;
	n = n < 10 ? '0' + n : n ;
	return n;
}

/*
 *	Get current date in RFC 3164 format. If no date is supplied, the default
 *	is the current time in GMT + 0.
 *	@param {Date} dateObject optional Date object
 *	@returns {String}
 *
 *	Features code taken from https://github.com/akaspin/ain
 */
function generateBSDDate(dateObject) {
	if(!dateObject) dateObject = new Date();
	var hours   = leadZero(dateObject.getHours());
	var minutes = leadZero(dateObject.getMinutes());
	var seconds = leadZero(dateObject.getSeconds());
	var month   = dateObject.getMonth();
	var day     = dateObject.getDate();
	(day < 10) && (day = ' ' + day);
	return BSDDateIndex[month] + " " + day + " " + hours + ":" + minutes
		+ ":" + seconds;
}

module.exports = SyslogProduce;

<|endoftext|>


<commit_before>'use strict';

var assert = require('assert');

var Pdf = require('../index.js'),
	child = require('../child.js'),
	FP = process.env.PWD || process.cwd() || __dirname;

describe('child#supports()', function(){
	it('checks to see if phantomjs is installed', function(d){
		child.supports(function(exists){
			assert.ok(exists);
			d();
		});
	});
});

describe('child#supports()', function(){
	it('checks to see if foobarbaz is installed', function(d){
		child.supports(function(exists){
			assert.ok(!exists);
			d();
		}, 'foobarbaz');
	});
});

// google vs yahoo

describe('pdf#done() 1', function(){
	it('fires done when ', function(d){
		this.timeout(20000);
		var pdf1 = new Pdf('http://www.google.com', 'google.pdf');
		pdf1.on('done', function(msg){
			assert.ok(msg);
			assert.equal(FP + '/google.pdf', msg);
			d();
		});
		pdf1.on('error', function(msg){
			assert.ok(false);
			d();
		});
	});
});

describe('pdf#content', function() {
	it('fires done when content is loaded', function(d) {
		var pdf1 = new Pdf(null, 'google.pdf', {
			'content': ''
		});
		pdf1.on('done', function(msg){
			assert.ok(msg);
			assert.equal(FP + '/google.pdf', msg);
			d();
		});
		pdf1.on('error', function(msg){
			console.log(msg)
			assert.ok(false);
			d();
		});
	})
})

describe('pdf#done() 2', function(){
	it('fires done when ', function(d){
		this.timeout(20000);
		var pdf2 = new Pdf('http://yahoo.com', 'yahoo.pdf', {
			'viewportSize': {
				'width': 3000,
				'height': 9000
			},
			'paperSize': {
				'pageFormat': 'A4',
				'margin': {
					'top': '2cm'
				},
				'header': {
					'height': '4cm',
					'contents': 'HEADER {currentPage} / {pages}'
				},
				'footer': {
					'height': '4cm',
					'contents': 'FOOTER {currentPage} / {pages}'
				}
			},
			'zoomFactor': 1.1,
			'cookies': [
				{
					'name':     'Valid-Cookie-Name 1',   /* required property */
					'value':    'Valid-Cookie-Value 1',  /* required property */
					'domain':   'localhost',           /* required property */
					'path':     '/foo',
					'httponly': true,
					'secure':   false,
					'expires':  (new Date()).getTime() + (1000 * 60 * 60)   /* <-- expires in 1 hour */
				},
				{
					'name':     'Valid-Cookie-Name 2',
					'value':    'Valid-Cookie-Value 2',
					'domain':   'localhost'
				}
			]
		});
		pdf2.on('done', function(msg){
			assert.ok(msg);
			assert.equal(FP + '/yahoo.pdf', msg);
			d();
		});
		pdf2.on('error', function(msg){
			assert.ok(false);
			console.log(msg);
		});
	});
});
<commit_msg>cleanup test<commit_after>'use strict';

var assert = require('assert');

var Pdf = require('../index.js'),
	child = require('../child.js'),
	FP = process.env.PWD || process.cwd() || __dirname;

describe('child#supports()', function(){
	it('checks to see if phantomjs is installed', function(d){
		child.supports(function(exists){
			assert.ok(exists);
			d();
		});
	});
});

describe('child#supports()', function(){
	it('checks to see if foobarbaz is installed', function(d){
		child.supports(function(exists){
			assert.ok(!exists);
			d();
		}, 'foobarbaz');
	});
});

// google vs yahoo

describe('pdf#done() 1', function(){
	it('fires done when ', function(d){
		this.timeout(20000);
		var pdf1 = new Pdf('http://www.google.com', 'google.pdf');
		pdf1.on('done', function(msg){
			assert.ok(msg);
			assert.equal(FP + '/google.pdf', msg);
			d();
		});
		pdf1.on('error', function(msg){
			assert.ok(false);
			d();
		});
	});
});

describe('pdf#content', function() {
	it('fires done when content is loaded', function(d) {
		var pdf1 = new Pdf(null, 'google.pdf', {
			'content': ''
		});
		pdf1.on('done', function(msg){
			assert.ok(msg);
			assert.equal(FP + '/google.pdf', msg);
			d();
		});
		pdf1.on('error', function(msg){
			console.log(msg)
			assert.ok(false);
			d();
		});
	})
})

describe('pdf#done() 2', function(){
	it('fires done when ', function(d){
		this.timeout(20000);
		var pdf2 = new Pdf('http://yahoo.com', 'yahoo.pdf', {
			'viewportSize': {
				'width': 3000,
				'height': 9000
			},
			'paperSize': {
				'pageFormat': 'A4',
				'margin': {
					'top': '2cm'
				},
				'header': {
					'height': '4cm',
					'contents': 'HEADER {currentPage} / {pages}'
				},
				'footer': {
					'height': '4cm',
					'contents': 'FOOTER {currentPage} / {pages}'
				}
			},
			'zoomFactor': 1.1,
			'cookies': [
				{
					'name':     'Valid-Cookie-Name 1',   /* required property */
					'value':    'Valid-Cookie-Value 1',  /* required property */
					'domain':   'localhost',           /* required property */
					'path':     '/foo',
					'httponly': true,
					'secure':   false,
					'expires':  (new Date()).getTime() + (1000 * 60 * 60)   /* <-- expires in 1 hour */
				},
				{
					'name':     'Valid-Cookie-Name 2',
					'value':    'Valid-Cookie-Value 2',
					'domain':   'localhost'
				}
			]
		});
		pdf2.on('done', function(msg){
			assert.ok(msg);
			assert.equal(FP + '/yahoo.pdf', msg);
			d();
		});
		pdf2.on('error', function(msg){
			console.log(msg);
			assert.ok(false);
			d();
		});
	});
});
<|endoftext|>


<commit_before>// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngResource'])

.config(function($stateProvider, $urlRouterProvider, $httpProvider) {

  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider.state('index', {
    url:"/index",
    abstract: true,
    templateURL: 'client/index.html'
  })
  .state('index.home', {
    url: "/home",
    views:  {
    'index-home' : {
    templateUrl: "templates/home.html",
    controller: 'HomeCtrl'
    }
    }
  })

  .state('register', {
    url: "/register",
    templateUrl: "templates/two.html",
    controller: 'RegisterCtrl'
  })

  .state('login', {
    url: "/login",
    templateUrl: "templates/one.html",
    controller: 'LoginCtrl'
  })

  .state('one', {
    url: "/one",
    templateURL: "templates/one.html"
  })

   .state('two', {
   url: "/two",
   templateURL: "templates/two.html",
   controller: 'TwoCtrl'
  })

   .state('three', {
    url: "/three",
    templateURL: "templates/three.html"
  })

   .state('all', {
   url: "/all",
   templateURL: "templates/all.html",
   controller: 'HomeCtrl'
  })

  .state('1B1', {
  url: "/1B1",
  templateURL: "templates/1B1.html",
  controller: '1B1Ctrl'
  })

   .state('1B2', {
   url: "/1B2",
   templateURL: "templates/1B2.html",
   controller: '1B2Ctrl'
  })

  .state('1B3', {
  url: "/1B3",
  templateURL: "templates/1B3.html",
  controller: '1B3Ctrl'
  })

  .state('2B1', {
  url: "/2B1",
  templateURL: "templates/2B1.html",
  controller: '2B1Ctrl'
  })

   .state('2B2', {
   url: "/2B2",
   templateURL: "templates/2B2.html",
   controller: '2B2Ctrl'
  })

  .state('2B3', {
  url: "/2B3",
  templateURL: "templates/2B3.html",
  controller: '2B3Ctrl'
  })

    .state('2B4', {
  url: "/2B4",
  templateURL: "templates/2B4.html",
  controller: '2B4Ctrl'
  })

   .state('2B5', {
   url: "/2B5",
   templateURL: "templates/2B5.html",
   controller: '2B5Ctrl'
  })

  .state('2B6', {
  url: "/2B6",
  templateURL: "templates/2B6.html",
  controller: '2B6Ctrl'
  })

    .state('3B1', {
  url: "/3B1",
  templateURL: "templates/3B1.html",
  controller: '3B1Ctrl'
  })

   .state('3B2', {
   url: "/3B2",
   templateURL: "templates/3B2.html",
   controller: '3B2Ctrl'
  })

  .state('3B3', {
  url: "/3B3",
  templateURL: "templates/3B3.html",
  controller: '3B3Ctrl'
  })

    .state('3B4', {
  url: "/3B4",
  templateURL: "templates/3B4.html",
  controller: '3B4Ctrl'
  })

   .state('3B5', {
   url: "/3B5",
   templateURL: "templates/3B5.html",
   controller: '3B5Ctrl'
  })

  .state('3B6', {
  url: "/3B6",
  templateURL: "templates/3B6.html",
  controller: '3B6Ctrl'
  })

  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise('/home');

  // Register middleware to ensure our auth token is passed to the server
  $httpProvider.interceptors.push('TokenInterceptor');

})

.run(function($window, $location, $ionicPlatform, $rootScope, AuthenticationService) {
  $rootScope.user = {
    name: $window.sessionStorage.name,
    is_admin: $window.sessionStorage.is_admin
  };

  if ($rootScope.user.is_admin) {
    AuthenticationService.isAdmin = true;
  }

  $rootScope.$on("$stateChangeStart", function(event, toState) {
    //redirect only if both isAuthenticated is false and no token is set

    if (['home', 'login', 'logout', 'register', 'one', 'two', 'three', 'all', '1B1', '1B2', '1B3', '2B1', '2B2', '2B3', '2B4', '2B5', '2B6', '3B1', '3B2', '3B3', '3B4', '3B5', '3B6'].indexOf(toState.name) === -1) {
      if (!AuthenticationService.isAuthenticated && !$window.localStorage.token) {
        event.preventDefault();
        $location.path("/home");
      }
    }

  });

  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
  });
})
<commit_msg>Update app.js<commit_after>// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngResource'])

.config(function($stateProvider, $urlRouterProvider, $httpProvider) {

  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider.state('index', {
    url:"/index",
    abstract: true,
    templateURL: 'client/index.html'
  })
  .state('index.home', {
    url: "/home",
    views:  {
    'index-home' : {
    templateUrl: "templates/home.html",
    controller: 'HomeCtrl'
    }
    }
  })

  .state('register', {
    url: "/register",
    templateUrl: "templates/two.html",
    controller: 'RegisterCtrl'
  })

  .state('login', {
    url: "/login",
    templateUrl: "templates/one.html",
    controller: 'LoginCtrl'
  })

  .state('one', {
    url: "/one",
    templateURL: "templates/one.html"
  })

   .state('two', {
   url: "/two",
   templateURL: "templates/two.html",
   controller: 'TwoCtrl'
  })

   .state('three', {
    url: "/three",
    templateURL: "templates/three.html"
  })

   .state('all', {
   url: "/all",
   templateURL: "templates/all.html",
   controller: 'HomeCtrl'
  })

  .state('1B1', {
  url: "/1B1",
  templateURL: "templates/1B1.html",
  controller: '1B1Ctrl'
  })

   .state('1B2', {
   url: "/1B2",
   templateURL: "templates/1B2.html",
   controller: '1B2Ctrl'
  })

  .state('1B3', {
  url: "/1B3",
  templateURL: "templates/1B3.html",
  controller: '1B3Ctrl'
  })

  .state('2B1', {
  url: "/2B1",
  templateURL: "templates/2B1.html",
  controller: '2B1Ctrl'
  })

   .state('2B2', {
   url: "/2B2",
   templateURL: "templates/2B2.html",
   controller: '2B2Ctrl'
  })

  .state('2B3', {
  url: "/2B3",
  templateURL: "templates/2B3.html",
  controller: '2B3Ctrl'
  })

    .state('2B4', {
  url: "/2B4",
  templateURL: "templates/2B4.html",
  controller: '2B4Ctrl'
  })

   .state('2B5', {
   url: "/2B5",
   templateURL: "templates/2B5.html",
   controller: '2B5Ctrl'
  })

  .state('2B6', {
  url: "/2B6",
  templateURL: "templates/2B6.html",
  controller: '2B6Ctrl'
  })

    .state('3B1', {
  url: "/3B1",
  templateURL: "templates/3B1.html",
  controller: '3B1Ctrl'
  })

   .state('3B2', {
   url: "/3B2",
   templateURL: "templates/3B2.html",
   controller: '3B2Ctrl'
  })

  .state('3B3', {
  url: "/3B3",
  templateURL: "templates/3B3.html",
  controller: '3B3Ctrl'
  })

    .state('3B4', {
  url: "/3B4",
  templateURL: "templates/3B4.html",
  controller: '3B4Ctrl'
  })

   .state('3B5', {
   url: "/3B5",
   templateURL: "templates/3B5.html",
   controller: '3B5Ctrl'
  })

  .state('3B6', {
  url: "/3B6",
  templateURL: "templates/3B6.html",
  controller: '3B6Ctrl'
  })

  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise('/index/home');

  // Register middleware to ensure our auth token is passed to the server
  $httpProvider.interceptors.push('TokenInterceptor');

})

.run(function($window, $location, $ionicPlatform, $rootScope, AuthenticationService) {
  $rootScope.user = {
    name: $window.sessionStorage.name,
    is_admin: $window.sessionStorage.is_admin
  };

  if ($rootScope.user.is_admin) {
    AuthenticationService.isAdmin = true;
  }

  $rootScope.$on("$stateChangeStart", function(event, toState) {
    //redirect only if both isAuthenticated is false and no token is set

    if (['home', 'login', 'logout', 'register', 'one', 'two', 'three', 'all', '1B1', '1B2', '1B3', '2B1', '2B2', '2B3', '2B4', '2B5', '2B6', '3B1', '3B2', '3B3', '3B4', '3B5', '3B6'].indexOf(toState.name) === -1) {
      if (!AuthenticationService.isAuthenticated && !$window.localStorage.token) {
        event.preventDefault();
        $location.path("/home");
      }
    }

  });

  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
  });
})
<|endoftext|>


<commit_before>/*

browserclient.js
================

Model objects (via Backbone) for a browser client (a connection to a browser + the test run session)
and the returned test results for a run of tests.

*/

var log = require('winston')
  , Backbone = require('backbone')
  , tap = require('tap')

var TestResults = Backbone.Model.extend({
    initialize: function(){
        this.reset()
    }
    , reset: function(){
        this.set({
            topLevelError: null
            , failed: 0
            , passed: 0
            , total: 0
            , tests: new Backbone.Collection
            , all: false
        })
    }
    , addResult: function(result){
        var total = this.get('total')
          , passed = this.get('passed')
          , failed = this.get('failed')
        total++
        if (result.failed == 0)
            passed++
        else
            failed++
        this.set({
            total: total
            , passed: passed
            , failed: failed
            , items: result.items
        })
        this.get('tests').push(result)
    }
})

exports.TestResults = TestResults

var BrowserRunner = Backbone.Model.extend({
    defaults: {
        type: 'browser'
    }
    , initialize: function(attrs){
        var client = this.client = attrs.client
        this.app = attrs.app
        this.server = this.app.server
        this.set({
            name: null
            , messages: new Backbone.Collection
            , results: new TestResults
        })
        var self = this
          , server = this.server
          , results = this.get('results')
          , messages = this.get('messages')

        client.on('top-level-error', function(msg, url, line){
            var message = msg + ' at ' + url + ', line ' + line + '\n'
            messages.push({
                type: 'error'
                , text: message
            })
        })
        client.on('error', function(message){
            messages.push({
                type: 'error'
                , text: message
            })
        })
        client.on('warn', function(message){
            messages.push({
                type: 'warn'
                , text: message
            })
        })
        client.on('log', function(message){
            messages.push({
                type: 'log'
                , text: message
            })
        })
        client.on('browser-login', function(browserName){
            self.set('name', browserName)
        })
        client.on('tests-start', function(){
            self.trigger('tests-start')
        })
        client.on('test-result', function(result){
            results.addResult(result)
            self.server.emit('test-result', result)
        })
        client.on('all-test-results', function(){
            results.set('all', true)
            self.trigger('tests-end')
            self.app.emit('all-test-results', results, self)
        })
        client.on('disconnect', function(){
            server.removeBrowser(self)
        })

    }
    , startTests: function(){
        this.get('results').reset()
        this.client.emit('start-tests')
    }
    , hasResults: function(){
        var results = this.get('results')
        var total = results.get('total')
        return total > 0
    }
    , hasMessages: function(){
        return this.get('messages').length > 0
    }
})

exports.BrowserRunner = BrowserRunner

var ProcessRunner = Backbone.Model.extend({
    defaults: {
        type: 'process'
    }
    , initialize: function(attrs){
        this.launcher = attrs.launcher
        this.app = attrs.app
        // Assume launcher has already launched
        this.set({
            name: this.launcher.name
            , messages: new Backbone.Collection
            , results: this.isTap() ? new TestResults : null
        })

        this.startTests()
    }
    , isTap: function(){
        return this.launcher.settings.protocol === 'tap'
    }
    , hasResults: function(){
        return this.isTap()
    }
    , hasMessages: function(){
        return this.get('messages').length > 0
    }
    , registerProcess: function(process){
        var settings = this.launcher.settings
        var stdout = process.stdout
        var stderr = process.stderr
        var self = this
        if (!settings.hide_stdout){
            stdout.on('data', function(data){
                self.get('messages').push({
                    type: 'log'
                    , text: '' + data
                })
            })
        }
        if (!settings.hide_stderr){
            stderr.on('data', function(data){
                self.get('messages').push({
                    type: 'error'
                    , text: '' + data
                })
            })
        }
        process.on('exit', function(code){
            self.set('allPassed', code === 0)
            self.trigger('tests-end')
        })
        if (this.isTap()){
            this.setupTapConsumer(process)
        }
    }
    , setupTapConsumer: function(process){
        var stdout = process.stdout
        this.message = null
        this.stacktrace = []
        this.tapConsumer = new tap.Consumer
        this.tapConsumer.on('data', this.onTapData.bind(this))
        this.tapConsumer.on('end', this.onTapEnd.bind(this))
        this.tapConsumer.on('bailout', this.onTapError.bind(this))
        stdout.pipe(this.tapConsumer)
    }
    , onTapData: function(data){
        if (typeof data === 'string'){
            if (this.message === null){
                this.message = data
            }else{
                this.stacktrace.push(data)
            }
            return
        }
        if (data.skip){
            return
        }
        var results = this.get('results')

        if (data.id === undefined) {
            return
        }

        var test = {
            passed: 0
            , failed: 0
            , total: 1
            , id: data.id
            , name: data.name.trim()
            , items: []
        }

        if (!data.ok) {
            test.items.push({
                passed: false
                , message: this.message
                , stacktrace: this.stacktrace.join('\n')
            })
            test.failed++
        } else {
            test.passed++
        }
        results.addResult(test)
        this.message = null
        this.stacktrace = []
    }
    , onTapError: function(){
        this.set('results', null)
    }
    , onTapEnd: function(err, testCount){
        var results = this.get('results')
        results.set('all', true)
        this.tapConsumer.removeAllListeners()
        this.tapConsumer = null
        this.app.emit('all-test-results', results)
        this.launcher.kill()
    }
    , startTests: function(){
        var self = this
        if (this.get('results')){
            this.get('results').reset()
        }else{
            this.set('results', this.isTap() ? new TestResults : null)
        }
        this.get('messages').reset([])
        this.set('allPassed', undefined)

        this.launcher.launch(function(process){
            self.registerProcess(process)
            setTimeout(function(){
                self.trigger('tests-start')
            }, 1)
        })
    }
})

exports.ProcessRunner = ProcessRunner

<commit_msg>Fixed browser's console.log new line endings.<commit_after>/*

browserclient.js
================

Model objects (via Backbone) for a browser client (a connection to a browser + the test run session)
and the returned test results for a run of tests.

*/

var log = require('winston')
  , Backbone = require('backbone')
  , tap = require('tap')

var TestResults = Backbone.Model.extend({
    initialize: function(){
        this.reset()
    }
    , reset: function(){
        this.set({
            topLevelError: null
            , failed: 0
            , passed: 0
            , total: 0
            , tests: new Backbone.Collection
            , all: false
        })
    }
    , addResult: function(result){
        var total = this.get('total')
          , passed = this.get('passed')
          , failed = this.get('failed')
        total++
        if (result.failed == 0)
            passed++
        else
            failed++
        this.set({
            total: total
            , passed: passed
            , failed: failed
            , items: result.items
        })
        this.get('tests').push(result)
    }
})

exports.TestResults = TestResults

var BrowserRunner = Backbone.Model.extend({
    defaults: {
        type: 'browser'
    }
    , initialize: function(attrs){
        var client = this.client = attrs.client
        this.app = attrs.app
        this.server = this.app.server
        this.set({
            name: null
            , messages: new Backbone.Collection
            , results: new TestResults
        })
        var self = this
          , server = this.server
          , results = this.get('results')
          , messages = this.get('messages')

        client.on('top-level-error', function(msg, url, line){
            var message = msg + ' at ' + url + ', line ' + line + '\n'
            messages.push({
                type: 'error'
                , text: message
            })
        })
        client.on('error', function(message){
            messages.push({
                type: 'error'
                , text: message + '\n'
            })
        })
        client.on('warn', function(message){
            messages.push({
                type: 'warn'
                , text: message + '\n'
            })
        })
        client.on('log', function(message){
            messages.push({
                type: 'log'
                , text: message + '\n'
            })
        })
        client.on('browser-login', function(browserName){
            self.set('name', browserName)
        })
        client.on('tests-start', function(){
            self.trigger('tests-start')
        })
        client.on('test-result', function(result){
            results.addResult(result)
            self.server.emit('test-result', result)
        })
        client.on('all-test-results', function(){
            results.set('all', true)
            self.trigger('tests-end')
            self.app.emit('all-test-results', results, self)
        })
        client.on('disconnect', function(){
            server.removeBrowser(self)
        })

    }
    , startTests: function(){
        this.get('results').reset()
        this.client.emit('start-tests')
    }
    , hasResults: function(){
        var results = this.get('results')
        var total = results.get('total')
        return total > 0
    }
    , hasMessages: function(){
        return this.get('messages').length > 0
    }
})

exports.BrowserRunner = BrowserRunner

var ProcessRunner = Backbone.Model.extend({
    defaults: {
        type: 'process'
    }
    , initialize: function(attrs){
        this.launcher = attrs.launcher
        this.app = attrs.app
        // Assume launcher has already launched
        this.set({
            name: this.launcher.name
            , messages: new Backbone.Collection
            , results: this.isTap() ? new TestResults : null
        })

        this.startTests()
    }
    , isTap: function(){
        return this.launcher.settings.protocol === 'tap'
    }
    , hasResults: function(){
        return this.isTap()
    }
    , hasMessages: function(){
        return this.get('messages').length > 0
    }
    , registerProcess: function(process){
        var settings = this.launcher.settings
        var stdout = process.stdout
        var stderr = process.stderr
        var self = this
        if (!settings.hide_stdout){
            stdout.on('data', function(data){
                self.get('messages').push({
                    type: 'log'
                    , text: '' + data
                })
            })
        }
        if (!settings.hide_stderr){
            stderr.on('data', function(data){
                self.get('messages').push({
                    type: 'error'
                    , text: '' + data
                })
            })
        }
        process.on('exit', function(code){
            self.set('allPassed', code === 0)
            self.trigger('tests-end')
        })
        if (this.isTap()){
            this.setupTapConsumer(process)
        }
    }
    , setupTapConsumer: function(process){
        var stdout = process.stdout
        this.message = null
        this.stacktrace = []
        this.tapConsumer = new tap.Consumer
        this.tapConsumer.on('data', this.onTapData.bind(this))
        this.tapConsumer.on('end', this.onTapEnd.bind(this))
        this.tapConsumer.on('bailout', this.onTapError.bind(this))
        stdout.pipe(this.tapConsumer)
    }
    , onTapData: function(data){
        if (typeof data === 'string'){
            if (this.message === null){
                this.message = data
            }else{
                this.stacktrace.push(data)
            }
            return
        }
        if (data.skip){
            return
        }
        var results = this.get('results')

        if (data.id === undefined) {
            return
        }

        var test = {
            passed: 0
            , failed: 0
            , total: 1
            , id: data.id
            , name: data.name.trim()
            , items: []
        }

        if (!data.ok) {
            test.items.push({
                passed: false
                , message: this.message
                , stacktrace: this.stacktrace.join('\n')
            })
            test.failed++
        } else {
            test.passed++
        }
        results.addResult(test)
        this.message = null
        this.stacktrace = []
    }
    , onTapError: function(){
        this.set('results', null)
    }
    , onTapEnd: function(err, testCount){
        var results = this.get('results')
        results.set('all', true)
        this.tapConsumer.removeAllListeners()
        this.tapConsumer = null
        this.app.emit('all-test-results', results)
        this.launcher.kill()
    }
    , startTests: function(){
        var self = this
        if (this.get('results')){
            this.get('results').reset()
        }else{
            this.set('results', this.isTap() ? new TestResults : null)
        }
        this.get('messages').reset([])
        this.set('allPassed', undefined)

        this.launcher.launch(function(process){
            self.registerProcess(process)
            setTimeout(function(){
                self.trigger('tests-start')
            }, 1)
        })
    }
})

exports.ProcessRunner = ProcessRunner

<|endoftext|>


<commit_before>// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngResource'])

.config(function($stateProvider, $urlRouterProvider, $httpProvider) {

  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider.state('home', {
    url: "/",
    templateUrl: "templates/home.html",
    controller: 'HomeCtrl'
  })

  .state('register', {
    url: "/register",
    templateUrl: "templates/two.html",
    controller: 'TwoCtrl'
  })

  .state('login', {
    url: "/login",
    templateUrl: "templates/one.html",
    controller: 'LoginCtrl'
  })

  .state('one', {
    url: "/one",
    templateURL: "templates/one.html",
    controller: 'OneCtrl'
  })

   .state('two', {
   url: "/two",
   templateURL: "templates/two.html",
   controller: 'TwoCtrl'
  })

   .state('three', {
   url: "/three",
   templateURL: "templates/three.html",
   controller: 'ThreeCtrl'
  })

   .state('all', {
   url: "/all",
   templateURL: "templates/all.html",
   controller: 'AllCtrl'
  })

  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise('/');

  // Register middleware to ensure our auth token is passed to the server
  $httpProvider.interceptors.push('TokenInterceptor');

})

.run(function($window, $location, $ionicPlatform, $rootScope, AuthenticationService) {
  $rootScope.user = {
    name: $window.sessionStorage.name,
    is_admin: $window.sessionStorage.is_admin
  };

  if ($rootScope.user.is_admin) {
    AuthenticationService.isAdmin = true;
  }

  $rootScope.$on("$stateChangeStart", function(event, toState) {
    //redirect only if both isAuthenticated is false and no token is set

    if (['home', 'login', 'logout', 'register', 'one', 'two', 'three', 'all'].indexOf(toState.name) === -1) {
      if (!AuthenticationService.isAuthenticated && !$window.localStorage.token) {
        event.preventDefault();
        $location.path("/home");
      }
    }

  });

  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
  });
})
<commit_msg>Create app.js<commit_after>// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngResource'])

.config(function($stateProvider, $urlRouterProvider, $httpProvider) {

  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider.state('home', {
    url: "/",
    templateUrl: "templates/home.html",
    controller: 'HomeCtrl'
  })

  .state('register', {
    url: "/register",
    templateUrl: "templates/two.html",
    controller: 'RegisterCtrl'
  })

  .state('login', {
    url: "/login",
    templateUrl: "templates/one.html",
    controller: 'LoginCtrl'
  })

  .state('one', {
    url: "/one",
    templateURL: "templates/one.html",
    controller: 'OneCtrl'
  })

   .state('two', {
   url: "/two",
   templateURL: "templates/two.html",
   controller: 'TwoCtrl'
  })

   .state('three', {
   url: "/three",
   templateURL: "templates/three.html",
   controller: 'ThreeCtrl'
  })

   .state('all', {
   url: "/all",
   templateURL: "templates/all.html",
   controller: 'AllCtrl'
  })

  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise('/');

  // Register middleware to ensure our auth token is passed to the server
  $httpProvider.interceptors.push('TokenInterceptor');

})

.run(function($window, $location, $ionicPlatform, $rootScope, AuthenticationService) {
  $rootScope.user = {
    name: $window.sessionStorage.name,
    is_admin: $window.sessionStorage.is_admin
  };

  if ($rootScope.user.is_admin) {
    AuthenticationService.isAdmin = true;
  }

  $rootScope.$on("$stateChangeStart", function(event, toState) {
    //redirect only if both isAuthenticated is false and no token is set

    if (['home', 'login', 'logout', 'register', 'one', 'two', 'three', 'all'].indexOf(toState.name) === -1) {
      if (!AuthenticationService.isAuthenticated && !$window.localStorage.token) {
        event.preventDefault();
        $location.path("/home");
      }
    }

  });

  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
  });
})
<|endoftext|>


<commit_before>/* constructs bar graph from scratch */

const textBlock = document.getElementById('bar-text');
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 100 100'); // change height in css
document.getElementById('graph').appendChild(svg);

textBlock.setAttribute('height', svg.getBoundingClientRect().height);

const counts = [1, 3, 1, 2];
const genres = ['Pop Music', 'Other Music', 'Electronic Music', 'Music of Latin America'];
const maxCount = 3;
const graphHeight = 100;
const barHeightContainer = graphHeight / counts.length;
const barSpace = 0.1 * barHeightContainer;
const barThickness = 0.8 * barHeightContainer;
const leftBarMargin = 5;
const unitBarLength = 100 / maxCount;

for (var i = 0; i < counts.length; i++) {
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  g.setAttribute('class', 'bar');
  svg.appendChild(g);

  const bar = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  bar.setAttribute('width', unitBarLength * counts[i]);
  bar.setAttribute('height', barThickness);
  bar.setAttribute('x', leftBarMargin);
  bar.setAttribute('y', barSpace + barHeightContainer * i);
  g.appendChild(bar);

  const textContainer = document.createElement('div');
  textBlock.appendChild(textContainer);
  textContainer.setAttribute('class', 'category-container');

  const text = document.createElement('span');
  textContainer.appendChild(text);
  text.setAttribute('class', 'category');
  text.textContent = genres[i];
  console.log(barHeightContainer / 100 * textBlock.getAttribute('height'));
  textContainer.style.height = barHeightContainer / 100 * textBlock.getAttribute('height') + 'px';

  const toolTip = document.createElement('span');
  toolTip.textContent = genres[i] + ': ' + counts[i];
  toolTip.setAttribute('class', 'toolTip');
  textContainer.appendChild(toolTip);
}

<commit_msg>added files<commit_after>/* constructs bar graph from scratch */

const textBlock = document.getElementById('bar-text');
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 100 100'); // change height in css
document.getElementById('graph').appendChild(svg);

textBlock.setAttribute('height', svg.getBoundingClientRect().height);

const counts = [1, 3, 1, 2];
const genres = ['Pop Music', 'Other Music', 'Electronic Music', 'Music of Latin America'];
const maxCount = 3;
const graphHeight = 100;
const barHeightContainer = graphHeight / counts.length;
const barSpace = 0.1 * barHeightContainer;
const barThickness = 0.8 * barHeightContainer;
const leftBarMargin = 5;
const unitBarLength = 100 / maxCount;

for (var i = 0; i < counts.length; i++) {
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  g.setAttribute('class', 'bar');
  svg.appendChild(g);

  const bar = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  bar.setAttribute('width', unitBarLength * counts[i]);
  bar.setAttribute('height', barThickness);
  bar.setAttribute('x', leftBarMargin);
  bar.setAttribute('y', barSpace + barHeightContainer * i);
  g.appendChild(bar);

  const textContainer = document.createElement('div');
  textBlock.appendChild(textContainer);
  textContainer.setAttribute('class', 'category-container');

  const text = document.createElement('span');
  textContainer.appendChild(text);
  text.setAttribute('class', 'category');
  text.textContent = genres[i];
  console.log(barHeightContainer / 100 * textBlock.getAttribute('height'));
  textContainer.style.height = barHeightContainer / 100 * textBlock.getAttribute('height') + 'px';
}

<|endoftext|>


<commit_before>var Post = Backbone.Model.extend({
	idAttribute: 'num',
});

var Replies = Backbone.Collection.extend({model: Post});

var Thread = Backbone.Model.extend({
	idAttribute: 'num',
	initialize: function () {
		if (!this.get('replies'))
			this.set('replies', new Replies([]));
	},
});

var ThreadCollection = Backbone.Collection.extend({
	model: Thread,

	lookup: function (num, op) {
		var thread = this.get(op) || UnknownThread;
		return (num == op) ? thread : thread.get('replies').get(num);
	},
});

var Threads = new ThreadCollection();
var UnknownThread = new Thread();

function model_link(key) {
	return function (event) {
		this.model.set(key, $(event.target).val());
	};
}

function renderRelativeTime(){
	if (oneeSama.rTime){
		var $time = this.$el.find('time').first();
		var t = date_from_time_el($time[0]).getTime();
		var timer = setInterval(function(){
			$time.html(oneeSama.relative_time(t, new Date().getTime()));
		}, 60000);
		this.listenTo(this.model, 'removeSelf', function(){
			clearInterval(timer);
		});
	}
}

// Keeps threads non-laggy by keeping displayed post count within lastN
function unloadTopPost(){
	var m = location.search.match(/last=(\d+)/);
	if (!m || $(mouseoverTarget).is('a, img, video') || CurThread.get('replies').length <= parseInt(m[1], 10)+5)
		return;
	CurThread.get('replies').shift().trigger('removeSelf');
	var $omit = $('.omit');
	if (!$omit.length){
		$omit = $('\t<span/>', {'class': 'omit'}).text(abbrev_msg(1));
		var url = THREAD;
		if (!!location.hash)
			url += location.hash;
		$omit.append(action_link_html(url, 'See all')+'\n');
		$('section>blockquote').after($omit);
	}
	else {
		var m = $omit.html().match(/^(\d+)(.*)/);
		$('.omit').html(parseInt(m[1])+1+m[2]);
	}
	unloadTopPost();
}

var Section = Backbone.View.extend({
	tagName: 'section',

	initialize: function () {
		this.listenTo(this.model, {
			'change:hide': this.renderHide,
			'change:locked': this.renderLocked,
			'change:spoiler': this.renderSpoiler,
			destroy: this.remove,
			'add': renderRelativeTime,
		});
		this.listenTo(this.model.get('replies'), {
			remove: this.removePost,
		});
	},

	renderHide: function (model, hide) {
		this.$el.next('hr.sectionHr').andSelf().toggle(!hide);
	},

	renderLocked: function (model, locked) {
		this.$el.toggleClass('locked', !!locked);
	},

	renderSpoiler: function (model, spoiler) {
		var $img = this.$el.children('figure').find('img');
		var $spoiltag = this.$el.children('figure').find('i');
		var sp = oneeSama.spoiler_info(spoiler, true);
		if (oneeSama.spoilToggle) {
			$spoiltag.first().text($spoiltag.first().text().replace(/^\(/,"\(Spoiler, "));
		}
		else
		{
			$img.replaceWith($('<img>', {
				src: sp.thumb, width: sp.dims[0], height: sp.dims[1],
			}));
		}
	},

	remove: function () {
		var replies = this.model.get('replies');
		replies.each(function (post) {
			clear_post_links(post, replies);
		});
		replies.reset();

		this.$el.next('hr.sectionHr').andSelf().remove();
		this.stopListening();
	},

	removePost: function (model) {
		model.trigger('removeSelf');
	},
});

/* XXX: Move into own views module once more substantial */
var Article = Backbone.View.extend({
	tagName: 'article',
	initialize: function () {
		this.listenTo(this.model, {
			'change:backlinks': this.renderBacklinks,
			'change:editing': this.renderEditing,
			'change:hide': this.renderHide,
			'change:image': this.renderImage,
			'change:spoiler': this.renderSpoiler,
			'removeSelf': this.bumplessRemove,
			'add': renderRelativeTime,
		});
		if (!options.get('postUnloading') && CurThread)
			this.listenTo(this.model, {
				'add': unloadTopPost
			});
	},

	render: function () {
		var html = oneeSama.mono(this.model.attributes);
		this.setElement($($.parseHTML(html)).filter('article')[0]);
		return this;
	},

	renderBacklinks: function () {
		if (options.get('nobacklinks'))
			return this; /* ought to disconnect handler? */
		var backlinks = this.model.get('backlinks');
		var $list = this.$el.find('small');
		if (!backlinks || !backlinks.length) {
			$list.remove();
			return this;
		}
		if (!$list.length)
			$list = $('<small/>', {text: 'Replies:'}).appendTo(
					this.$el);
		// TODO: Sync up DOM gracefully instead of clobbering
		$list.find('a').remove();
		backlinks.forEach(function (num) {
			var $a = $('<a/>', {href: '#'+num, text: '>>'+num});
			$list.append(' ', $a);
		});
		return this;
	},

	renderEditing: function (model, editing) {
		this.$el.toggleClass('editing', !!editing);
		if (!editing)
			this.$('blockquote')[0].normalize();
	},

	renderHide: function (model, hide) {
		this.$el.toggle(!hide);
	},

	renderImage: function (model, image) {
		var hd = this.$('header'), fig = this.$('figure');
		if (!image)
			fig.remove();
		else if (hd.length && !fig.length) {
			/* Is this focus business necessary here? */
			var focus = get_focus();

			insert_image(image, hd, false);

			if (focus)
				focus.focus();
		}
	},

	renderSpoiler: function (model, spoiler) {
		var $img = this.$('figure').find('img');
		var $spoiltag = this.$el.children('figure').find('i');
		var sp = oneeSama.spoiler_info(spoiler, false);
		if (oneeSama.spoilToggle) {
			$spoiltag.first().text($spoiltag.first().text().replace(/^\(/,"\(Spoiler, "));
		}
		else
		{
			$img.replaceWith($('<img>', {
				src: sp.thumb,
				width: sp.dims[0], height: sp.dims[1],
			}));
		}
	},
	// To not shift the scroll position on remove
	bumplessRemove: function(){
		var pos = $(window).scrollTop();
		if (!at_bottom && this.$el.offset().top < pos)
			// Not sure why we need the extra 2 pixels, but we do
			$(window).scrollTop(pos - this.$el.outerHeight() - 2);
		this.remove();
	},
});

/* BATCH DOM UPDATE DEFER */

var deferredChanges = {links: {}, backlinks: {}};
var haveDeferredChanges = false;

/* this runs just before every _outermost_ wrap_dom completion */
Backbone.on('flushDomUpdates', function () {
	if (!haveDeferredChanges)
		return;
	haveDeferredChanges = false;

	for (var attr in deferredChanges) {
		var deferred = deferredChanges[attr];
		var empty = true;
		for (var id in deferred) {
			deferred[id].trigger('change:'+attr);
			empty = false;
		}
		if (!empty)
			deferredChanges[attr] = {};
	}
});

/* LINKS */

function add_post_links(src, links, op) {
	if (!src || !links)
		return;
	var thread = Threads.get(op);
	var srcLinks = src.get('links') || [];
	var repliedToMe = false;
	for (var destId in links) {
		var dest = thread && thread.get('replies').get(destId);
		if (!dest) {
			/* Dest doesn't exist yet; track it anyway */
			dest = new Post({id: destId, shallow: true});
			UnknownThread.get('replies').add(dest);
		}
		if (dest.get('mine'))
			repliedToMe = true;
		var destLinks = dest.get('backlinks') || [];
		/* Update links and backlinks arrays in-order */
		var i = _.sortedIndex(srcLinks, dest.id);
		if (srcLinks[i] == dest.id)
			continue;
		srcLinks.splice(i, 0, dest.id);
		destLinks.splice(_.sortedIndex(destLinks, src.id), 0, src.id);
		force_post_change(src, 'links', srcLinks);
		force_post_change(dest, 'backlinks', destLinks);
	}

	if (repliedToMe && !src.get('mine')) {
		/* Should really be triggered only on the thread */
		Backbone.trigger('repliedToMe',src);
	}
}

function force_post_change(post, attr, val) {
	if (val === undefined && post.has(attr))
		post.unset(attr);
	else if (post.get(attr) !== val)
		post.set(attr, val);
	else if (!(post.id in deferredChanges[attr])) {
		/* We mutated the existing array, so `change` won't fire.
		   Dumb hack ensues. Should extend Backbone or something. */
		/* Also, here we coalesce multiple changes just in case. */
		/* XXX: holding a direct reference to post is gross */
		deferredChanges[attr][post.id] = post;
		haveDeferredChanges = true;
	}
}

function clear_post_links(post, replies) {
	if (!post)
		return;
	(post.get('links') || []).forEach(function (destId) {
		var dest = replies.get(destId);
		if (!dest)
			return;
		var backlinks = dest.get('backlinks') || [];
		var i = backlinks.indexOf(post.id);
		if (i < 0)
			return;
		backlinks.splice(i, 1);
		if (!backlinks.length)
			backlinks = undefined;
		force_post_change(dest, 'backlinks', backlinks);
	});
	(post.get('backlinks') || []).forEach(function (srcId) {
		var src = replies.get(srcId);
		if (!src)
			return;
		var links = src.get('links') || [];
		var i = links.indexOf(post.id);
		if (i < 0)
			return;
		links.splice(i, 1);
		if (!links.length)
			links = undefined;
		force_post_change(src, 'links', links);
	});
	post.unset('links', {silent: true});
	post.unset('backlinks');
}

<commit_msg>Maximum bwaka<commit_after>var Post = Backbone.Model.extend({
	idAttribute: 'num',
});

var Replies = Backbone.Collection.extend({model: Post});

var Thread = Backbone.Model.extend({
	idAttribute: 'num',
	initialize: function () {
		if (!this.get('replies'))
			this.set('replies', new Replies([]));
	},
});

var ThreadCollection = Backbone.Collection.extend({
	model: Thread,

	lookup: function (num, op) {
		var thread = this.get(op) || UnknownThread;
		return (num == op) ? thread : thread.get('replies').get(num);
	},
});

var Threads = new ThreadCollection();
var UnknownThread = new Thread();

function model_link(key) {
	return function (event) {
		this.model.set(key, $(event.target).val());
	};
}

function renderRelativeTime(){
	if (oneeSama.rTime){
		var $time = this.$el.find('time').first();
		var t = date_from_time_el($time[0]).getTime();
		var timer = setInterval(function(){
			$time.html(oneeSama.relative_time(t, new Date().getTime()));
		}, 60000);
		this.listenTo(this.model, 'removeSelf', function(){
			clearInterval(timer);
		});
	}
}

// Keeps threads non-laggy by keeping displayed post count within lastN
function unloadTopPost(){
	var m = location.search.match(/last=(\d+)/);
	if (!m || $(mouseoverTarget).is('a, img, video') || CurThread.get('replies').length <= parseInt(m[1], 10)+5)
		return;
	CurThread.get('replies').shift().trigger('removeSelf');
	var $omit = $('.omit');
	if (!$omit.length){
		$omit = $('\t<span/>', {'class': 'omit'}).text(abbrev_msg(1));
		var url = THREAD;
		if (!!location.hash)
			url += location.hash;
		$omit.append(action_link_html(url, 'See all')+'\n');
		$('section>blockquote').after($omit);
	}
	else {
		var m = $omit.html().match(/^(\d+)(.*)/);
		$('.omit').html(parseInt(m[1])+1+m[2]);
	}
	unloadTopPost();
}

var Section = Backbone.View.extend({
	tagName: 'section',

	initialize: function () {
		this.listenTo(this.model, {
			'change:hide': this.renderHide,
			'change:locked': this.renderLocked,
			'change:spoiler': this.renderSpoiler,
			destroy: this.remove,
			'add': renderRelativeTime,
		});
		this.listenTo(this.model.get('replies'), {
			remove: this.removePost,
		});
	},

	renderHide: function (model, hide) {
		this.$el.next('hr.sectionHr').andSelf().toggle(!hide);
	},

	renderLocked: function (model, locked) {
		this.$el.toggleClass('locked', !!locked);
	},

	renderSpoiler: function (model, spoiler) {
		var $img = this.$el.children('figure').find('img');
		var $spoiltag = this.$el.children('figure').find('i');
		var sp = oneeSama.spoiler_info(spoiler, true);
		if (oneeSama.spoilToggle) {
			$spoiltag.first().text($spoiltag.first().text().replace(/^\(/,"\(Spoiler, "));
		}
		else
		{
			$img.replaceWith($('<img>', {
				src: sp.thumb, width: sp.dims[0], height: sp.dims[1],
			}));
		}
	},

	remove: function () {
		var replies = this.model.get('replies');
		replies.each(function (post) {
			clear_post_links(post, replies);
		});
		replies.reset();

		this.$el.next('hr.sectionHr').andSelf().remove();
		this.stopListening();
	},

	removePost: function (model) {
		model.trigger('removeSelf');
	},
});

/* XXX: Move into own views module once more substantial */
var Article = Backbone.View.extend({
	tagName: 'article',
	initialize: function () {
		this.listenTo(this.model, {
			'change:backlinks': this.renderBacklinks,
			'change:editing': this.renderEditing,
			'change:hide': this.renderHide,
			'change:image': this.renderImage,
			'change:spoiler': this.renderSpoiler,
			'removeSelf': this.bumplessRemove,
			'add': renderRelativeTime,
		});
		if (!options.get('postUnloading') && CurThread)
			this.listenTo(this.model, {
				'add': unloadTopPost
			});
	},

	render: function () {
		var html = oneeSama.mono(this.model.attributes);
		this.setElement($($.parseHTML(html)).filter('article')[0]);
		return this;
	},

	renderBacklinks: function () {
		if (options.get('nobacklinks'))
			return this; /* ought to disconnect handler? */
		var backlinks = this.model.get('backlinks');
		var $list = this.$el.find('small');
		if (!backlinks || !backlinks.length) {
			$list.remove();
			return this;
		}
		if (!$list.length)
			$list = $('<small/>', {text: 'Replies:'}).appendTo(
					this.$el);
		// TODO: Sync up DOM gracefully instead of clobbering
		$list.find('a').remove();
		backlinks.forEach(function (num) {
			var $a = $('<a/>', {href: '#'+num, text: '>>'+num});
			$list.append(' ', $a);
		});
		return this;
	},

	renderEditing: function (model, editing) {
		this.$el.toggleClass('editing', !!editing);
		if (!editing)
			this.$('blockquote')[0].normalize();
	},

	renderHide: function (model, hide) {
		this.$el.toggle(!hide);
	},

	renderImage: function (model, image) {
		var hd = this.$('header'), fig = this.$('figure');
		if (!image)
			fig.remove();
		else if (hd.length && !fig.length) {
			/* Is this focus business necessary here? */
			var focus = get_focus();

			insert_image(image, hd, false);

			if (focus)
				focus.focus();
		}
	},

	renderSpoiler: function (model, spoiler) {
		var $img = this.$('figure').find('img');
		var $spoiltag = this.$el.children('figure').find('i');
		var sp = oneeSama.spoiler_info(spoiler, false);
		if (oneeSama.spoilToggle) {
			$spoiltag.first().text($spoiltag.first().text().replace(/^\(/,"\(Spoiler, "));
		}
		else
		{
			$img.replaceWith($('<img>', {
				src: sp.thumb,
				width: sp.dims[0], height: sp.dims[1],
			}));
		}
	},
	// To not shift the scroll position on remove
	bumplessRemove: function(){
		var pos = $(window).scrollTop();
		if (!at_bottom() && this.$el.offset().top < pos)
			// Not sure why we need the extra 2 pixels, but we do
			$(window).scrollTop(pos - this.$el.outerHeight() - 2);
		this.remove();
	},
});

/* BATCH DOM UPDATE DEFER */

var deferredChanges = {links: {}, backlinks: {}};
var haveDeferredChanges = false;

/* this runs just before every _outermost_ wrap_dom completion */
Backbone.on('flushDomUpdates', function () {
	if (!haveDeferredChanges)
		return;
	haveDeferredChanges = false;

	for (var attr in deferredChanges) {
		var deferred = deferredChanges[attr];
		var empty = true;
		for (var id in deferred) {
			deferred[id].trigger('change:'+attr);
			empty = false;
		}
		if (!empty)
			deferredChanges[attr] = {};
	}
});

/* LINKS */

function add_post_links(src, links, op) {
	if (!src || !links)
		return;
	var thread = Threads.get(op);
	var srcLinks = src.get('links') || [];
	var repliedToMe = false;
	for (var destId in links) {
		var dest = thread && thread.get('replies').get(destId);
		if (!dest) {
			/* Dest doesn't exist yet; track it anyway */
			dest = new Post({id: destId, shallow: true});
			UnknownThread.get('replies').add(dest);
		}
		if (dest.get('mine'))
			repliedToMe = true;
		var destLinks = dest.get('backlinks') || [];
		/* Update links and backlinks arrays in-order */
		var i = _.sortedIndex(srcLinks, dest.id);
		if (srcLinks[i] == dest.id)
			continue;
		srcLinks.splice(i, 0, dest.id);
		destLinks.splice(_.sortedIndex(destLinks, src.id), 0, src.id);
		force_post_change(src, 'links', srcLinks);
		force_post_change(dest, 'backlinks', destLinks);
	}

	if (repliedToMe && !src.get('mine')) {
		/* Should really be triggered only on the thread */
		Backbone.trigger('repliedToMe',src);
	}
}

function force_post_change(post, attr, val) {
	if (val === undefined && post.has(attr))
		post.unset(attr);
	else if (post.get(attr) !== val)
		post.set(attr, val);
	else if (!(post.id in deferredChanges[attr])) {
		/* We mutated the existing array, so `change` won't fire.
		   Dumb hack ensues. Should extend Backbone or something. */
		/* Also, here we coalesce multiple changes just in case. */
		/* XXX: holding a direct reference to post is gross */
		deferredChanges[attr][post.id] = post;
		haveDeferredChanges = true;
	}
}

function clear_post_links(post, replies) {
	if (!post)
		return;
	(post.get('links') || []).forEach(function (destId) {
		var dest = replies.get(destId);
		if (!dest)
			return;
		var backlinks = dest.get('backlinks') || [];
		var i = backlinks.indexOf(post.id);
		if (i < 0)
			return;
		backlinks.splice(i, 1);
		if (!backlinks.length)
			backlinks = undefined;
		force_post_change(dest, 'backlinks', backlinks);
	});
	(post.get('backlinks') || []).forEach(function (srcId) {
		var src = replies.get(srcId);
		if (!src)
			return;
		var links = src.get('links') || [];
		var i = links.indexOf(post.id);
		if (i < 0)
			return;
		links.splice(i, 1);
		if (!links.length)
			links = undefined;
		force_post_change(src, 'links', links);
	});
	post.unset('links', {silent: true});
	post.unset('backlinks');
}

<|endoftext|>


<commit_before>import React from 'react';
import Downshift from 'downshift';
import PropTypes from 'prop-types';
import uniqueId from 'lodash/uniqueId';
import TetherComponent from 'react-tether';
import TextField from '../TextField';
import formField from '../FormField';
import css from './AutoSuggest.css';

const defaultProps = {
  includeItem: (item, searchString) => item.value.includes(searchString),
  onChange: () => {},
  onFocus: () => {},
  renderOption: item => (item ? item.value : ''),
  renderValue: item => (item ? item.value : ''),
  tether: {
    attachment: 'top left',
    renderElementTo: null,
    targetAttachment: 'bottom left',
    optimizations: {
      gpu: false,
    },
    constraints: [
      {
        to: 'scrollParent',
      },
    ],
  },
  valueKey: 'value',
  validationEnabled: true,
};

const propTypes = {
  error: PropTypes.node,
  id: PropTypes.string,
  includeItem: PropTypes.func,
  items: PropTypes.arrayOf(PropTypes.object).isRequired,
  label: PropTypes.node,
  name: PropTypes.string,
  onBlur: PropTypes.func,
  onChange: PropTypes.func,
  onFocus: PropTypes.func,
  renderOption: PropTypes.func,
  renderValue: PropTypes.func,
  required: PropTypes.bool,
  screenReaderMessage: PropTypes.string,
  tether: PropTypes.object,
  validationEnabled: PropTypes.bool,
  value: PropTypes.string,
  valueKey: PropTypes.string,
};

class AutoSuggest extends React.Component {
  static propTypes = propTypes;
  static defaultProps = defaultProps;

  constructor(props) {
    super(props);
    this.container = React.createRef();
    this.textfield = React.createRef();
    this.testId = this.props.id || uniqueId('autoSuggest-');
    this.autoSuggestId = `list-dropdown-${this.testId}`;
  }

  getInputWidth = () => { // eslint-disable-line consistent-return
    if (this.container.current) {
      return this.container.current.offsetWidth;
    }
  }

  render() {
    const {
      error,
      includeItem,
      items,
      label,
      name,
      onBlur,
      onChange,
      onFocus,
      renderOption,
      renderValue,
      required,
      tether,
      validationEnabled,
      value,
      valueKey,
    } = this.props;

    const mergedTetherProps = { ...AutoSuggest.defaultProps.tether, ...tether };
    const testId = this.testId;
    const autoSuggestId = this.autoSuggestId;
    const inputProps = {
      autoComplete: 'off',
      required,
      label,
      name,
      validationEnabled,
      inputRef: this.textfield,
      id: testId,
      error,
    };

    return (
      <Downshift
        style={{ display: 'inline-block', position: 'relative' }}
        itemToString={renderValue}
        onChange={selectedItem => onChange(selectedItem[valueKey])}
        onStateChange={({ inputValue }) => onChange(inputValue)}
        selectedItem={value}
        inputValue={value}
      >
        {({
          getInputProps,
          getItemProps,
          getMenuProps,
          selectedItem,
          highlightedIndex,
          isOpen,
          inputValue,
        }) => (

            <TetherComponent {...mergedTetherProps}>

                <TextField {...getInputProps(
                  { ...inputProps,
                    onBlur,
                    onFocus }
                )}
                />

              <ul
                className={css.autoSuggest}
                {...getMenuProps({
                  id: autoSuggestId,
                  style: {
                    width: `${this.getInputWidth()}px`,
                    display: isOpen ? 'block' : 'none'
                  }
                }, { suppressRefError: true })}
              >
                {isOpen
                  ? items
                    .filter(item => !inputValue || includeItem(item, inputValue))
                    .map((item, index) => (
                      <li
                        {...getItemProps({
                          key: item[valueKey],
                          index,
                          item,
                          style: {
                            padding: '1rem',
                            cursor: 'default',
                            display: 'flex',
                            justifyContent: 'space-between',
                            backgroundColor:
                                highlightedIndex === index ? 'lightgray' : 'white',
                            fontWeight: selectedItem === item ? 'bold' : 'normal',
                          },
                        })}
                      >
                        {renderOption(item)}

                    ))
                  : null}

            </TetherComponent>

        )
            }
      </Downshift>
    );
  }
}

export default formField(
  AutoSuggest,
  ({ meta }) => ({
    dirty: meta.dirty,
    error: (meta.touched && meta.error ? meta.error : ''),
    valid: meta.valid,
    warning: (meta.touched && meta.warning ? meta.warning : ''),
  })
);
<commit_msg>UIORGS-71 pass down `onSelect` prop<commit_after>import React from 'react';
import Downshift from 'downshift';
import PropTypes from 'prop-types';
import uniqueId from 'lodash/uniqueId';
import TetherComponent from 'react-tether';
import TextField from '../TextField';
import formField from '../FormField';
import css from './AutoSuggest.css';

const defaultProps = {
  includeItem: (item, searchString) => item.value.includes(searchString),
  onChange: () => {},
  onFocus: () => {},
  onSelect: () => {},
  renderOption: item => (item ? item.value : ''),
  renderValue: item => (item ? item.value : ''),
  tether: {
    attachment: 'top left',
    renderElementTo: null,
    targetAttachment: 'bottom left',
    optimizations: {
      gpu: false,
    },
    constraints: [
      {
        to: 'scrollParent',
      },
    ],
  },
  valueKey: 'value',
  validationEnabled: true,
};

const propTypes = {
  error: PropTypes.node,
  id: PropTypes.string,
  includeItem: PropTypes.func,
  items: PropTypes.arrayOf(PropTypes.object).isRequired,
  label: PropTypes.node,
  name: PropTypes.string,
  onBlur: PropTypes.func,
  onChange: PropTypes.func,
  onFocus: PropTypes.func,
  onSelect: PropTypes.func,
  renderOption: PropTypes.func,
  renderValue: PropTypes.func,
  required: PropTypes.bool,
  screenReaderMessage: PropTypes.string,
  tether: PropTypes.object,
  validationEnabled: PropTypes.bool,
  value: PropTypes.string,
  valueKey: PropTypes.string,
};

class AutoSuggest extends React.Component {
  static propTypes = propTypes;
  static defaultProps = defaultProps;

  constructor(props) {
    super(props);
    this.container = React.createRef();
    this.textfield = React.createRef();
    this.testId = this.props.id || uniqueId('autoSuggest-');
    this.autoSuggestId = `list-dropdown-${this.testId}`;
  }

  getInputWidth = () => { // eslint-disable-line consistent-return
    if (this.container.current) {
      return this.container.current.offsetWidth;
    }
  }

  render() {
    const {
      error,
      includeItem,
      items,
      label,
      name,
      onBlur,
      onChange,
      onFocus,
      onSelect,
      renderOption,
      renderValue,
      required,
      tether,
      validationEnabled,
      value,
      valueKey,
    } = this.props;

    const mergedTetherProps = { ...AutoSuggest.defaultProps.tether, ...tether };
    const testId = this.testId;
    const autoSuggestId = this.autoSuggestId;
    const inputProps = {
      autoComplete: 'off',
      required,
      label,
      name,
      validationEnabled,
      inputRef: this.textfield,
      id: testId,
      error,
    };

    return (
      <Downshift
        style={{ display: 'inline-block', position: 'relative' }}
        itemToString={renderValue}
        onChange={selectedItem => onChange(selectedItem[valueKey])}
        onStateChange={({ inputValue }) => onChange(inputValue)}
        selectedItem={value}
        inputValue={value}
        onSelect={onSelect}
      >
        {({
          getInputProps,
          getItemProps,
          getMenuProps,
          selectedItem,
          highlightedIndex,
          isOpen,
          inputValue,
        }) => (

            <TetherComponent {...mergedTetherProps}>

                <TextField {...getInputProps(
                  { ...inputProps,
                    onBlur,
                    onFocus }
                )}
                />

              <ul
                className={css.autoSuggest}
                {...getMenuProps({
                  id: autoSuggestId,
                  style: {
                    width: `${this.getInputWidth()}px`,
                    display: isOpen ? 'block' : 'none'
                  }
                }, { suppressRefError: true })}
              >
                {isOpen
                  ? items
                    .filter(item => !inputValue || includeItem(item, inputValue))
                    .map((item, index) => (
                      <li
                        {...getItemProps({
                          key: item[valueKey],
                          index,
                          item,
                          style: {
                            padding: '1rem',
                            cursor: 'default',
                            display: 'flex',
                            justifyContent: 'space-between',
                            backgroundColor:
                                highlightedIndex === index ? 'lightgray' : 'white',
                            fontWeight: selectedItem === item ? 'bold' : 'normal',
                          },
                        })}
                      >
                        {renderOption(item)}

                    ))
                  : null}

            </TetherComponent>

        )
            }
      </Downshift>
    );
  }
}

export default formField(
  AutoSuggest,
  ({ meta }) => ({
    dirty: meta.dirty,
    error: (meta.touched && meta.error ? meta.error : ''),
    valid: meta.valid,
    warning: (meta.touched && meta.warning ? meta.warning : ''),
  })
);
<|endoftext|>