<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2021 The Stdlib Authors.
*
* 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.
*/

'use strict';

// MODULES //

var promotionRules = require( '@stdlib/ndarray/promotion-rules' );
var safeCasts = require( '@stdlib/ndarray/safe-casts' );
var resolveEnum = require( '@stdlib/strided/base/dtype-resolve-enum' );
var resolveStr = require( '@stdlib/strided/base/dtype-resolve-str' );
var format = require( '@stdlib/string/format' );

// FUNCTIONS //

/**
* Returns the intersection of two sorted lists.
*
* @private
* @param {ArrayLikeObject} list1 - first sorted list
* @param {ArrayLikeObject} list2 - second sorted list
* @returns {ArrayLikeObject} result
*
* @example
* var list1 = [ 'a', 'b', 'c', 'd' ];
* var list2 = [ 'b', 'd', 'e' ];
*
* var out = intersection( list1, list2 );
* // returns [ 'b', 'd' ]
*/
function intersection( list1, list2 ) {
	var out;
	var M;
	var N;
	var v;
	var i;
	var j;
	var k;

	M = list1.length;
	N = list2.length;

	out = [];
	k = 0;
	for ( i = 0; i < M; i++ ) {
		if ( k >= N ) {
			break;
		}
		v = list1[ i ];
		for ( j = k; j < N; j++ ) {
			if ( v === list2[ j ] ) {
				k = j + 1;
				out.push( v );
				break;
			}
		}
	}
	return out;
}

/**
* Resolves a list of data types to data type strings.
*
* @private
* @param {ArrayLikeObject} dtypes - list of data types
* @returns {(StringArray|Error)} data type strings (or an error)
*
* @example
* var out = resolve( [ 1, 2, 3 ] );
* // returns [...]
*/
function resolve( dtypes ) {
	var out;
	var dt;
	var i;

	out = [];
	for ( i = 0; i < dtypes.length; i++ ) {
		dt = resolveStr( dtypes[ i ] );
		if ( dt === null ) {
			return new TypeError( format( 'invalid argument. Must provided recognized data types. Unable to resolve a data type string. Value: `%s`.', dtypes[ i ] ) );
		}
		out.push( dt );
	}
	return out;
}

/**
* Tests whether a provided array contains a specified value.
*
* @private
* @param {Array} arr - input array
* @param {*} value - search value
* @returns {boolean} boolean indicating whether a provided array contains a specified value
*/
function contains( arr, value ) {
	var i;
	for ( i = 0; i < arr.length; i++ ) {
		if ( arr[ i ] === value ) {
			return true;
		}
	}
	return false;
}

// MAIN //

/**
* Generates a list of binary interface signatures from strided array data types.
*
* ## Notes
*
* -   The function returns a strided array having a stride length of `3` (i.e., every `3` elements define a binary interface signature).
* -   For each signature (i.e., set of three consecutive non-overlapping strided array elements), the first two elements are the input data types and the third element is the return data type.
* -   All signatures follow type promotion rules.
*
* @param {Array} dtypes1 - list of supported data types for the first argument
* @param {Array} dtypes2 - list of supported data types for the second argument
* @param {Array} dtypes3 - list of supported data types for the output
* @param {Options} [options] - options
* @param {boolean} [options.enums=false] - boolean flag indicating whether to return signatures as a list of enumeration constants
* @throws {TypeError} must provide recognized data types
* @returns {Array} strided array containing binary interface signatures
*
* @example
* var dtypes = [
*     'float64',
*     'float32',
*     'int32',
*     'uint8'
* ];
*
* var sigs = signatures( dtypes, dtypes, dtypes );
* // returns [ 'float32', 'float32', 'float32', ... ]
*/
function signatures( dtypes1, dtypes2, dtypes3, options ) {
	var cache;
	var casts;
	var opts;
	var tmp;
	var out;
	var dt1;
	var dt2;
	var dt3;
	var t1;
	var t2;
	var t3;
	var t4;
	var M;
	var N;
	var i;
	var j;
	var k;

	if ( arguments.length > 3 ) {
		opts = options;
	} else {
		opts = {};
	}
	// Resolve the list of provided data types to data type strings:
	dt1 = resolve( dtypes1 );
	if ( dt1 instanceof Error ) {
		throw dt1;
	}
	if ( dtypes2 === dtypes1 ) { // don't do work if we don't need to
		dt2 = dt1;
	} else {
		dt2 = resolve( dtypes2 );
		if ( dt2 instanceof Error ) {
			throw dt2;
		}
	}
	if ( dtypes3 === dtypes1 ) { // don't do work if we don't need to
		dt3 = dt1;
	} else if ( dtypes3 === dtypes2 ) {
		dt3 = dt2;
	} else {
		dt3 = resolve( dtypes3 );
		if ( dt3 instanceof Error ) {
			throw dt3;
		}
	}
	// Sort the list of return dtypes:
	dt3.sort();

	// Initialize a cache for storing the safe casts for promoted dtypes:
	cache = {};

	// Generate the list of signatures...
	M = dt1.length;
	N = dt2.length;
	out = [];
	for ( i = 0; i < M; i++ ) {
		t1 = dt1[ i ];
		for ( j = 0; j < N; j++ ) {
			t2 = dt2[ j ];

			// Resolve the promoted dtype for the current dtype pair:
			t3 = promotionRules( t1, t2 );

			// Check whether the dtype pair promotes...
			if ( t3 === -1 || t3 === null ) {
				// The dtype pair does not promote:
				continue;
			}
			// Check whether the promoted dtype is in our list of output dtypes...
			if ( contains( dt3, t3 ) ) {
				out.push( t1, t2, t3 );
			}
			// Retrieve the allowed casts for the promoted dtype:
			casts = cache[ t3 ];

			// If a list of allowed casts is not in the cache, we need to resolve them...
			if ( casts === void 0 ) {
				// Resolve the list of safe casts for the promoted dtype:
				casts = safeCasts( t3 );

				// Remove safe casts which are not among the supported output dtypes:
				casts = intersection( dt3, casts.sort() );

				// Store the list of safe casts in the cache:
				cache[ t3 ] = casts;
			}
			// Generate signatures for allowed casts...
			for ( k = 0; k < casts.length; k++ ) {
				t4 = casts[ k ];
				if ( t4 !== t3 ) {
					out.push( t1, t2, t4 );
				}
			}
		}
	}
	if ( opts.enums ) {
		tmp = [];
		for ( i = 0; i < out.length; i++ ) {
			tmp.push( resolveEnum( out[ i ] ) );
		}
		out = tmp;
	}
	return out;
}

// EXPORTS //

module.exports = signatures;
<commit_msg>Fix error message<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2021 The Stdlib Authors.
*
* 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.
*/

'use strict';

// MODULES //

var promotionRules = require( '@stdlib/ndarray/promotion-rules' );
var safeCasts = require( '@stdlib/ndarray/safe-casts' );
var resolveEnum = require( '@stdlib/strided/base/dtype-resolve-enum' );
var resolveStr = require( '@stdlib/strided/base/dtype-resolve-str' );
var format = require( '@stdlib/string/format' );

// FUNCTIONS //

/**
* Returns the intersection of two sorted lists.
*
* @private
* @param {ArrayLikeObject} list1 - first sorted list
* @param {ArrayLikeObject} list2 - second sorted list
* @returns {ArrayLikeObject} result
*
* @example
* var list1 = [ 'a', 'b', 'c', 'd' ];
* var list2 = [ 'b', 'd', 'e' ];
*
* var out = intersection( list1, list2 );
* // returns [ 'b', 'd' ]
*/
function intersection( list1, list2 ) {
	var out;
	var M;
	var N;
	var v;
	var i;
	var j;
	var k;

	M = list1.length;
	N = list2.length;

	out = [];
	k = 0;
	for ( i = 0; i < M; i++ ) {
		if ( k >= N ) {
			break;
		}
		v = list1[ i ];
		for ( j = k; j < N; j++ ) {
			if ( v === list2[ j ] ) {
				k = j + 1;
				out.push( v );
				break;
			}
		}
	}
	return out;
}

/**
* Resolves a list of data types to data type strings.
*
* @private
* @param {ArrayLikeObject} dtypes - list of data types
* @returns {(StringArray|Error)} data type strings (or an error)
*
* @example
* var out = resolve( [ 1, 2, 3 ] );
* // returns [...]
*/
function resolve( dtypes ) {
	var out;
	var dt;
	var i;

	out = [];
	for ( i = 0; i < dtypes.length; i++ ) {
		dt = resolveStr( dtypes[ i ] );
		if ( dt === null ) {
			return new TypeError( format( 'invalid argument. Must provide recognized data types. Unable to resolve a data type string. Value: `%s`.', dtypes[ i ] ) );
		}
		out.push( dt );
	}
	return out;
}

/**
* Tests whether a provided array contains a specified value.
*
* @private
* @param {Array} arr - input array
* @param {*} value - search value
* @returns {boolean} boolean indicating whether a provided array contains a specified value
*/
function contains( arr, value ) {
	var i;
	for ( i = 0; i < arr.length; i++ ) {
		if ( arr[ i ] === value ) {
			return true;
		}
	}
	return false;
}

// MAIN //

/**
* Generates a list of binary interface signatures from strided array data types.
*
* ## Notes
*
* -   The function returns a strided array having a stride length of `3` (i.e., every `3` elements define a binary interface signature).
* -   For each signature (i.e., set of three consecutive non-overlapping strided array elements), the first two elements are the input data types and the third element is the return data type.
* -   All signatures follow type promotion rules.
*
* @param {Array} dtypes1 - list of supported data types for the first argument
* @param {Array} dtypes2 - list of supported data types for the second argument
* @param {Array} dtypes3 - list of supported data types for the output
* @param {Options} [options] - options
* @param {boolean} [options.enums=false] - boolean flag indicating whether to return signatures as a list of enumeration constants
* @throws {TypeError} must provide recognized data types
* @returns {Array} strided array containing binary interface signatures
*
* @example
* var dtypes = [
*     'float64',
*     'float32',
*     'int32',
*     'uint8'
* ];
*
* var sigs = signatures( dtypes, dtypes, dtypes );
* // returns [ 'float32', 'float32', 'float32', ... ]
*/
function signatures( dtypes1, dtypes2, dtypes3, options ) {
	var cache;
	var casts;
	var opts;
	var tmp;
	var out;
	var dt1;
	var dt2;
	var dt3;
	var t1;
	var t2;
	var t3;
	var t4;
	var M;
	var N;
	var i;
	var j;
	var k;

	if ( arguments.length > 3 ) {
		opts = options;
	} else {
		opts = {};
	}
	// Resolve the list of provided data types to data type strings:
	dt1 = resolve( dtypes1 );
	if ( dt1 instanceof Error ) {
		throw dt1;
	}
	if ( dtypes2 === dtypes1 ) { // don't do work if we don't need to
		dt2 = dt1;
	} else {
		dt2 = resolve( dtypes2 );
		if ( dt2 instanceof Error ) {
			throw dt2;
		}
	}
	if ( dtypes3 === dtypes1 ) { // don't do work if we don't need to
		dt3 = dt1;
	} else if ( dtypes3 === dtypes2 ) {
		dt3 = dt2;
	} else {
		dt3 = resolve( dtypes3 );
		if ( dt3 instanceof Error ) {
			throw dt3;
		}
	}
	// Sort the list of return dtypes:
	dt3.sort();

	// Initialize a cache for storing the safe casts for promoted dtypes:
	cache = {};

	// Generate the list of signatures...
	M = dt1.length;
	N = dt2.length;
	out = [];
	for ( i = 0; i < M; i++ ) {
		t1 = dt1[ i ];
		for ( j = 0; j < N; j++ ) {
			t2 = dt2[ j ];

			// Resolve the promoted dtype for the current dtype pair:
			t3 = promotionRules( t1, t2 );

			// Check whether the dtype pair promotes...
			if ( t3 === -1 || t3 === null ) {
				// The dtype pair does not promote:
				continue;
			}
			// Check whether the promoted dtype is in our list of output dtypes...
			if ( contains( dt3, t3 ) ) {
				out.push( t1, t2, t3 );
			}
			// Retrieve the allowed casts for the promoted dtype:
			casts = cache[ t3 ];

			// If a list of allowed casts is not in the cache, we need to resolve them...
			if ( casts === void 0 ) {
				// Resolve the list of safe casts for the promoted dtype:
				casts = safeCasts( t3 );

				// Remove safe casts which are not among the supported output dtypes:
				casts = intersection( dt3, casts.sort() );

				// Store the list of safe casts in the cache:
				cache[ t3 ] = casts;
			}
			// Generate signatures for allowed casts...
			for ( k = 0; k < casts.length; k++ ) {
				t4 = casts[ k ];
				if ( t4 !== t3 ) {
					out.push( t1, t2, t4 );
				}
			}
		}
	}
	if ( opts.enums ) {
		tmp = [];
		for ( i = 0; i < out.length; i++ ) {
			tmp.push( resolveEnum( out[ i ] ) );
		}
		out = tmp;
	}
	return out;
}

// EXPORTS //

module.exports = signatures;
<|endoftext|>


<commit_before>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'playbackRateSelector', mw.KBaseComponent.extend({

		defaultConfig: {
			"parent": "controlsContainer",
		 	"order": 61,
		 	"displayImportance": "medium",
		 	"align": "right",
		 	"showTooltip": true,
		 	'defaultSpeed': '1',
			'speeds': ".5,.75,1,1.5,2",
			'enableKeyboardShortcuts': true
		},

		isDisabled: false,

		isSafeEnviornment: function(){
			var _this = this,
				deferred = $.Deferred();

			this.bind('playerReady', function(){
				deferred.resolve(!!_this.getPlayer().playbackRate);
			});
			return deferred.promise();
		},

		setup: function(){
			this.currentSpeed = this.getConfig('defaultSpeed') || 1;
			this.speedSet = this.getConfig('speeds').split(',');
			this.addBindings();
		},

		addBindings: function(){
			var _this = this;
			this.bind( 'playerReady', function(){
				_this.buildMenu();
			});
			this.bind( 'playbackRateChangeSpeed', function(e, arg ){
				_this.setSpeedFromApi( arg );
			});
			if( this.getConfig('enableKeyboardShortcuts') ){
				this.bind( 'addKeyBindCallback', function( e, addKeyCallback ){
					_this.addKeyboardShortcuts( addKeyCallback );
				});
			}
		},
		// API for this plugin. With this API any external plugin or JS code will be able to set
		// a specific speed, or a faster/slower/fastest/slowest
		setSpeedFromApi: function( arg ) {
			var newSpeed;
			switch(arg){
				case 'faster':
					newSpeed = this.getFasterSpeed();
				break;
				case 'fastest':
					newSpeed = this.speedSet[this.speedSet.length-1] ;
				break;
				case 'slower':
					newSpeed = this.getSlowerSpeed();
				break;
				case 'slowest':
					newSpeed = this.speedSet[0] ;
				break;
				default:
					newSpeed = arg;
				break
			}
			this.setSpeed(newSpeed);
		},
		addKeyboardShortcuts: function( addKeyCallback ){
			var _this = this;
			// Add + Sign for faster speed
			addKeyCallback( 'shift+187', function(){
				_this.setSpeed( _this.getFasterSpeed() );
			});
			// Add - Sigh for slower speed
			addKeyCallback( 189, function(){
				_this.setSpeed( _this.getSlowerSpeed() );
			});
			// Add = Sigh for normal speed
			addKeyCallback( 187, function(){
				_this.setSpeed( _this.getConfig('defaultSpeed') );
			});
		},

		getFasterSpeed: function(){
			if( this.speedSet[this.getCurrentSpeedIndex()+1] ){
				return this.speedSet[this.getCurrentSpeedIndex()+1];
			}
			return this.speedSet[this.getCurrentSpeedIndex()];
		},
		getSlowerSpeed: function(){
			if( this.speedSet[this.getCurrentSpeedIndex()-1] ){
				return this.speedSet[this.getCurrentSpeedIndex()-1];
			}
			return this.speedSet[this.getCurrentSpeedIndex()];
		},

		buildMenu: function(){
			var _this = this;

			// Destroy old menu
			this.getMenu().destroy();

			$.each( this.speedSet, function( idx, speedFloat ){
				var active = ( _this.currentSpeed == speedFloat ) ? true : false;
				_this.getMenu().addItem({
					'label': speedFloat + 'x',
					'callback': function(){
						_this.setSpeed( speedFloat );
					},
					'active': active
				});
			});
		},
		setSpeed: function( newSpeed ){
			this.log('Set Speed to: ' + newSpeed);
			this.currentSpeed = newSpeed;
			this.getPlayer().getPlayerElement().playbackRate = newSpeed;
			this.getBtn().text( newSpeed + 'x' );
			this.getPlayer().triggerHelper( 'updatedPlaybackRate', newSpeed);
		},
		getCurrentSpeedIndex: function(){
			var _this = this;
			var index = null;
			$.each(this.speedSet, function( idx, speed){
				if( _this.currentSpeed == speed ){
					index = idx;
					return true;
				}
			});
			return index;
		},
		toggleMenu: function(){
			if ( this.isDisabled ) {
				return;
			}
			this.getMenu().toggle();
		},
		getComponent: function() {
			var _this = this;
			if( !this.$el ) {
				var $menu = $( '<ul />' );
				var $button = $( '<button />' )
								.addClass( 'btn' )
								.attr('title', 'Playback Speed')
								.text( this.currentSpeed + 'x' )
								.click( function(e){
									_this.toggleMenu();
								});

				this.$el = $( '<div />' )
								.addClass( 'dropup' + this.getCssClass() )
								.append( $button, $menu );
			}
			return this.$el;
		},
		getMenu: function(){
			if( !this.menu ) {
				this.menu = new mw.KMenu(this.getComponent().find('ul'), {
					tabIndex: this.getBtn().attr('tabindex')
				});
			}
			return this.menu;
		},
		getBtn: function(){
			return this.getComponent().find( 'button' );
		},
		onEnable: function(){
			this.isDisabled = false;
			this.getBtn().removeClass( 'disabled' );
		},
		onDisable: function(){
			this.isDisabled = true;
			this.getComponent().removeClass( 'open' );
			this.getBtn().addClass( 'disabled' );
		},
	}));

} )( window.mw, window.jQuery );<commit_msg>#FEC-1303 #time 2h #comment due to native playback limitation on Firefox and IE - changed behaviour to start playback when changing speed before video playback started<commit_after>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'playbackRateSelector', mw.KBaseComponent.extend({

		defaultConfig: {
			"parent": "controlsContainer",
		 	"order": 61,
		 	"displayImportance": "medium",
		 	"align": "right",
		 	"showTooltip": true,
		 	'defaultSpeed': '1',
			'speeds': ".5,.75,1,1.5,2",
			'enableKeyboardShortcuts': true
		},

		isDisabled: false,

		isSafeEnviornment: function(){
			var _this = this,
				deferred = $.Deferred();

			this.bind('playerReady', function(){
				deferred.resolve(!!_this.getPlayer().playbackRate);
			});
			return deferred.promise();
		},

		setup: function(){
			this.currentSpeed = this.getConfig('defaultSpeed') || 1;
			this.speedSet = this.getConfig('speeds').split(',');
			this.addBindings();
		},

		addBindings: function(){
			var _this = this;
			this.bind( 'playerReady', function(){
				_this.buildMenu();
			});
			this.bind( 'playbackRateChangeSpeed', function(e, arg ){
				_this.setSpeedFromApi( arg );
			});
			if( this.getConfig('enableKeyboardShortcuts') ){
				this.bind( 'addKeyBindCallback', function( e, addKeyCallback ){
					_this.addKeyboardShortcuts( addKeyCallback );
				});
			}
		},
		// API for this plugin. With this API any external plugin or JS code will be able to set
		// a specific speed, or a faster/slower/fastest/slowest
		setSpeedFromApi: function( arg ) {
			var newSpeed;
			switch(arg){
				case 'faster':
					newSpeed = this.getFasterSpeed();
				break;
				case 'fastest':
					newSpeed = this.speedSet[this.speedSet.length-1] ;
				break;
				case 'slower':
					newSpeed = this.getSlowerSpeed();
				break;
				case 'slowest':
					newSpeed = this.speedSet[0] ;
				break;
				default:
					newSpeed = arg;
				break
			}
			this.setSpeed(newSpeed);
		},
		addKeyboardShortcuts: function( addKeyCallback ){
			var _this = this;
			// Add + Sign for faster speed
			addKeyCallback( 'shift+187', function(){
				_this.setSpeed( _this.getFasterSpeed() );
			});
			// Add - Sigh for slower speed
			addKeyCallback( 189, function(){
				_this.setSpeed( _this.getSlowerSpeed() );
			});
			// Add = Sigh for normal speed
			addKeyCallback( 187, function(){
				_this.setSpeed( _this.getConfig('defaultSpeed') );
			});
		},

		getFasterSpeed: function(){
			if( this.speedSet[this.getCurrentSpeedIndex()+1] ){
				return this.speedSet[this.getCurrentSpeedIndex()+1];
			}
			return this.speedSet[this.getCurrentSpeedIndex()];
		},
		getSlowerSpeed: function(){
			if( this.speedSet[this.getCurrentSpeedIndex()-1] ){
				return this.speedSet[this.getCurrentSpeedIndex()-1];
			}
			return this.speedSet[this.getCurrentSpeedIndex()];
		},

		buildMenu: function(){
			var _this = this;

			// Destroy old menu
			this.getMenu().destroy();

			$.each( this.speedSet, function( idx, speedFloat ){
				var active = ( _this.currentSpeed == speedFloat ) ? true : false;
				_this.getMenu().addItem({
					'label': speedFloat + 'x',
					'callback': function(){
						_this.setSpeed( speedFloat );
					},
					'active': active
				});
			});
		},
		setSpeed: function( newSpeed ){
			this.log('Set Speed to: ' + newSpeed);
			this.currentSpeed = newSpeed;
			// workaround for Firefox and IE - start playback upon changing speed when video wasn't played yet
			if (this.getPlayer().currentTime == 0 && !this.getPlayer().isPlaying()){
				this.getPlayer().play();
			}
			this.getPlayer().getPlayerElement().playbackRate = newSpeed;
			this.getBtn().text( newSpeed + 'x' );
			this.getPlayer().triggerHelper( 'updatedPlaybackRate', newSpeed);
		},
		getCurrentSpeedIndex: function(){
			var _this = this;
			var index = null;
			$.each(this.speedSet, function( idx, speed){
				if( _this.currentSpeed == speed ){
					index = idx;
					return true;
				}
			});
			return index;
		},
		toggleMenu: function(){
			if ( this.isDisabled ) {
				return;
			}
			this.getMenu().toggle();
		},
		getComponent: function() {
			var _this = this;
			if( !this.$el ) {
				var $menu = $( '<ul />' );
				var $button = $( '<button />' )
								.addClass( 'btn' )
								.attr('title', 'Playback Speed')
								.text( this.currentSpeed + 'x' )
								.click( function(e){
									_this.toggleMenu();
								});

				this.$el = $( '<div />' )
								.addClass( 'dropup' + this.getCssClass() )
								.append( $button, $menu );
			}
			return this.$el;
		},
		getMenu: function(){
			if( !this.menu ) {
				this.menu = new mw.KMenu(this.getComponent().find('ul'), {
					tabIndex: this.getBtn().attr('tabindex')
				});
			}
			return this.menu;
		},
		getBtn: function(){
			return this.getComponent().find( 'button' );
		},
		onEnable: function(){
			this.isDisabled = false;
			this.getBtn().removeClass( 'disabled' );
		},
		onDisable: function(){
			this.isDisabled = true;
			this.getComponent().removeClass( 'open' );
			this.getBtn().addClass( 'disabled' );
		},
	}));

} )( window.mw, window.jQuery );<|endoftext|>


<commit_before>/**
 *
 */
var EasyAssess = require('../../easyassess.application');

EasyAssess.directives["esFormGroupEdit"]
    = EasyAssess.app.directive("esFormGroupEdit", function (ngDialog,esRequestService) {

    return {
        restrict: 'E',
        replace: true,
        transclude: false,
        scope: {
            esGroup:"=",
            esData:"=?",
            esType:"@"
        },
        template:'
'
        +	 '
'
        + 	'

检测分类	样本	编码组

'
        +     '
'
        +			'
'
        +				''
        +					'
'
        +						'
'
        +							'	'
        +						'
'
        +						'
'
        +							'
{{row.item.subject}} - {{row.item.unit}}
	'
        +						'
'
        +					'
'
        +				'	'
        +				''
        +					'<es-form-group-specimens-assess ng-if="esType == \'assess\'" es-data="esData" es-group="esGroup"></es-form-group-specimens-assess>'
        +					'<es-form-group-specimens-track ng-if="esType != \'assess\'" es-data="esData" es-group="esGroup"></es-form-group-specimens-track>'
        +				'	'
        +  			''
        +					'
'
        +						'
'
        +							'

{{group.name}}

		'
        +						'
'
        +						'
'
        +							''
        +                               '<es-app-lookup subject-guid="{{row.guid}}" code-group-guid="{{group.guid}}" es-resource="code/list/categorized?group_id={{group.id}}" es-columns="codeFields" es-width="100" es-id="codeItemLookup" es-subject="row" es-value-field="codeNumber"></es-app-lookup>'
        +                           '	'
        +						'
'
        +					'
'
        +				'	'
        +       ''
        +          '
'
        +						'
'
        +							'试剂批号	'
        +							'试剂有效期	'
        //+							'检测日期	'
        +						'
'
        +            '
'
        +               '<input class="es-form-signature-line" ng-blur="detailChanged(row, \'batchNumber\' ,$event)" placeholder="输入试剂批号"/>	'
        +               '<input class="es-form-signature-line" ng-blur="detailChanged(row, \'expire\', $event)" placeholder="输入有效期"/>	'
        // +               '<es-app-calendar es-id="row" es-date="testDate" es-holder="请输入时间"></es-app-calendar>	'
        +            '
'
        +           '
'
        +       '	'
        +			'
'
        +	 	'

'
        +   '
'
        +       '
'
        +           '	'
        +           '<input class="es-form-signature-line" ng-blur="signatureChanged(\'tester\' ,$event)" placeholder="输入检测人"/>	'
        +           '<es-app-calendar es-id="testDate" es-date="testDate" es-holder="请输入检测日期"></es-app-calendar>	'
        +       '
'
        +       '
'
        +           '	'
        +           '<input class="es-form-signature-line" ng-blur="signatureChanged(\'reviewer\' ,$event)" placeholder="输入审核人"/>	'
        +           '<es-app-calendar es-id="reviewDate" es-date="reviewDate" es-holder="请输入审核日期"></es-app-calendar>	'
        +       '
'
        +       '
'
        +           '	'
        +           ''
        +               '备注:'
        +               '<textarea ng-blur="signatureChanged(\'comments\' ,$event)" class="form-control" rows="3"></textarea>'
        +           '	'
        +       '
'
        +   '
'
        + '
',

        controller: ["$scope", function($scope, $element, $attrs){
            $scope.codeFields = [
                {title:"代码", field:"codeNumber", type:"string",searchable:true,default:false},
                {title:"名称", field:"name", type:"string",searchable:true,default:true},
                {title:"代码组", field:"groupName", type:"string",searchable:true, default:false,cascadeField:"group.name"}
            ];

            $scope.codeGroups = [];

            $scope.formatDate = function (date) {
                function pad(n) {
                    return n < 10 ? '0' + n : n;
                }
                return date && date.getFullYear()
                  + '-' + pad(date.getMonth() + 1)
                  + '-' + pad(date.getDate());
            };

            $scope.hideDate = true;

            $scope.closeDate = function () {
                $scope.hideDate = true;
            };

            $scope.openDate = function () {
                $scope.hideDate = false;
            };

            $scope.detailChanged = function(row, field ,e){
                var targetValue = $(e.target).val();
                var value = {
                    subjectGuid:row.guid,
                    value:targetValue
                };
                $scope.$emit(field + 'Changed',value);
            };

            $scope.signatureChanged = function(field ,e){
                var targetValue = $(e.target).val();
                var value = {
                    groupGuid: $scope.esGroup.guid,
                    value: targetValue,
                    field: field
                };
                $scope.$emit('signatureChanged',value);
            };

            $scope.$on('$testDate_DateTimeSelected',function(e, data){
                var value = {
                    groupGuid: $scope.esGroup.guid,
                    value: data["value"],
                    field: 'testDate'
                };
                $scope.$emit('signatureChanged',value);
            });

            $scope.$on('$reviewDate_DateTimeSelected',function(e, data){
                var value = {
                    groupGuid: $scope.esGroup.guid,
                    value: data["value"],
                    field: 'reviewDate'
                };
                $scope.$emit('signatureChanged',value);
            });
        }],
        link: function($scope, ele, attrs, ctrl) {
        }
    }
});
<commit_msg>Fixed group name can not be see<commit_after>/**
 *
 */
var EasyAssess = require('../../easyassess.application');

EasyAssess.directives["esFormGroupEdit"]
    = EasyAssess.app.directive("esFormGroupEdit", function (ngDialog,esRequestService) {

    return {
        restrict: 'E',
        replace: true,
        transclude: false,
        scope: {
            esGroup:"=",
            esData:"=?",
            esType:"@"
        },
        template:'
'
        +	 '
'
        + 	'

检测分类	样本	编码组

'
        +     '
'
        +			'
'
        +				''
        +					'
'
        +						'
'
        +							'{{esGroup.name}}	'
        +						'
'
        +						'
'
        +							'
{{row.item.subject}} - {{row.item.unit}}
	'
        +						'
'
        +					'
'
        +				'	'
        +				''
        +					'<es-form-group-specimens-assess ng-if="esType == \'assess\'" es-data="esData" es-group="esGroup"></es-form-group-specimens-assess>'
        +					'<es-form-group-specimens-track ng-if="esType != \'assess\'" es-data="esData" es-group="esGroup"></es-form-group-specimens-track>'
        +				'	'
        +  			''
        +					'
'
        +						'
'
        +							'

{{group.name}}

		'
        +						'
'
        +						'
'
        +							''
        +                               '<es-app-lookup subject-guid="{{row.guid}}" code-group-guid="{{group.guid}}" es-resource="code/list/categorized?group_id={{group.id}}" es-columns="codeFields" es-width="100" es-id="codeItemLookup" es-subject="row" es-value-field="codeNumber"></es-app-lookup>'
        +                           '	'
        +						'
'
        +					'
'
        +				'	'
        +       ''
        +          '
'
        +						'
'
        +							'试剂批号	'
        +							'试剂有效期	'
        //+							'检测日期	'
        +						'
'
        +            '
'
        +               '<input class="es-form-signature-line" ng-blur="detailChanged(row, \'batchNumber\' ,$event)" placeholder="输入试剂批号"/>	'
        +               '<input class="es-form-signature-line" ng-blur="detailChanged(row, \'expire\', $event)" placeholder="输入有效期"/>	'
        // +               '<es-app-calendar es-id="row" es-date="testDate" es-holder="请输入时间"></es-app-calendar>	'
        +            '
'
        +           '
'
        +       '	'
        +			'
'
        +	 	'

'
        +   '
'
        +       '
'
        +           '	'
        +           '<input class="es-form-signature-line" ng-blur="signatureChanged(\'tester\' ,$event)" placeholder="输入检测人"/>	'
        +           '<es-app-calendar es-id="testDate" es-date="testDate" es-holder="请输入检测日期"></es-app-calendar>	'
        +       '
'
        +       '
'
        +           '	'
        +           '<input class="es-form-signature-line" ng-blur="signatureChanged(\'reviewer\' ,$event)" placeholder="输入审核人"/>	'
        +           '<es-app-calendar es-id="reviewDate" es-date="reviewDate" es-holder="请输入审核日期"></es-app-calendar>	'
        +       '
'
        +       '
'
        +           '	'
        +           ''
        +               '备注:'
        +               '<textarea ng-blur="signatureChanged(\'comments\' ,$event)" class="form-control" rows="3"></textarea>'
        +           '	'
        +       '
'
        +   '
'
        + '
',

        controller: ["$scope", function($scope, $element, $attrs){
            $scope.codeFields = [
                {title:"代码", field:"codeNumber", type:"string",searchable:true,default:false},
                {title:"名称", field:"name", type:"string",searchable:true,default:true},
                {title:"代码组", field:"groupName", type:"string",searchable:true, default:false,cascadeField:"group.name"}
            ];

            $scope.codeGroups = [];

            $scope.formatDate = function (date) {
                function pad(n) {
                    return n < 10 ? '0' + n : n;
                }
                return date && date.getFullYear()
                  + '-' + pad(date.getMonth() + 1)
                  + '-' + pad(date.getDate());
            };

            $scope.hideDate = true;

            $scope.closeDate = function () {
                $scope.hideDate = true;
            };

            $scope.openDate = function () {
                $scope.hideDate = false;
            };

            $scope.detailChanged = function(row, field ,e){
                var targetValue = $(e.target).val();
                var value = {
                    subjectGuid:row.guid,
                    value:targetValue
                };
                $scope.$emit(field + 'Changed',value);
            };

            $scope.signatureChanged = function(field ,e){
                var targetValue = $(e.target).val();
                var value = {
                    groupGuid: $scope.esGroup.guid,
                    value: targetValue,
                    field: field
                };
                $scope.$emit('signatureChanged',value);
            };

            $scope.$on('$testDate_DateTimeSelected',function(e, data){
                var value = {
                    groupGuid: $scope.esGroup.guid,
                    value: data["value"],
                    field: 'testDate'
                };
                $scope.$emit('signatureChanged',value);
            });

            $scope.$on('$reviewDate_DateTimeSelected',function(e, data){
                var value = {
                    groupGuid: $scope.esGroup.guid,
                    value: data["value"],
                    field: 'reviewDate'
                };
                $scope.$emit('signatureChanged',value);
            });
        }],
        link: function($scope, ele, attrs, ctrl) {
        }
    }
});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { c } from 'ttag';
import { getCountryByAbbr, correctAbbr } from '../../../helpers/countries';

const flags = require.context('design-system/assets/img/shared/flags/4x3', true, /.svg$/);
const flagsMap = flags.keys().reduce((acc, key) => {
    acc[key] = () => flags(key);
    return acc;
}, {});

const getFlagSvg = (abbreviation) => {
    const key = `./${abbreviation.toLowerCase()}.svg`;
    if (!flagsMap[key]) {
        return;
    }
    return flagsMap[key]().default;
};

const Country = ({ server: { EntryCountry, ExitCountry } }) => {
    const isRouted = EntryCountry && EntryCountry !== ExitCountry;
    const correctEntryCountry = correctAbbr(EntryCountry);
    const correctExitCountry = correctAbbr(ExitCountry);
    const entryCountryName = getCountryByAbbr(correctEntryCountry);
    const exitCountryName = getCountryByAbbr(correctExitCountry);

    return (

            {getCountryByAbbr(correctExitCountry)}
            {isRouted && {c('CountryInfo').t`(via ${entryCountryName})`}}

    );
};

Country.propTypes = {
    server: PropTypes.shape({
        EntryCountry: PropTypes.string,
        ExitCountry: PropTypes.string,
    }),
};

export default Country;
<commit_msg>[VPNFE-110] add a border on flags<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { c } from 'ttag';
import { getCountryByAbbr, correctAbbr } from '../../../helpers/countries';

const flags = require.context('design-system/assets/img/shared/flags/4x3', true, /.svg$/);
const flagsMap = flags.keys().reduce((acc, key) => {
    acc[key] = () => flags(key);
    return acc;
}, {});

const getFlagSvg = (abbreviation) => {
    const key = `./${abbreviation.toLowerCase()}.svg`;
    if (!flagsMap[key]) {
        return;
    }
    return flagsMap[key]().default;
};

const Country = ({ server: { EntryCountry, ExitCountry } }) => {
    const isRouted = EntryCountry && EntryCountry !== ExitCountry;
    const correctEntryCountry = correctAbbr(EntryCountry);
    const correctExitCountry = correctAbbr(ExitCountry);
    const entryCountryName = getCountryByAbbr(correctEntryCountry);
    const exitCountryName = getCountryByAbbr(correctExitCountry);

    return (

            {getCountryByAbbr(correctExitCountry)}
            {isRouted && {c('CountryInfo').t`(via ${entryCountryName})`}}

    );
};

Country.propTypes = {
    server: PropTypes.shape({
        EntryCountry: PropTypes.string,
        ExitCountry: PropTypes.string,
    }),
};

export default Country;
<|endoftext|>


<commit_before>// Module:
import { PageObjectsModule } from '../page-objects.module';

// Dependencies:
import { match, parse } from 'esquery';
import '../deprecated/element-parser.service';
import '../models/element';

// Queries:
const SELECTOR_QUERY = parse('CallExpression[callee.object.name="by"][callee.property.name="css"] > Literal');
const ELEMENT_QUERY = parse(`AssignmentExpression > CallExpression[callee.name="find"]`);
const ELEMENT_MULTIPLE_QUERY = parse('AssignmentExpression > FunctionExpression ReturnStatement > CallExpression[callee.object.callee.name="findAll"]');
const PAGE_OBJECT_QUERY = parse('AssignmentExpression > NewExpression[arguments.0.callee.name="find"]');
const PAGE_OBJECT_MULTIPLE_QUERY = parse('AssignmentExpression > FunctionExpression ReturnStatement > NewExpression');

function ElementParserService (
    ElementModel,
    astCompareService,
    deprecatedElementParserService
) {
    const QUERIES = {
        [ELEMENT_QUERY]: _elementParser,
        [ELEMENT_MULTIPLE_QUERY]: _elementMultipleParser,
        [PAGE_OBJECT_QUERY]: _pageObjectParser,
        [PAGE_OBJECT_MULTIPLE_QUERY]: _pageObjectMultipleParser,
    };

    return { parse };

    function parse (pageObject, astObject, meta = {}) {
        let element = new ElementModel(pageObject);
        element.name = meta.name;

        Object.keys(QUERIES).find(query => {
            let [result] = match(astObject, query);
            if (result) {
                QUERIES[query](element, result);
                return element;
            }
        });

        // Here we return if we parsed correctly, otherwise attempt to use the
        // deprecatedElementParserService.
        //
        // If the deprecatedElementParserService also fails, the action will be marked
        // as unparseable.
        let parsedCorrectly = astCompareService.compare(astObject, element.ast);
        if (parsedCorrectly) {
            return element;
        }

        return deprecatedElementParserService.parse(pageObject, astObject, meta);
    }

    function _selectorParser (element, astObject) {
        element.selector = astObject.value;
    }

    function _elementParser (element, astObject) {
        let [selector] = match(astObject, SELECTOR_QUERY);
        if (selector) {
            _selectorParser(element, selector);
        }
    }

    function _elementMultipleParser (element, astObject) {
        element.group = true;
        _elementParser(element, astObject);
    }

    function _pageObjectParser (element, astObject) {
        let [selector] = match(astObject, SELECTOR_QUERY);
        if (selector) {
            _selectorParser(element, selector);
        }

        element.type = element.pageObject.availablePageObjects.find(pageObject => {
            return pageObject.variableName === astObject.callee.name;
        });
        if (element.type) {
            element.actions = element.type.actions;
        }
    }

    function _pageObjectMultipleParser (element, astObject) {
        element.group = true;
        _pageObjectParser(element, astObject);
    }
}

PageObjectsModule.service('elementParserService', ElementParserService);
<commit_msg>fix(page-objects :page_facing_up:) - fixing element parser for updated esquery<commit_after>// Module:
import { PageObjectsModule } from '../page-objects.module';

// Dependencies:
import { match, parse } from 'esquery';
import '../deprecated/element-parser.service';
import '../models/element';

// Queries:
const SELECTOR_QUERY = parse('CallExpression[callee.object.name="by"][callee.property.name="css"] > Literal');
const ELEMENT_QUERY = parse(`AssignmentExpression > CallExpression[callee.name="find"]`);
const ELEMENT_MULTIPLE_QUERY = parse('AssignmentExpression > FunctionExpression ReturnStatement > CallExpression[callee.object.callee.name="findAll"]');
const PAGE_OBJECT_QUERY = parse('AssignmentExpression > NewExpression[arguments.0.callee.name="find"]');
const PAGE_OBJECT_MULTIPLE_QUERY = parse('AssignmentExpression > FunctionExpression ReturnStatement > NewExpression');

function ElementParserService (
    ElementModel,
    astCompareService,
    deprecatedElementParserService
) {
    const QUERY_SELECTOR = {
        element: ELEMENT_QUERY,
        elementMultiple: ELEMENT_MULTIPLE_QUERY,
        pageObject: PAGE_OBJECT_QUERY,
        pageObjectMultiple: PAGE_OBJECT_MULTIPLE_QUERY
    };
    const QUERY_HANDLER = {
        element: _elementParser,
        elementMultiple: _elementMultipleParser,
        pageObject: _pageObjectParser,
        pageObjectMultiple: _pageObjectMultipleParser
    };

    return { parse };

    function parse (pageObject, astObject, meta = {}) {
        let element = new ElementModel(pageObject);
        element.name = meta.name;

        Object.keys(QUERY_SELECTOR).find(key => {
            let [result] = match(astObject, QUERY_SELECTOR[key]);
            if (result) {
                QUERY_HANDLER[key](element, result);
                return element;
            }
        });

        // Here we return if we parsed correctly, otherwise attempt to use the
        // deprecatedElementParserService.
        //
        // If the deprecatedElementParserService also fails, the action will be marked
        // as unparseable.
        let parsedCorrectly = astCompareService.compare(astObject, element.ast);
        if (parsedCorrectly) {
            return element;
        }

        return deprecatedElementParserService.parse(pageObject, astObject, meta);
    }

    function _selectorParser (element, astObject) {
        element.selector = astObject.value;
    }

    function _elementParser (element, astObject) {
        let [selector] = match(astObject, SELECTOR_QUERY);
        if (selector) {
            _selectorParser(element, selector);
        }
    }

    function _elementMultipleParser (element, astObject) {
        element.group = true;
        _elementParser(element, astObject);
    }

    function _pageObjectParser (element, astObject) {
        let [selector] = match(astObject, SELECTOR_QUERY);
        if (selector) {
            _selectorParser(element, selector);
        }

        element.type = element.pageObject.availablePageObjects.find(pageObject => {
            return pageObject.variableName === astObject.callee.name;
        });
        if (element.type) {
            element.actions = element.type.actions;
        }
    }

    function _pageObjectMultipleParser (element, astObject) {
        element.group = true;
        _pageObjectParser(element, astObject);
    }
}

PageObjectsModule.service('elementParserService', ElementParserService);
<|endoftext|>


<commit_before>import EVENTS from '../events.js';
import mouseToolEventDispatcher from './mouseToolEventDispatcher.js';
import {
  mouseDown,
  mouseDownActivate,
  mouseDoubleClick,
  mouseDrag,
  mouseMove,
  mouseWheel
} from './mouseEventHandlers/index.js';

jest.mock('./mouseEventHandlers/index.js', () => ({
  mouseDown: jest.fn(),
  mouseDownActivate: jest.fn(),
  mouseDoubleClick: jest.fn(),
  mouseDrag: jest.fn(),
  mouseMove: jest.fn(),
  mouseWheel: jest.fn()
}));

describe('mouseToolEventDispatcher.js', () => {
  let element;

  beforeEach(() => {
    element = document.createElement('div');
    element.addEventListener = jest.fn();
    element.removeEventListener = jest.fn();
  });

  test('enable adds 6 event listeners to an element', () => {
    mouseToolEventDispatcher.enable(element);

    expect(element.addEventListener).toHaveBeenCalledTimes(6);
  });

  test('enable adds event listeners for all tap/touch events', () => {
    mouseToolEventDispatcher.enable(element);

    // https://github.com/jasmine/jasmine/issues/228#issuecomment-270599719
    expect(element.addEventListener.mock.calls).toEqual([
      [EVENTS.MOUSE_DOWN, mouseDown], // First call
      [EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownActivate], // Second call
      [EVENTS.MOUSE_DOUBLE_CLICK, mouseDoubleClick],
      [EVENTS.MOUSE_DRAG, mouseDrag],
      [EVENTS.MOUSE_MOVE, mouseMove],
      [EVENTS.MOUSE_WHEEL, mouseWheel]
    ]);
  });

  test('disable removes 6 event listeners to an element', () => {
    mouseToolEventDispatcher.disable(element);

    expect(element.removeEventListener).toHaveBeenCalledTimes(6);
  });

  test('disable removes event listeners for all tap/touch events', () => {
    mouseToolEventDispatcher.disable(element);

    // https://github.com/jasmine/jasmine/issues/228#issuecomment-270599719
    expect(element.removeEventListener.mock.calls).toEqual([
      [EVENTS.MOUSE_DOWN, mouseDown], // First call
      [EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownActivate], // Second call
      [EVENTS.MOUSE_DOUBLE_CLICK, mouseDoubleClick],
      [EVENTS.MOUSE_DRAG, mouseDrag],
      [EVENTS.MOUSE_MOVE, mouseMove],
      [EVENTS.MOUSE_WHEEL, mouseWheel]
    ]);
  });
});
<commit_msg>Fixing broken tests<commit_after>import EVENTS from '../events.js';
import mouseToolEventDispatcher from './mouseToolEventDispatcher.js';
import {
  mouseDown,
  mouseDownActivate,
  mouseDoubleClick,
  mouseDrag,
  mouseMove,
  mouseWheel
} from './mouseEventHandlers/index.js';

jest.mock('./mouseEventHandlers/index.js', () => ({
  mouseDown: jest.fn(),
  mouseDownActivate: jest.fn(),
  mouseDoubleClick: jest.fn(),
  mouseDrag: jest.fn(),
  mouseMove: jest.fn(),
  mouseWheel: jest.fn()
}));

describe('mouseToolEventDispatcher.js', () => {
  let element;

  beforeEach(() => {
    element = document.createElement('div');
    element.addEventListener = jest.fn();
    element.removeEventListener = jest.fn();
  });

  test('enable adds 7 event listeners to an element', () => {
    mouseToolEventDispatcher.enable(element);

    expect(element.addEventListener).toHaveBeenCalledTimes(7);
  });

  test('enable adds event listeners for all tap/touch events', () => {
    mouseToolEventDispatcher.enable(element);

    // https://github.com/jasmine/jasmine/issues/228#issuecomment-270599719
    expect(element.addEventListener.mock.calls).toEqual([
      [EVENTS.MOUSE_DOWN, mouseDown], // First call
      [EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownActivate], // Second call
      [EVENTS.MOUSE_DOUBLE_CLICK, mouseDoubleClick],
      [EVENTS.MOUSE_DRAG, mouseDrag],
      [EVENTS.MOUSE_MOVE, mouseMove],
      [EVENTS.MOUSE_WHEEL, mouseWheel]
    ]);
  });

  test('disable removes 6 event listeners to an element', () => {
    mouseToolEventDispatcher.disable(element);

    expect(element.removeEventListener).toHaveBeenCalledTimes(7);
  });

  test('disable removes event listeners for all tap/touch events', () => {
    mouseToolEventDispatcher.disable(element);

    // https://github.com/jasmine/jasmine/issues/228#issuecomment-270599719
    expect(element.removeEventListener.mock.calls).toEqual([
      [EVENTS.MOUSE_DOWN, mouseDown], // First call
      [EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownActivate], // Second call
      [EVENTS.MOUSE_DOUBLE_CLICK, mouseDoubleClick],
      [EVENTS.MOUSE_DRAG, mouseDrag],
      [EVENTS.MOUSE_MOVE, mouseMove],
      [EVENTS.MOUSE_WHEEL, mouseWheel]
    ]);
  });
});
<|endoftext|>


<commit_before>/* Formatblock
 * Is used to insert block level elements
 * It tries to solve the case that some block elements should not contain other block level elements (h1-6, p, ...)
 *
*/
(function(wysihtml5) {

  var dom = wysihtml5.dom,
      // When the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
      // instead of creating a H4 within a H1 which would result in semantically invalid html
      UNNESTABLE_BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, pre > code";
      BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote, pre > code";

  // Removes empty block level elements
  function cleanup (container) {
    var elements = container.querySelectorAll(BLOCK_ELEMENTS);
    for (var i = elements.length; i--;) {
      if (elements[i].innerHTML.trim() === "") {
        elements[i].parentNode.removeChild(elements[i]);
      }
    }
  }

  function defaultNodeName(composer) {
    return composer.config.useLineBreaks ? "DIV" : "P";
  }

  // The outermost un-nestable block element parent of from node
  function findOuterBlock(node, container) {
    var n = node;
        block = null;

    while (n && container && n !== container) {
      if (n.nodeType === 1 && n.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
        block = n;
      }
      n = n.parentNode;
    }

    return block;
  }

  // Formats an element according to options nodeName, className, styleProperty, styleValue
  // If element is not defined, creates new element
  function applyOptionsToElement(element, options, composer) {

    if (!element) {
      element = composer.doc.createElement(options.nodeName || defaultNodeName(composer));
      // Add invisible space as otherwise webkit cannot set selection or range to it correctly
      element.appendChild(composer.doc.createTextNode(wysihtml5.INVISIBLE_SPACE));
    }

    if (options.nodeName && element.nodeName !== options.nodeName) {
      element = dom.renameElement(element, options.nodeName);
    }
    if (options.className) {
      element.classList.add(options.className);
    }
    if (options.styleProperty && typeof options.styleValue !== "undefined") {
      element.style[wysihtml5.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
    }
    return element;
  }

  // Unwraps block level elements from inside content
  // Useful as not all block level elements can contain other block-levels
  function unwrapBlocksFromContent (element) {
    var contentBlocks = element.querySelectorAll(BLOCK_ELEMENTS) || []; // Find unnestable block elements in extracted contents

    for (var i = contentBlocks.length; i--;) {
      if (!contentBlocks[i].nextSibling || contentBlocks[i].nextSibling.nodeType !== 1 || contentBlocks[i].nextSibling.nodeName !== 'BR') {
        contentBlocks[i].parentNode.insertBefore(contentBlocks[i].ownerDocument.createElement('BR'), contentBlocks[i].nextSibling);
      }
      wysihtml5.dom.unwrap(contentBlocks[i]);
    }
  }

  // Wrap the range with a block level element
  // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
  function wrapRangeWithElement(range, options, defaultName, composer) {
    var r = range.cloneRange(),
        rangeStartContainer = r.startContainer,
        content = r.extractContents(),
        fragment = composer.doc.createDocumentFragment(),
        defaultOptions = wysihtml5.lang.object(options).clone(true),
        firstOuterBlock = findOuterBlock(rangeStartContainer, composer.element), // The outermost un-nestable block element parent of selection start
        wrapper, blocks;

    defaultOptions.nodeName = defaultOptions.nodeName || defaultName || defaultNodeName(composer);

    while(content.firstChild) {
      if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
        applyOptionsToElement(content.firstChild, options, composer);
        if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
          unwrapBlocksFromContent(content.firstChild);
        }
        fragment.appendChild(content.firstChild);
      } else {
        wrapper = applyOptionsToElement(null, defaultOptions, composer);
        while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
          if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
            unwrapBlocksFromContent(content.firstChild);
          }
          wrapper.appendChild(content.firstChild);
        }
        fragment.appendChild(wrapper);
      }
    }

    blocks = wysihtml5.lang.array(fragment.childNodes).get();

    if (firstOuterBlock) {
      // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between
      composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
    } else {
      // Otherwise just insert
      r.insertNode(fragment);
    }

    return blocks;
  }

  // If no nodename given try to find closest block level element and if found use it's nodeName in options
  function getOptionsWithNodeName(options, composer) {
    if (!options.nodeName) {
      var correctedOptions = wysihtml5.lang.object(options).clone(true),
          element = composer.selection.getOwnRanges()[0].startContainer;

      correctedOptions.nodeName = getParentBlockNodeName(element, composer) || defaultNodeName(composer);
      return correctedOptions;
    } else {
      return options;
    }
  }

  function getParentBlockNodeName(element, composer) {
    var parentNode = wysihtml5.dom.getParentElement(element, {
          query: BLOCK_ELEMENTS
        }, null, composer.element);

    return (parentNode) ? parentNode.nodeName : null;
  }

  // Used for removing styleValue in options, as it will return all elements with appropriate style property then
  function withoutStyleValue(options) {
    var valuelessOptions = wysihtml5.lang.object(options).clone(true);
    if (typeof valuelessOptions.styleValue !== "undefined") {
      delete valuelessOptions.styleValue;
    }
    return valuelessOptions;
  }

  wysihtml5.commands.formatBlock = {
    exec: function(composer, command, options) {

      // If properties is passed as a string, look for tag with that tagName/query
      if (typeof options === "string") {
        options = {
          nodeName: options.toUpperCase()
        };
      }

      var doc = composer.doc,

          defaultNodeName = composer.config.useLineBreaks ? "DIV" : "P",
          newNodeName = options.nodeName || defaultNodeName,

          newBlockElements = [],

          shouldSplitElement, blockElement, ranges, range;

      if (composer.selection.isCollapsed()) {
        // Selection is caret

        // Create new block wrapper element (For node creation nodeName is needed)
        blockElement = applyOptionsToElement(null, getOptionsWithNodeName(options, composer), composer);

        // Find current selection states unwrappable block level elements (blocks-levels can not be inserted into those)
        // If found, split outermost block element (assumed last in list) at caret.
        // Then insert new element
        shouldSplitElement = this.state(composer, command, blockElement.matches(UNNESTABLE_BLOCK_ELEMENTS) ? { query:  UNNESTABLE_BLOCK_ELEMENTS } : withoutStyleValue(options));
        if (shouldSplitElement.length > 0) {
          composer.selection.splitElementAtCaret(shouldSplitElement.pop(), blockElement);
        } else {
          composer.selection.insertNode(blockElement);
        }

        composer.selection.selectNode(blockElement.firstChild);

      } else {
        // Selection is not collapsed

        // Get all selection ranges of current composer and iterate
        ranges = composer.selection.getOwnRanges();
        for (var i = ranges.length; i--;) {
          newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, getParentBlockNodeName(ranges[i].startContainer, composer), composer));
        }

        // Remove empty block elements that may be left behind
        cleanup(composer.element);

        // Restore correct selection
        range = composer.selection.createRange();
        range.setStartBefore(newBlockElements[0]);
        range.setEndAfter(newBlockElements[newBlockElements.length - 1]);
        composer.selection.setSelection(range);
      }

    },

    state: function(composer, command, properties) {

      // If properties is passed as a string, look for tag with that tagName/query
      if (typeof properties === "string") {
        properties = {
          query: properties
        };
      }

      var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection
            return wysihtml5.dom.domNode(element).test(properties);
          }).bind(this)),
          parentNodes = composer.selection.getSelectedOwnNodes(),
          parent;

      // Finds matching elements that are parents of selection and adds to nodes list
      for (var i = 0, maxi = parentNodes.length; i < maxi; i++) {
        parent = dom.getParentElement(parentNodes[i], properties, null, composer.element);
        if (parent && nodes.indexOf(parent) === -1) {
          nodes.push(parent);
        }
      }

      return (nodes.length === 0) ? false : nodes;
    }

  };
})(wysihtml5);
<commit_msg>Cleanup<commit_after>/* Formatblock
 * Is used to insert block level elements
 * It tries to solve the case that some block elements should not contain other block level elements (h1-6, p, ...)
 *
*/
(function(wysihtml5) {

  var dom = wysihtml5.dom,
      // When the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
      // instead of creating a H4 within a H1 which would result in semantically invalid html
      UNNESTABLE_BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, pre > code";
      BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote, pre > code";

  // Removes empty block level elements
  function cleanup (container) {
    var elements = container.querySelectorAll(BLOCK_ELEMENTS);
    for (var i = elements.length; i--;) {
      if (elements[i].innerHTML.trim() === "") {
        elements[i].parentNode.removeChild(elements[i]);
      }
    }
  }

  function defaultNodeName(composer) {
    return composer.config.useLineBreaks ? "DIV" : "P";
  }

  // The outermost un-nestable block element parent of from node
  function findOuterBlock(node, container) {
    var n = node;
        block = null;

    while (n && container && n !== container) {
      if (n.nodeType === 1 && n.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
        block = n;
      }
      n = n.parentNode;
    }

    return block;
  }

  // Formats an element according to options nodeName, className, styleProperty, styleValue
  // If element is not defined, creates new element
  function applyOptionsToElement(element, options, composer) {

    if (!element) {
      element = composer.doc.createElement(options.nodeName || defaultNodeName(composer));
      // Add invisible space as otherwise webkit cannot set selection or range to it correctly
      element.appendChild(composer.doc.createTextNode(wysihtml5.INVISIBLE_SPACE));
    }

    if (options.nodeName && element.nodeName !== options.nodeName) {
      element = dom.renameElement(element, options.nodeName);
    }
    if (options.className) {
      element.classList.add(options.className);
    }
    if (options.styleProperty && typeof options.styleValue !== "undefined") {
      element.style[wysihtml5.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
    }
    return element;
  }

  // Unwraps block level elements from inside content
  // Useful as not all block level elements can contain other block-levels
  function unwrapBlocksFromContent (element) {
    var contentBlocks = element.querySelectorAll(BLOCK_ELEMENTS) || []; // Find unnestable block elements in extracted contents

    for (var i = contentBlocks.length; i--;) {
      if (!contentBlocks[i].nextSibling || contentBlocks[i].nextSibling.nodeType !== 1 || contentBlocks[i].nextSibling.nodeName !== 'BR') {
        contentBlocks[i].parentNode.insertBefore(contentBlocks[i].ownerDocument.createElement('BR'), contentBlocks[i].nextSibling);
      }
      wysihtml5.dom.unwrap(contentBlocks[i]);
    }
  }

  // Wrap the range with a block level element
  // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
  function wrapRangeWithElement(range, options, defaultName, composer) {
    var r = range.cloneRange(),
        rangeStartContainer = r.startContainer,
        content = r.extractContents(),
        fragment = composer.doc.createDocumentFragment(),
        defaultOptions = wysihtml5.lang.object(options).clone(true),
        firstOuterBlock = findOuterBlock(rangeStartContainer, composer.element), // The outermost un-nestable block element parent of selection start
        wrapper, blocks;

    defaultOptions.nodeName = defaultOptions.nodeName || defaultName || defaultNodeName(composer);

    while(content.firstChild) {
      if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
        applyOptionsToElement(content.firstChild, options, composer);
        if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
          unwrapBlocksFromContent(content.firstChild);
        }
        fragment.appendChild(content.firstChild);
      } else {
        wrapper = applyOptionsToElement(null, defaultOptions, composer);
        while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
          if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
            unwrapBlocksFromContent(content.firstChild);
          }
          wrapper.appendChild(content.firstChild);
        }
        fragment.appendChild(wrapper);
      }
    }

    blocks = wysihtml5.lang.array(fragment.childNodes).get();

    if (firstOuterBlock) {
      // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between
      composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
    } else {
      // Otherwise just insert
      r.insertNode(fragment);
    }

    return blocks;
  }

  // If no nodename given try to find closest block level element and if found use it's nodeName in options
  function getOptionsWithNodeName(options, composer) {
    if (!options.nodeName) {
      var correctedOptions = wysihtml5.lang.object(options).clone(true),
          element = composer.selection.getOwnRanges()[0].startContainer;

      correctedOptions.nodeName = getParentBlockNodeName(element, composer) || defaultNodeName(composer);
      return correctedOptions;
    } else {
      return options;
    }
  }

  function getParentBlockNodeName(element, composer) {
    var parentNode = wysihtml5.dom.getParentElement(element, {
          query: BLOCK_ELEMENTS
        }, null, composer.element);

    return (parentNode) ? parentNode.nodeName : null;
  }

  // Used for removing styleValue in options, as it will return all elements with appropriate style property then
  function withoutStyleValue(options) {
    var valuelessOptions = wysihtml5.lang.object(options).clone(true);
    if (typeof valuelessOptions.styleValue !== "undefined") {
      delete valuelessOptions.styleValue;
    }
    return valuelessOptions;
  }

  wysihtml5.commands.formatBlock = {
    exec: function(composer, command, options) {
      var newBlockElements = [],
          shouldSplitElement, blockElement, ranges, range;

      // If properties is passed as a string, look for tag with that tagName/query
      if (typeof options === "string") {
        options = {
          nodeName: options.toUpperCase()
        };
      }

      if (composer.selection.isCollapsed()) {

        // Create new block wrapper element (For node creation nodeName is needed)
        blockElement = applyOptionsToElement(null, getOptionsWithNodeName(options, composer), composer);

        // Find current selection states unwrappable block level elements (blocks-levels can not be inserted into those)
        // If found, split outermost block element (assumed last in list) at caret.
        // Then insert new element
        shouldSplitElement = this.state(composer, command, blockElement.matches(UNNESTABLE_BLOCK_ELEMENTS) ? { query:  UNNESTABLE_BLOCK_ELEMENTS } : withoutStyleValue(options));
        if (shouldSplitElement.length > 0) {
          composer.selection.splitElementAtCaret(shouldSplitElement.pop(), blockElement);
        } else {
          composer.selection.insertNode(blockElement);
        }

        // Restore correct selection
        composer.selection.selectNode(blockElement.firstChild);

      } else {

        // Get all selection ranges of current composer and iterate
        ranges = composer.selection.getOwnRanges();
        for (var i = ranges.length; i--;) {
          newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, getParentBlockNodeName(ranges[i].startContainer, composer), composer));
        }

        // Remove empty block elements that may be left behind
        cleanup(composer.element);

        // Restore correct selection
        range = composer.selection.createRange();
        range.setStartBefore(newBlockElements[0]);
        range.setEndAfter(newBlockElements[newBlockElements.length - 1]);
        composer.selection.setSelection(range);
      }

    },

    state: function(composer, command, properties) {

      // If properties is passed as a string, look for tag with that tagName/query
      if (typeof properties === "string") {
        properties = {
          query: properties
        };
      }

      var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection
            return wysihtml5.dom.domNode(element).test(properties);
          }).bind(this)),
          parentNodes = composer.selection.getSelectedOwnNodes(),
          parent;

      // Finds matching elements that are parents of selection and adds to nodes list
      for (var i = 0, maxi = parentNodes.length; i < maxi; i++) {
        parent = dom.getParentElement(parentNodes[i], properties, null, composer.element);
        if (parent && nodes.indexOf(parent) === -1) {
          nodes.push(parent);
        }
      }

      return (nodes.length === 0) ? false : nodes;
    }

  };
})(wysihtml5);
<|endoftext|>


<commit_before>/* marry.js - Marry your imaginary waifu or someone
 *
 *
 *  Contributed by Capuccino
 */

exports.commands = [
    'marry'
];

exports.marry = {
    desc: 'Marry Someone',
    longDesc: 'Marry your imagfinary waifu or a member in the guild.',
    usage: '<Mention | Name> ',
    main(bot, ctx) {
        return new Promise((resolve, reject) => {
            if (!ctx.args) {
                ctx.msg.channel.createMessage('Mention or cite your partner\'s name.');
            } else if (ctx.args[0].mentions.id === ctx.msg.author.id) {
                ctx.msg.channel.createMessage('Hey! You\'re not allowed to marry yourself.');
            } else {
                // we'll add somth here later
            }
        });
    }
};<commit_msg>:tada: MARRYING SOEONE IS NOW ILLEGAL :tada:<commit_after>/* marry.js - Marry your imaginary waifu or someone
 *
 *
 *  Contributed by Capuccino
 */

/* eslint-env node */

exports.commands = [
    'marry'
];

exports.marry = {
    desc: 'Marry Someone',
    longDesc: 'Marry your waifu in the guild.',
    usage: '<Mention>',
    main(bot, ctx) {
        return new Promise((resolve, reject) => {
            if (!ctx.suffix) {
                ctx.createMessage('Mention or cite your partner\'s name.');
            } else if (ctx.msg.mentions[0].id === ctx.msg.author.id) {
                ctx.createMessage('Hey! You\'re not allowed to marry yourself.');
            } else {
                ctx.createMessage(`${ctx.mentions[0]}, Will you marry ${$ctx.msg.author}? (yes/no) (You have 30 seconds to respond).`).then(() => {
                    bot.awaitMessage(ctx.msg.channel.id, ctx.mentions[0].id, 30000).then(m => {
                        if (/y(es)?/i.test(m.content)) {
                            return m.createMessage(`${ctx.msg.author}, congrats! You are now married to ${ctx.msg.mention[0]}!`);
                        } else if (/no?/i.test(m.content)) {
                            return m.createMessage(`I'm sorry ${ctx.msg.author}, but your partner declined.`);
                        }
                    }).catch(() => {
                        ctx.createMessage('Your partner didn\'t respond in time.');
                    });
                }).then(resolve).catch(reject);
            }
        });
    }
};<|endoftext|>


<commit_before>export default {
  // Discipline Priest:
  PENANCE: {
    id: 47666,
    name: 'Penance',
    icon: 'spell_holy_penance',
    manaCost: 30800,
  },
  POWER_WORD_SHIELD: {
    id: 17,
    name: 'Power Word: Shield',
    icon: 'spell_holy_powerwordshield',
    manaCost: 22000,
  },
  SMITE: {
    id: 585,
    name: 'Smite',
    icon: 'spell_holy_holysmite',
    manaCost: 11000,
  },
  POWER_WORD_RADIANCE: {
    id: 194509,
    name: 'Power Word: Radiance',
    icon: 'spell_priest_power-word',
    manaCost: 71500,
  },
  SHADOW_MEND: {
    id: 186263,
    name: 'Shadow Mend',
    icon: 'spell_shadow_shadowmend',
  },
  LIGHTS_WRATH: {
    id: 207946,
    name: 'Light\'s Wrath',
    icon: 'inv_staff_2h_artifacttome_d_01',
  },
  RAPTURE: {
    id: 47536,
    name: 'Rapture',
    icon: 'spell_holy_rapture',
  },
  PAIN_SUPPRESSION: {
    id: 33206,
    name: 'Pain Suppression',
    icon: 'spell_holy_painsupression',
    manaCost: 17600,
  },
  PLEA: {
    id: 200829,
    name: 'Plea',
    icon: 'spell_priest_plea_blue',
  },
  MASS_DISPEL: {
    id: 32375,
    name: 'Mass Dispel',
    icon: 'spell_arcane_massdispel',
  },
  DISPEL_MAGIC: {
    id: 528,
    name: 'Dispel Magic',
    icon: 'spell_nature_nullifydisease',
  },
  LEAP_OF_FAITH: {
    id: 73325,
    name: 'Leap of Faith',
    icon: 'priest_spell_leapoffaith_a',
  },
  LEVITATE: {
    id: 1706,
    name: 'Levitate',
    icon: 'spell_holy_layonhands',
  },
  MIND_CONTROL: {
    id: 605,
    name: 'Mind Control',
    icon: 'spell_shadow_shadowworddominate',
  },
  POWER_WORD_BARRIER_CAST: {
    id: 62618,
    name: 'Power Word: Barrier',
    icon: 'spell_holy_powerwordbarrier',
  },
  POWER_WORD_BARRIER_BUFF: {
    id: 81782,
    name: 'Power Word: Barrier',
    icon: 'spell_holy_powerwordbarrier',
  },
  PURIFY: {
    id: 527,
    name: 'Purify',
    icon: 'spell_holy_dispelmagic',
  },
  SHACKLE_UNDEAD: {
    id: 9484,
    name: 'Shackle Undead',
    icon: 'spell_nature_slow',
  },
  SHADOWFIEND: {
    id: 34433,
    name: 'Shadowfiend',
    icon: 'spell_shadow_shadowfiend',
  },
  SHADOWFIEND_WITH_GLYPH_OF_THE_SHA: {
    id: 132603,
    name: 'Shadowfiend',
    icon: 'spell_shadow_shadowfiend',
  },
  SHADOW_WORD_PAIN: {
    id: 589,
    name: 'Shadow Word: Pain',
    icon: 'spell_shadow_shadowwordpain',
  },
  FADE: {
    id: 586,
    name: 'Fade',
    icon: 'spell_magic_lesserinvisibilty',
  },
  PSYCHIC_SCREAM: {
    id: 8122,
    name: 'Psychic Scream',
    icon: 'spell_shadow_psychicscream',
  },
  ATONEMENT_HEAL_NON_CRIT: {
    id: 81751,
    name: 'Atonement',
    icon: 'spell_holy_circleofrenewal',
  },
  ATONEMENT_HEAL_CRIT: {
    id: 94472,
    name: 'Atonement',
    icon: 'spell_holy_circleofrenewal',
  },
  ATONEMENT_BUFF: {
    id: 194384,
    name: 'Atonement',
    icon: 'ability_priest_atonement',
  },
  HALO_HEAL: {
    id: 120692,
    name: 'Halo',
    icon: 'ability_priest_halo',
  },
  DISC_PRIEST_T19_2SET_BONUS_BUFF: {
    id: 211556,
    name: 'T19 2 set bonus',
    icon: 'spell_holy_powerwordshield',
  },
  KAM_XIRAFF_BUFF: {
    id: 233997,
    name: 'Kam Xi\'raff',
    icon: 'ability_priest_savinggrace',
  },
  SEPHUZS_SECRET_BUFF: {
    id: 208052,
    name: 'Sephuz\'s Secret',
    icon: 'inv_jewelry_ring_149',
  },
  TWIST_OF_FATE_BUFF: {
    id: 123254,
    name: 'Twist of Fate',
    icon: 'spell_shadow_mindtwisting',
  },
  PURGE_THE_WICKED_BUFF: {
    id: 204213,
    name: 'Purge the Wicked',
    icon: 'ability_mage_firestarter',
  },

  // Talents:
  // Discipline Priest:
  // lv15
  THE_PENITENT_TALENT: {
    id: 200347,
    name: 'The Penitent',
    icon: 'spell_priest_finalprayer',
  },
  CASTIGATION_TALENT: {
    id: 193134,
    name: 'Castigation',
    icon: 'spell_holy_searinglightpriest',
  },
  SCHISM_TALENT: {
    id: 214621,
    name: 'Schism',
    icon: 'spell_warlock_focusshadow',
  },
  // lv30
  ANGELIC_FEATHER_TALENT: {
    id: 121536,
    name: 'Angelic Feather',
    icon: 'ability_priest_angelicfeather',
  },
  BODY_AND_SOUL_TALENT: {
    id: 64129,
    name: 'Body and Soul',
    icon: 'spell_holy_symbolofhope',
  },
  MASOCHISM_TALENT: {
    id: 193063,
    name: 'Masochism',
    icon: 'spell_shadow_misery',
  },
  // lv45
  SHINING_FORCE_TALENT: {
    id: 204263,
    name: 'Shining Force',
    icon: 'ability_paladin_blindinglight2',
  },
  PSYCHIC_VOICE_TALENT: {
    id: 196704,
    name: 'Psychic Voice',
    icon: 'spell_shadow_psychichorrors',
  },
  DOMINANT_MIND_TALENT: {
    id: 205367,
    name: 'Dominant Mind',
    icon: 'spell_priest_void-flay',
  },
  // lv60
  POWER_WORD_SOLACE_TALENT: {
    id: 129250,
    name: 'Power Word: Solace',
    icon: 'ability_priest_flashoflight',
  },
  SHIELD_DISCIPLINE_TALENT: {
    id: 197045,
    name: 'Shield Discipline',
    icon: 'spell_holy_divineprotection',
  },
  MINDBENDER_TALENT: {
    id: 123040,
    name: 'Mindbender',
    icon: 'spell_shadow_soulleech_3',
  },
  // lv75
  CONTRITION_TALENT: {
    id: 197419,
    name: 'Contrition',
    icon: 'ability_priest_savinggrace',
  },
  POWER_INFUSION_TALENT: {
    id: 10060,
    name: 'Power Infusion',
    icon: 'spell_holy_powerinfusion',
  },
  TWIST_OF_FATE_TALENT: {
    id: 109142,
    name: 'Twist of Fate',
    icon: 'spell_shadow_mindtwisting',
  },
  // lv90
  CLARITY_OF_WILL_TALENT: {
    id: 152118,
    name: 'Clarity of Will',
    icon: 'ability_priest_clarityofwill',
  },
  DIVINE_STAR_TALENT: {
    id: 110744,
    name: 'Divine Star',
    icon: 'spell_priest_divinestar',
  },
  HALO_TALENT: {
    id: 120517,
    name: 'Halo',
    icon: 'ability_priest_halo',
    manaCost: 39600,
  },
  // lv100
  PURGE_THE_WICKED_TALENT: {
    id: 204197,
    name: 'Purge the Wicked',
    icon: 'ability_mage_firestarter',
    manaCost: 22000,
  },
  GRACE_TALENT: {
    id: 200309,
    name: 'Grace',
    icon: 'spell_holy_hopeandgrace',
  },
  SHADOW_COVENANT_TALENT: {
    id: 204065,
    name: 'Shadow Covenant',
    icon: 'spell_shadow_summonvoidwalker',
  },

  // Traits:
  // Disc Priest:
  SHARE_IN_THE_LIGHT: {
    id: 210027,
    name: 'Share in the Light',
    icon: 'spell_holy_powerwordshield',
  },
  PAIN_IS_IN_YOUR_MIND: {
    id: 197713,
    name: 'Pain is in Your Mind',
    icon: 'spell_holy_painsupression',
  },
  DOOMSAYER: {
    id: 197727,
    name: 'Doomsayer',
    icon: 'spell_holy_rapture',
  },
};
<commit_msg>Talent Changes for 7.2.5<commit_after>export default {
  // Discipline Priest:
  PENANCE: {
    id: 47666,
    name: 'Penance',
    icon: 'spell_holy_penance',
    manaCost: 30800,
  },
  POWER_WORD_SHIELD: {
    id: 17,
    name: 'Power Word: Shield',
    icon: 'spell_holy_powerwordshield',
    manaCost: 22000,
  },
  SMITE: {
    id: 585,
    name: 'Smite',
    icon: 'spell_holy_holysmite',
    manaCost: 11000,
  },
  POWER_WORD_RADIANCE: {
    id: 194509,
    name: 'Power Word: Radiance',
    icon: 'spell_priest_power-word',
    manaCost: 71500,
  },
  SHADOW_MEND: {
    id: 186263,
    name: 'Shadow Mend',
    icon: 'spell_shadow_shadowmend',
  },
  LIGHTS_WRATH: {
    id: 207946,
    name: 'Light\'s Wrath',
    icon: 'inv_staff_2h_artifacttome_d_01',
  },
  RAPTURE: {
    id: 47536,
    name: 'Rapture',
    icon: 'spell_holy_rapture',
  },
  PAIN_SUPPRESSION: {
    id: 33206,
    name: 'Pain Suppression',
    icon: 'spell_holy_painsupression',
    manaCost: 17600,
  },
  PLEA: {
    id: 200829,
    name: 'Plea',
    icon: 'spell_priest_plea_blue',
  },
  MASS_DISPEL: {
    id: 32375,
    name: 'Mass Dispel',
    icon: 'spell_arcane_massdispel',
  },
  DISPEL_MAGIC: {
    id: 528,
    name: 'Dispel Magic',
    icon: 'spell_nature_nullifydisease',
  },
  LEAP_OF_FAITH: {
    id: 73325,
    name: 'Leap of Faith',
    icon: 'priest_spell_leapoffaith_a',
  },
  LEVITATE: {
    id: 1706,
    name: 'Levitate',
    icon: 'spell_holy_layonhands',
  },
  MIND_CONTROL: {
    id: 605,
    name: 'Mind Control',
    icon: 'spell_shadow_shadowworddominate',
  },
  POWER_WORD_BARRIER_CAST: {
    id: 62618,
    name: 'Power Word: Barrier',
    icon: 'spell_holy_powerwordbarrier',
  },
  POWER_WORD_BARRIER_BUFF: {
    id: 81782,
    name: 'Power Word: Barrier',
    icon: 'spell_holy_powerwordbarrier',
  },
  PURIFY: {
    id: 527,
    name: 'Purify',
    icon: 'spell_holy_dispelmagic',
  },
  SHACKLE_UNDEAD: {
    id: 9484,
    name: 'Shackle Undead',
    icon: 'spell_nature_slow',
  },
  SHADOWFIEND: {
    id: 34433,
    name: 'Shadowfiend',
    icon: 'spell_shadow_shadowfiend',
  },
  SHADOWFIEND_WITH_GLYPH_OF_THE_SHA: {
    id: 132603,
    name: 'Shadowfiend',
    icon: 'spell_shadow_shadowfiend',
  },
  SHADOW_WORD_PAIN: {
    id: 589,
    name: 'Shadow Word: Pain',
    icon: 'spell_shadow_shadowwordpain',
  },
  FADE: {
    id: 586,
    name: 'Fade',
    icon: 'spell_magic_lesserinvisibilty',
  },
  PSYCHIC_SCREAM: {
    id: 8122,
    name: 'Psychic Scream',
    icon: 'spell_shadow_psychicscream',
  },
  ATONEMENT_HEAL_NON_CRIT: {
    id: 81751,
    name: 'Atonement',
    icon: 'spell_holy_circleofrenewal',
  },
  ATONEMENT_HEAL_CRIT: {
    id: 94472,
    name: 'Atonement',
    icon: 'spell_holy_circleofrenewal',
  },
  ATONEMENT_BUFF: {
    id: 194384,
    name: 'Atonement',
    icon: 'ability_priest_atonement',
  },
  HALO_HEAL: {
    id: 120692,
    name: 'Halo',
    icon: 'ability_priest_halo',
  },
  DISC_PRIEST_T19_2SET_BONUS_BUFF: {
    id: 211556,
    name: 'T19 2 set bonus',
    icon: 'spell_holy_powerwordshield',
  },
  KAM_XIRAFF_BUFF: {
    id: 233997,
    name: 'Kam Xi\'raff',
    icon: 'ability_priest_savinggrace',
  },
  SEPHUZS_SECRET_BUFF: {
    id: 208052,
    name: 'Sephuz\'s Secret',
    icon: 'inv_jewelry_ring_149',
  },
  TWIST_OF_FATE_BUFF: {
    id: 123254,
    name: 'Twist of Fate',
    icon: 'spell_shadow_mindtwisting',
  },
  PURGE_THE_WICKED_BUFF: {
    id: 204213,
    name: 'Purge the Wicked',
    icon: 'ability_mage_firestarter',
  },

  // Talents:
  // Discipline Priest:
  // lv15
  TWIST_OF_FATE_TALENT: {
    id: 109142,
    name: 'Twist of Fate',
    icon: 'spell_shadow_mindtwisting',
  },
  CASTIGATION_TALENT: {
    id: 193134,
    name: 'Castigation',
    icon: 'spell_holy_searinglightpriest',
  },
  SCHISM_TALENT: {
    id: 214621,
    name: 'Schism',
    icon: 'spell_warlock_focusshadow',
  },
  // lv30
  ANGELIC_FEATHER_TALENT: {
    id: 121536,
    name: 'Angelic Feather',
    icon: 'ability_priest_angelicfeather',
  },
  BODY_AND_SOUL_TALENT: {
    id: 64129,
    name: 'Body and Soul',
    icon: 'spell_holy_symbolofhope',
  },
  MASOCHISM_TALENT: {
    id: 193063,
    name: 'Masochism',
    icon: 'spell_shadow_misery',
  },
  // lv45
  SHINING_FORCE_TALENT: {
    id: 204263,
    name: 'Shining Force',
    icon: 'ability_paladin_blindinglight2',
  },
  PSYCHIC_VOICE_TALENT: {
    id: 196704,
    name: 'Psychic Voice',
    icon: 'spell_shadow_psychichorrors',
  },
  DOMINANT_MIND_TALENT: {
    id: 205367,
    name: 'Dominant Mind',
    icon: 'spell_priest_void-flay',
  },
  // lv60
  POWER_WORD_SOLACE_TALENT: {
    id: 129250,
    name: 'Power Word: Solace',
    icon: 'ability_priest_flashoflight',
  },
  SHIELD_DISCIPLINE_TALENT: {
    id: 197045,
    name: 'Shield Discipline',
    icon: 'spell_holy_divineprotection',
  },
  MINDBENDER_TALENT: {
    id: 123040,
    name: 'Mindbender',
    icon: 'spell_shadow_soulleech_3',
  },
  // lv75
  SANCTUARY_TALENT: {
    id: 246393,
    name: 'Sanctuary',
    icon: 'spell_holy_holysmite',
  },
  CLARITY_OF_WILL_TALENT: {
    id: 152118,
    name: 'Clarity of Will',
    icon: 'ability_priest_clarityofwill',
  },
  SHADOW_COVENANT_TALENT: {
    id: 204065,
    name: 'Shadow Covenant',
    icon: 'spell_shadow_summonvoidwalker',
  },
  // lv90
  PURGE_THE_WICKED_TALENT: {
    id: 204197,
    name: 'Purge the Wicked',
    icon: 'ability_mage_firestarter',
    manaCost: 22000,
  },
  DIVINE_STAR_TALENT: {
    id: 110744,
    name: 'Divine Star',
    icon: 'spell_priest_divinestar',
  },
  HALO_TALENT: {
    id: 120517,
    name: 'Halo',
    icon: 'ability_priest_halo',
    manaCost: 39600,
  },
  // lv100
  POWER_INFUSION_TALENT: {
    id: 10060,
    name: 'Power Infusion',
    icon: 'spell_holy_powerinfusion',
  },
  GRACE_TALENT: {
    id: 200309,
    name: 'Grace',
    icon: 'spell_holy_hopeandgrace',
  },
  EVANGELISM_TALENT: {
    id: 246287,
    name: 'Evangelism',
    icon: 'spell_holy_divineillumination',
  },

  // Traits:
  // Disc Priest:
  SHARE_IN_THE_LIGHT: {
    id: 210027,
    name: 'Share in the Light',
    icon: 'spell_holy_powerwordshield',
  },
  PAIN_IS_IN_YOUR_MIND: {
    id: 197713,
    name: 'Pain is in Your Mind',
    icon: 'spell_holy_painsupression',
  },
  DOOMSAYER: {
    id: 197727,
    name: 'Doomsayer',
    icon: 'spell_holy_rapture',
  },
};
<|endoftext|>


<commit_before>export default {
  // Discipline Priest:
  PENANCE: {
    id: 47666,
    name: 'Penance',
    icon: 'spell_holy_penance',
    manaCost: 30800,
  },
  POWER_WORD_SHIELD: {
    id: 17,
    name: 'Power Word: Shield',
    icon: 'spell_holy_powerwordshield',
    manaCost: 22000,
  },
  SMITE: {
    id: 585,
    name: 'Smite',
    icon: 'spell_holy_holysmite',
    manaCost: 11000,
  },
  POWER_WORD_RADIANCE: {
    id: 194509,
    name: 'Power Word: Radiance',
    icon: 'spell_priest_power-word',
    manaCost: 71500,
  },
  SHADOW_MEND: {
    id: 186263,
    name: 'Shadow Mend',
    icon: 'spell_shadow_shadowmend',
  },
  LIGHTS_WRATH: {
    id: 207946,
    name: 'Light\'s Wrath',
    icon: 'inv_staff_2h_artifacttome_d_01',
  },
  RAPTURE: {
    id: 47536,
    name: 'Rapture',
    icon: 'spell_holy_rapture',
  },
  PAIN_SUPPRESSION: {
    id: 33206,
    name: 'Pain Suppression',
    icon: 'spell_holy_painsupression',
    manaCost: 17600,
  },
  PLEA: {
    id: 200829,
    name: 'Plea',
    icon: 'spell_priest_plea_blue',
  },
  MASS_DISPEL: {
    id: 32375,
    name: 'Mass Dispel',
    icon: 'spell_arcane_massdispel',
  },
  DISPEL_MAGIC: {
    id: 528,
    name: 'Dispel Magic',
    icon: 'spell_nature_nullifydisease',
  },
  LEAP_OF_FAITH: {
    id: 73325,
    name: 'Leap of Faith',
    icon: 'priest_spell_leapoffaith_a',
  },
  LEVITATE: {
    id: 1706,
    name: 'Levitate',
    icon: 'spell_holy_layonhands',
  },
  MIND_CONTROL: {
    id: 605,
    name: 'Mind Control',
    icon: 'spell_shadow_shadowworddominate',
  },
  POWER_WORD_BARRIER_CAST: {
    id: 62618,
    name: 'Power Word: Barrier',
    icon: 'spell_holy_powerwordbarrier',
  },
  POWER_WORD_BARRIER_BUFF: {
    id: 81782,
    name: 'Power Word: Barrier',
    icon: 'spell_holy_powerwordbarrier',
  },
  PURIFY: {
    id: 527,
    name: 'Purify',
    icon: 'spell_holy_dispelmagic',
  },
  SHACKLE_UNDEAD: {
    id: 9484,
    name: 'Shackle Undead',
    icon: 'spell_nature_slow',
  },
  SHADOWFIEND: {
    id: 34433,
    name: 'Shadowfiend',
    icon: 'spell_shadow_shadowfiend',
  },
  SHADOWFIEND_WITH_GLYPH_OF_THE_SHA: {
    id: 132603,
    name: 'Shadowfiend',
    icon: 'spell_shadow_shadowfiend',
  },
  SHADOW_WORD_PAIN: {
    id: 589,
    name: 'Shadow Word: Pain',
    icon: 'spell_shadow_shadowwordpain',
  },
  FADE: {
    id: 586,
    name: 'Fade',
    icon: 'spell_magic_lesserinvisibilty',
  },
  PSYCHIC_SCREAM: {
    id: 8122,
    name: 'Psychic Scream',
    icon: 'spell_shadow_psychicscream',
  },
  ATONEMENT_HEAL_NON_CRIT: {
    id: 81751,
    name: 'Atonement',
    icon: 'spell_holy_circleofrenewal',
  },
  ATONEMENT_HEAL_CRIT: {
    id: 94472,
    name: 'Atonement',
    icon: 'spell_holy_circleofrenewal',
  },
  ATONEMENT_BUFF: {
    id: 194384,
    name: 'Atonement',
    icon: 'ability_priest_atonement',
  },
  HALO_HEAL: {
    id: 120692,
    name: 'Halo',
    icon: 'ability_priest_halo',
  },
  DISC_PRIEST_T19_2SET_BONUS_BUFF: {
    id: 211556,
    name: 'T19 2 set bonus',
    icon: 'spell_holy_powerwordshield',
  },
  KAM_XIRAFF_BUFF: {
    id: 233997,
    name: 'Kam Xi\'raff',
    icon: 'ability_priest_savinggrace',
  },
  SEPHUZS_SECRET_BUFF: {
    id: 208052,
    name: 'Sephuz\'s Secret',
    icon: 'inv_jewelry_ring_149',
  },
  TWIST_OF_FATE_BUFF: {
    id: 123254,
    name: 'Twist of Fate',
    icon: 'spell_shadow_mindtwisting',
  },
  PURGE_THE_WICKED_BUFF: {
    id: 204213,
    name: 'Purge the Wicked',
    icon: 'ability_mage_firestarter',
  },

  // Talents:
  // Discipline Priest:
  // lv15
  TWIST_OF_FATE_TALENT: {
    id: 109142,
    name: 'Twist of Fate',
    icon: 'spell_shadow_mindtwisting',
  },
  CASTIGATION_TALENT: {
    id: 193134,
    name: 'Castigation',
    icon: 'spell_holy_searinglightpriest',
  },
  SCHISM_TALENT: {
    id: 214621,
    name: 'Schism',
    icon: 'spell_warlock_focusshadow',
  },
  // lv30
  ANGELIC_FEATHER_TALENT: {
    id: 121536,
    name: 'Angelic Feather',
    icon: 'ability_priest_angelicfeather',
  },
  BODY_AND_SOUL_TALENT: {
    id: 64129,
    name: 'Body and Soul',
    icon: 'spell_holy_symbolofhope',
  },
  MASOCHISM_TALENT: {
    id: 193063,
    name: 'Masochism',
    icon: 'spell_shadow_misery',
  },
  // lv45
  SHINING_FORCE_TALENT: {
    id: 204263,
    name: 'Shining Force',
    icon: 'ability_paladin_blindinglight2',
  },
  PSYCHIC_VOICE_TALENT: {
    id: 196704,
    name: 'Psychic Voice',
    icon: 'spell_shadow_psychichorrors',
  },
  DOMINANT_MIND_TALENT: {
    id: 205367,
    name: 'Dominant Mind',
    icon: 'spell_priest_void-flay',
  },
  // lv60
  POWER_WORD_SOLACE_TALENT: {
    id: 129250,
    name: 'Power Word: Solace',
    icon: 'ability_priest_flashoflight',
  },
  SHIELD_DISCIPLINE_TALENT: {
    id: 197045,
    name: 'Shield Discipline',
    icon: 'spell_holy_divineprotection',
  },
  MINDBENDER_TALENT: {
    id: 123040,
    name: 'Mindbender',
    icon: 'spell_shadow_soulleech_3',
  },
  // lv75
  SANCTUARY_TALENT: {
    id: 246393,
    name: 'Sanctuary',
    icon: 'spell_holy_holysmite',
  },
  CLARITY_OF_WILL_TALENT: {
    id: 152118,
    name: 'Clarity of Will',
    icon: 'ability_priest_clarityofwill',
  },
  SHADOW_COVENANT_TALENT: {
    id: 204065,
    name: 'Shadow Covenant',
    icon: 'spell_shadow_summonvoidwalker',
  },
  // lv90
  PURGE_THE_WICKED_TALENT: {
    id: 204197,
    name: 'Purge the Wicked',
    icon: 'ability_mage_firestarter',
    manaCost: 22000,
  },
  DIVINE_STAR_TALENT: {
    id: 110744,
    name: 'Divine Star',
    icon: 'spell_priest_divinestar',
  },
  HALO_TALENT: {
    id: 120517,
    name: 'Halo',
    icon: 'ability_priest_halo',
    manaCost: 39600,
  },
  // lv100
  POWER_INFUSION_TALENT: {
    id: 10060,
    name: 'Power Infusion',
    icon: 'spell_holy_powerinfusion',
  },
  GRACE_TALENT: {
    id: 200309,
    name: 'Grace',
    icon: 'spell_holy_hopeandgrace',
  },
  EVANGELISM_TALENT: {
    id: 246287,
    name: 'Evangelism',
    icon: 'spell_holy_divineillumination',
  },

  // Traits:
  // Disc Priest:
  SHARE_IN_THE_LIGHT: {
    id: 210027,
    name: 'Share in the Light',
    icon: 'spell_holy_powerwordshield',
  },
  PAIN_IS_IN_YOUR_MIND: {
    id: 197713,
    name: 'Pain is in Your Mind',
    icon: 'spell_holy_painsupression',
  },
  DOOMSAYER: {
    id: 197727,
    name: 'Doomsayer',
    icon: 'spell_holy_rapture',
  },
};
<commit_msg>Update mana costs for 7.2.5<commit_after>export default {
  // Discipline Priest:
  PENANCE: {
    id: 47666,
    name: 'Penance',
    icon: 'spell_holy_penance',
    manaCost: 30800,
  },
  POWER_WORD_SHIELD: {
    id: 17,
    name: 'Power Word: Shield',
    icon: 'spell_holy_powerwordshield',
    manaCost: 25300,
  },
  SMITE: {
    id: 585,
    name: 'Smite',
    icon: 'spell_holy_holysmite',
    manaCost: 5500,
  },
  POWER_WORD_RADIANCE: {
    id: 194509,
    name: 'Power Word: Radiance',
    icon: 'spell_priest_power-word',
    manaCost: 71500,
  },
  SHADOW_MEND: {
    id: 186263,
    name: 'Shadow Mend',
    icon: 'spell_shadow_shadowmend',
  },
  LIGHTS_WRATH: {
    id: 207946,
    name: 'Light\'s Wrath',
    icon: 'inv_staff_2h_artifacttome_d_01',
  },
  RAPTURE: {
    id: 47536,
    name: 'Rapture',
    icon: 'spell_holy_rapture',
  },
  PAIN_SUPPRESSION: {
    id: 33206,
    name: 'Pain Suppression',
    icon: 'spell_holy_painsupression',
    manaCost: 17600,
  },
  PLEA: {
    id: 200829,
    name: 'Plea',
    icon: 'spell_priest_plea_blue',
  },
  MASS_DISPEL: {
    id: 32375,
    name: 'Mass Dispel',
    icon: 'spell_arcane_massdispel',
  },
  DISPEL_MAGIC: {
    id: 528,
    name: 'Dispel Magic',
    icon: 'spell_nature_nullifydisease',
  },
  LEAP_OF_FAITH: {
    id: 73325,
    name: 'Leap of Faith',
    icon: 'priest_spell_leapoffaith_a',
  },
  LEVITATE: {
    id: 1706,
    name: 'Levitate',
    icon: 'spell_holy_layonhands',
  },
  MIND_CONTROL: {
    id: 605,
    name: 'Mind Control',
    icon: 'spell_shadow_shadowworddominate',
  },
  POWER_WORD_BARRIER_CAST: {
    id: 62618,
    name: 'Power Word: Barrier',
    icon: 'spell_holy_powerwordbarrier',
  },
  POWER_WORD_BARRIER_BUFF: {
    id: 81782,
    name: 'Power Word: Barrier',
    icon: 'spell_holy_powerwordbarrier',
  },
  PURIFY: {
    id: 527,
    name: 'Purify',
    icon: 'spell_holy_dispelmagic',
  },
  SHACKLE_UNDEAD: {
    id: 9484,
    name: 'Shackle Undead',
    icon: 'spell_nature_slow',
  },
  SHADOWFIEND: {
    id: 34433,
    name: 'Shadowfiend',
    icon: 'spell_shadow_shadowfiend',
  },
  SHADOWFIEND_WITH_GLYPH_OF_THE_SHA: {
    id: 132603,
    name: 'Shadowfiend',
    icon: 'spell_shadow_shadowfiend',
  },
  SHADOW_WORD_PAIN: {
    id: 589,
    name: 'Shadow Word: Pain',
    icon: 'spell_shadow_shadowwordpain',
  },
  FADE: {
    id: 586,
    name: 'Fade',
    icon: 'spell_magic_lesserinvisibilty',
  },
  PSYCHIC_SCREAM: {
    id: 8122,
    name: 'Psychic Scream',
    icon: 'spell_shadow_psychicscream',
  },
  ATONEMENT_HEAL_NON_CRIT: {
    id: 81751,
    name: 'Atonement',
    icon: 'spell_holy_circleofrenewal',
  },
  ATONEMENT_HEAL_CRIT: {
    id: 94472,
    name: 'Atonement',
    icon: 'spell_holy_circleofrenewal',
  },
  ATONEMENT_BUFF: {
    id: 194384,
    name: 'Atonement',
    icon: 'ability_priest_atonement',
  },
  HALO_HEAL: {
    id: 120692,
    name: 'Halo',
    icon: 'ability_priest_halo',
  },
  DISC_PRIEST_T19_2SET_BONUS_BUFF: {
    id: 211556,
    name: 'T19 2 set bonus',
    icon: 'spell_holy_powerwordshield',
  },
  KAM_XIRAFF_BUFF: {
    id: 233997,
    name: 'Kam Xi\'raff',
    icon: 'ability_priest_savinggrace',
  },
  SEPHUZS_SECRET_BUFF: {
    id: 208052,
    name: 'Sephuz\'s Secret',
    icon: 'inv_jewelry_ring_149',
  },
  TWIST_OF_FATE_BUFF: {
    id: 123254,
    name: 'Twist of Fate',
    icon: 'spell_shadow_mindtwisting',
  },
  PURGE_THE_WICKED_BUFF: {
    id: 204213,
    name: 'Purge the Wicked',
    icon: 'ability_mage_firestarter',
  },

  // Talents:
  // Discipline Priest:
  // lv15
  TWIST_OF_FATE_TALENT: {
    id: 109142,
    name: 'Twist of Fate',
    icon: 'spell_shadow_mindtwisting',
  },
  CASTIGATION_TALENT: {
    id: 193134,
    name: 'Castigation',
    icon: 'spell_holy_searinglightpriest',
  },
  SCHISM_TALENT: {
    id: 214621,
    name: 'Schism',
    icon: 'spell_warlock_focusshadow',
  },
  // lv30
  ANGELIC_FEATHER_TALENT: {
    id: 121536,
    name: 'Angelic Feather',
    icon: 'ability_priest_angelicfeather',
  },
  BODY_AND_SOUL_TALENT: {
    id: 64129,
    name: 'Body and Soul',
    icon: 'spell_holy_symbolofhope',
  },
  MASOCHISM_TALENT: {
    id: 193063,
    name: 'Masochism',
    icon: 'spell_shadow_misery',
  },
  // lv45
  SHINING_FORCE_TALENT: {
    id: 204263,
    name: 'Shining Force',
    icon: 'ability_paladin_blindinglight2',
  },
  PSYCHIC_VOICE_TALENT: {
    id: 196704,
    name: 'Psychic Voice',
    icon: 'spell_shadow_psychichorrors',
  },
  DOMINANT_MIND_TALENT: {
    id: 205367,
    name: 'Dominant Mind',
    icon: 'spell_priest_void-flay',
  },
  // lv60
  POWER_WORD_SOLACE_TALENT: {
    id: 129250,
    name: 'Power Word: Solace',
    icon: 'ability_priest_flashoflight',
  },
  SHIELD_DISCIPLINE_TALENT: {
    id: 197045,
    name: 'Shield Discipline',
    icon: 'spell_holy_divineprotection',
  },
  MINDBENDER_TALENT: {
    id: 123040,
    name: 'Mindbender',
    icon: 'spell_shadow_soulleech_3',
  },
  // lv75
  SANCTUARY_TALENT: {
    id: 246393,
    name: 'Sanctuary',
    icon: 'spell_holy_holysmite',
  },
  CLARITY_OF_WILL_TALENT: {
    id: 152118,
    name: 'Clarity of Will',
    icon: 'ability_priest_clarityofwill',
  },
  SHADOW_COVENANT_TALENT: {
    id: 204065,
    name: 'Shadow Covenant',
    icon: 'spell_shadow_summonvoidwalker',
  },
  // lv90
  PURGE_THE_WICKED_TALENT: {
    id: 204197,
    name: 'Purge the Wicked',
    icon: 'ability_mage_firestarter',
    manaCost: 22000,
  },
  DIVINE_STAR_TALENT: {
    id: 110744,
    name: 'Divine Star',
    icon: 'spell_priest_divinestar',
  },
  HALO_TALENT: {
    id: 120517,
    name: 'Halo',
    icon: 'ability_priest_halo',
    manaCost: 39600,
  },
  // lv100
  POWER_INFUSION_TALENT: {
    id: 10060,
    name: 'Power Infusion',
    icon: 'spell_holy_powerinfusion',
  },
  GRACE_TALENT: {
    id: 200309,
    name: 'Grace',
    icon: 'spell_holy_hopeandgrace',
  },
  EVANGELISM_TALENT: {
    id: 246287,
    name: 'Evangelism',
    icon: 'spell_holy_divineillumination',
  },

  // Traits:
  // Disc Priest:
  SHARE_IN_THE_LIGHT: {
    id: 210027,
    name: 'Share in the Light',
    icon: 'spell_holy_powerwordshield',
  },
  PAIN_IS_IN_YOUR_MIND: {
    id: 197713,
    name: 'Pain is in Your Mind',
    icon: 'spell_holy_painsupression',
  },
  DOOMSAYER: {
    id: 197727,
    name: 'Doomsayer',
    icon: 'spell_holy_rapture',
  },
};
<|endoftext|>