<commit_before>// Copyright 2012 Selenium committers
// Copyright 2012 Software Freedom Conservancy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('safaridriver.extension.Session');

goog.require('goog.string');
goog.require('goog.userAgent');
goog.require('webdriver.Session');

/**
 * Represents a session with the SafariDriver.
 * @param {!safaridriver.extension.TabManager} tabManager The tab manager to
 *     use with this session.
 * @constructor
 * @extends {webdriver.Session}
 */
safaridriver.extension.Session = function(tabManager) {
  goog.base(this, goog.string.getRandomString(),
      safaridriver.extension.Session.CAPABILITIES);

  /**
   * @type {!safaridriver.extension.TabManager}
   * @private
   */
  this.tabManager_ = tabManager;
};
goog.inherits(safaridriver.extension.Session, webdriver.Session);

/**
 * The capabilites of the SafariDriver.
 * @type {!Object.<*>}
 * @const
 */
safaridriver.extension.Session.CAPABILITIES = {
  'browserName': 'safari',
  'version': goog.userAgent.VERSION,
  'platform': goog.userAgent.MAC ? 'MAC' : 'WINDOWS',
  'javascriptEnabled': true,
  'takesScreenshot': true,
  'cssSelectorsEnabled': true,
  // The SafariDriver cannot handle insecure SSL, so indicate that in the
  // returned capabilities.
  'secureSsl': true
};

/**
 * The command currently being executed with this session, if any.
 * @type {safaridriver.Command}
 * @private
 */
safaridriver.extension.Session.prototype.currentCommand_ = null;

/**
 * The text from an alert that was opened between commands, or null.
 * @type {?string}
 * @private
 * @see http://code.google.com/p/selenium/issues/detail?id=3862
 * @see http://code.google.com/p/selenium/issues/detail?id=3969
 */
safaridriver.extension.Session.prototype.unhandledAlertText_ = null;

/**
 * @type {number}
 * @private
 */
safaridriver.extension.Session.prototype.implicitWait_ = 0;

/**
 * @type {number}
 * @private
 */
safaridriver.extension.Session.prototype.scriptTimeout_ = 0;

/**
 * @param {safaridriver.Command} command The command.
 */
safaridriver.extension.Session.prototype.setCurrentCommand = function(command) {
  if (command && this.currentCommand_) {
    throw Error('Session is executing: ' + this.currentCommand_.getName() +
        '; cannot set current to: ' + command.getName());
  }
  this.currentCommand_ = command;
};

/**
 * @return {boolean} Whether this session is currently executing a command.
 */
safaridriver.extension.Session.prototype.isExecutingCommand = function() {
  return !!this.currentCommand_;
};

/**
 * @param {?string} txt The text from an unhandled alert.
 */
safaridriver.extension.Session.prototype.setUnhandledAlertText = function(txt) {
  this.unhandledAlertText_ = txt;
};

/**
 * @return {?string} The text from the last unhandled alert, or null.
 */
safaridriver.extension.Session.prototype.getUnhandledAlertText = function() {
  return this.unhandledAlertText_;
};

/**
 * @return {!safaridriver.extension.Tab} The tab commands should be routed to.
 * @throws {bot.Error} If there are no open windows, or the focused tab has been
 *     closed.
 */
safaridriver.extension.Session.prototype.getCommandTab = function() {
  return this.tabManager_.getCommandTab();
};

/**
 * Sets the tab that all commands should be routed to.
 * @param {!safaridriver.extension.Tab} tab The tab that commands should be
 *     routed to.
 */
safaridriver.extension.Session.prototype.setCommandTab = function(tab) {
  this.tabManager_.setCommandTab(tab);
};

/**
 * Retrieves the entry matching the provided ID or SafariBrowserTab.
 * @param {string} id The ID of the tab to look up.
 * @return {safaridriver.extension.Tab} The located entry, or {@code null} if
 *     none was found.
 */
safaridriver.extension.Session.prototype.getTab = function(id) {
  return this.tabManager_.getTab(id);
};

/**
 * @return {!Array.<string>} A list of IDs for the open tabs.
 */
safaridriver.extension.Session.prototype.getTabIds = function() {
  return this.tabManager_.getIds();
};

/** @return {number} The current implicit wait setting, in milliseconds. */
safaridriver.extension.Session.prototype.getImplicitWait = function() {
  return this.implicitWait_;
};

/** @param {number} wait How long to wait, in milliseconds. */
safaridriver.extension.Session.prototype.setImplicitWait = function(wait) {
  this.implicitWait_ = wait;
};

/** @return {number} The current script timeout setting, in milliseconds. */
safaridriver.extension.Session.prototype.getScriptTimeout = function() {
  return this.scriptTimeout_;
};

/** @param {number} wait How long to wait, in milliseconds. */
safaridriver.extension.Session.prototype.setScriptTimeout = function(wait) {
  this.scriptTimeout_ = wait;
};

<commit_msg>DanielWagnerHall: Revert to checking the product version, not engine version<commit_after>// Copyright 2012 Selenium committers
// Copyright 2012 Software Freedom Conservancy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('safaridriver.extension.Session');

goog.require('goog.string');
goog.require('goog.userAgent');
goog.require('goog.userAgent.product.isVersion');
goog.require('webdriver.Session');

/**
 * Represents a session with the SafariDriver.
 * @param {!safaridriver.extension.TabManager} tabManager The tab manager to
 *     use with this session.
 * @constructor
 * @extends {webdriver.Session}
 */
safaridriver.extension.Session = function(tabManager) {
  goog.base(this, goog.string.getRandomString(),
      safaridriver.extension.Session.CAPABILITIES);

  /**
   * @type {!safaridriver.extension.TabManager}
   * @private
   */
  this.tabManager_ = tabManager;
};
goog.inherits(safaridriver.extension.Session, webdriver.Session);

/**
 * The capabilites of the SafariDriver.
 * @type {!Object.<*>}
 * @const
 */
safaridriver.extension.Session.CAPABILITIES = {
  'browserName': 'safari',
  'version': goog.userAgent.product.VERSION,
  'platform': goog.userAgent.MAC ? 'MAC' : 'WINDOWS',
  'javascriptEnabled': true,
  'takesScreenshot': true,
  'cssSelectorsEnabled': true,
  // The SafariDriver cannot handle insecure SSL, so indicate that in the
  // returned capabilities.
  'secureSsl': true
};

/**
 * The command currently being executed with this session, if any.
 * @type {safaridriver.Command}
 * @private
 */
safaridriver.extension.Session.prototype.currentCommand_ = null;

/**
 * The text from an alert that was opened between commands, or null.
 * @type {?string}
 * @private
 * @see http://code.google.com/p/selenium/issues/detail?id=3862
 * @see http://code.google.com/p/selenium/issues/detail?id=3969
 */
safaridriver.extension.Session.prototype.unhandledAlertText_ = null;

/**
 * @type {number}
 * @private
 */
safaridriver.extension.Session.prototype.implicitWait_ = 0;

/**
 * @type {number}
 * @private
 */
safaridriver.extension.Session.prototype.scriptTimeout_ = 0;

/**
 * @param {safaridriver.Command} command The command.
 */
safaridriver.extension.Session.prototype.setCurrentCommand = function(command) {
  if (command && this.currentCommand_) {
    throw Error('Session is executing: ' + this.currentCommand_.getName() +
        '; cannot set current to: ' + command.getName());
  }
  this.currentCommand_ = command;
};

/**
 * @return {boolean} Whether this session is currently executing a command.
 */
safaridriver.extension.Session.prototype.isExecutingCommand = function() {
  return !!this.currentCommand_;
};

/**
 * @param {?string} txt The text from an unhandled alert.
 */
safaridriver.extension.Session.prototype.setUnhandledAlertText = function(txt) {
  this.unhandledAlertText_ = txt;
};

/**
 * @return {?string} The text from the last unhandled alert, or null.
 */
safaridriver.extension.Session.prototype.getUnhandledAlertText = function() {
  return this.unhandledAlertText_;
};

/**
 * @return {!safaridriver.extension.Tab} The tab commands should be routed to.
 * @throws {bot.Error} If there are no open windows, or the focused tab has been
 *     closed.
 */
safaridriver.extension.Session.prototype.getCommandTab = function() {
  return this.tabManager_.getCommandTab();
};

/**
 * Sets the tab that all commands should be routed to.
 * @param {!safaridriver.extension.Tab} tab The tab that commands should be
 *     routed to.
 */
safaridriver.extension.Session.prototype.setCommandTab = function(tab) {
  this.tabManager_.setCommandTab(tab);
};

/**
 * Retrieves the entry matching the provided ID or SafariBrowserTab.
 * @param {string} id The ID of the tab to look up.
 * @return {safaridriver.extension.Tab} The located entry, or {@code null} if
 *     none was found.
 */
safaridriver.extension.Session.prototype.getTab = function(id) {
  return this.tabManager_.getTab(id);
};

/**
 * @return {!Array.<string>} A list of IDs for the open tabs.
 */
safaridriver.extension.Session.prototype.getTabIds = function() {
  return this.tabManager_.getIds();
};

/** @return {number} The current implicit wait setting, in milliseconds. */
safaridriver.extension.Session.prototype.getImplicitWait = function() {
  return this.implicitWait_;
};

/** @param {number} wait How long to wait, in milliseconds. */
safaridriver.extension.Session.prototype.setImplicitWait = function(wait) {
  this.implicitWait_ = wait;
};

/** @return {number} The current script timeout setting, in milliseconds. */
safaridriver.extension.Session.prototype.getScriptTimeout = function() {
  return this.scriptTimeout_;
};

/** @param {number} wait How long to wait, in milliseconds. */
safaridriver.extension.Session.prototype.setScriptTimeout = function(wait) {
  this.scriptTimeout_ = wait;
};

<|endoftext|>


<commit_before>const stream_from = require('rillet').from;
const wrap_edition = require('./edition_classes.js');

async function find(db, query, projection) {
  const editions = [];
  const editions_collection = db.get('editions');
  await editions_collection.find(query).
	each((edition, {close, pause, resume}) => {
	  editions.push(wrap_edition(edition));
	  resume();
	});
  return editions;
} // find
async function findOne(db, query, projection) {
  const editions = await find(db, query, projection);
  return editions.length ? editions[0] : null;
} // findOne

async function for_artefacts(db, artefacts, state = 'published') {
  if (artefacts.length == 0)
    return [];

  const slugs = artefacts.map(a => a.slug);
  const query = {
    'slug': { '$in': slugs },
    'state': state
  };
  const editions = await find(db, query);

  const slug_edition = {};
  editions.forEach(e => slug_edition[e.slug] = e);
  return slug_edition;
} // for_artefacts

async function for_artefact(db, artefact, version_number = null) {
  if (version_number)
    return findOne(db, { 'panopticon_id': artefact._id, 'version_number': version_number });

  return await findOne(db, {'panopticon_id': artefact._id, 'state': 'published'});
  if (!edition)
    edition = await findOne(db, {'panopticon_id': artefact._id }, { 'sort': {'version_number': -1} });
  return edition;
} // for_artefact

async function map_onto(db, artefacts) {
  const editions = await for_artefacts(db, artefacts);

  // plug editions into artefacts, discarding any artefacts without editions
  const results = stream_from(artefacts).
	//filter(a => a.owning_app == 'publisher').
	filter(a => editions[a.slug]).
	map(a => (a.edition = editions[a.slug], a)).
	toArray();

  return results;
} // map_onto

async function attach_edition(db, artefact, version_number) {
  const edition = await for_artefact(db, artefact, version_number);
  artefact.edition = edition;
  return edition;
} // attach_edition

exports.map_onto = map_onto;
exports.for_artefact = attach_edition;
<commit_msg>use slug rather than panopticon_id to grab editions<commit_after>const stream_from = require('rillet').from;
const wrap_edition = require('./edition_classes.js');

async function find(db, query, projection) {
  const editions = [];
  const editions_collection = db.get('editions');
  await editions_collection.find(query).
	each((edition, {close, pause, resume}) => {
	  editions.push(wrap_edition(edition));
	  resume();
	});
  return editions;
} // find
async function findOne(db, query, projection) {
  const editions = await find(db, query, projection);
  return editions.length ? editions[0] : null;
} // findOne

async function for_artefacts(db, artefacts, state = 'published') {
  if (artefacts.length == 0)
    return [];

  const slugs = artefacts.map(a => a.slug);
  const query = {
    'slug': { '$in': slugs },
    'state': state
  };
  const editions = await find(db, query);

  const slug_edition = {};
  editions.forEach(e => slug_edition[e.slug] = e);
  return slug_edition;
} // for_artefacts

async function for_artefact(db, artefact, version_number = null) {
  if (version_number)
    return findOne(db, { 'slug': artefact.slug, 'version_number': version_number });

  return await findOne(db, {'slug': artefact.slug, 'state': 'published'});
  if (!edition)
    edition = await findOne(db, {'slug': artefact.slug }, { 'sort': {'version_number': -1} });
  return edition;
} // for_artefact

async function map_onto(db, artefacts) {
  const editions = await for_artefacts(db, artefacts);

  // plug editions into artefacts, discarding any artefacts without editions
  const results = stream_from(artefacts).
	//filter(a => a.owning_app == 'publisher').
	filter(a => editions[a.slug]).
	map(a => (a.edition = editions[a.slug], a)).
	toArray();

  return results;
} // map_onto

async function attach_edition(db, artefact, version_number) {
  const edition = await for_artefact(db, artefact, version_number);
  artefact.edition = edition;
  return edition;
} // attach_edition

exports.map_onto = map_onto;
exports.for_artefact = attach_edition;
<|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief Replication management
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var internal = require("internal");
var arangosh = require("org/arangodb/arangosh");

// -----------------------------------------------------------------------------
// --SECTION--                                 module "org/arangodb/replication"
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                 private functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

var logger  = { };
var applier = { };

////////////////////////////////////////////////////////////////////////////////
/// @brief starts the replication logger
////////////////////////////////////////////////////////////////////////////////

logger.start = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.PUT("_api/replication/logger-start", "");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief stops the replication logger
////////////////////////////////////////////////////////////////////////////////

logger.stop = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.PUT("_api/replication/logger-stop", "");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return the replication logger state
////////////////////////////////////////////////////////////////////////////////

logger.state = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.GET("_api/replication/logger-state");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief configures the replication logger
////////////////////////////////////////////////////////////////////////////////

logger.properties = function (config) {
  'use strict';

  var db = internal.db;

  var requestResult;
  if (config === undefined) {
    requestResult = db._connection.GET("_api/replication/logger-config");
  }
  else {
    requestResult = db._connection.PUT("_api/replication/logger-config",
      JSON.stringify(config));
  }

  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief starts the replication applier
////////////////////////////////////////////////////////////////////////////////

applier.start = function (initialTick) {
  'use strict';

  var db = internal.db;
  var append = "";

  if (initialTick !== undefined) {
    append = "?from=" + encodeURIComponent(initialTick);
  }

  var requestResult = db._connection.PUT("_api/replication/applier-start" + append, "");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief stops the replication applier
////////////////////////////////////////////////////////////////////////////////

applier.stop = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.PUT("_api/replication/applier-stop", "");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return the replication applier state
////////////////////////////////////////////////////////////////////////////////

applier.state = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.GET("_api/replication/applier-state");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief stop the replication applier state and "forget" all state
////////////////////////////////////////////////////////////////////////////////

applier.forget = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.DELETE("_api/replication/applier-state");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief configures the replication applier
////////////////////////////////////////////////////////////////////////////////

applier.properties = function (config) {
  'use strict';

  var db = internal.db;

  var requestResult;
  if (config === undefined) {
    requestResult = db._connection.GET("_api/replication/applier-config");
  }
  else {
    requestResult = db._connection.PUT("_api/replication/applier-config",
      JSON.stringify(config));
  }

  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                   other functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief performs a one-time synchronisation with a remote endpoint
////////////////////////////////////////////////////////////////////////////////

var sync = function (config) {
  'use strict';

  var db = internal.db;

  var body = JSON.stringify(config || { });
  var requestResult = db._connection.PUT("_api/replication/sync", body);

  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief fetches a server's id
////////////////////////////////////////////////////////////////////////////////

var serverId = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.GET("_api/replication/server-id");

  arangosh.checkRequestResult(requestResult);

  return requestResult.serverId;
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                    module exports
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

exports.logger   = logger;
exports.applier  = applier;
exports.sync     = sync;
exports.serverId = serverId;

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\|/\\*jslint"
// End:

<commit_msg>made paths absolute<commit_after>/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief Replication management
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var internal = require("internal");
var arangosh = require("org/arangodb/arangosh");

// -----------------------------------------------------------------------------
// --SECTION--                                 module "org/arangodb/replication"
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                 private functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

var logger  = { };
var applier = { };

////////////////////////////////////////////////////////////////////////////////
/// @brief starts the replication logger
////////////////////////////////////////////////////////////////////////////////

logger.start = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.PUT("/_api/replication/logger-start", "");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief stops the replication logger
////////////////////////////////////////////////////////////////////////////////

logger.stop = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.PUT("/_api/replication/logger-stop", "");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return the replication logger state
////////////////////////////////////////////////////////////////////////////////

logger.state = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.GET("/_api/replication/logger-state");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief configures the replication logger
////////////////////////////////////////////////////////////////////////////////

logger.properties = function (config) {
  'use strict';

  var db = internal.db;

  var requestResult;
  if (config === undefined) {
    requestResult = db._connection.GET("/_api/replication/logger-config");
  }
  else {
    requestResult = db._connection.PUT("/_api/replication/logger-config",
      JSON.stringify(config));
  }

  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief starts the replication applier
////////////////////////////////////////////////////////////////////////////////

applier.start = function (initialTick) {
  'use strict';

  var db = internal.db;
  var append = "";

  if (initialTick !== undefined) {
    append = "?from=" + encodeURIComponent(initialTick);
  }

  var requestResult = db._connection.PUT("/_api/replication/applier-start" + append, "");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief stops the replication applier
////////////////////////////////////////////////////////////////////////////////

applier.stop = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.PUT("/_api/replication/applier-stop", "");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return the replication applier state
////////////////////////////////////////////////////////////////////////////////

applier.state = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.GET("/_api/replication/applier-state");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief stop the replication applier state and "forget" all state
////////////////////////////////////////////////////////////////////////////////

applier.forget = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.DELETE("/_api/replication/applier-state");
  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief configures the replication applier
////////////////////////////////////////////////////////////////////////////////

applier.properties = function (config) {
  'use strict';

  var db = internal.db;

  var requestResult;
  if (config === undefined) {
    requestResult = db._connection.GET("/_api/replication/applier-config");
  }
  else {
    requestResult = db._connection.PUT("/_api/replication/applier-config",
      JSON.stringify(config));
  }

  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                   other functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief performs a one-time synchronisation with a remote endpoint
////////////////////////////////////////////////////////////////////////////////

var sync = function (config) {
  'use strict';

  var db = internal.db;

  var body = JSON.stringify(config || { });
  var requestResult = db._connection.PUT("/_api/replication/sync", body);

  arangosh.checkRequestResult(requestResult);

  return requestResult;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief fetches a server's id
////////////////////////////////////////////////////////////////////////////////

var serverId = function () {
  'use strict';

  var db = internal.db;

  var requestResult = db._connection.GET("/_api/replication/server-id");

  arangosh.checkRequestResult(requestResult);

  return requestResult.serverId;
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                    module exports
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

exports.logger   = logger;
exports.applier  = applier;
exports.sync     = sync;
exports.serverId = serverId;

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\|/\\*jslint"
// End:

<|endoftext|>


<commit_before>'use strict';

/*global KEY_SET */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var _ = require('underscore'),
  flatten = require('internal').flatten,
  arangodb = require('org/arangodb'),
  console = require('console'),
  db = arangodb.db;

function failImmutable(name) {
  return function () {
    throw new Error(name + ' is not mutable');
  };
}

var queueMap = { };
var jobMap = { };

var queues = {
  _jobTypes: { },
  _clearCache: function () {
    try {
      KEY_SET("queue-control", "skip", 0);
    }
    catch (err) {
      // ignore error if key does not exist
    }
  },
  get: function (key) {
    var dbName = db._name();
    if (! queueMap.hasOwnProperty(dbName)) {
      queueMap[dbName] = { };
    }
    if (! queueMap[dbName][key]) {
      if (!db._queues.exists(key)) {
        throw new Error('Queue does not exist: ' + key);
      }
      queueMap[dbName][key] = new Queue(key);
    }
    return queueMap[dbName][key];
  },
  create: function (key, maxWorkers) {
    try {
      db._queues.save({_key: key, maxWorkers: maxWorkers || 1});
    } catch (err) {
      if (!err instanceof arangodb.ArangoError ||
          err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
        throw err;
      }
      if (maxWorkers) {
        db._queues.update(key, {maxWorkers: maxWorkers});
      }
    }
    this._clearCache();
    var dbName = db._name();
    if (! queueMap.hasOwnProperty(dbName)) {
      queueMap[dbName] = { };
    }
    if (! queueMap[dbName].hasOwnProperty(key)) {
      queueMap[dbName][key] = new Queue(key);
    }
    return queueMap[dbName][key];
  },
  delete: function (key) {
    var result = false;
    db._executeTransaction({
      collections: {
        read: ['_queues'],
        write: ['_queues']
      },
      action: function () {
        if (db._queues.exists(key)) {
          db._queues.remove(key);
          result = true;
        }
      }
    });
    if (result) {
      this._clearCache();
    }
    return result;
  },
  registerJobType: function (type, opts) {
    if (typeof opts === 'function') {
      opts = {execute: opts};
    }
    if (typeof opts.execute !== 'function') {
      throw new Error('Must provide a function to execute!');
    }
    if (opts.schema && typeof opts.schema.validate !== 'function') {
      throw new Error('Schema must be a joi schema!');
    }
    var cfg = _.extend({maxFailures: 0}, opts);

    // _jobTypes are database-specific
    var dbName = db._name();
    if (! queues._jobTypes.hasOwnProperty(dbName)) {
      queues._jobTypes[dbName] = { };
    }
    queues._jobTypes[dbName][type] = cfg;
  }
};

function getJobs(queue, status, type) {
  var vars = {},
    aql = 'FOR job IN _jobs';
  if (queue !== undefined) {
    aql += ' FILTER job.queue == @queue';
    vars.queue = queue;
  }
  if (status !== undefined) {
    aql += ' FILTER job.status == @status';
    vars.status = status;
  }
  if (type !== undefined) {
    aql += ' FILTER job.type == @type';
    vars.type = type;
  }
  aql += ' SORT job.delayUntil ASC RETURN job._id';
  return db._createStatement({
    query: aql,
    bindVars: vars
  }).execute().toArray();
}

function Job(id) {
  var self = this;
  Object.defineProperty(self, 'id', {
    get: function () {
      return id;
    },
    configurable: false,
    enumerable: true
  });
  _.each(['data', 'status', 'type', 'failures'], function (key) {
    Object.defineProperty(self, key, {
      get: function () {
        var value = db._jobs.document(this.id)[key];
        return (value && typeof value === 'object') ? Object.freeze(value) : value;
      },
      set: failImmutable(key),
      configurable: false,
      enumerable: true
    });
  });
}

_.extend(Job.prototype, {
  abort: function () {
    var self = this;
    db._executeTransaction({
      collections: {
        read: ['_jobs'],
        write: ['_jobs']
      },
      action: function () {
        var job = db._jobs.document(self.id);
        if (job.status !== 'completed') {
          db._jobs.update(job, {
            status: 'failed',
            modified: Date.now(),
            failures: job.failures.concat([
              flatten(new Error('Job aborted.'))
            ])
          });
        }
      }
    });
  },
  reset: function () {
    db._jobs.update(this.id, {
      status: 'pending'
    });
    queues._clearCache();
  }
});

function Queue(name) {
  Object.defineProperty(this, 'name', {
    get: function () {
      return name;
    },
    set: failImmutable('name'),
    configurable: false,
    enumerable: true
  });
}

_.extend(Queue.prototype, {
  push: function (name, data, opts) {
    var type, result, now;
    if (typeof name !== 'string') {
      throw new Error('Must pass a job type!');
    }
    if (!opts) {
      opts = {};
    }
    // _jobTypes are database-specific
    var dbName = db._name();
    if (! queues._jobTypes.hasOwnProperty(dbName)) {
      queues._jobTypes[dbName] = { };
    }
    type = queues._jobTypes[dbName][name];

    if (type !== undefined) {
      if (type.schema) {
        result = type.schema.validate(data);
        if (result.error) {
          throw result.error;
        }
        data = result.value;
      }
      if (type.preprocess) {
        data = type.preprocess(data);
      }
    } else if (opts.allowUnknown) {
      console.warn('Unknown job type: ' + name);
    } else {
      throw new Error('Unknown job type: ' + name);
    }
    queues._clearCache();
    now = Date.now();
    return db._jobs.save({
      status: 'pending',
      queue: this.name,
      type: name,
      failures: [],
      data: data,
      created: now,
      modified: now,
      maxFailures: opts.maxFailures === Infinity ? -1 : opts.maxFailures,
      backOff: typeof opts.backOff === 'function' ? opts.backOff.toString() : opts.backOff,
      delayUntil: opts.delayUntil || now,
      onSuccess: opts.success ? opts.success.toString() : null,
      onFailure: opts.failure ? opts.failure.toString() : null
    })._id;
  },
  get: function (id) {
    if (id === undefined || id === null) {
      throw new Error('Invalid job id');
    }
    if (!id.match(/^_jobs\//)) {
      id = '_jobs/' + id;
    }
    // jobs are database-specific
    var dbName = db._name();
    if (! jobMap.hasOwnProperty(dbName)) {
      jobMap[dbName] = { };
    }
    if (! jobMap[dbName][id]) {
      jobMap[dbName][id] = new Job(id);
    }
    return jobMap[dbName][id];
  },
  delete: function (id) {
    return db._executeTransaction({
      collections: {
        read: ['_jobs'],
        write: ['_jobs']
      },
      action: function () {
        try {
          db._jobs.remove(id);
          queues._clearCache();
          return true;
        } catch (err) {
          return false;
        }
      }
    });
  },
  pending: function (jobType) {
    return getJobs(this.name, 'pending', jobType);
  },
  complete: function (jobType) {
    return getJobs(this.name, 'complete', jobType);
  },
  failed: function (jobType) {
    return getJobs(this.name, 'failed', jobType);
  },
  progress: function (jobType) {
    return getJobs(this.name, 'progress', jobType);
  },
  all: function (jobType) {
    return getJobs(this.name, undefined, jobType);
  }
});

queues.create('default');

module.exports = queues;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:
<commit_msg>No globals.<commit_after>'use strict';

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var _ = require('underscore'),
  flatten = require('internal').flatten,
  arangodb = require('org/arangodb'),
  console = require('console'),
  db = arangodb.db;

function failImmutable(name) {
  return function () {
    throw new Error(name + ' is not mutable');
  };
}

var queueMap = { };
var jobMap = { };

var queues = {
  _jobTypes: { },
  _clearCache: function () {
    try {
      global.KEY_SET("queue-control", "skip", 0);
    }
    catch (err) {
      // ignore error if key does not exist
    }
  },
  get: function (key) {
    var dbName = db._name();
    if (! queueMap.hasOwnProperty(dbName)) {
      queueMap[dbName] = { };
    }
    if (! queueMap[dbName][key]) {
      if (!db._queues.exists(key)) {
        throw new Error('Queue does not exist: ' + key);
      }
      queueMap[dbName][key] = new Queue(key);
    }
    return queueMap[dbName][key];
  },
  create: function (key, maxWorkers) {
    try {
      db._queues.save({_key: key, maxWorkers: maxWorkers || 1});
    } catch (err) {
      if (!err instanceof arangodb.ArangoError ||
          err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
        throw err;
      }
      if (maxWorkers) {
        db._queues.update(key, {maxWorkers: maxWorkers});
      }
    }
    this._clearCache();
    var dbName = db._name();
    if (! queueMap.hasOwnProperty(dbName)) {
      queueMap[dbName] = { };
    }
    if (! queueMap[dbName].hasOwnProperty(key)) {
      queueMap[dbName][key] = new Queue(key);
    }
    return queueMap[dbName][key];
  },
  delete: function (key) {
    var result = false;
    db._executeTransaction({
      collections: {
        read: ['_queues'],
        write: ['_queues']
      },
      action: function () {
        if (db._queues.exists(key)) {
          db._queues.remove(key);
          result = true;
        }
      }
    });
    if (result) {
      this._clearCache();
    }
    return result;
  },
  registerJobType: function (type, opts) {
    if (typeof opts === 'function') {
      opts = {execute: opts};
    }
    if (typeof opts.execute !== 'function') {
      throw new Error('Must provide a function to execute!');
    }
    if (opts.schema && typeof opts.schema.validate !== 'function') {
      throw new Error('Schema must be a joi schema!');
    }
    var cfg = _.extend({maxFailures: 0}, opts);

    // _jobTypes are database-specific
    var dbName = db._name();
    if (! queues._jobTypes.hasOwnProperty(dbName)) {
      queues._jobTypes[dbName] = { };
    }
    queues._jobTypes[dbName][type] = cfg;
  }
};

function getJobs(queue, status, type) {
  var vars = {},
    aql = 'FOR job IN _jobs';
  if (queue !== undefined) {
    aql += ' FILTER job.queue == @queue';
    vars.queue = queue;
  }
  if (status !== undefined) {
    aql += ' FILTER job.status == @status';
    vars.status = status;
  }
  if (type !== undefined) {
    aql += ' FILTER job.type == @type';
    vars.type = type;
  }
  aql += ' SORT job.delayUntil ASC RETURN job._id';
  return db._createStatement({
    query: aql,
    bindVars: vars
  }).execute().toArray();
}

function Job(id) {
  var self = this;
  Object.defineProperty(self, 'id', {
    get: function () {
      return id;
    },
    configurable: false,
    enumerable: true
  });
  _.each(['data', 'status', 'type', 'failures'], function (key) {
    Object.defineProperty(self, key, {
      get: function () {
        var value = db._jobs.document(this.id)[key];
        return (value && typeof value === 'object') ? Object.freeze(value) : value;
      },
      set: failImmutable(key),
      configurable: false,
      enumerable: true
    });
  });
}

_.extend(Job.prototype, {
  abort: function () {
    var self = this;
    db._executeTransaction({
      collections: {
        read: ['_jobs'],
        write: ['_jobs']
      },
      action: function () {
        var job = db._jobs.document(self.id);
        if (job.status !== 'completed') {
          db._jobs.update(job, {
            status: 'failed',
            modified: Date.now(),
            failures: job.failures.concat([
              flatten(new Error('Job aborted.'))
            ])
          });
        }
      }
    });
  },
  reset: function () {
    db._jobs.update(this.id, {
      status: 'pending'
    });
    queues._clearCache();
  }
});

function Queue(name) {
  Object.defineProperty(this, 'name', {
    get: function () {
      return name;
    },
    set: failImmutable('name'),
    configurable: false,
    enumerable: true
  });
}

_.extend(Queue.prototype, {
  push: function (name, data, opts) {
    var type, result, now;
    if (typeof name !== 'string') {
      throw new Error('Must pass a job type!');
    }
    if (!opts) {
      opts = {};
    }
    // _jobTypes are database-specific
    var dbName = db._name();
    if (! queues._jobTypes.hasOwnProperty(dbName)) {
      queues._jobTypes[dbName] = { };
    }
    type = queues._jobTypes[dbName][name];

    if (type !== undefined) {
      if (type.schema) {
        result = type.schema.validate(data);
        if (result.error) {
          throw result.error;
        }
        data = result.value;
      }
      if (type.preprocess) {
        data = type.preprocess(data);
      }
    } else if (opts.allowUnknown) {
      console.warn('Unknown job type: ' + name);
    } else {
      throw new Error('Unknown job type: ' + name);
    }
    queues._clearCache();
    now = Date.now();
    return db._jobs.save({
      status: 'pending',
      queue: this.name,
      type: name,
      failures: [],
      data: data,
      created: now,
      modified: now,
      maxFailures: opts.maxFailures === Infinity ? -1 : opts.maxFailures,
      backOff: typeof opts.backOff === 'function' ? opts.backOff.toString() : opts.backOff,
      delayUntil: opts.delayUntil || now,
      onSuccess: opts.success ? opts.success.toString() : null,
      onFailure: opts.failure ? opts.failure.toString() : null
    })._id;
  },
  get: function (id) {
    if (id === undefined || id === null) {
      throw new Error('Invalid job id');
    }
    if (!id.match(/^_jobs\//)) {
      id = '_jobs/' + id;
    }
    // jobs are database-specific
    var dbName = db._name();
    if (! jobMap.hasOwnProperty(dbName)) {
      jobMap[dbName] = { };
    }
    if (! jobMap[dbName][id]) {
      jobMap[dbName][id] = new Job(id);
    }
    return jobMap[dbName][id];
  },
  delete: function (id) {
    return db._executeTransaction({
      collections: {
        read: ['_jobs'],
        write: ['_jobs']
      },
      action: function () {
        try {
          db._jobs.remove(id);
          queues._clearCache();
          return true;
        } catch (err) {
          return false;
        }
      }
    });
  },
  pending: function (jobType) {
    return getJobs(this.name, 'pending', jobType);
  },
  complete: function (jobType) {
    return getJobs(this.name, 'complete', jobType);
  },
  failed: function (jobType) {
    return getJobs(this.name, 'failed', jobType);
  },
  progress: function (jobType) {
    return getJobs(this.name, 'progress', jobType);
  },
  all: function (jobType) {
    return getJobs(this.name, undefined, jobType);
  }
});

queues.create('default');

module.exports = queues;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:
<|endoftext|>


<commit_before>import Modal from 'flarum/components/Modal';
import DiscussionPage from 'flarum/components/DiscussionPage';
import Button from 'flarum/components/Button';
import highlight from 'flarum/helpers/highlight';
import classList from 'flarum/utils/classList';
import extractText from 'flarum/utils/extractText';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import Stream from 'flarum/utils/Stream';

import tagLabel from '../../common/helpers/tagLabel';
import tagIcon from '../../common/helpers/tagIcon';
import sortTags from '../../common/utils/sortTags';

export default class TagDiscussionModal extends Modal {
  oninit(vnode) {
    super.oninit(vnode);

    this.tags = app.store.all('tags');

    if (this.attrs.discussion) {
      this.tags = this.tags.filter(tag => tag.canAddToDiscussion() || this.attrs.discussion.tags().indexOf(tag) !== -1);
    } else {
      this.tags = this.tags.filter(tag => tag.canStartDiscussion());
    }

    this.tags = sortTags(this.tags);

    this.selected = [];
    this.filter = Stream('');
    this.index = this.tags[0].id();
    this.focused = false;

    if (this.attrs.selectedTags) {
      this.attrs.selectedTags.map(this.addTag.bind(this));
    } else if (this.attrs.discussion) {
      this.attrs.discussion.tags().map(this.addTag.bind(this));
    }

    this.minPrimary = app.forum.attribute('minPrimaryTags');
    this.maxPrimary = app.forum.attribute('maxPrimaryTags');
    this.minSecondary = app.forum.attribute('minSecondaryTags');
    this.maxSecondary = app.forum.attribute('maxSecondaryTags');

    this.navigator = new KeyboardNavigatable();
    this.navigator
      .onUp(() => this.setIndex(this.getCurrentNumericIndex() - 1, true))
      .onDown(() => this.setIndex(this.getCurrentNumericIndex() + 1, true))
      .onSelect(this.select.bind(this))
      .onRemove(() => this.selected.splice(this.selected.length - 1, 1));
  }

  primaryCount() {
    return this.selected.filter(tag => tag.isPrimary()).length;
  }

  secondaryCount() {
    return this.selected.filter(tag => !tag.isPrimary()).length;
  }

  /**
   * Add the given tag to the list of selected tags.
   *
   * @param {Tag} tag
   */
  addTag(tag) {
    if (!tag.canStartDiscussion()) return;

    // If this tag has a parent, we'll also need to add the parent tag to the
    // selected list if it's not already in there.
    const parent = tag.parent();
    if (parent && this.selected.indexOf(parent) === -1) {
      this.selected.push(parent);
    }

    if (this.selected.indexOf(tag) === -1) {
      this.selected.push(tag);
    }
  }

  /**
   * Remove the given tag from the list of selected tags.
   *
   * @param {Tag} tag
   */
  removeTag(tag) {
    const index = this.selected.indexOf(tag);
    if (index !== -1) {
      this.selected.splice(index, 1);

      // Look through the list of selected tags for any tags which have the tag
      // we just removed as their parent. We'll need to remove them too.
      this.selected
        .filter(selected => selected.parent() === tag)
        .forEach(this.removeTag.bind(this));
    }
  }

  className() {
    return 'TagDiscussionModal';
  }

  title() {
    return this.attrs.discussion
      ? app.translator.trans('flarum-tags.forum.choose_tags.edit_title', {title: {this.attrs.discussion.title()}})
      : app.translator.trans('flarum-tags.forum.choose_tags.title');
  }

  getInstruction(primaryCount, secondaryCount) {
    if (primaryCount < this.minPrimary) {
      const remaining = this.minPrimary - primaryCount;
      return app.translator.transChoice('flarum-tags.forum.choose_tags.choose_primary_placeholder', remaining, {count: remaining});
    } else if (secondaryCount < this.minSecondary) {
      const remaining = this.minSecondary - secondaryCount;
      return app.translator.transChoice('flarum-tags.forum.choose_tags.choose_secondary_placeholder', remaining, {count: remaining});
    }

    return '';
  }

  content() {
    let tags = this.tags;
    const filter = this.filter().toLowerCase();
    const primaryCount = this.primaryCount();
    const secondaryCount = this.secondaryCount();

    // Filter out all child tags whose parents have not been selected. This
    // makes it impossible to select a child if its parent hasn't been selected.
    tags = tags.filter(tag => {
      const parent = tag.parent();
      return parent === false || this.selected.indexOf(parent) !== -1;
    });

    // If the number of selected primary/secondary tags is at the maximum, then
    // we'll filter out all other tags of that type.
    if (primaryCount >= this.maxPrimary) {
      tags = tags.filter(tag => !tag.isPrimary() || this.selected.indexOf(tag) !== -1);
    }

    if (secondaryCount >= this.maxSecondary) {
      tags = tags.filter(tag => tag.isPrimary() || this.selected.indexOf(tag) !== -1);
    }

    // If the user has entered text in the filter input, then filter by tags
    // whose name matches what they've entered.
    if (filter) {
      tags = tags.filter(tag => tag.name().substr(0, filter.length).toLowerCase() === filter);
    }

    if (tags.indexOf(this.index) === -1) this.index = tags[0];

    const inputWidth = Math.max(extractText(this.getInstruction(primaryCount, secondaryCount)).length, this.filter().length);

    return [

 this.$('.TagsInput input').focus()}
            >

                {this.selected.map(tag =>
                   {
                    this.removeTag(tag);
                    this.onready();
                  }}>
                    {tagLabel(tag)}

                )}

              <input className="FormControl"
                placeholder={extractText(this.getInstruction(primaryCount, secondaryCount))}
                bidi={this.filter}
                style={{ width: inputWidth + 'ch' }}
                onkeydown={this.navigator.navigate.bind(this.navigator)}
                onfocus={() => this.focused = true}
                onblur={() => this.focused = false}/>

            <Button type="submit" className="Button Button--primary" disabled={!this.meetsRequirements(primaryCount, secondaryCount)} icon="fas fa-check">
              {app.translator.trans('flarum-tags.forum.choose_tags.submit_button')}
            </Button>

,

          {tags
            .filter(tag => filter || !tag.parent() || this.selected.indexOf(tag.parent()) !== -1)
            .map(tag => (

 this.index = tag}
                onclick={this.toggleTag.bind(this, tag)}
              >
                {tagIcon(tag)}

                  {highlight(tag.name(), filter)}

                {tag.description()
                  ? (

                      {tag.description()}

                  ) : ''}

            ))}

    ];
  }

  meetsRequirements(primaryCount, secondaryCount) {
    if (app.forum.attribute('canBypassTagCounts')) {
      return true;
    }

    return primaryCount >= this.minPrimary && secondaryCount >= this.minSecondary;
  }

  toggleTag(tag) {
    const index = this.selected.indexOf(tag);

    if (index !== -1) {
      this.removeTag(tag);
    } else {
      this.addTag(tag);
    }

    if (this.filter()) {
      this.filter('');
      this.index = this.tags[0];
    }

    this.onready();
  }

  select(e) {
    // Ctrl + Enter submits the selection, just Enter completes the current entry
    if (e.metaKey || e.ctrlKey || this.selected.indexOf(this.index) !== -1) {
      if (this.selected.length) {
        this.$('form').submit();
      }
    } else {
      this.getItem(this.index)[0].dispatchEvent(new Event('click'));
    }
  }

  selectableItems() {
    return this.$('.TagDiscussionModal-list > li');
  }

  getCurrentNumericIndex() {
    return this.selectableItems().index(
      this.getItem(this.index)
    );
  }

  getItem(index) {
    return this.selectableItems().filter(`[data-index="${index.id()}"]`);
  }

  setIndex(index, scrollToItem) {
    const $items = this.selectableItems();
    const $dropdown = $items.parent();

    if (index < 0) {
      index = $items.length - 1;
    } else if (index >= $items.length) {
      index = 0;
    }

    const $item = $items.eq(index);

    this.index = app.store.getById('tags', $item.attr('data-index'));

    m.redraw();

    if (scrollToItem) {
      const dropdownScroll = $dropdown.scrollTop();
      const dropdownTop = $dropdown.offset().top;
      const dropdownBottom = dropdownTop + $dropdown.outerHeight();
      const itemTop = $item.offset().top;
      const itemBottom = itemTop + $item.outerHeight();

      let scrollTop;
      if (itemTop < dropdownTop) {
        scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);
      } else if (itemBottom > dropdownBottom) {
        scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);
      }

      if (typeof scrollTop !== 'undefined') {
        $dropdown.stop(true).animate({scrollTop}, 100);
      }
    }
  }

  onsubmit(e) {
    e.preventDefault();

    const discussion = this.attrs.discussion;
    const tags = this.selected;

    if (discussion) {
      discussion.save({relationships: {tags}})
        .then(() => {
          if (app.current.matches(DiscussionPage)) {
            app.current.get('stream').update();
          }
          m.redraw();
        });
    }

    if (this.attrs.onsubmit) this.attrs.onsubmit(tags);

    this.hide();
  }
}
<commit_msg>Fix submitting TagDiscussionModal via enter key<commit_after>import Modal from 'flarum/components/Modal';
import DiscussionPage from 'flarum/components/DiscussionPage';
import Button from 'flarum/components/Button';
import highlight from 'flarum/helpers/highlight';
import classList from 'flarum/utils/classList';
import extractText from 'flarum/utils/extractText';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import Stream from 'flarum/utils/Stream';

import tagLabel from '../../common/helpers/tagLabel';
import tagIcon from '../../common/helpers/tagIcon';
import sortTags from '../../common/utils/sortTags';

export default class TagDiscussionModal extends Modal {
  oninit(vnode) {
    super.oninit(vnode);

    this.tags = app.store.all('tags');

    if (this.attrs.discussion) {
      this.tags = this.tags.filter(tag => tag.canAddToDiscussion() || this.attrs.discussion.tags().indexOf(tag) !== -1);
    } else {
      this.tags = this.tags.filter(tag => tag.canStartDiscussion());
    }

    this.tags = sortTags(this.tags);

    this.selected = [];
    this.filter = Stream('');
    this.index = this.tags[0].id();
    this.focused = false;

    if (this.attrs.selectedTags) {
      this.attrs.selectedTags.map(this.addTag.bind(this));
    } else if (this.attrs.discussion) {
      this.attrs.discussion.tags().map(this.addTag.bind(this));
    }

    this.minPrimary = app.forum.attribute('minPrimaryTags');
    this.maxPrimary = app.forum.attribute('maxPrimaryTags');
    this.minSecondary = app.forum.attribute('minSecondaryTags');
    this.maxSecondary = app.forum.attribute('maxSecondaryTags');

    this.navigator = new KeyboardNavigatable();
    this.navigator
      .onUp(() => this.setIndex(this.getCurrentNumericIndex() - 1, true))
      .onDown(() => this.setIndex(this.getCurrentNumericIndex() + 1, true))
      .onSelect(this.select.bind(this))
      .onRemove(() => this.selected.splice(this.selected.length - 1, 1));
  }

  primaryCount() {
    return this.selected.filter(tag => tag.isPrimary()).length;
  }

  secondaryCount() {
    return this.selected.filter(tag => !tag.isPrimary()).length;
  }

  /**
   * Add the given tag to the list of selected tags.
   *
   * @param {Tag} tag
   */
  addTag(tag) {
    if (!tag.canStartDiscussion()) return;

    // If this tag has a parent, we'll also need to add the parent tag to the
    // selected list if it's not already in there.
    const parent = tag.parent();
    if (parent && this.selected.indexOf(parent) === -1) {
      this.selected.push(parent);
    }

    if (this.selected.indexOf(tag) === -1) {
      this.selected.push(tag);
    }
  }

  /**
   * Remove the given tag from the list of selected tags.
   *
   * @param {Tag} tag
   */
  removeTag(tag) {
    const index = this.selected.indexOf(tag);
    if (index !== -1) {
      this.selected.splice(index, 1);

      // Look through the list of selected tags for any tags which have the tag
      // we just removed as their parent. We'll need to remove them too.
      this.selected
        .filter(selected => selected.parent() === tag)
        .forEach(this.removeTag.bind(this));
    }
  }

  className() {
    return 'TagDiscussionModal';
  }

  title() {
    return this.attrs.discussion
      ? app.translator.trans('flarum-tags.forum.choose_tags.edit_title', {title: {this.attrs.discussion.title()}})
      : app.translator.trans('flarum-tags.forum.choose_tags.title');
  }

  getInstruction(primaryCount, secondaryCount) {
    if (primaryCount < this.minPrimary) {
      const remaining = this.minPrimary - primaryCount;
      return app.translator.transChoice('flarum-tags.forum.choose_tags.choose_primary_placeholder', remaining, {count: remaining});
    } else if (secondaryCount < this.minSecondary) {
      const remaining = this.minSecondary - secondaryCount;
      return app.translator.transChoice('flarum-tags.forum.choose_tags.choose_secondary_placeholder', remaining, {count: remaining});
    }

    return '';
  }

  content() {
    let tags = this.tags;
    const filter = this.filter().toLowerCase();
    const primaryCount = this.primaryCount();
    const secondaryCount = this.secondaryCount();

    // Filter out all child tags whose parents have not been selected. This
    // makes it impossible to select a child if its parent hasn't been selected.
    tags = tags.filter(tag => {
      const parent = tag.parent();
      return parent === false || this.selected.indexOf(parent) !== -1;
    });

    // If the number of selected primary/secondary tags is at the maximum, then
    // we'll filter out all other tags of that type.
    if (primaryCount >= this.maxPrimary) {
      tags = tags.filter(tag => !tag.isPrimary() || this.selected.indexOf(tag) !== -1);
    }

    if (secondaryCount >= this.maxSecondary) {
      tags = tags.filter(tag => tag.isPrimary() || this.selected.indexOf(tag) !== -1);
    }

    // If the user has entered text in the filter input, then filter by tags
    // whose name matches what they've entered.
    if (filter) {
      tags = tags.filter(tag => tag.name().substr(0, filter.length).toLowerCase() === filter);
    }

    if (tags.indexOf(this.index) === -1) this.index = tags[0];

    const inputWidth = Math.max(extractText(this.getInstruction(primaryCount, secondaryCount)).length, this.filter().length);

    return [

 this.$('.TagsInput input').focus()}
            >

                {this.selected.map(tag =>
                   {
                    this.removeTag(tag);
                    this.onready();
                  }}>
                    {tagLabel(tag)}

                )}

              <input className="FormControl"
                placeholder={extractText(this.getInstruction(primaryCount, secondaryCount))}
                bidi={this.filter}
                style={{ width: inputWidth + 'ch' }}
                onkeydown={this.navigator.navigate.bind(this.navigator)}
                onfocus={() => this.focused = true}
                onblur={() => this.focused = false}/>

            <Button type="submit" className="Button Button--primary" disabled={!this.meetsRequirements(primaryCount, secondaryCount)} icon="fas fa-check">
              {app.translator.trans('flarum-tags.forum.choose_tags.submit_button')}
            </Button>

,

          {tags
            .filter(tag => filter || !tag.parent() || this.selected.indexOf(tag.parent()) !== -1)
            .map(tag => (

 this.index = tag}
                onclick={this.toggleTag.bind(this, tag)}
              >
                {tagIcon(tag)}

                  {highlight(tag.name(), filter)}

                {tag.description()
                  ? (

                      {tag.description()}

                  ) : ''}

            ))}

    ];
  }

  meetsRequirements(primaryCount, secondaryCount) {
    if (app.forum.attribute('canBypassTagCounts')) {
      return true;
    }

    return primaryCount >= this.minPrimary && secondaryCount >= this.minSecondary;
  }

  toggleTag(tag) {
    const index = this.selected.indexOf(tag);

    if (index !== -1) {
      this.removeTag(tag);
    } else {
      this.addTag(tag);
    }

    if (this.filter()) {
      this.filter('');
      this.index = this.tags[0];
    }

    this.onready();
  }

  select(e) {
    // Ctrl + Enter submits the selection, just Enter completes the current entry
    if (e.metaKey || e.ctrlKey || this.selected.indexOf(this.index) !== -1) {
      if (this.selected.length) {
        // The DOM submit method doesn't emit a `submit event, so we
        // simulate a manual submission so our `onsubmit` logic is run.
        this.$('button[type="submit"]').click();
      }
    } else {
      this.getItem(this.index)[0].dispatchEvent(new Event('click'));
    }
  }

  selectableItems() {
    return this.$('.TagDiscussionModal-list > li');
  }

  getCurrentNumericIndex() {
    return this.selectableItems().index(
      this.getItem(this.index)
    );
  }

  getItem(index) {
    return this.selectableItems().filter(`[data-index="${index.id()}"]`);
  }

  setIndex(index, scrollToItem) {
    const $items = this.selectableItems();
    const $dropdown = $items.parent();

    if (index < 0) {
      index = $items.length - 1;
    } else if (index >= $items.length) {
      index = 0;
    }

    const $item = $items.eq(index);

    this.index = app.store.getById('tags', $item.attr('data-index'));

    m.redraw();

    if (scrollToItem) {
      const dropdownScroll = $dropdown.scrollTop();
      const dropdownTop = $dropdown.offset().top;
      const dropdownBottom = dropdownTop + $dropdown.outerHeight();
      const itemTop = $item.offset().top;
      const itemBottom = itemTop + $item.outerHeight();

      let scrollTop;
      if (itemTop < dropdownTop) {
        scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);
      } else if (itemBottom > dropdownBottom) {
        scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);
      }

      if (typeof scrollTop !== 'undefined') {
        $dropdown.stop(true).animate({scrollTop}, 100);
      }
    }
  }

  onsubmit(e) {
    e.preventDefault();

    const discussion = this.attrs.discussion;
    const tags = this.selected;

    if (discussion) {
      discussion.save({relationships: {tags}})
        .then(() => {
          if (app.current.matches(DiscussionPage)) {
            app.current.get('stream').update();
          }
          m.redraw();
        });
    }

    if (this.attrs.onsubmit) this.attrs.onsubmit(tags);

    this.hide();
  }
}
<|endoftext|>


<commit_before>//  LauncherOSX
//
//  Created by Boris Schneiderman.
// Modified by Daniel Weck
//  Copyright (c) 2012-2013 The Readium Foundation.
//
//  The Readium SDK is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License

ReadiumSDK.Views.MediaOverlayElementHighlighter = function(reader) {

    var DEFAULT_MO_ACTIVE_CLASS = "mo-active-default";
    //var BACK_COLOR = "#99CCCC";

    var _highlightedElement = undefined;
    var _highlightedCfi = undefined;

    var _activeClass = "";
    var _playbackActiveClass = "";

    var _reader = reader;

    var USE_RANGY = true;
    var HIGHLIGHT_ID = "MO_SPEAK";

    var self = this;

    var $userStyle = undefined;
    this.clearUserStyle = function()
    {
        if ($userStyle)
        {
            $userStyle.remove();
        }
        $userStyle = undefined;
    };

    function ensureUserStyle($element)
    {
        if ($userStyle)
        {
            if ($userStyle[0].ownerDocument === $element[0].ownerDocument)
            {
                return;
            }

            //self.clearUserStyle();
        }

        $head = $("head", $element[0].ownerDocument.documentElement);

        $userStyle = $("<style type='text/css'> </style>");

        $userStyle.append("." + DEFAULT_MO_ACTIVE_CLASS + " {");

        var style = _reader.userStyles().findStyle("." + DEFAULT_MO_ACTIVE_CLASS);
        if (style)
        {
            for(var prop in style.declarations)
            {
                if(!style.declarations.hasOwnProperty(prop))
                {
                    continue;
                }

                $userStyle.append(prop + ": " + style.declarations[prop] + "; ");
            }
        }

        $userStyle.append("}");

        // ---- CFI
        //$userStyle.append(" .highlight {background-color: blue; border: 2x solid green;}"); //.hover-highlight

        $userStyle.appendTo($head);

//console.debug($userStyle[0].textContent);
    }

    this.highlightElement = function(element, activeClass, playbackActiveClass) {

        if(!element || element === _highlightedElement) {
            return;
        }

        this.reset();

        _highlightedElement = element;
        _highlightedCfi = undefined;
        _activeClass = activeClass;
        _playbackActiveClass = playbackActiveClass;

        if (_playbackActiveClass && _playbackActiveClass !== "")
        {
            //console.debug("MO playbackActiveClass: " + _playbackActiveClass);
            $(_highlightedElement.ownerDocument.documentElement).addClass(_playbackActiveClass);
            //console.debug("MO playbackActiveClass 2: " + _highlightedElement.ownerDocument.documentElement.classList);
        }

        var $hel = $(_highlightedElement);

        var hasAuthorStyle = _activeClass && _activeClass !== "";
        var overrideWithUserStyle = _reader.userStyles().findStyle("." + DEFAULT_MO_ACTIVE_CLASS); // TODO: performance issue?

        ensureUserStyle($hel);

        if (overrideWithUserStyle || !hasAuthorStyle)
        {
            //console.debug("MO active NO CLASS: " + _activeClass);

            $hel.addClass(DEFAULT_MO_ACTIVE_CLASS);

            //$(_highlightedElement).css("background", BACK_COLOR);
        }
        else
        {
            //console.debug("MO activeClass: " + _activeClass);
            $hel.addClass(_activeClass);
        }

// ---- CFI
//         try
//         {
//             // //noinspection JSUnresolvedVariable
//             // var cfi = EPUBcfi.Generator.generateElementCFIComponent(_highlightedElement); //$hel[0]
//             // if(cfi[0] == "!") {
//             //     cfi = cfi.substring(1);
//             // }
//
// //console.log(_highlightedElement);
//
//             var firstTextNode = getFirstTextNode(_highlightedElement);
//             var txtFirst = firstTextNode.textContent;
// //console.log(txtFirst);
//
//             var lastTextNode = getLastTextNode(_highlightedElement);
//             var txtLast = lastTextNode.textContent;
// //console.log(txtLast);
//
//             var cfi = EPUBcfi.Generator.generateCharOffsetRangeComponent(
//                     firstTextNode,
//                     0,
//                     lastTextNode,
//                     txtLast.length,
//                     ["cfi-marker"],
//                     [],
//                     ["MathJax_Message"]
//                     );
//
//             var id = $hel.data("mediaOverlayData").par.getSmil().spineItemId;
//             _reader.addHighlight(id, cfi, HIGHLIGHT_ID,
//             "highlight", //"underline"
//             undefined // styles
//                         );
//         }
//         catch(error)
//         {
//             console.error(error);
//
//             removeHighlight();
//         }
    };

    this.highlightCfi = function(par, activeClass, playbackActiveClass) {

        this.reset();

        _highlightedElement = undefined;
        _highlightedCfi = par.cfi;
        _activeClass = activeClass;
        _playbackActiveClass = playbackActiveClass;

        var $hel = $(_highlightedCfi.cfiTextParent);

        var hasAuthorStyle = _activeClass && _activeClass !== "";
        var overrideWithUserStyle = _reader.userStyles().findStyle("." + DEFAULT_MO_ACTIVE_CLASS); // TODO: performance issue?

        ensureUserStyle($hel);

        var clazz = (overrideWithUserStyle || !hasAuthorStyle) ? DEFAULT_MO_ACTIVE_CLASS : _activeClass;

        if (USE_RANGY)
        {
            var doc = _highlightedCfi.cfiTextParent.ownerDocument;

            var range = rangy.createRange(doc); //createNativeRange

            var startCFI = "epubcfi(" + _highlightedCfi.partialStartCfi + ")";
            var infoStart = EPUBcfi.getTextTerminusInfoWithPartialCFI(startCFI, doc);
//console.log(infoStart);

            var endCFI = "epubcfi(" + _highlightedCfi.partialEndCfi + ")";
            var infoEnd = EPUBcfi.getTextTerminusInfoWithPartialCFI(endCFI, doc);
//console.log(infoEnd);

            range.setStartAndEnd(
                infoStart.textNode[0], infoStart.textOffset,
                infoEnd.textNode[0], infoEnd.textOffset
            );

            if (range.canSurroundContents())
            {
                var span = doc.createElementNS("http://www.w3.org/1999/xhtml", 'span');
                span.id = HIGHLIGHT_ID;
                span.setAttribute("id", span.id);
                span.setAttribute("class", clazz);

                range.surroundContents(span);
            }
            else
            {
                console.error("RANGY unsupported text range");
            }
        }
        else
        {
            try
            {
                //var id = $hel.data("mediaOverlayData").par.getSmil().spineItemId;
                var id = par.getSmil().spineItemId;
                _reader.addHighlight(id, par.cfi.partialRangeCfi, HIGHLIGHT_ID,
                "highlight", //"underline"
                undefined // styles
                            );
            }
            catch(error)
            {
                console.error(error);
            }
        }
    };

// ---- CFI
//
//     function getFirstTextNode(node)
//     {
//         if (node.nodeType === Node.TEXT_NODE)
//         {
//             if (node.textContent.trim().length > 0)
//                 return node;
//         }
//
//         for (var i = 0; i < node.childNodes.length; i++)
//         {
//             var child = node.childNodes[i];
//             var first = getFirstTextNode(child);
//             if (first)
//             {
//                 return first;
//             }
//         }
//
//         return undefined;
//     }
//
//     function getLastTextNode(node)
//     {
//         if (node.nodeType === Node.TEXT_NODE)
//         {
//             if (node.textContent.trim().length > 0)
//                 return node;
//         }
//
//         for (var i = node.childNodes.length-1; i >= 0; i--)
//         {
//             var child = node.childNodes[i];
//             var last = getLastTextNode(child);
//             if (last)
//             {
//                 return last;
//             }
//         }
//
//         return undefined;
//     }
//

    this.reset = function() {

        if (_highlightedCfi)
        {
            var doc = _highlightedCfi.cfiTextParent.ownerDocument;
            if (USE_RANGY)
            {
                    var toRemove = undefined;
                    while ((toRemove = doc.getElementById(HIGHLIGHT_ID)) !== null)
                    {
                        var txt = toRemove.textContent; // innerHTML?
                        var txtNode = doc.createTextNode(txt);

                        toRemove.parentNode.replaceChild(txtNode, toRemove);
                        txtNode.parentNode.normalize();
                    }
            }
            else
            {
                try
                {
                    _reader.removeHighlight(HIGHLIGHT_ID);

                    var toRemove = undefined;
                    while ((toRemove = doc.getElementById("start-" + HIGHLIGHT_ID)) !== null)
                    {
            console.log("toRemove START");
            console.log(toRemove);
                        toRemove.parentNode.removeChild(toRemove);
                    }
                    while ((toRemove = doc.getElementById("end-" + HIGHLIGHT_ID)) !== null)
                    {
            console.log("toRemove END");
            console.log(toRemove);
                        toRemove.parentNode.removeChild(toRemove);
                    }
                }
                catch(error)
                {
                    console.error(error);
                }
            }
        }

        if(_highlightedElement) {

            if (_playbackActiveClass && _playbackActiveClass !== "")
            {
                //console.debug("MO RESET playbackActiveClass: " + _playbackActiveClass);
                $(_highlightedElement.ownerDocument.documentElement).removeClass(_playbackActiveClass);
            }

            if (_activeClass && _activeClass !== "")
            {
                //console.debug("MO RESET activeClass: " + _activeClass);
                $(_highlightedElement).removeClass(_activeClass);
            }
            //else
            //{
                //console.debug("MO RESET active NO CLASS: " + _activeClass);
                $(_highlightedElement).removeClass(DEFAULT_MO_ACTIVE_CLASS);
                //$(_highlightedElement).css("background", '');
            //}

            _highlightedElement = undefined;
            _activeClass = "";
            _playbackActiveClass = "";
        }
    }

};<commit_msg>rangy check<commit_after>//  LauncherOSX
//
//  Created by Boris Schneiderman.
// Modified by Daniel Weck
//  Copyright (c) 2012-2013 The Readium Foundation.
//
//  The Readium SDK is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License

ReadiumSDK.Views.MediaOverlayElementHighlighter = function(reader) {

    var DEFAULT_MO_ACTIVE_CLASS = "mo-active-default";
    //var BACK_COLOR = "#99CCCC";

    var _highlightedElement = undefined;
    var _highlightedCfi = undefined;

    var _activeClass = "";
    var _playbackActiveClass = "";

    var _reader = reader;

    var USE_RANGY = true && (typeof rangy !== undefined);
    var HIGHLIGHT_ID = "MO_SPEAK";

    var self = this;

    var $userStyle = undefined;
    this.clearUserStyle = function()
    {
        if ($userStyle)
        {
            $userStyle.remove();
        }
        $userStyle = undefined;
    };

    function ensureUserStyle($element)
    {
        if ($userStyle)
        {
            if ($userStyle[0].ownerDocument === $element[0].ownerDocument)
            {
                return;
            }

            //self.clearUserStyle();
        }

        $head = $("head", $element[0].ownerDocument.documentElement);

        $userStyle = $("<style type='text/css'> </style>");

        $userStyle.append("." + DEFAULT_MO_ACTIVE_CLASS + " {");

        var style = _reader.userStyles().findStyle("." + DEFAULT_MO_ACTIVE_CLASS);
        if (style)
        {
            for(var prop in style.declarations)
            {
                if(!style.declarations.hasOwnProperty(prop))
                {
                    continue;
                }

                $userStyle.append(prop + ": " + style.declarations[prop] + "; ");
            }
        }

        $userStyle.append("}");

        // ---- CFI
        //$userStyle.append(" .highlight {background-color: blue; border: 2x solid green;}"); //.hover-highlight

        $userStyle.appendTo($head);

//console.debug($userStyle[0].textContent);
    }

    this.highlightElement = function(element, activeClass, playbackActiveClass) {

        if(!element || element === _highlightedElement) {
            return;
        }

        this.reset();

        _highlightedElement = element;
        _highlightedCfi = undefined;
        _activeClass = activeClass;
        _playbackActiveClass = playbackActiveClass;

        if (_playbackActiveClass && _playbackActiveClass !== "")
        {
            //console.debug("MO playbackActiveClass: " + _playbackActiveClass);
            $(_highlightedElement.ownerDocument.documentElement).addClass(_playbackActiveClass);
            //console.debug("MO playbackActiveClass 2: " + _highlightedElement.ownerDocument.documentElement.classList);
        }

        var $hel = $(_highlightedElement);

        var hasAuthorStyle = _activeClass && _activeClass !== "";
        var overrideWithUserStyle = _reader.userStyles().findStyle("." + DEFAULT_MO_ACTIVE_CLASS); // TODO: performance issue?

        ensureUserStyle($hel);

        if (overrideWithUserStyle || !hasAuthorStyle)
        {
            //console.debug("MO active NO CLASS: " + _activeClass);

            $hel.addClass(DEFAULT_MO_ACTIVE_CLASS);

            //$(_highlightedElement).css("background", BACK_COLOR);
        }
        else
        {
            //console.debug("MO activeClass: " + _activeClass);
            $hel.addClass(_activeClass);
        }

// ---- CFI
//         try
//         {
//             // //noinspection JSUnresolvedVariable
//             // var cfi = EPUBcfi.Generator.generateElementCFIComponent(_highlightedElement); //$hel[0]
//             // if(cfi[0] == "!") {
//             //     cfi = cfi.substring(1);
//             // }
//
// //console.log(_highlightedElement);
//
//             var firstTextNode = getFirstTextNode(_highlightedElement);
//             var txtFirst = firstTextNode.textContent;
// //console.log(txtFirst);
//
//             var lastTextNode = getLastTextNode(_highlightedElement);
//             var txtLast = lastTextNode.textContent;
// //console.log(txtLast);
//
//             var cfi = EPUBcfi.Generator.generateCharOffsetRangeComponent(
//                     firstTextNode,
//                     0,
//                     lastTextNode,
//                     txtLast.length,
//                     ["cfi-marker"],
//                     [],
//                     ["MathJax_Message"]
//                     );
//
//             var id = $hel.data("mediaOverlayData").par.getSmil().spineItemId;
//             _reader.addHighlight(id, cfi, HIGHLIGHT_ID,
//             "highlight", //"underline"
//             undefined // styles
//                         );
//         }
//         catch(error)
//         {
//             console.error(error);
//
//             removeHighlight();
//         }
    };

    this.highlightCfi = function(par, activeClass, playbackActiveClass) {

        this.reset();

        _highlightedElement = undefined;
        _highlightedCfi = par.cfi;
        _activeClass = activeClass;
        _playbackActiveClass = playbackActiveClass;

        var $hel = $(_highlightedCfi.cfiTextParent);

        var hasAuthorStyle = _activeClass && _activeClass !== "";
        var overrideWithUserStyle = _reader.userStyles().findStyle("." + DEFAULT_MO_ACTIVE_CLASS); // TODO: performance issue?

        ensureUserStyle($hel);

        var clazz = (overrideWithUserStyle || !hasAuthorStyle) ? DEFAULT_MO_ACTIVE_CLASS : _activeClass;

        if (USE_RANGY)
        {
            var doc = _highlightedCfi.cfiTextParent.ownerDocument;

            var range = rangy.createRange(doc); //createNativeRange

            var startCFI = "epubcfi(" + _highlightedCfi.partialStartCfi + ")";
            var infoStart = EPUBcfi.getTextTerminusInfoWithPartialCFI(startCFI, doc);
//console.log(infoStart);

            var endCFI = "epubcfi(" + _highlightedCfi.partialEndCfi + ")";
            var infoEnd = EPUBcfi.getTextTerminusInfoWithPartialCFI(endCFI, doc);
//console.log(infoEnd);

            range.setStartAndEnd(
                infoStart.textNode[0], infoStart.textOffset,
                infoEnd.textNode[0], infoEnd.textOffset
            );

            if (range.canSurroundContents())
            {
                var span = doc.createElementNS("http://www.w3.org/1999/xhtml", 'span');
                span.id = HIGHLIGHT_ID;
                span.setAttribute("id", span.id);
                span.setAttribute("class", clazz);

                range.surroundContents(span);
            }
            else
            {
                console.error("RANGY unsupported text range");
            }
        }
        else
        {
            try
            {
                //var id = $hel.data("mediaOverlayData").par.getSmil().spineItemId;
                var id = par.getSmil().spineItemId;
                _reader.addHighlight(id, par.cfi.partialRangeCfi, HIGHLIGHT_ID,
                "highlight", //"underline"
                undefined // styles
                            );
            }
            catch(error)
            {
                console.error(error);
            }
        }
    };

// ---- CFI
//
//     function getFirstTextNode(node)
//     {
//         if (node.nodeType === Node.TEXT_NODE)
//         {
//             if (node.textContent.trim().length > 0)
//                 return node;
//         }
//
//         for (var i = 0; i < node.childNodes.length; i++)
//         {
//             var child = node.childNodes[i];
//             var first = getFirstTextNode(child);
//             if (first)
//             {
//                 return first;
//             }
//         }
//
//         return undefined;
//     }
//
//     function getLastTextNode(node)
//     {
//         if (node.nodeType === Node.TEXT_NODE)
//         {
//             if (node.textContent.trim().length > 0)
//                 return node;
//         }
//
//         for (var i = node.childNodes.length-1; i >= 0; i--)
//         {
//             var child = node.childNodes[i];
//             var last = getLastTextNode(child);
//             if (last)
//             {
//                 return last;
//             }
//         }
//
//         return undefined;
//     }
//

    this.reset = function() {

        if (_highlightedCfi)
        {
            var doc = _highlightedCfi.cfiTextParent.ownerDocument;
            if (USE_RANGY)
            {
                    var toRemove = undefined;
                    while ((toRemove = doc.getElementById(HIGHLIGHT_ID)) !== null)
                    {
                        var txt = toRemove.textContent; // innerHTML?
                        var txtNode = doc.createTextNode(txt);

                        toRemove.parentNode.replaceChild(txtNode, toRemove);
                        txtNode.parentNode.normalize();
                    }
            }
            else
            {
                try
                {
                    _reader.removeHighlight(HIGHLIGHT_ID);

                    var toRemove = undefined;
                    while ((toRemove = doc.getElementById("start-" + HIGHLIGHT_ID)) !== null)
                    {
            console.log("toRemove START");
            console.log(toRemove);
                        toRemove.parentNode.removeChild(toRemove);
                    }
                    while ((toRemove = doc.getElementById("end-" + HIGHLIGHT_ID)) !== null)
                    {
            console.log("toRemove END");
            console.log(toRemove);
                        toRemove.parentNode.removeChild(toRemove);
                    }
                }
                catch(error)
                {
                    console.error(error);
                }
            }
        }

        if(_highlightedElement) {

            if (_playbackActiveClass && _playbackActiveClass !== "")
            {
                //console.debug("MO RESET playbackActiveClass: " + _playbackActiveClass);
                $(_highlightedElement.ownerDocument.documentElement).removeClass(_playbackActiveClass);
            }

            if (_activeClass && _activeClass !== "")
            {
                //console.debug("MO RESET activeClass: " + _activeClass);
                $(_highlightedElement).removeClass(_activeClass);
            }
            //else
            //{
                //console.debug("MO RESET active NO CLASS: " + _activeClass);
                $(_highlightedElement).removeClass(DEFAULT_MO_ACTIVE_CLASS);
                //$(_highlightedElement).css("background", '');
            //}

            _highlightedElement = undefined;
            _activeClass = "";
            _playbackActiveClass = "";
        }
    }

};<|endoftext|>