PageSourceSearch

https://biblewebapp.com/study/build/build.js?v=3.8.6

js biblewebapp.com collected 2026-09-24 19:08:56 UTC 739,246 bytes, 24,608 lines download raw bytes

1
2/*********
3* ../app/js/core/namespace.js
4**********/
5// replacement IE console
6if (typeof console == 'undefined') { window.console = {log:function() {}}; }
7
8// everything goes here
9window.sofia = {};
10
11// version
12sofia.version = '3.8.5';
13
14// allows items to register themselves
15sofia.plugins = [];
16sofia.windowTypes = [];
17sofia.menuComponents = [];
18sofia.initMethods = [];
19
20sofia.globals = {};
21
22// language
23sofia.resources = {};
24
25
26if (window.location.protocol === 'file:') {
27	sofia.protocol = 'https:';
28} else {
29	sofia.protocol = '';
30}
31
32
33
34/*********
35* ../app/js/core/config-default.js
36**********/
37sofia.config = {
38
39	// Change this to clear all user settings
40	settingsPrefix: '20140307',
41
42	// Enables the use of online sources (Google Maps, FCBH, Jesus Film, etc.)
43	enableOnlineSources: true,
44
45	// first load
46	windows: [
47		{type: 'bible', data: {textid: 'ENGNAS', fragmentid: 'JN1_1'}},
48		{type: 'bible', data: {textid: 'grc_tisch', fragmentid: 'JN1_1'}},
49		{type: 'search', data: {textid: 'ENGNAS', searchtext:'truth love'}}
50	],
51
52	// URL to content
53	// (1) Leave blank to use local content folder.
54	// (2) Enter URL (http://www.biblesite.com/) for CORS enabled sites
55	// (3) Enter URL (http://www.biblesite.com/) and update
56	baseContentUrl: '',
57
58	// (1) Leave blank for local files or for CORS enabled CDN
59	// (2) Enter path of script that will convert all files to JSONP (e.g., api.php)
60	baseContentApiPath: '',
61
62	// if your are creating a CDN for your content (see above) and want to send a key
63	baseContentApiKey: '',
64
65	// file name of texts lists
66	textsIndexPath: 'texts.json',
67
68	// URL to about page
69	aboutPagePath: 'about.html',
70
71	// (1) Leave blank for JSON search
72	// (2) Enter path of script that will return JSON data
73	serverSearchPath: '',
74
75	// texts shown before the "MORE" button ("eng-NASB1995", "eng-kjv", "eng_net")
76	topTexts: [],
77
78	// new window
79	newBibleWindowVersion: 'eng-NASB1995',
80
81	// new bible verse
82	newWindowFragmentid: 'JN1_1',
83
84	// new commentary window
85	newCommentaryWindowTextId: 'comm_eng_wesley',
86
87	// language for top
88	pinnedLanguage: 'English',
89
90	// language(s) for top
91	pinnedLanguages: ['English', 'Spanish'],
92
93	// Override the browser and user's choice for UI language
94	defaultLanguage: '',
95
96	// URL to custom CSS
97	customCssUrl: '',
98
99	// Faith Comes by Hearing
100	fcbhKey: '',
101
102	// any texts you want to ignore from FCBH
103	fcbhTextExclusions: [''],
104
105	// true: live parse all versions
106	// false: loads texts_fcbh.json
107	fcbhLoadVersions: false,
108
109	// jesus film media
110	jfmKey: ''
111};
112
113sofia.customConfigs = {
114	"dbs": {
115		customCssUrl: 'dbs.css'
116	}
117};
118
119
120
121/*********
122* ../app/js/core/windowmanager.js
123**********/
124
125var WindowManager = function(node, app) {
126
127	var windows = [];
128
129	function addWindow(className, data) {
130
131		var id = 'win' + (new Date()).getTime().toString();
132
133		if (className == 'TextWindow') {
134			className = 'BibleWindow';
135		}
136
137		// test for classname
138		if (typeof window[className] == 'undefined')
139			return;
140
141		// create window and add to array
142		var win = new Window(id, node, className, data, ext);
143		windows.push(win);
144
145		var tabWidth = win.tab.outerWidth();
146
147		//win.tab.css({right: '0px'});
148
149
150		//size();
151		setTimeout(function() {
152			app.resize();
153		},10);
154	}
155	function removeWindow(id) {
156
157		// find window
158		var windowsToClose = windows.filter(function(win) {
159				return win.id == id;
160			}),
161			windowToClose = (windowsToClose.length == 1) ? windowsToClose[0] : null;
162
163		if (windowToClose == null) {
164			console.log('ERROR', "Can't find window", id);
165			return;
166		}
167
168		// remove from array
169		windows = windows.filter(function(win) {
170			return win.id != id;
171		});
172
173		PlaceKeeper.storePlace();
174
175		// remove from DOM, run delete functions
176		windowToClose.close();
177		windowToClose = null;
178
179		// resize and reset
180		//size();
181		PlaceKeeper.restorePlace();
182
183		/*
184		var tabWidth = windows[0].tab.outerWidth() - 10;
185		for (var i=0,il=windows.length; i<il; i++) {
186			windows[i].tab.css({right: ((il-i) * tabWidth) +'px'});
187		}
188		*/
189
190		windows[0].tab.addClass('active');
191		windows[0].node.addClass('active');
192
193		setTimeout(function() {
194			app.resize();
195		},10);
196
197
198		// trigger save
199		ext.trigger('settingschange', {type: 'settingschange', target: this, data: null});
200	}
201
202	function size(width, height) {
203		// set container size
204		if (width && height) {
205			node.width(width)
206				.height(height);
207		} else {
208			width = node.width();
209			height = node.height();
210		}
211
212		//console.log('manage resize', windows.length);
213
214		var sizeThreshold = 560;
215
216		if (width < sizeThreshold) {
217			$('body').addClass('compact-ui');
218		} else {
219			$('body').removeClass('compact-ui');
220		}
221
222
223		if (windows.length > 0) {
224
225			if (width < sizeThreshold) {
226
227				//$('body').addClass('compact-ui');
228
229
230				var tabWidth = windows[0].tab.outerWidth()-10;
231
232				// resize all windows to the same
233				for (var i=0, il=windows.length; i<il; i++) {
234					windows[i].size(width, height);
235					windows[i].tab.css({right: ((il-i-1) * tabWidth) +'px'});
236				}
237
238			} else {
239
240				//$('body').removeClass('compact-ui');
241
242				// all windows to a percent of the width
243
244				var windowWidth = Math.floor(width/windows.length),
245					firstMarginLeft = parseInt(windows[0].node.css('margin-left'), 10),
246					firstMarginRight =parseInt(windows[0].node.css('margin-right'), 10);
247
248
249				if (!isNaN(firstMarginLeft)) {
250					windowWidth = windowWidth - firstMarginLeft;
251				}
252				if (!isNaN(firstMarginRight)) {
253					windowWidth = windowWidth - firstMarginRight;
254				}
255
256				for (var i=0, il=windows.length; i<il; i++) {
257					windows[i].size(windowWidth, height);
258				}
259			}
260		}
261	}
262
263	function getSettings() {
264		var settingsForAllWindows = [];
265
266		for (var i=0, il=windows.length; i<il; i++) {
267			settingsForAllWindows.push({
268				'windowType': windows[i].className,
269				'data': windows[i].getData()
270			});
271		}
272
273		return settingsForAllWindows;
274	}
275
276	var ext = {
277		add: addWindow,
278		remove: removeWindow,
279		size: size,
280		getSettings: getSettings,
281		getWindows: function() { return windows; }
282	};
283
284	ext = $.extend(true, ext, EventEmitter);
285
286	return ext;
287};
288
289var Window = function(id, parentNode, className, data, manager) {
290
291	var ext = {},
292		node = $('<div class="window ' + className + ' active"></div>')
293					.appendTo(parentNode),
294		closeBtn = $('<div class="close-container"><span class="close-button"></span></div>')
295					.appendTo(node)
296					.find('.close-button')
297					.on('click', function() {
298						console.log(id, 'remove', manager);
299
300
301						manager.remove(id);
302					}),
303		tab = $('<div class="window-tab ' + className + ' active">' +
304					'<div class="window-tab-inner">' +
305						'<span class="window-tab-label ' + className + '-tab">' + className + '</span>' +
306					'</div>' +
307				'</div>')
308					.appendTo( $('body') );
309
310	// make sure this one is selected
311	node.siblings('.window').removeClass('active');
312	tab.siblings('.window-tab').removeClass('active');
313
314	ext.id = id;
315	ext.className = className;
316	ext.node = node;
317	ext.tab = tab;
318
319	var controller = new window[className](id, ext, data);
320	ext.controller = controller;
321
322	// send settings up to the manager, up to the app
323	controller.on('settingschange', function(e) {
324		ext.trigger('settingschange', e); // {type: e.type, target: this, data: e.data});
325	});
326	controller.on('globalmessage', function(e) {
327		e.id = id;
328		ext.trigger('globalmessage', e); // {type: e.type, target: this, data: e.data});
329	});
330
331
332	// send focus/blur events down to controller
333	node.on('mouseenter touchstart', function(e) {
334		controller.trigger('focus', {});
335		node
336			.addClass('focused')
337			.siblings()
338				.removeClass('focused')
339				.trigger('windowblur');
340	});
341	node.on('mouseleave windowblur', function(e) {
342		node
343			.removeClass('focused')		
344		
345		controller.trigger('blur', {});
346	});
347
348	tab.on('click', function(e) {
349		// dectivate all other tabs and windows
350		$('.window, .window-tab').removeClass('active');
351
352		tab.addClass('active');
353		node.addClass('active');
354	});
355
356
357	function size(width, height) {
358		node.outerWidth(width)
359			.outerHeight(height);
360
361		controller.size(width, height);
362	}
363
364	function quit() {
365		controller.quit();
366	}
367
368	function close() {
369
370		if (typeof controller.close != 'undefined') {
371			controller.close();
372		}
373		controller = null;
374
375		ext.clearListeners();
376
377		tab.remove();
378		node.remove();
379	}
380
381
382	ext.size = size;
383	ext.quit = quit;
384	ext.getData =  function() {
385			return controller.getData();
386	};
387	ext.close =  close;
388
389	ext = $.extend(true, ext, EventEmitter);
390
391	// receive from App, send down to controller
392	ext.on('message', function(e) {
393		controller.trigger('message', e);
394
395		if (e.data.labelTab) {
396			tab.find('span').html( e.data.labelTab );
397		}
398	});
399	
400
401	// when a window reports a settings change
402	ext.on('settingschange', function(e) {
403
404		// pass up to root
405		manager.trigger('settingschange', e);
406	});
407
408	ext.on('globalmessage', sofia.app.handleGlobalMessage);	
409
410	return ext;
411};
412
413
414
415/*********
416* ../app/js/core/windowapp.js
417**********/
418
419var App = function() {
420	// create nodes
421	var	win = $(window),
422		body = $(document.body),
423		container = $('<div class="windows-container"></div>').appendTo(body),
424		header = $('<div class="windows-header"></div>').appendTo(container),
425		main = $('<div class="windows-main"></div>').appendTo(container),
426		footer = $('<div class="windows-footer"></div>').appendTo(container),
427		settingsKey = 'app-windows',
428		ext = {},
429		windowManager = null,
430		mainMenu = null;
431
432	function init() {
433		// create objects
434		mainMenu = new MainMenu(header);
435		windowManager = new WindowManager(main, ext);
436
437		ext.windowManager = windowManager;
438
439		// combine nodes and objects
440		win.on('resize', resize);
441		win.on('orientationchange', resize);
442		resize();
443
444
445		// if not fullscreen and is touch screen
446		//if (Detection.hasTouch) {
447		try {
448			window.top.scrollTo(0, 1);
449		} catch (ex) {
450
451		}
452		//}
453
454
455		var settings = getWindowSettings(),
456			windowWidth = win.width();
457		//console.log('settings',settings, settings.length);
458
459		/*
460		if (windowWidth < 768 && settings.windows.length >= 3) {
461			settings.windows = settings.windows.slice(0,2);
462		}
463		if (windowWidth < 460 && settings.windows.length >= 2) {
464			settings.windows = settings.windows.slice(0,1);
465		}
466		*/
467
468		// create windows
469		for (var i=0, il=settings.windows.length; i<il; i++) {
470			var setting = settings.windows[i],
471				windowClassName = setting.windowType;
472
473			// try to find ClassName by the parameter
474			if (!windowClassName) {
475				var param = setting.type,
476					filteredWindowTypes = sofia.windowTypes.filter(function(wt) {
477						return wt.param == param;
478					});
479
480				if (filteredWindowTypes.length == 1) {
481					windowClassName = filteredWindowTypes[0].className;
482				}
483			}
484
485			//console.log('create window', setting, windowClassName);
486			windowManager.add(windowClassName, setting.data);
487		}
488		// make sure first is active
489		$('.window, .window-tab').removeClass('active');
490		$('.window:first, .window-tab:first').addClass('active');
491
492		// get first window
493		var bibleWindows = settings.windows.filter(function(s) { return s.windowType == 'BibleWindow'; }),
494			firstBibleWindow = bibleWindows.length > 0 ? bibleWindows[0] : null,
495			firstFragmentid = firstBibleWindow != null && firstBibleWindow.data != null ? firstBibleWindow.data.fragmentid : null;
496
497		if (firstFragmentid != null) {
498			TextNavigation.firstState( firstFragmentid );
499		}
500
501		var settingsTimer = new Timer(storeSettings, 1000);
502
503		windowManager.on('settingschange', function(e) {
504
505			// title to show active window's position
506			if (e.data && e.data.label && e.data.hasFocus) {
507				document.title = e.data.labelLong;
508			}
509
510			settingsTimer.start();
511
512		});
513
514		var plugins = []
515		ext.plugins = plugins;
516		// run plugins
517		for (var x in sofia.plugins) {
518			plugin = new window[ sofia.plugins[x] ](this);
519			plugins.push(plugin);
520
521			if (plugin.on) {
522
523				plugin.on('globalmessage', handleGlobalMessage);
524			}
525		}
526	}
527
528	function resize() {
529		//console.log('app resize');
530		PlaceKeeper.storePlace();
531
532		if (windowManager && windowManager.getWindows().length == 1) {
533			body.addClass('one-window')
534		} else {
535			body.removeClass('one-window')
536		}
537
538		// get window size
539		var width = win.width(),
540			height = win.height(),
541
542			// calculate size
543			areaHeight = height - header.outerHeight() + footer.outerHeight(),
544			areaWidth = width - parseInt(main.css('margin-left'), 10) - parseInt(main.css('margin-right'), 10);
545
546		/*
547		if (width < 460) {
548			//header.hide();
549			areaHeight = height;
550		} else {
551			header.show();
552		}
553		*/
554
555		// set height
556		main.height(areaHeight);
557		main.width(areaWidth);
558
559		// pass new size down to area
560		ext.windowManager.size(areaWidth, areaHeight);
561
562		PlaceKeeper.restorePlace();
563	}
564	ext.resize = resize;
565
566	function getWindowSettings() {
567
568		// (1) get default settings
569		var settings = {
570			windows: sofia.config.windows
571		};
572
573		// (2) replace defaults with user settings
574		settings = AppSettings.getValue(settingsKey, settings);
575
576		// (3) overwrite with QueryString when present
577		var queryData = stringUtility.parseQuerystring();
578		if (queryData["w1"]) {
579
580			var tempSettings = [];
581
582			for (var i=1; i<=4; i++) {
583				var winTypeName = queryData["w" + i.toString()],
584					winTypeInfo = sofia.windowTypes.filter(function(winType) { return winType.param == winTypeName; })[0],
585					winTypeParamKeys = typeof winTypeInfo != 'undefined' ? Object.keys(winTypeInfo.paramKeys) : [],
586					setting = {
587						type: winTypeName,
588						data: {}
589					};
590
591				if (typeof winTypeName != 'undefined') {
592					// go though all querystring values, and anything that ends with '1' goes with this data
593					for (var q in queryData) {
594						var key = q.substring(0, q.length-1),
595							number = q.substring(q.length-1),
596							value = queryData[q];
597
598						if (key != 'w' && number == i.toString()) {
599
600							// convert short querystring key to longer one if needed
601							var longParamKey = winTypeParamKeys.filter(function(lpk) {
602								console.log(key, lpk, winTypeInfo.paramKeys[lpk]);
603								return key == winTypeInfo.paramKeys[lpk] || key == lpk;
604							})[0];
605
606							setting.data[longParamKey] = value;
607						}
608					}
609
610					tempSettings.push(setting);
611				}
612			}
613
614			// overwrite settings
615			if (tempSettings.length > 0) {
616				settings.windows = tempSettings;
617			}
618
619		}
620		return settings;
621	}
622
623	function storeSettings() {
624			// get settings from all windows
625		var windowSettings = ext.windowManager.getSettings(),
626			// later we'll need these
627			headerSettings = {},
628			settings = {
629				windows: windowSettings
630			};
631
632		//console.log('storeSettings', settings);
633
634		// store
635		AppSettings.setValue(settingsKey, settings );
636	}
637
638	function handleGlobalMessage(e) {
639		// give to other windows
640		var windows = ext.windowManager.getWindows();
641
642		for (var i=0, il=windows.length; i<il; i++) {
643			var w = windows[i];
644
645			if (w.id != e.id) {
646		 		// pass message down
647		 		w.trigger('message', e);
648			}
649		}
650
651		// plugins
652		if (ext.plugins) {
653			for (var i=0, il=ext.plugins.length; i<il; i++) {
654				var p = sofia.app.plugins[i];
655
656				if (p.trigger) {
657					// pass message down
658					p.trigger('message', e);
659				}
660			}
661		}
662
663	}
664
665	ext.init = init;
666	ext.handleGlobalMessage = handleGlobalMessage;
667
668	// internationalization: i18n
669
670	// if this is the first visit, we need a way to set the language
671	var lngSetting = '',
672		i18nCookieValue = AppSettings.getCookieValue('i18next');
673
674	if (i18nCookieValue == '' || i18nCookieValue == null && sofia.config.defaultLanguage != '') {
675		lngSetting = sofia.config.defaultLanguage;
676	}
677
678	i18n.init({fallbackLng: 'en', lng: lngSetting, resStore: sofia.resources});
679
680	setTimeout(function() {
681
682		var lang = i18n.lng(),
683			langSelector = $('#config-language');
684
685		langSelector.val( lang );
686
687		if (lang != langSelector.val() ) {
688			langSelector.val(  lang.split('-')[0] );
689		}
690
691		if (langSelector[0] && langSelector[0].localizeLanguages) {
692			langSelector[0].localizeLanguages();
693		}
694
695	}, 50);
696
697	return ext;
698};
699
700
701
702/*********
703* ../app/js/core/startup.js
704**********/
705
706$(function() {
707	// hide initial text area
708	$('#startup').hide();
709
710	// test for local file support
711	if (window.location.protocol === 'file:') {
712
713		$.ajax({
714			dataType: 'text',
715
716			url: 'about.html',
717			success: function() {
718				init();
719			},
720			error: function(e) {
721				var modal = new MovableWindow( Math.min(500, $(window).width()) ,250, 'Local Files Error'),
722					errorMessage = '',
723					ua = navigator.userAgent.toLowerCase();
724				//modal.size(500, 200).center();
725
726				if (ua.indexOf('chrome') > -1) {
727					if (ua.indexOf('mac os') > -1) {
728						errorMessage =
729							'<p>Mac, Terminal</p>' +
730							'<code>/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --allow-file-access-from-files</code>';
731					} else
732					if (ua.indexOf('windows') > -1) {
733						errorMessage =
734							'<p>Mac, Terminal</p>' +
735							'<code>/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --allow-file-access-from-files</code>';
736					}
737				} else {
738					errorMessage =
739						'<p>Unknown error loading files (cannot load about.html)' + e + '</p>';
740				}
741
742				modal.body.css({background: '#000', color: '#fff' }).html(
743					'<div style="padding: 20px;">' +
744						errorMessage +
745					'</div>'
746				);
747				modal.show().center();
748			}
749		});
750
751	} else {
752		init();
753	}
754
755	function init() {
756		// load config
757		var params = stringUtility.parseQuerystring(),
758			custom = params["custom"];
759
760		if (typeof custom != 'undefined' && custom != '') {
761			var customizations = sofia.customConfigs[custom];
762
763			if (typeof customizations != 'undefined' && customizations != null) {
764				sofia.config = $.extend(sofia.config, customizations);
765			}
766		}
767
768		// load css
769		if (typeof sofia.config.customCssUrl != 'undefined' && sofia.config.customCssUrl != '') {
770			$('<link href="' + sofia.config.customCssUrl + '" rel="stylesheet" />').appendTo( $('head') );
771		}
772
773		var isiOSApp = (navigator.userAgent.toLowerCase().indexOf('ipad') > -1 || navigator.userAgent.toLowerCase().indexOf('iphone') > -1) && window.location.protocol === 'file:';
774
775		if (window.navigator.standalone === true || isiOSApp) {
776			$('body').addClass('app-mobile-fullscreen');
777		}
778
779		// run inits
780		for (var i=0, il=sofia.initMethods.length; i<il; i++) {
781			sofia.initMethods[i]();
782		}
783
784		// create app
785		sofia.app = new App();
786		sofia.app.init();
787
788		$('.i18n').i18n();
789	}
790});
791
792
793
794/*********
795* ../app/js/lib/xregexp.js
796**********/
797/*!
798 * XRegExp v2.0.0
799 * (c) 2007-2012 Steven Levithan <http://xregexp.com/>
800 * MIT License
801 */
802
803/**
804 * XRegExp provides augmented, extensible JavaScript regular expressions. You get new syntax,
805 * flags, and methods beyond what browsers support natively. XRegExp is also a regex utility belt
806 * with tools to make your client-side grepping simpler and more powerful, while freeing you from
807 * worrying about pesky cross-browser inconsistencies and the dubious `lastIndex` property. See
808 * XRegExp's documentation (http://xregexp.com/) for more details.
809 * @module xregexp
810 * @requires N/A
811 */
812var XRegExp;
813
814// Avoid running twice; that would reset tokens and could break references to native globals
815XRegExp = XRegExp || (function (undef) {
816    "use strict";
817
818/*--------------------------------------
819 *  Private variables
820 *------------------------------------*/
821
822    var self,
823        addToken,
824        add,
825
826// Optional features; can be installed and uninstalled
827        features = {
828            natives: false,
829            extensibility: false
830        },
831
832// Store native methods to use and restore ("native" is an ES3 reserved keyword)
833        nativ = {
834            exec: RegExp.prototype.exec,
835            test: RegExp.prototype.test,
836            match: String.prototype.match,
837            replace: String.prototype.replace,
838            split: String.prototype.split
839        },
840
841// Storage for fixed/extended native methods
842        fixed = {},
843
844// Storage for cached regexes
845        cache = {},
846
847// Storage for addon tokens
848        tokens = [],
849
850// Token scopes
851        defaultScope = "default",
852        classScope = "class",
853
854// Regexes that match native regex syntax
855        nativeTokens = {
856            // Any native multicharacter token in default scope (includes octals, excludes character classes)
857            "default": /^(?:\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??)/,
858            // Any native multicharacter token in character class scope (includes octals)
859            "class": /^(?:\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S]))/
860        },
861
862// Any backreference in replacement strings
863        replacementToken = /\$(?:{([\w$]+)}|(\d\d?|[\s\S]))/g,
864
865// Any character with a later instance in the string
866        duplicateFlags = /([\s\S])(?=[\s\S]*\1)/g,
867
868// Any greedy/lazy quantifier
869        quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/,
870
871// Check for correct `exec` handling of nonparticipating capturing groups
872        compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undef,
873
874// Check for flag y support (Firefox 3+)
875        hasNativeY = RegExp.prototype.sticky !== undef,
876
877// Used to kill infinite recursion during XRegExp construction
878        isInsideConstructor = false,
879
880// Storage for known flags, including addon flags
881        registeredFlags = "gim" + (hasNativeY ? "y" : "");
882
883/*--------------------------------------
884 *  Private helper functions
885 *------------------------------------*/
886
887/**
888 * Attaches XRegExp.prototype properties and named capture supporting data to a regex object.
889 * @private
890 * @param {RegExp} regex Regex to augment.
891 * @param {Array} captureNames Array with capture names, or null.
892 * @param {Boolean} [isNative] Whether the regex was created by `RegExp` rather than `XRegExp`.
893 * @returns {RegExp} Augmented regex.
894 */
895    function augment(regex, captureNames, isNative) {
896        var p;
897        // Can't auto-inherit these since the XRegExp constructor returns a nonprimitive value
898        for (p in self.prototype) {
899            if (self.prototype.hasOwnProperty(p)) {
900                regex[p] = self.prototype[p];
901            }
902        }
903        regex.xregexp = {captureNames: captureNames, isNative: !!isNative};
904        return regex;
905    }
906
907/**
908 * Returns native `RegExp` flags used by a regex object.
909 * @private
910 * @param {RegExp} regex Regex to check.
911 * @returns {String} Native flags in use.
912 */
913    function getNativeFlags(regex) {
914        //return nativ.exec.call(/\/([a-z]*)$/i, String(regex))[1];
915        return (regex.global     ? "g" : "") +
916               (regex.ignoreCase ? "i" : "") +
917               (regex.multiline  ? "m" : "") +
918               (regex.extended   ? "x" : "") + // Proposed for ES6, included in AS3
919               (regex.sticky     ? "y" : ""); // Proposed for ES6, included in Firefox 3+
920    }
921
922/**
923 * Copies a regex object while preserving special properties for named capture and augmenting with
924 * `XRegExp.prototype` methods. The copy has a fresh `lastIndex` property (set to zero). Allows
925 * adding and removing flags while copying the regex.
926 * @private
927 * @param {RegExp} regex Regex to copy.
928 * @param {String} [addFlags] Flags to be added while copying the regex.
929 * @param {String} [removeFlags] Flags to be removed while copying the regex.
930 * @returns {RegExp} Copy of the provided regex, possibly with modified flags.
931 */
932    function copy(regex, addFlags, removeFlags) {
933        if (!self.isRegExp(regex)) {
934            throw new TypeError("type RegExp expected");
935        }
936        var flags = nativ.replace.call(getNativeFlags(regex) + (addFlags || ""), duplicateFlags, "");
937        if (removeFlags) {
938            // Would need to escape `removeFlags` if this was public
939            flags = nativ.replace.call(flags, new RegExp("[" + removeFlags + "]+", "g"), "");
940        }
941        if (regex.xregexp && !regex.xregexp.isNative) {
942            // Compiling the current (rather than precompilation) source preserves the effects of nonnative source flags
943            regex = augment(self(regex.source, flags),
944                            regex.xregexp.captureNames ? regex.xregexp.captureNames.slice(0) : null);
945        } else {
946            // Augment with `XRegExp.prototype` methods, but use native `RegExp` (avoid searching for special tokens)
947            regex = augment(new RegExp(regex.source, flags), null, true);
948        }
949        return regex;
950    }
951
952/*
953 * Returns the last index at which a given value can be found in an array, or `-1` if it's not
954 * present. The array is searched backwards.
955 * @private
956 * @param {Array} array Array to search.
957 * @param {*} value Value to locate in the array.
958 * @returns {Number} Last zero-based index at which the item is found, or -1.
959 */
960    function lastIndexOf(array, value) {
961        var i = array.length;
962        if (Array.prototype.lastIndexOf) {
963            return array.lastIndexOf(value); // Use the native method if available
964        }
965        while (i--) {
966            if (array[i] === value) {
967                return i;
968            }
969        }
970        return -1;
971    }
972
973/**
974 * Determines whether an object is of the specified type.
975 * @private
976 * @param {*} value Object to check.
977 * @param {String} type Type to check for, in lowercase.
978 * @returns {Boolean} Whether the object matches the type.
979 */
980    function isType(value, type) {
981        return Object.prototype.toString.call(value).toLowerCase() === "[object " + type + "]";
982    }
983
984/**
985 * Prepares an options object from the given value.
986 * @private
987 * @param {String|Object} value Value to convert to an options object.
988 * @returns {Object} Options object.
989 */
990    function prepareOptions(value) {
991        value = value || {};
992        if (value === "all" || value.all) {
993            value = {natives: true, extensibility: true};
994        } else if (isType(value, "string")) {
995            value = self.forEach(value, /[^\s,]+/, function (m) {
996                this[m] = true;
997            }, {});
998        }
999        return value;
1000    }
1001
1002/**
1003 * Runs built-in/custom tokens in reverse insertion order, until a match is found.
1004 * @private
1005 * @param {String} pattern Original pattern from which an XRegExp object is being built.
1006 * @param {Number} pos Position to search for tokens within `pattern`.
1007 * @param {Number} scope Current regex scope.
1008 * @param {Object} context Context object assigned to token handler functions.
1009 * @returns {Object} Object with properties `output` (the substitution string returned by the
1010 *   successful token handler) and `match` (the token's match array), or null.
1011 */
1012    function runTokens(pattern, pos, scope, context) {
1013        var i = tokens.length,
1014            result = null,
1015            match,
1016            t;
1017        // Protect against constructing XRegExps within token handler and trigger functions
1018        isInsideConstructor = true;
1019        // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws
1020        try {
1021            while (i--) { // Run in reverse order
1022                t = tokens[i];
1023                if ((t.scope === "all" || t.scope === scope) && (!t.trigger || t.trigger.call(context))) {
1024                    t.pattern.lastIndex = pos;
1025                    match = fixed.exec.call(t.pattern, pattern); // Fixed `exec` here allows use of named backreferences, etc.
1026                    if (match && match.index === pos) {
1027                        result = {
1028                            output: t.handler.call(context, match, scope),
1029                            match: match
1030                        };
1031                        break;
1032                    }
1033                }
1034            }
1035        } catch (err) {
1036            throw err;
1037        } finally {
1038            isInsideConstructor = false;
1039        }
1040        return result;
1041    }
1042
1043/**
1044 * Enables or disables XRegExp syntax and flag extensibility.
1045 * @private
1046 * @param {Boolean} on `true` to enable; `false` to disable.
1047 */
1048    function setExtensibility(on) {
1049        self.addToken = addToken[on ? "on" : "off"];
1050        features.extensibility = on;
1051    }
1052
1053/**
1054 * Enables or disables native method overrides.
1055 * @private
1056 * @param {Boolean} on `true` to enable; `false` to disable.
1057 */
1058    function setNatives(on) {
1059        RegExp.prototype.exec = (on ? fixed : nativ).exec;
1060        RegExp.prototype.test = (on ? fixed : nativ).test;
1061        String.prototype.match = (on ? fixed : nativ).match;
1062        String.prototype.replace = (on ? fixed : nativ).replace;
1063        String.prototype.split = (on ? fixed : nativ).split;
1064        features.natives = on;
1065    }
1066
1067/*--------------------------------------
1068 *  Constructor
1069 *------------------------------------*/
1070
1071/**
1072 * Creates an extended regular expression object for matching text with a pattern. Differs from a
1073 * native regular expression in that additional syntax and flags are supported. The returned object
1074 * is in fact a native `RegExp` and works with all native methods.
1075 * @class XRegExp
1076 * @constructor
1077 * @param {String|RegExp} pattern Regex pattern string, or an existing `RegExp` object to copy.
1078 * @param {String} [flags] Any combination of flags:
1079 *   <li>`g` - global
1080 *   <li>`i` - ignore case
1081 *   <li>`m` - multiline anchors
1082 *   <li>`n` - explicit capture
1083 *   <li>`s` - dot matches all (aka singleline)
1084 *   <li>`x` - free-spacing and line comments (aka extended)
1085 *   <li>`y` - sticky (Firefox 3+ only)
1086 *   Flags cannot be provided when constructing one `RegExp` from another.
1087 * @returns {RegExp} Extended regular expression object.
1088 * @example
1089 *
1090 * // With named capture and flag x
1091 * date = XRegExp('(?<year>  [0-9]{4}) -?  # year  \n\
1092 *                 (?<month> [0-9]{2}) -?  # month \n\
1093 *                 (?<day>   [0-9]{2})     # day   ', 'x');
1094 *
1095 * // Passing a regex object to copy it. The copy maintains special properties for named capture,
1096 * // is augmented with `XRegExp.prototype` methods, and has a fresh `lastIndex` property (set to
1097 * // zero). Native regexes are not recompiled using XRegExp syntax.
1098 * XRegExp(/regex/);
1099 */
1100    self = function (pattern, flags) {
1101        if (self.isRegExp(pattern)) {
1102            if (flags !== undef) {
1103                throw new TypeError("can't supply flags when constructing one RegExp from another");
1104            }
1105            return copy(pattern);
1106        }
1107        // Tokens become part of the regex construction process, so protect against infinite recursion
1108        // when an XRegExp is constructed within a token handler function
1109        if (isInsideConstructor) {
1110            throw new Error("can't call the XRegExp constructor within token definition functions");
1111        }
1112
1113        var output = [],
1114            scope = defaultScope,
1115            tokenContext = {
1116                hasNamedCapture: false,
1117                captureNames: [],
1118                hasFlag: function (flag) {
1119                    return flags.indexOf(flag) > -1;
1120                }
1121            },
1122            pos = 0,
1123            tokenResult,
1124            match,
1125            chr;
1126        pattern = pattern === undef ? "" : String(pattern);
1127        flags = flags === undef ? "" : String(flags);
1128
1129        if (nativ.match.call(flags, duplicateFlags)) { // Don't use test/exec because they would update lastIndex
1130            throw new SyntaxError("invalid duplicate regular expression flag");
1131        }
1132        // Strip/apply leading mode modifier with any combination of flags except g or y: (?imnsx)
1133        pattern = nativ.replace.call(pattern, /^\(\?([\w$]+)\)/, function ($0, $1) {
1134            if (nativ.test.call(/[gy]/, $1)) {
1135                throw new SyntaxError("can't use flag g or y in mode modifier");
1136            }
1137            flags = nativ.replace.call(flags + $1, duplicateFlags, "");
1138            return "";
1139        });
1140        self.forEach(flags, /[\s\S]/, function (m) {
1141            if (registeredFlags.indexOf(m[0]) < 0) {
1142                throw new SyntaxError("invalid regular expression flag " + m[0]);
1143            }
1144        });
1145
1146        while (pos < pattern.length) {
1147            // Check for custom tokens at the current position
1148            tokenResult = runTokens(pattern, pos, scope, tokenContext);
1149            if (tokenResult) {
1150                output.push(tokenResult.output);
1151                pos += (tokenResult.match[0].length || 1);
1152            } else {
1153                // Check for native tokens (except character classes) at the current position
1154                match = nativ.exec.call(nativeTokens[scope], pattern.slice(pos));
1155                if (match) {
1156                    output.push(match[0]);
1157                    pos += match[0].length;
1158                } else {
1159                    chr = pattern.charAt(pos);
1160                    if (chr === "[") {
1161                        scope = classScope;
1162                    } else if (chr === "]") {
1163                        scope = defaultScope;
1164                    }
1165                    // Advance position by one character
1166                    output.push(chr);
1167                    ++pos;
1168                }
1169            }
1170        }
1171
1172        return augment(new RegExp(output.join(""), nativ.replace.call(flags, /[^gimy]+/g, "")),
1173                       tokenContext.hasNamedCapture ? tokenContext.captureNames : null);
1174    };
1175
1176/*--------------------------------------
1177 *  Public methods/properties
1178 *------------------------------------*/
1179
1180// Installed and uninstalled states for `XRegExp.addToken`
1181    addToken = {
1182        on: function (regex, handler, options) {
1183            options = options || {};
1184            if (regex) {
1185                tokens.push({
1186                    pattern: copy(regex, "g" + (hasNativeY ? "y" : "")),
1187                    handler: handler,
1188                    scope: options.scope || defaultScope,
1189                    trigger: options.trigger || null
1190                });
1191            }
1192            // Providing `customFlags` with null `regex` and `handler` allows adding flags that do
1193            // nothing, but don't throw an error
1194            if (options.customFlags) {
1195                registeredFlags = nativ.replace.call(registeredFlags + options.customFlags, duplicateFlags, "");
1196            }
1197        },
1198        off: function () {
1199            throw new Error("extensibility must be installed before using addToken");
1200        }
1201    };
1202
1203/**
1204 * Extends or changes XRegExp syntax and allows custom flags. This is used internally and can be
1205 * used to create XRegExp addons. `XRegExp.install('extensibility')` must be run before calling
1206 * this function, or an error is thrown. If more than one token can match the same string, the last
1207 * added wins.
1208 * @memberOf XRegExp
1209 * @param {RegExp} regex Regex object that matches the new token.
1210 * @param {Function} handler Function that returns a new pattern string (using native regex syntax)
1211 *   to replace the matched token within all future XRegExp regexes. Has access to persistent
1212 *   properties of the regex being built, through `this`. Invoked with two arguments:
1213 *   <li>The match array, with named backreference properties.
1214 *   <li>The regex scope where the match was found.
1215 * @param {Object} [options] Options object with optional properties:
1216 *   <li>`scope` {String} Scopes where the token applies: 'default', 'class', or 'all'.
1217 *   <li>`trigger` {Function} Function that returns `true` when the token should be applied; e.g.,
1218 *     if a flag is set. If `false` is returned, the matched string can be matched by other tokens.
1219 *     Has access to persistent properties of the regex being built, through `this` (including
1220 *     function `this.hasFlag`).
1221 *   <li>`customFlags` {String} Nonnative flags used by the token's handler or trigger functions.
1222 *     Prevents XRegExp from throwing an invalid flag error when the specified flags are used.
1223 * @example
1224 *
1225 * // Basic usage: Adds \a for ALERT character
1226 * XRegExp.addToken(
1227 *   /\\a/,
1228 *   function () {return '\\x07';},
1229 *   {scope: 'all'}
1230 * );
1231 * XRegExp('\\a[\\a-\\n]+').test('\x07\n\x07'); // -> true
1232 */
1233    self.addToken = addToken.off;
1234
1235/**
1236 * Caches and returns the result of calling `XRegExp(pattern, flags)`. On any subsequent call with
1237 * the same pattern and flag combination, the cached copy is returned.
1238 * @memberOf XRegExp
1239 * @param {String} pattern Regex pattern string.
1240 * @param {String} [flags] Any combination of XRegExp flags.
1241 * @returns {RegExp} Cached XRegExp object.
1242 * @example
1243 *
1244 * while (match = XRegExp.cache('.', 'gs').exec(str)) {
1245 *   // The regex is compiled once only
1246 * }
1247 */
1248    self.cache = function (pattern, flags) {
1249        var key = pattern + "/" + (flags || "");
1250        return cache[key] || (cache[key] = self(pattern, flags));
1251    };
1252
1253/**
1254 * Escapes any regular expression metacharacters, for use when matching literal strings. The result
1255 * can safely be used at any point within a regex that uses any flags.
1256 * @memberOf XRegExp
1257 * @param {String} str String to escape.
1258 * @returns {String} String with regex metacharacters escaped.
1259 * @example
1260 *
1261 * XRegExp.escape('Escaped? <.>');
1262 * // -> 'Escaped\?\ <\.>'
1263 */
1264    self.escape = function (str) {
1265        return nativ.replace.call(str, /[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
1266    };
1267
1268/**
1269 * Executes a regex search in a specified string. Returns a match array or `null`. If the provided
1270 * regex uses named capture, named backreference properties are included on the match array.
1271 * Optional `pos` and `sticky` arguments specify the search start position, and whether the match
1272 * must start at the specified position only. The `lastIndex` property of the provided regex is not
1273 * used, but is updated for compatibility. Also fixes browser bugs compared to the native
1274 * `RegExp.prototype.exec` and can be used reliably cross-browser.
1275 * @memberOf XRegExp
1276 * @param {String} str String to search.
1277 * @param {RegExp} regex Regex to search with.
1278 * @param {Number} [pos=0] Zero-based index at which to start the search.
1279 * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position
1280 *   only. The string `'sticky'` is accepted as an alternative to `true`.
1281 * @returns {Array} Match array with named backreference properties, or null.
1282 * @example
1283 *
1284 * // Basic use, with named backreference
1285 * var match = XRegExp.exec('U+2620', XRegExp('U\\+(?<hex>[0-9A-F]{4})'));
1286 * match.hex; // -> '2620'
1287 *
1288 * // With pos and sticky, in a loop
1289 * var pos = 2, result = [], match;
1290 * while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) {
1291 *   result.push(match[1]);
1292 *   pos = match.index + match[0].length;
1293 * }
1294 * // result -> ['2', '3', '4']
1295 */
1296    self.exec = function (str, regex, pos, sticky) {
1297        var r2 = copy(regex, "g" + (sticky && hasNativeY ? "y" : ""), (sticky === false ? "y" : "")),
1298            match;
1299        r2.lastIndex = pos = pos || 0;
1300        match = fixed.exec.call(r2, str); // Fixed `exec` required for `lastIndex` fix, etc.
1301        if (sticky && match && match.index !== pos) {
1302            match = null;
1303        }
1304        if (regex.global) {
1305            regex.lastIndex = match ? r2.lastIndex : 0;
1306        }
1307        return match;
1308    };
1309
1310/**
1311 * Executes a provided function once per regex match.
1312 * @memberOf XRegExp
1313 * @param {String} str String to search.
1314 * @param {RegExp} regex Regex to search with.
1315 * @param {Function} callback Function to execute for each match. Invoked with four arguments:
1316 *   <li>The match array, with named backreference properties.
1317 *   <li>The zero-based match index.
1318 *   <li>The string being traversed.
1319 *   <li>The regex object being used to traverse the string.
1320 * @param {*} [context] Object to use as `this` when executing `callback`.
1321 * @returns {*} Provided `context` object.
1322 * @example
1323 *
1324 * // Extracts every other digit from a string
1325 * XRegExp.forEach('1a2345', /\d/, function (match, i) {
1326 *   if (i % 2) this.push(+match[0]);
1327 * }, []);
1328 * // -> [2, 4]
1329 */
1330    self.forEach = function (str, regex, callback, context) {
1331        var pos = 0,
1332            i = -1,
1333            match;
1334        while ((match = self.exec(str, regex, pos))) {
1335            callback.call(context, match, ++i, str, regex);
1336            pos = match.index + (match[0].length || 1);
1337        }
1338        return context;
1339    };
1340
1341/**
1342 * Copies a regex object and adds flag `g`. The copy maintains special properties for named
1343 * capture, is augmented with `XRegExp.prototype` methods, and has a fresh `lastIndex` property
1344 * (set to zero). Native regexes are not recompiled using XRegExp syntax.
1345 * @memberOf XRegExp
1346 * @param {RegExp} regex Regex to globalize.
1347 * @returns {RegExp} Copy of the provided regex with flag `g` added.
1348 * @example
1349 *
1350 * var globalCopy = XRegExp.globalize(/regex/);
1351 * globalCopy.global; // -> true
1352 */
1353    self.globalize = function (regex) {
1354        return copy(regex, "g");
1355    };
1356
1357/**
1358 * Installs optional features according to the specified options.
1359 * @memberOf XRegExp
1360 * @param {Object|String} options Options object or string.
1361 * @example
1362 *
1363 * // With an options object
1364 * XRegExp.install({
1365 *   // Overrides native regex methods with fixed/extended versions that support named
1366 *   // backreferences and fix numerous cross-browser bugs
1367 *   natives: true,
1368 *
1369 *   // Enables extensibility of XRegExp syntax and flags
1370 *   extensibility: true
1371 * });
1372 *
1373 * // With an options string
1374 * XRegExp.install('natives extensibility');
1375 *
1376 * // Using a shortcut to install all optional features
1377 * XRegExp.install('all');
1378 */
1379    self.install = function (options) {
1380        options = prepareOptions(options);
1381        if (!features.natives && options.natives) {
1382            setNatives(true);
1383        }
1384        if (!features.extensibility && options.extensibility) {
1385            setExtensibility(true);
1386        }
1387    };
1388
1389/**
1390 * Checks whether an individual optional feature is installed.
1391 * @memberOf XRegExp
1392 * @param {String} feature Name of the feature to check. One of:
1393 *   <li>`natives`
1394 *   <li>`extensibility`
1395 * @returns {Boolean} Whether the feature is installed.
1396 * @example
1397 *
1398 * XRegExp.isInstalled('natives');
1399 */
1400    self.isInstalled = function (feature) {
1401        return !!(features[feature]);
1402    };
1403
1404/**
1405 * Returns `true` if an object is a regex; `false` if it isn't. This works correctly for regexes
1406 * created in another frame, when `instanceof` and `constructor` checks would fail.
1407 * @memberOf XRegExp
1408 * @param {*} value Object to check.
1409 * @returns {Boolean} Whether the object is a `RegExp` object.
1410 * @example
1411 *
1412 * XRegExp.isRegExp('string'); // -> false
1413 * XRegExp.isRegExp(/regex/i); // -> true
1414 * XRegExp.isRegExp(RegExp('^', 'm')); // -> true
1415 * XRegExp.isRegExp(XRegExp('(?s).')); // -> true
1416 */
1417    self.isRegExp = function (value) {
1418        return isType(value, "regexp");
1419    };
1420
1421/**
1422 * Retrieves the matches from searching a string using a chain of regexes that successively search
1423 * within previous matches. The provided `chain` array can contain regexes and objects with `regex`
1424 * and `backref` properties. When a backreference is specified, the named or numbered backreference
1425 * is passed forward to the next regex or returned.
1426 * @memberOf XRegExp
1427 * @param {String} str String to search.
1428 * @param {Array} chain Regexes that each search for matches within preceding results.
1429 * @returns {Array} Matches by the last regex in the chain, or an empty array.
1430 * @example
1431 *
1432 * // Basic usage; matches numbers within <b> tags
1433 * XRegExp.matchChain('1 <b>2</b> 3 <b>4 a 56</b>', [
1434 *   XRegExp('(?is)<b>.*?</b>'),
1435 *   /\d+/
1436 * ]);
1437 * // -> ['2', '4', '56']
1438 *
1439 * // Passing forward and returning specific backreferences
1440 * html = '<a href="http://xregexp.com/api/">XRegExp</a>\
1441 *         <a href="http://www.google.com/">Google</a>';
1442 * XRegExp.matchChain(html, [
1443 *   {regex: /<a href="([^"]+)">/i, backref: 1},
1444 *   {regex: XRegExp('(?i)^https?://(?<domain>[^/?#]+)'), backref: 'domain'}
1445 * ]);
1446 * // -> ['xregexp.com', 'www.google.com']
1447 */
1448    self.matchChain = function (str, chain) {
1449        return (function recurseChain(values, level) {
1450            var item = chain[level].regex ? chain[level] : {regex: chain[level]},
1451                matches = [],
1452                addMatch = function (match) {
1453                    matches.push(item.backref ? (match[item.backref] || "") : match[0]);
1454                },
1455                i;
1456            for (i = 0; i < values.length; ++i) {
1457                self.forEach(values[i], item.regex, addMatch);
1458            }
1459            return ((level === chain.length - 1) || !matches.length) ?
1460                    matches :
1461                    recurseChain(matches, level + 1);
1462        }([str], 0));
1463    };
1464
1465/**
1466 * Returns a new string with one or all matches of a pattern replaced. The pattern can be a string
1467 * or regex, and the replacement can be a string or a function to be called for each match. To
1468 * perform a global search and replace, use the optional `scope` argument or include flag `g` if
1469 * using a regex. Replacement strings can use `${n}` for named and numbered backreferences.
1470 * Replacement functions can use named backreferences via `arguments[0].name`. Also fixes browser
1471 * bugs compared to the native `String.prototype.replace` and can be used reliably cross-browser.
1472 * @memberOf XRegExp
1473 * @param {String} str String to search.
1474 * @param {RegExp|String} search Search pattern to be replaced.
1475 * @param {String|Function} replacement Replacement string or a function invoked to create it.
1476 *   Replacement strings can include special replacement syntax:
1477 *     <li>$$ - Inserts a literal '$'.
1478 *     <li>$&, $0 - Inserts the matched substring.
1479 *     <li>$` - Inserts the string that precedes the matched substring (left context).
1480 *     <li>$' - Inserts the string that follows the matched substring (right context).
1481 *     <li>$n, $nn - Where n/nn are digits referencing an existent capturing group, inserts
1482 *       backreference n/nn.
1483 *     <li>${n} - Where n is a name or any number of digits that reference an existent capturing
1484 *       group, inserts backreference n.
1485 *   Replacement functions are invoked with three or more arguments:
1486 *     <li>The matched substring (corresponds to $& above). Named backreferences are accessible as
1487 *       properties of this first argument.
1488 *     <li>0..n arguments, one for each backreference (corresponding to $1, $2, etc. above).
1489 *     <li>The zero-based index of the match within the total search string.
1490 *     <li>The total string being searched.
1491 * @param {String} [scope='one'] Use 'one' to replace the first match only, or 'all'. If not
1492 *   explicitly specified and using a regex with flag `g`, `scope` is 'all'.
1493 * @returns {String} New string with one or all matches replaced.
1494 * @example
1495 *
1496 * // Regex search, using named backreferences in replacement string
1497 * var name = XRegExp('(?<first>\\w+) (?<last>\\w+)');
1498 * XRegExp.replace('John Smith', name, '${last}, ${first}');
1499 * // -> 'Smith, John'
1500 *
1501 * // Regex search, using named backreferences in replacement function
1502 * XRegExp.replace('John Smith', name, function (match) {
1503 *   return match.last + ', ' + match.first;
1504 * });
1505 * // -> 'Smith, John'
1506 *
1507 * // Global string search/replacement
1508 * XRegExp.replace('RegExp builds RegExps', 'RegExp', 'XRegExp', 'all');
1509 * // -> 'XRegExp builds XRegExps'
1510 */
1511    self.replace = function (str, search, replacement, scope) {
1512        var isRegex = self.isRegExp(search),
1513            search2 = search,
1514            result;
1515        if (isRegex) {
1516            if (scope === undef && search.global) {
1517                scope = "all"; // Follow flag g when `scope` isn't explicit
1518            }
1519            // Note that since a copy is used, `search`'s `lastIndex` isn't updated *during* replacement iterations
1520            search2 = copy(search, scope === "all" ? "g" : "", scope === "all" ? "" : "g");
1521        } else if (scope === "all") {
1522            search2 = new RegExp(self.escape(String(search)), "g");
1523        }
1524        result = fixed.replace.call(String(str), search2, replacement); // Fixed `replace` required for named backreferences, etc.
1525        if (isRegex && search.global) {
1526            search.lastIndex = 0; // Fixes IE, Safari bug (last tested IE 9, Safari 5.1)
1527        }
1528        return result;
1529    };
1530
1531/**
1532 * Splits a string into an array of strings using a regex or string separator. Matches of the
1533 * separator are not included in the result array. However, if `separator` is a regex that contains
1534 * capturing groups, backreferences are spliced into the result each time `separator` is matched.
1535 * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
1536 * cross-browser.
1537 * @memberOf XRegExp
1538 * @param {String} str String to split.
1539 * @param {RegExp|String} separator Regex or string to use for separating the string.
1540 * @param {Number} [limit] Maximum number of items to include in the result array.
1541 * @returns {Array} Array of substrings.
1542 * @example
1543 *
1544 * // Basic use
1545 * XRegExp.split('a b c', ' ');
1546 * // -> ['a', 'b', 'c']
1547 *
1548 * // With limit
1549 * XRegExp.split('a b c', ' ', 2);
1550 * // -> ['a', 'b']
1551 *
1552 * // Backreferences in result array
1553 * XRegExp.split('..word1..', /([a-z]+)(\d+)/i);
1554 * // -> ['..', 'word', '1', '..']
1555 */
1556    self.split = function (str, separator, limit) {
1557        return fixed.split.call(str, separator, limit);
1558    };
1559
1560/**
1561 * Executes a regex search in a specified string. Returns `true` or `false`. Optional `pos` and
1562 * `sticky` arguments specify the search start position, and whether the match must start at the
1563 * specified position only. The `lastIndex` property of the provided regex is not used, but is
1564 * updated for compatibility. Also fixes browser bugs compared to the native
1565 * `RegExp.prototype.test` and can be used reliably cross-browser.
1566 * @memberOf XRegExp
1567 * @param {String} str String to search.
1568 * @param {RegExp} regex Regex to search with.
1569 * @param {Number} [pos=0] Zero-based index at which to start the search.
1570 * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position
1571 *   only. The string `'sticky'` is accepted as an alternative to `true`.
1572 * @returns {Boolean} Whether the regex matched the provided value.
1573 * @example
1574 *
1575 * // Basic use
1576 * XRegExp.test('abc', /c/); // -> true
1577 *
1578 * // With pos and sticky
1579 * XRegExp.test('abc', /c/, 0, 'sticky'); // -> false
1580 */
1581    self.test = function (str, regex, pos, sticky) {
1582        // Do this the easy way :-)
1583        return !!self.exec(str, regex, pos, sticky);
1584    };
1585
1586/**
1587 * Uninstalls optional features according to the specified options.
1588 * @memberOf XRegExp
1589 * @param {Object|String} options Options object or string.
1590 * @example
1591 *
1592 * // With an options object
1593 * XRegExp.uninstall({
1594 *   // Restores native regex methods
1595 *   natives: true,
1596 *
1597 *   // Disables additional syntax and flag extensions
1598 *   extensibility: true
1599 * });
1600 *
1601 * // With an options string
1602 * XRegExp.uninstall('natives extensibility');
1603 *
1604 * // Using a shortcut to uninstall all optional features
1605 * XRegExp.uninstall('all');
1606 */
1607    self.uninstall = function (options) {
1608        options = prepareOptions(options);
1609        if (features.natives && options.natives) {
1610            setNatives(false);
1611        }
1612        if (features.extensibility && options.extensibility) {
1613            setExtensibility(false);
1614        }
1615    };
1616
1617/**
1618 * Returns an XRegExp object that is the union of the given patterns. Patterns can be provided as
1619 * regex objects or strings. Metacharacters are escaped in patterns provided as strings.
1620 * Backreferences in provided regex objects are automatically renumbered to work correctly. Native
1621 * flags used by provided regexes are ignored in favor of the `flags` argument.
1622 * @memberOf XRegExp
1623 * @param {Array} patterns Regexes and strings to combine.
1624 * @param {String} [flags] Any combination of XRegExp flags.
1625 * @returns {RegExp} Union of the provided regexes and strings.
1626 * @example
1627 *
1628 * XRegExp.union(['a+b*c', /(dogs)\1/, /(cats)\1/], 'i');
1629 * // -> /a\+b\*c|(dogs)\1|(cats)\2/i
1630 *
1631 * XRegExp.union([XRegExp('(?<pet>dogs)\\k<pet>'), XRegExp('(?<pet>cats)\\k<pet>')]);
1632 * // -> XRegExp('(?<pet>dogs)\\k<pet>|(?<pet>cats)\\k<pet>')
1633 */
1634    self.union = function (patterns, flags) {
1635        var parts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g,
1636            numCaptures = 0,
1637            numPriorCaptures,
1638            captureNames,
1639            rewrite = function (match, paren, backref) {
1640                var name = captureNames[numCaptures - numPriorCaptures];
1641                if (paren) { // Capturing group
1642                    ++numCaptures;
1643                    if (name) { // If the current capture has a name
1644                        return "(?<" + name + ">";
1645                    }
1646                } else if (backref) { // Backreference
1647                    return "\\" + (+backref + numPriorCaptures);
1648                }
1649                return match;
1650            },
1651            output = [],
1652            pattern,
1653            i;
1654        if (!(isType(patterns, "array") && patterns.length)) {
1655            throw new TypeError("patterns must be a nonempty array");
1656        }
1657        for (i = 0; i < patterns.length; ++i) {
1658            pattern = patterns[i];
1659            if (self.isRegExp(pattern)) {
1660                numPriorCaptures = numCaptures;
1661                captureNames = (pattern.xregexp && pattern.xregexp.captureNames) || [];
1662                // Rewrite backreferences. Passing to XRegExp dies on octals and ensures patterns
1663                // are independently valid; helps keep this simple. Named captures are put back
1664                output.push(self(pattern.source).source.replace(parts, rewrite));
1665            } else {
1666                output.push(self.escape(pattern));
1667            }
1668        }
1669        return self(output.join("|"), flags);
1670    };
1671
1672/**
1673 * The XRegExp version number.
1674 * @static
1675 * @memberOf XRegExp
1676 * @type String
1677 */
1678    self.version = "2.0.0";
1679
1680/*--------------------------------------
1681 *  Fixed/extended native methods
1682 *------------------------------------*/
1683
1684/**
1685 * Adds named capture support (with backreferences returned as `result.name`), and fixes browser
1686 * bugs in the native `RegExp.prototype.exec`. Calling `XRegExp.install('natives')` uses this to
1687 * override the native method. Use via `XRegExp.exec` without overriding natives.
1688 * @private
1689 * @param {String} str String to search.
1690 * @returns {Array} Match array with named backreference properties, or null.
1691 */
1692    fixed.exec = function (str) {
1693        var match, name, r2, origLastIndex, i;
1694        if (!this.global) {
1695            origLastIndex = this.lastIndex;
1696        }
1697        match = nativ.exec.apply(this, arguments);
1698        if (match) {
1699            // Fix browsers whose `exec` methods don't consistently return `undefined` for
1700            // nonparticipating capturing groups
1701            if (!compliantExecNpcg && match.length > 1 && lastIndexOf(match, "") > -1) {
1702                r2 = new RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", ""));
1703                // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed
1704                // matching due to characters outside the match
1705                nativ.replace.call(String(str).slice(match.index), r2, function () {
1706                    var i;
1707                    for (i = 1; i < arguments.length - 2; ++i) {
1708                        if (arguments[i] === undef) {
1709                            match[i] = undef;
1710                        }
1711                    }
1712                });
1713            }
1714            // Attach named capture properties
1715            if (this.xregexp && this.xregexp.captureNames) {
1716                for (i = 1; i < match.length; ++i) {
1717                    name = this.xregexp.captureNames[i - 1];
1718                    if (name) {
1719                        match[name] = match[i];
1720                    }
1721                }
1722            }
1723            // Fix browsers that increment `lastIndex` after zero-length matches
1724            if (this.global && !match[0].length && (this.lastIndex > match.index)) {
1725                this.lastIndex = match.index;
1726            }
1727        }
1728        if (!this.global) {
1729            this.lastIndex = origLastIndex; // Fixes IE, Opera bug (last tested IE 9, Opera 11.6)
1730        }
1731        return match;
1732    };
1733
1734/**
1735 * Fixes browser bugs in the native `RegExp.prototype.test`. Calling `XRegExp.install('natives')`
1736 * uses this to override the native method.
1737 * @private
1738 * @param {String} str String to search.
1739 * @returns {Boolean} Whether the regex matched the provided value.
1740 */
1741    fixed.test = function (str) {
1742        // Do this the easy way :-)
1743        return !!fixed.exec.call(this, str);
1744    };
1745
1746/**
1747 * Adds named capture support (with backreferences returned as `result.name`), and fixes browser
1748 * bugs in the native `String.prototype.match`. Calling `XRegExp.install('natives')` uses this to
1749 * override the native method.
1750 * @private
1751 * @param {RegExp} regex Regex to search with.
1752 * @returns {Array} If `regex` uses flag g, an array of match strings or null. Without flag g, the
1753 *   result of calling `regex.exec(this)`.
1754 */
1755    fixed.match = function (regex) {
1756        if (!self.isRegExp(regex)) {
1757            regex = new RegExp(regex); // Use native `RegExp`
1758        } else if (regex.global) {
1759            var result = nativ.match.apply(this, arguments);
1760            regex.lastIndex = 0; // Fixes IE bug
1761            return result;
1762        }
1763        return fixed.exec.call(regex, this);
1764    };
1765
1766/**
1767 * Adds support for `${n}` tokens for named and numbered backreferences in replacement text, and
1768 * provides named backreferences to replacement functions as `arguments[0].name`. Also fixes
1769 * browser bugs in replacement text syntax when performing a replacement using a nonregex search
1770 * value, and the value of a replacement regex's `lastIndex` property during replacement iterations
1771 * and upon completion. Note that this doesn't support SpiderMonkey's proprietary third (`flags`)
1772 * argument. Calling `XRegExp.install('natives')` uses this to override the native method. Use via
1773 * `XRegExp.replace` without overriding natives.
1774 * @private
1775 * @param {RegExp|String} search Search pattern to be replaced.
1776 * @param {String|Function} replacement Replacement string or a function invoked to create it.
1777 * @returns {String} New string with one or all matches replaced.
1778 */
1779    fixed.replace = function (search, replacement) {
1780        var isRegex = self.isRegExp(search), captureNames, result, str, origLastIndex;
1781        if (isRegex) {
1782            if (search.xregexp) {
1783                captureNames = search.xregexp.captureNames;
1784            }
1785            if (!search.global) {
1786                origLastIndex = search.lastIndex;
1787            }
1788        } else {
1789            search += "";
1790        }
1791        if (isType(replacement, "function")) {
1792            result = nativ.replace.call(String(this), search, function () {
1793                var args = arguments, i;
1794                if (captureNames) {
1795                    // Change the `arguments[0]` string primitive to a `String` object that can store properties
1796                    args[0] = new String(args[0]);
1797                    // Store named backreferences on the first argument
1798                    for (i = 0; i < captureNames.length; ++i) {
1799                        if (captureNames[i]) {
1800                            args[0][captureNames[i]] = args[i + 1];
1801                        }
1802                    }
1803                }
1804                // Update `lastIndex` before calling `replacement`.
1805                // Fixes IE, Chrome, Firefox, Safari bug (last tested IE 9, Chrome 17, Firefox 11, Safari 5.1)
1806                if (isRegex && search.global) {
1807                    search.lastIndex = args[args.length - 2] + args[0].length;
1808                }
1809                return replacement.apply(null, args);
1810            });
1811        } else {
1812            str = String(this); // Ensure `args[args.length - 1]` will be a string when given nonstring `this`
1813            result = nativ.replace.call(str, search, function () {
1814                var args = arguments; // Keep this function's `arguments` available through closure
1815                return nativ.replace.call(String(replacement), replacementToken, function ($0, $1, $2) {
1816                    var n;
1817                    // Named or numbered backreference with curly brackets
1818                    if ($1) {
1819                        /* XRegExp behavior for `${n}`:
1820                         * 1. Backreference to numbered capture, where `n` is 1+ digits. `0`, `00`, etc. is the entire match.
1821                         * 2. Backreference to named capture `n`, if it exists and is not a number overridden by numbered capture.
1822                         * 3. Otherwise, it's an error.
1823                         */
1824                        n = +$1; // Type-convert; drop leading zeros
1825                        if (n <= args.length - 3) {
1826                            return args[n] || "";
1827                        }
1828                        n = captureNames ? lastIndexOf(captureNames, $1) : -1;
1829                        if (n < 0) {
1830                            throw new SyntaxError("backreference to undefined group " + $0);
1831                        }
1832                        return args[n + 1] || "";
1833                    }
1834                    // Else, special variable or numbered backreference (without curly brackets)
1835                    if ($2 === "$") return "$";
1836                    if ($2 === "&" || +$2 === 0) return args[0]; // $&, $0 (not followed by 1-9), $00
1837                    if ($2 === "`") return args[args.length - 1].slice(0, args[args.length - 2]);
1838                    if ($2 === "'") return args[args.length - 1].slice(args[args.length - 2] + args[0].length);
1839                    // Else, numbered backreference (without curly brackets)
1840                    $2 = +$2; // Type-convert; drop leading zero
1841                    /* XRegExp behavior:
1842                     * - Backreferences without curly brackets end after 1 or 2 digits. Use `${..}` for more digits.
1843                     * - `$1` is an error if there are no capturing groups.
1844                     * - `$10` is an error if there are less than 10 capturing groups. Use `${1}0` instead.
1845                     * - `$01` is equivalent to `$1` if a capturing group exists, otherwise it's an error.
1846                     * - `$0` (not followed by 1-9), `$00`, and `$&` are the entire match.
1847                     * Native behavior, for comparison:
1848                     * - Backreferences end after 1 or 2 digits. Cannot use backreference to capturing group 100+.
1849                     * - `$1` is a literal `$1` if there are no capturing groups.
1850                     * - `$10` is `$1` followed by a literal `0` if there are less than 10 capturing groups.
1851                     * - `$01` is equivalent to `$1` if a capturing group exists, otherwise it's a literal `$01`.
1852                     * - `$0` is a literal `$0`. `$&` is the entire match.
1853                     */
1854                    if (!isNaN($2)) {
1855                        if ($2 > args.length - 3) {
1856                            throw new SyntaxError("backreference to undefined group " + $0);
1857                        }
1858                        return args[$2] || "";
1859                    }
1860                    throw new SyntaxError("invalid token " + $0);
1861                });
1862            });
1863        }
1864        if (isRegex) {
1865            if (search.global) {
1866                search.lastIndex = 0; // Fixes IE, Safari bug (last tested IE 9, Safari 5.1)
1867            } else {
1868                search.lastIndex = origLastIndex; // Fixes IE, Opera bug (last tested IE 9, Opera 11.6)
1869            }
1870        }
1871        return result;
1872    };
1873
1874/**
1875 * Fixes browser bugs in the native `String.prototype.split`. Calling `XRegExp.install('natives')`
1876 * uses this to override the native method. Use via `XRegExp.split` without overriding natives.
1877 * @private
1878 * @param {RegExp|String} separator Regex or string to use for separating the string.
1879 * @param {Number} [limit] Maximum number of items to include in the result array.
1880 * @returns {Array} Array of substrings.
1881 */
1882    fixed.split = function (separator, limit) {
1883        if (!self.isRegExp(separator)) {
1884            return nativ.split.apply(this, arguments); // use faster native method
1885        }
1886        var str = String(this),
1887            origLastIndex = separator.lastIndex,
1888            output = [],
1889            lastLastIndex = 0,
1890            lastLength;
1891        /* Values for `limit`, per the spec:
1892         * If undefined: pow(2,32) - 1
1893         * If 0, Infinity, or NaN: 0
1894         * If positive number: limit = floor(limit); if (limit >= pow(2,32)) limit -= pow(2,32);
1895         * If negative number: pow(2,32) - floor(abs(limit))
1896         * If other: Type-convert, then use the above rules
1897         */
1898        limit = (limit === undef ? -1 : limit) >>> 0;
1899        self.forEach(str, separator, function (match) {
1900            if ((match.index + match[0].length) > lastLastIndex) { // != `if (match[0].length)`
1901                output.push(str.slice(lastLastIndex, match.index));
1902                if (match.length > 1 && match.index < str.length) {
1903                    Array.prototype.push.apply(output, match.slice(1));
1904                }
1905                lastLength = match[0].length;
1906                lastLastIndex = match.index + lastLength;
1907            }
1908        });
1909        if (lastLastIndex === str.length) {
1910            if (!nativ.test.call(separator, "") || lastLength) {
1911                output.push("");
1912            }
1913        } else {
1914            output.push(str.slice(lastLastIndex));
1915        }
1916        separator.lastIndex = origLastIndex;
1917        return output.length > limit ? output.slice(0, limit) : output;
1918    };
1919
1920/*--------------------------------------
1921 *  Built-in tokens
1922 *------------------------------------*/
1923
1924// Shortcut
1925    add = addToken.on;
1926
1927/* Letter identity escapes that natively match literal characters: \p, \P, etc.
1928 * Should be SyntaxErrors but are allowed in web reality. XRegExp makes them errors for cross-
1929 * browser consistency and to reserve their syntax, but lets them be superseded by XRegExp addons.
1930 */
1931    add(/\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4})|x(?![\dA-Fa-f]{2}))/,
1932        function (match, scope) {
1933            // \B is allowed in default scope only
1934            if (match[1] === "B" && scope === defaultScope) {
1935                return match[0];
1936            }
1937            throw new SyntaxError("invalid escape " + match[0]);
1938        },
1939        {scope: "all"});
1940
1941/* Empty character class: [] or [^]
1942 * Fixes a critical cross-browser syntax inconsistency. Unless this is standardized (per the spec),
1943 * regex syntax can't be accurately parsed because character class endings can't be determined.
1944 */
1945    add(/\[(\^?)]/,
1946        function (match) {
1947            // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S].
1948            // (?!) should work like \b\B, but is unreliable in Firefox
1949            return match[1] ? "[\\s\\S]" : "\\b\\B";
1950        });
1951
1952/* Comment pattern: (?# )
1953 * Inline comments are an alternative to the line comments allowed in free-spacing mode (flag x).
1954 */
1955    add(/(?:\(\?#[^)]*\))+/,
1956        function (match) {
1957            // Keep tokens separated unless the following token is a quantifier
1958            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
1959        });
1960
1961/* Named backreference: \k<name>
1962 * Backreference names can use the characters A-Z, a-z, 0-9, _, and $ only.
1963 */
1964    add(/\\k<([\w$]+)>/,
1965        function (match) {
1966            var index = isNaN(match[1]) ? (lastIndexOf(this.captureNames, match[1]) + 1) : +match[1],
1967                endIndex = match.index + match[0].length;
1968            if (!index || index > this.captureNames.length) {
1969                throw new SyntaxError("backreference to undefined group " + match[0]);
1970            }
1971            // Keep backreferences separate from subsequent literal numbers
1972            return "\\" + index + (
1973                endIndex === match.input.length || isNaN(match.input.charAt(endIndex)) ? "" : "(?:)"
1974            );
1975        });
1976
1977/* Whitespace and line comments, in free-spacing mode (aka extended mode, flag x) only.
1978 */
1979    add(/(?:\s+|#.*)+/,
1980        function (match) {
1981            // Keep tokens separated unless the following token is a quantifier
1982            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
1983        },
1984        {
1985            trigger: function () {
1986                return this.hasFlag("x");
1987            },
1988            customFlags: "x"
1989        });
1990
1991/* Dot, in dotall mode (aka singleline mode, flag s) only.
1992 */
1993    add(/\./,
1994        function () {
1995            return "[\\s\\S]";
1996        },
1997        {
1998            trigger: function () {
1999                return this.hasFlag("s");
2000            },
2001            customFlags: "s"
2002        });
2003
2004/* Named capturing group; match the opening delimiter only: (?<name>
2005 * Capture names can use the characters A-Z, a-z, 0-9, _, and $ only. Names can't be integers.
2006 * Supports Python-style (?P<name> as an alternate syntax to avoid issues in recent Opera (which
2007 * natively supports the Python-style syntax). Otherwise, XRegExp might treat numbered
2008 * backreferences to Python-style named capture as octals.
2009 */
2010    add(/\(\?P?<([\w$]+)>/,
2011        function (match) {
2012            if (!isNaN(match[1])) {
2013                // Avoid incorrect lookups, since named backreferences are added to match arrays
2014                throw new SyntaxError("can't use integer as capture name " + match[0]);
2015            }
2016            this.captureNames.push(match[1]);
2017            this.hasNamedCapture = true;
2018            return "(";
2019        });
2020
2021/* Numbered backreference or octal, plus any following digits: \0, \11, etc.
2022 * Octals except \0 not followed by 0-9 and backreferences to unopened capture groups throw an
2023 * error. Other matches are returned unaltered. IE <= 8 doesn't support backreferences greater than
2024 * \99 in regex syntax.
2025 */
2026    add(/\\(\d+)/,
2027        function (match, scope) {
2028            if (!(scope === defaultScope && /^[1-9]/.test(match[1]) && +match[1] <= this.captureNames.length) &&
2029                    match[1] !== "0") {
2030                throw new SyntaxError("can't use octal escape or backreference to undefined group " + match[0]);
2031            }
2032            return match[0];
2033        },
2034        {scope: "all"});
2035
2036/* Capturing group; match the opening parenthesis only.
2037 * Required for support of named capturing groups. Also adds explicit capture mode (flag n).
2038 */
2039    add(/\((?!\?)/,
2040        function () {
2041            if (this.hasFlag("n")) {
2042                return "(?:";
2043            }
2044            this.captureNames.push(null);
2045            return "(";
2046        },
2047        {customFlags: "n"});
2048
2049/*--------------------------------------
2050 *  Expose XRegExp
2051 *------------------------------------*/
2052
2053// For CommonJS enviroments
2054    if (typeof exports !== "undefined") {
2055        exports.XRegExp = self;
2056    }
2057
2058    return self;
2059
2060}());
2061
2062
2063
2064/*********
2065* ../app/js/lib/clipboard.js
2066**********/
2067/*!
2068 * clipboard.js v1.5.12
2069 * https://zenorocha.github.io/clipboard.js
2070 *
2071 * Licensed MIT © Zeno Rocha
2072 */
2073(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2074var matches = require('matches-selector')
2075
2076module.exports = function (element, selector, checkYoSelf) {
2077  var parent = checkYoSelf ? element : element.parentNode
2078
2079  while (parent && parent !== document) {
2080    if (matches(parent, selector)) return parent;
2081    parent = parent.parentNode
2082  }
2083}
2084
2085},{"matches-selector":5}],2:[function(require,module,exports){
2086var closest = require('closest');
2087
2088/**
2089 * Delegates event to a selector.
2090 *
2091 * @param {Element} element
2092 * @param {String} selector
2093 * @param {String} type
2094 * @param {Function} callback
2095 * @param {Boolean} useCapture
2096 * @return {Object}
2097 */
2098function delegate(element, selector, type, callback, useCapture) {
2099    var listenerFn = listener.apply(this, arguments);
2100
2101    element.addEventListener(type, listenerFn, useCapture);
2102
2103    return {
2104        destroy: function() {
2105            element.removeEventListener(type, listenerFn, useCapture);
2106        }
2107    }
2108}
2109
2110/**
2111 * Finds closest match and invokes callback.
2112 *
2113 * @param {Element} element
2114 * @param {String} selector
2115 * @param {String} type
2116 * @param {Function} callback
2117 * @return {Function}
2118 */
2119function listener(element, selector, type, callback) {
2120    return function(e) {
2121        e.delegateTarget = closest(e.target, selector, true);
2122
2123        if (e.delegateTarget) {
2124            callback.call(element, e);
2125        }
2126    }
2127}
2128
2129module.exports = delegate;
2130
2131},{"closest":1}],3:[function(require,module,exports){
2132/**
2133 * Check if argument is a HTML element.
2134 *
2135 * @param {Object} value
2136 * @return {Boolean}
2137 */
2138exports.node = function(value) {
2139    return value !== undefined
2140        && value instanceof HTMLElement
2141        && value.nodeType === 1;
2142};
2143
2144/**
2145 * Check if argument is a list of HTML elements.
2146 *
2147 * @param {Object} value
2148 * @return {Boolean}
2149 */
2150exports.nodeList = function(value) {
2151    var type = Object.prototype.toString.call(value);
2152
2153    return value !== undefined
2154        && (type === '[object NodeList]' || type === '[object HTMLCollection]')
2155        && ('length' in value)
2156        && (value.length === 0 || exports.node(value[0]));
2157};
2158
2159/**
2160 * Check if argument is a string.
2161 *
2162 * @param {Object} value
2163 * @return {Boolean}
2164 */
2165exports.string = function(value) {
2166    return typeof value === 'string'
2167        || value instanceof String;
2168};
2169
2170/**
2171 * Check if argument is a function.
2172 *
2173 * @param {Object} value
2174 * @return {Boolean}
2175 */
2176exports.fn = function(value) {
2177    var type = Object.prototype.toString.call(value);
2178
2179    return type === '[object Function]';
2180};
2181
2182},{}],4:[function(require,module,exports){
2183var is = require('./is');
2184var delegate = require('delegate');
2185
2186/**
2187 * Validates all params and calls the right
2188 * listener function based on its target type.
2189 *
2190 * @param {String|HTMLElement|HTMLCollection|NodeList} target
2191 * @param {String} type
2192 * @param {Function} callback
2193 * @return {Object}
2194 */
2195function listen(target, type, callback) {
2196    if (!target && !type && !callback) {
2197        throw new Error('Missing required arguments');
2198    }
2199
2200    if (!is.string(type)) {
2201        throw new TypeError('Second argument must be a String');
2202    }
2203
2204    if (!is.fn(callback)) {
2205        throw new TypeError('Third argument must be a Function');
2206    }
2207
2208    if (is.node(target)) {
2209        return listenNode(target, type,
2209 callback);
2210    }
2211    else if (is.nodeList(target)) {
2212        return listenNodeList(target, type, callback);
2213    }
2214    else if (is.string(target)) {
2215        return listenSelector(target, type, callback);
2216    }
2217    else {
2218        throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');
2219    }
2220}
2221
2222/**
2223 * Adds an event listener to a HTML element
2224 * and returns a remove listener function.
2225 *
2226 * @param {HTMLElement} node
2227 * @param {String} type
2228 * @param {Function} callback
2229 * @return {Object}
2230 */
2231function listenNode(node, type, callback) {
2232    node.addEventListener(type, callback);
2233
2234    return {
2235        destroy: function() {
2236            node.removeEventListener(type, callback);
2237        }
2238    }
2239}
2240
2241/**
2242 * Add an event listener to a list of HTML elements
2243 * and returns a remove listener function.
2244 *
2245 * @param {NodeList|HTMLCollection} nodeList
2246 * @param {String} type
2247 * @param {Function} callback
2248 * @return {Object}
2249 */
2250function listenNodeList(nodeList, type, callback) {
2251    Array.prototype.forEach.call(nodeList, function(node) {
2252        node.addEventListener(type, callback);
2253    });
2254
2255    return {
2256        destroy: function() {
2257            Array.prototype.forEach.call(nodeList, function(node) {
2258                node.removeEventListener(type, callback);
2259            });
2260        }
2261    }
2262}
2263
2264/**
2265 * Add an event listener to a selector
2266 * and returns a remove listener function.
2267 *
2268 * @param {String} selector
2269 * @param {String} type
2270 * @param {Function} callback
2271 * @return {Object}
2272 */
2273function listenSelector(selector, type, callback) {
2274    return delegate(document.body, selector, type, callback);
2275}
2276
2277module.exports = listen;
2278
2279},{"./is":3,"delegate":2}],5:[function(require,module,exports){
2280
2281/**
2282 * Element prototype.
2283 */
2284
2285var proto = Element.prototype;
2286
2287/**
2288 * Vendor function.
2289 */
2290
2291var vendor = proto.matchesSelector
2292  || proto.webkitMatchesSelector
2293  || proto.mozMatchesSelector
2294  || proto.msMatchesSelector
2295  || proto.oMatchesSelector;
2296
2297/**
2298 * Expose `match()`.
2299 */
2300
2301module.exports = match;
2302
2303/**
2304 * Match `el` to `selector`.
2305 *
2306 * @param {Element} el
2307 * @param {String} selector
2308 * @return {Boolean}
2309 * @api public
2310 */
2311
2312function match(el, selector) {
2313  if (vendor) return vendor.call(el, selector);
2314  var nodes = el.parentNode.querySelectorAll(selector);
2315  for (var i = 0; i < nodes.length; ++i) {
2316    if (nodes[i] == el) return true;
2317  }
2318  return false;
2319}
2320},{}],6:[function(require,module,exports){
2321function select(element) {
2322    var selectedText;
2323
2324    if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
2325        element.focus();
2326        element.setSelectionRange(0, element.value.length);
2327
2328        selectedText = element.value;
2329    }
2330    else {
2331        if (element.hasAttribute('contenteditable')) {
2332            element.focus();
2333        }
2334
2335        var selection = window.getSelection();
2336        var range = document.createRange();
2337
2338        range.selectNodeContents(element);
2339        selection.removeAllRanges();
2340        selection.addRange(range);
2341
2342        selectedText = selection.toString();
2343    }
2344
2345    return selectedText;
2346}
2347
2348module.exports = select;
2349
2350},{}],7:[function(require,module,exports){
2351function E () {
2352	// Keep this empty so it's easier to inherit from
2353  // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
2354}
2355
2356E.prototype = {
2357	on: function (name, callback, ctx) {
2358    var e = this.e || (this.e = {});
2359
2360    (e[name] || (e[name] = [])).push({
2361      fn: callback,
2362      ctx: ctx
2363    });
2364
2365    return this;
2366  },
2367
2368  once: function (name, callback, ctx) {
2369    var self = this;
2370    function listener () {
2371      self.off(name, listener);
2372      callback.apply(ctx, arguments);
2373    };
2374
2375    listener._ = callback
2376    return this.on(name, listener, ctx);
2377  },
2378
2379  emit: function (name) {
2380    var data = [].slice.call(arguments, 1);
2381    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
2382    var i = 0;
2383    var len = evtArr.length;
2384
2385    for (i; i < len; i++) {
2386      evtArr[i].fn.apply(evtArr[i].ctx, data);
2387    }
2388
2389    return this;
2390  },
2391
2392  off: function (name, callback) {
2393    var e = this.e || (this.e = {});
2394    var evts = e[name];
2395    var liveEvents = [];
2396
2397    if (evts && callback) {
2398      for (var i = 0, len = evts.length; i < len; i++) {
2399        if (evts[i].fn !== callback && evts[i].fn._ !== callback)
2400          liveEvents.push(evts[i]);
2401      }
2402    }
2403
2404    // Remove event from queue to prevent memory leak
2405    // Suggested by https://github.com/lazd
2406    // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
2407
2408    (liveEvents.length)
2409      ? e[name] = liveEvents
2410      : delete e[name];
2411
2412    return this;
2413  }
2414};
2415
2416module.exports = E;
2417
2418},{}],8:[function(require,module,exports){
2419(function (global, factory) {
2420    if (typeof define === "function" && define.amd) {
2421        define(['module', 'select'], factory);
2422    } else if (typeof exports !== "undefined") {
2423        factory(module, require('select'));
2424    } else {
2425        var mod = {
2426            exports: {}
2427        };
2428        factory(mod, global.select);
2429        global.clipboardAction = mod.exports;
2430    }
2431})(this, function (module, _select) {
2432    'use strict';
2433
2434    var _select2 = _interopRequireDefault(_select);
2435
2436    function _interopRequireDefault(obj) {
2437        return obj && obj.__esModule ? obj : {
2438            default: obj
2439        };
2440    }
2441
2442    var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
2443        return typeof obj;
2444    } : function (obj) {
2445        return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj;
2446    };
2447
2448    function _classCallCheck(instance, Constructor) {
2449        if (!(instance instanceof Constructor)) {
2450            throw new TypeError("Cannot call a class as a function");
2451        }
2452    }
2453
2454    var _createClass = function () {
2455        function defineProperties(target, props) {
2456            for (var i = 0; i < props.length; i++) {
2457                var descriptor = props[i];
2458                descriptor.enumerable = descriptor.enumerable || false;
2459                descriptor.configurable = true;
2460                if ("value" in descriptor) descriptor.writable = true;
2461                Object.defineProperty(target, descriptor.key, descriptor);
2462            }
2463        }
2464
2465        return function (Constructor, protoProps, staticProps) {
2466            if (protoProps) defineProperties(Constructor.prototype, protoProps);
2467            if (staticProps) defineProperties(Constructor, staticProps);
2468            return Constructor;
2469        };
2470    }();
2471
2472    var ClipboardAction = function () {
2473        /**
2474         * @param {Object} options
2475         */
2476
2477        function ClipboardAction(options) {
2478            _classCallCheck(this, ClipboardAction);
2479
2480            this.resolveOptions(options);
2481            this.initSelection();
2482        }
2483
2484        /**
2485         * Defines base properties passed from constructor.
2486         * @param {Object} options
2487         */
2488
2489
2490        ClipboardAction.prototype.resolveOptions = function resolveOptions() {
2491            var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
2492
2493            this.action = options.action;
2494            this.emitter = options.emitter;
2495            this.target = options.target;
2496            this.text = options.text;
2497            this.trigger = options.trigger;
2498
2499            this.selectedText = '';
2500        };
2501
2502        ClipboardAction.prototype.initSelection = function initSelection() {
2503            if (this.text) {
2504                this.selectFake();
2505            } else if (this.target) {
2506                this.selectTarget();
2507            }
2508        };
2509
2510        ClipboardAction.prototype.selectFake = function selectFake() {
2511            var _this = this;
2512
2513            var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
2514
2515            this.removeFake();
2516
2517            this.fakeHandlerCallback = function () {
2518                return _this.removeFake();
2519            };
2520            this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || true;
2521
2522            this.fakeElem = document.createElement('textarea');
2523            // Prevent zooming on iOS
2524            this.fakeElem.style.fontSize = '12pt';
2525            // Reset box model
2526            this.fakeElem.style.border = '0';
2527            this.fakeElem.style.padding = '0';
2528            this.fakeElem.style.margin = '0';
2529            // Move element out of screen horizontally
2530            this.fakeElem.style.position = 'absolute';
2531            this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
2532            // Move element to the same position vertically
2533            this.fakeElem.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
2534            this.fakeElem.setAttribute('readonly', '');
2535            this.fakeElem.value = this.text;
2536
2537            document.body.appendChild(this.fakeElem);
2538
2539            this.selectedText = (0, _select2.default)(this.fakeElem);
2540            this.copyText();
2541        };
2542
2543        ClipboardAction.prototype.removeFake = function removeFake() {
2544            if (this.fakeHandler) {
2545                document.body.removeEventListener('click', this.fakeHandlerCallback);
2546                this.fakeHandler = null;
2547                this.fakeHandlerCallback = null;
2548            }
2549
2550            if (this.fakeElem) {
2551                document.body.removeChild(this.fakeElem);
2552                this.fakeElem = null;
2553            }
2554        };
2555
2556        ClipboardAction.prototype.selectTarget = function selectTarget() {
2557            this.selectedText = (0, _select2.default)(this.target);
2558            this.copyText();
2559        };
2560
2561        ClipboardAction.prototype.copyText = function copyText() {
2562            var succeeded = undefined;
2563
2564            try {
2565                succeeded = document.execCommand(this.action);
2566            } catch (err) {
2567                succeeded = false;
2568            }
2569
2570            this.handleResult(succeeded);
2571        };
2572
2573        ClipboardAction.prototype.handleResult = function handleResult(succeeded) {
2574            if (succeeded) {
2575                this.emitter.emit('success', {
2576                    action: this.action,
2577                    text: this.selectedText,
2578                    trigger: this.trigger,
2579                    clearSelection: this.clearSelection.bind(this)
2580                });
2581            } else {
2582                this.emitter.emit('error', {
2583                    action: this.action,
2584                    trigger: this.trigger,
2585                    clearSelection: this.clearSelection.bind(this)
2586                });
2587            }
2588        };
2589
2590        ClipboardAction.prototype.clearSelection = function clearSelection() {
2591            if (this.target) {
2592                this.target.blur();
2593            }
2594
2595            window.getSelection().removeAllRanges();
2596        };
2597
2598        ClipboardAction.prototype.destroy = function destroy() {
2599            this.removeFake();
2600        };
2601
2602        _createClass(ClipboardAction, [{
2603            key: 'action',
2604            set: function set() {
2605                var action = arguments.length <= 0 || arguments[0] === undefined ? 'copy' : arguments[0];
2606
2607                this._action = action;
2608
2609                if (this._action !== 'copy' && this._action !== 'cut') {
2610                    throw new Error('Invalid "action" value, use either "copy" or "cut"');
2611                }
2612            },
2613            get: function get() {
2614                return this._action;
2615            }
2616        }, {
2617            key: 'target',
2618            set: function set(target) {
2619                if (target !== undefined) {
2620                    if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
2621                        if (this.action === 'copy' && target.hasAttribute('disabled')) {
2622                            throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
2623                        }
2624
2625                        if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
2626                            throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
2627                        }
2628
2629                        this._target = target;
2630                    } else {
2631                        throw new Error('Invalid "target" value, use a valid Element');
2632                    }
2633                }
2634            },
2635            get: function get() {
2636                return this._target;
2637            }
2638        }]);
2639
2640        return ClipboardAction;
2641    }();
2642
2643    module.exports = ClipboardAction;
2644});
2645
2646},{"select":6}],9:[function(require,module,exports){
2647(function (global, factory) {
2648    if (typeof define === "function" && define.amd) {
2649        define(['module', './clipboard-action', 'tiny-emitter', 'good-l
2649istener'], factory);
2650    } else if (typeof exports !== "undefined") {
2651        factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener'));
2652    } else {
2653        var mod = {
2654            exports: {}
2655        };
2656        factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener);
2657        global.clipboard = mod.exports;
2658    }
2659})(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) {
2660    'use strict';
2661
2662    var _clipboardAction2 = _interopRequireDefault(_clipboardAction);
2663
2664    var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);
2665
2666    var _goodListener2 = _interopRequireDefault(_goodListener);
2667
2668    function _interopRequireDefault(obj) {
2669        return obj && obj.__esModule ? obj : {
2670            default: obj
2671        };
2672    }
2673
2674    function _classCallCheck(instance, Constructor) {
2675        if (!(instance instanceof Constructor)) {
2676            throw new TypeError("Cannot call a class as a function");
2677        }
2678    }
2679
2680    function _possibleConstructorReturn(self, call) {
2681        if (!self) {
2682            throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
2683        }
2684
2685        return call && (typeof call === "object" || typeof call === "function") ? call : self;
2686    }
2687
2688    function _inherits(subClass, superClass) {
2689        if (typeof superClass !== "function" && superClass !== null) {
2690            throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
2691        }
2692
2693        subClass.prototype = Object.create(superClass && superClass.prototype, {
2694            constructor: {
2695                value: subClass,
2696                enumerable: false,
2697                writable: true,
2698                configurable: true
2699            }
2700        });
2701        if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
2702    }
2703
2704    var Clipboard = function (_Emitter) {
2705        _inherits(Clipboard, _Emitter);
2706
2707        /**
2708         * @param {String|HTMLElement|HTMLCollection|NodeList} trigger
2709         * @param {Object} options
2710         */
2711
2712        function Clipboard(trigger, options) {
2713            _classCallCheck(this, Clipboard);
2714
2715            var _this = _possibleConstructorReturn(this, _Emitter.call(this));
2716
2717            _this.resolveOptions(options);
2718            _this.listenClick(trigger);
2719            return _this;
2720        }
2721
2722        /**
2723         * Defines if attributes would be resolved using internal setter functions
2724         * or custom functions that were passed in the constructor.
2725         * @param {Object} options
2726         */
2727
2728
2729        Clipboard.prototype.resolveOptions = function resolveOptions() {
2730            var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
2731
2732            this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
2733            this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
2734            this.text = typeof options.text === 'function' ? options.text : this.defaultText;
2735        };
2736
2737        Clipboard.prototype.listenClick = function listenClick(trigger) {
2738            var _this2 = this;
2739
2740            this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) {
2741                return _this2.onClick(e);
2742            });
2743        };
2744
2745        Clipboard.prototype.onClick = function onClick(e) {
2746            var trigger = e.delegateTarget || e.currentTarget;
2747
2748            if (this.clipboardAction) {
2749                this.clipboardAction = null;
2750            }
2751
2752            this.clipboardAction = new _clipboardAction2.default({
2753                action: this.action(trigger),
2754                target: this.target(trigger),
2755                text: this.text(trigger),
2756                trigger: trigger,
2757                emitter: this
2758            });
2759        };
2760
2761        Clipboard.prototype.defaultAction = function defaultAction(trigger) {
2762            return getAttributeValue('action', trigger);
2763        };
2764
2765        Clipboard.prototype.defaultTarget = function defaultTarget(trigger) {
2766            var selector = getAttributeValue('target', trigger);
2767
2768            if (selector) {
2769                return document.querySelector(selector);
2770            }
2771        };
2772
2773        Clipboard.prototype.defaultText = function defaultText(trigger) {
2774            return getAttributeValue('text', trigger);
2775        };
2776
2777        Clipboard.prototype.destroy = function destroy() {
2778            this.listener.destroy();
2779
2780            if (this.clipboardAction) {
2781                this.clipboardAction.destroy();
2782                this.clipboardAction = null;
2783            }
2784        };
2785
2786        return Clipboard;
2787    }(_tinyEmitter2.default);
2788
2789    /**
2790     * Helper function to retrieve attribute value.
2791     * @param {String} suffix
2792     * @param {Element} element
2793     */
2794    function getAttributeValue(suffix, element) {
2795        var attribute = 'data-clipboard-' + suffix;
2796
2797        if (!element.hasAttribute(attribute)) {
2798            return;
2799        }
2800
2801        return element.getAttribute(attribute);
2802    }
2803
2804    module.exports = Clipboard;
2805});
2806
2807},{"./clipboard-action":8,"good-listener":4,"tiny-emitter":7}]},{},[9])(9)
2808});
2809
2810
2811/*********
2812* ../app/js/lib/i18next-1.7.2.js
2813**********/
2814// i18next, v1.7.2
2815// Copyright (c)2014 Jan Mühlemann (jamuhl).
2816// Distributed under MIT license
2817// http://i18next.com
2818(function() {
2819
2820    // add indexOf to non ECMA-262 standard compliant browsers
2821    if (!Array.prototype.indexOf) {
2822        Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
2823            "use strict";
2824            if (this == null) {
2825                throw new TypeError();
2826            }
2827            var t = Object(this);
2828            var len = t.length >>> 0;
2829            if (len === 0) {
2830                return -1;
2831            }
2832            var n = 0;
2833            if (arguments.length > 0) {
2834                n = Number(arguments[1]);
2835                if (n != n) { // shortcut for verifying if it's NaN
2836                    n = 0;
2837                } else if (n != 0 && n != Infinity && n != -Infinity) {
2838                    n = (n > 0 || -1) * Math.floor(Math.abs(n));
2839                }
2840            }
2841            if (n >= len) {
2842                return -1;
2843            }
2844            var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
2845            for (; k < len; k++) {
2846                if (k in t && t[k] === searchElement) {
2847                    return k;
2848                }
2849            }
2850            return -1;
2851        }
2852    }
2853
2854    // add lastIndexOf to non ECMA-262 standard compliant browsers
2855    if (!Array.prototype.lastIndexOf) {
2856        Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) {
2857            "use strict";
2858            if (this == null) {
2859                throw new TypeError();
2860            }
2861            var t = Object(this);
2862            var len = t.length >>> 0;
2863            if (len === 0) {
2864                return -1;
2865            }
2866            var n = len;
2867            if (arguments.length > 1) {
2868                n = Number(arguments[1]);
2869                if (n != n) {
2870                    n = 0;
2871                } else if (n != 0 && n != (1 / 0) && n != -(1 / 0)) {
2872                    n = (n > 0 || -1) * Math.floor(Math.abs(n));
2873                }
2874            }
2875            var k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n);
2876            for (; k >= 0; k--) {
2877                if (k in t && t[k] === searchElement) {
2878                    return k;
2879                }
2880            }
2881            return -1;
2882        };
2883    }
2884
2885    var root = this
2886      , $ = root.jQuery || root.Zepto
2887      , i18n = {}
2888      , resStore = {}
2889      , currentLng
2890      , replacementCounter = 0
2891      , languages = []
2892      , initialized = false;
2893
2894
2895    // Export the i18next object for **CommonJS**.
2896    // If we're not in CommonJS, add `i18n` to the
2897    // global object or to jquery.
2898    if (typeof module !== 'undefined' && module.exports) {
2899        module.exports = i18n;
2900    } else {
2901        if ($) {
2902            $.i18n = $.i18n || i18n;
2903        }
2904
2905        root.i18n = root.i18n || i18n;
2906    }
2907    // defaults
2908    var o = {
2909        lng: undefined,
2910        load: 'all',
2911        preload: [],
2912        lowerCaseLng: false,
2913        returnObjectTrees: false,
2914        fallbackLng: 'dev',
2915        fallbackNS: [],
2916        detectLngQS: 'setLng',
2917        ns: 'translation',
2918        fallbackOnNull: true,
2919        fallbackOnEmpty: false,
2920        fallbackToDefaultNS: false,
2921        nsseparator: ':',
2922        keyseparator: '.',
2923        selectorAttr: 'data-i18n',
2924        debug: false,
2925
2926        resGetPath: 'locales/__lng__/__ns__.json',
2927        resPostPath: 'locales/add/__lng__/__ns__',
2928
2929        getAsync: true,
2930        postAsync: true,
2931
2932        resStore: undefined,
2933        useLocalStorage: false,
2934        localStorageExpirationTime: 7*24*60*60*1000,
2935
2936        dynamicLoad: false,
2937        sendMissing: false,
2938        sendMissingTo: 'fallback', // current | all
2939        sendType: 'POST',
2940
2941        interpolationPrefix: '__',
2942        interpolationSuffix: '__',
2943        reusePrefix: '$t(',
2944        reuseSuffix: ')',
2945        pluralSuffix: '_plural',
2946        pluralNotFound: ['plural_not_found', Math.random()].join(''),
2947        contextNotFound: ['context_not_found', Math.random()].join(''),
2948        escapeInterpolation: false,
2949
2950        setJqueryExt: true,
2951        defaultValueFromContent: true,
2952        useDataAttrOptions: false,
2953        cookieExpirationTime: undefined,
2954        useCookie: true,
2955        cookieName: 'i18next',
2956        cookieDomain: undefined,
2957
2958        objectTreeKeyHandler: undefined,
2959        postProcess: undefined,
2960        parseMissingKey: undefined,
2961
2962        shortcutFunction: 'sprintf' // or: defaultValue
2963    };
2964    function _extend(target, source) {
2965        if (!source || typeof source === 'function') {
2966            return target;
2967        }
2968
2969        for (var attr in source) { target[attr] = source[attr]; }
2970        return target;
2971    }
2972
2973    function _each(object, callback, args) {
2974        var name, i = 0,
2975            length = object.length,
2976            isObj = length === undefined || typeof object === "function";
2977
2978        if (args) {
2979            if (isObj) {
2980                for (name in object) {
2981                    if (callback.apply(object[name], args) === false) {
2982                        break;
2983                    }
2984                }
2985            } else {
2986                for ( ; i < length; ) {
2987                    if (callback.apply(object[i++], args) === false) {
2988                        break;
2989                    }
2990                }
2991            }
2992
2993        // A special, fast, case for the most common use of each
2994        } else {
2995            if (isObj) {
2996                for (name in object) {
2997                    if (callback.call(object[name], name, object[name]) === false) {
2998                        break;
2999                    }
3000                }
3001            } else {
3002                for ( ; i < length; ) {
3003                    if (callback.call(object[i], i, object[i++]) === false) {
3004                        break;
3005                    }
3006                }
3007            }
3008        }
3009
3010        return object;
3011    }
3012
3013    var _entityMap = {
3014        "&": "&amp;",
3015        "<": "&lt;",
3016        ">": "&gt;",
3017        '"': '&quot;',
3018        "'": '&#39;',
3019        "/": '&#x2F;'
3020    };
3021
3022    function _escape(data) {
3023        if (typeof data === 'string') {
3024            return data.replace(/[&<>
3024"'\/]/g, function (s) {
3025                return _entityMap[s];
3026            });
3027        }else{
3028            return data;
3029        }
3030    }
3031
3032    function _ajax(options) {
3033
3034        // v0.5.0 of https://github.com/goloroden/http.js
3035        var getXhr = function (callback) {
3036            // Use the native XHR object if the browser supports it.
3037            if (window.XMLHttpRequest) {
3038                return callback(null, new XMLHttpRequest());
3039            } else if (window.ActiveXObject) {
3040                // In Internet Explorer check for ActiveX versions of the XHR object.
3041                try {
3042                    return callback(null, new ActiveXObject("Msxml2.XMLHTTP"));
3043                } catch (e) {
3044                    return callback(null, new ActiveXObject("Microsoft.XMLHTTP"));
3045                }
3046            }
3047
3048            // If no XHR support was found, throw an error.
3049            return callback(new Error());
3050        };
3051
3052        var encodeUsingUrlEncoding = function (data) {
3053            if(typeof data === 'string') {
3054                return data;
3055            }
3056
3057            var result = [];
3058            for(var dataItem in data) {
3059                if(data.hasOwnProperty(dataItem)) {
3060                    result.push(encodeURIComponent(dataItem) + '=' + encodeURIComponent(data[dataItem]));
3061                }
3062            }
3063
3064            return result.join('&');
3065        };
3066
3067        var utf8 = function (text) {
3068            text = text.replace(/\r\n/g, '\n');
3069            var result = '';
3070
3071            for(var i = 0; i < text.length; i++) {
3072                var c = text.charCodeAt(i);
3073
3074                if(c < 128) {
3075                        result += String.fromCharCode(c);
3076                } else if((c > 127) && (c < 2048)) {
3077                        result += String.fromCharCode((c >> 6) | 192);
3078                        result += String.fromCharCode((c & 63) | 128);
3079                } else {
3080                        result += String.fromCharCode((c >> 12) | 224);
3081                        result += String.fromCharCode(((c >> 6) & 63) | 128);
3082                        result += String.fromCharCode((c & 63) | 128);
3083                }
3084            }
3085
3086            return result;
3087        };
3088
3089        var base64 = function (text) {
3090            var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
3091
3092            text = utf8(text);
3093            var result = '',
3094                    chr1, chr2, chr3,
3095                    enc1, enc2, enc3, enc4,
3096                    i = 0;
3097
3098            do {
3099                chr1 = text.charCodeAt(i++);
3100                chr2 = text.charCodeAt(i++);
3101                chr3 = text.charCodeAt(i++);
3102
3103                enc1 = chr1 >> 2;
3104                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
3105                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
3106                enc4 = chr3 & 63;
3107
3108                if(isNaN(chr2)) {
3109                    enc3 = enc4 = 64;
3110                } else if(isNaN(chr3)) {
3111                    enc4 = 64;
3112                }
3113
3114                result +=
3115                    keyStr.charAt(enc1) +
3116                    keyStr.charAt(enc2) +
3117                    keyStr.charAt(enc3) +
3118                    keyStr.charAt(enc4);
3119                chr1 = chr2 = chr3 = '';
3120                enc1 = enc2 = enc3 = enc4 = '';
3121            } while(i < text.length);
3122
3123            return result;
3124        };
3125
3126        var mergeHeaders = function () {
3127            // Use the first header object as base.
3128            var result = arguments[0];
3129
3130            // Iterate through the remaining header objects and add them.
3131            for(var i = 1; i < arguments.length; i++) {
3132                var currentHeaders = arguments[i];
3133                for(var header in currentHeaders) {
3134                    if(currentHeaders.hasOwnProperty(header)) {
3135                        result[header] = currentHeaders[header];
3136                    }
3137                }
3138            }
3139
3140            // Return the merged headers.
3141            return result;
3142        };
3143
3144        var ajax = function (method, url, options, callback) {
3145            // Adjust parameters.
3146            if(typeof options === 'function') {
3147                callback = options;
3148                options = {};
3149            }
3150
3151            // Set default parameter values.
3152            options.cache = options.cache || false;
3153            options.data = options.data || {};
3154            options.headers = options.headers || {};
3155            options.jsonp = options.jsonp || false;
3156            options.async = options.async === undefined ? true : options.async;
3157
3158            // Merge the various header objects.
3159            var headers = mergeHeaders({
3160                'accept': '*/*',
3161                'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
3162            }, ajax.headers, options.headers);
3163
3164            // Encode the data according to the content-type.
3165            var payload;
3166            if (headers['content-type'] === 'application/json') {
3167                payload = JSON.stringify(options.data);
3168            } else {
3169                payload = encodeUsingUrlEncoding(options.data);
3170            }
3171
3172            // Specially prepare GET requests: Setup the query string, handle caching and make a JSONP call
3173            // if neccessary.
3174            if(method === 'GET') {
3175                // Setup the query string.
3176                var queryString = [];
3177                if(payload) {
3178                    queryString.push(payload);
3179                    payload = null;
3180                }
3181
3182                // Handle caching.
3183                if(!options.cache) {
3184                    queryString.push('_=' + (new Date()).getTime());
3185                }
3186
3187                // If neccessary prepare the query string for a JSONP call.
3188                if(options.jsonp) {
3189                    queryString.push('callback=' + options.jsonp);
3190                    queryString.push('jsonp=' + options.jsonp);
3191                }
3192
3193                // Merge the query string and attach it to the url.
3194                queryString = queryString.join('&');
3195                if (queryString.length > 1) {
3196                    if (url.indexOf('?') > -1) {
3197                        url += '&' + queryString;
3198                    } else {
3199                        url += '?' + queryString;
3200                    }
3201                }
3202
3203                // Make a JSONP call if neccessary.
3204                if(options.jsonp) {
3205                    var head = document.getElementsByTagName('head')[0];
3206                    var script = document.createElement('script');
3207                    script.type = 'text/javascript';
3208                    script.src = url;
3209                    head.appendChild(script);
3210                    return;
3211                }
3212            }
3213
3214            // Since we got here, it is no JSONP request, so make a normal XHR request.
3215            getXhr(function (err, xhr) {
3216                if(err) return callback(err);
3217
3218                // Open the request.
3219                xhr.open(method, url, options.async);
3220
3221                // Set the request headers.
3222                for(var header in headers) {
3223                    if(headers.hasOwnProperty(header)) {
3224                        xhr.setRequestHeader(header, headers[header]);
3225                    }
3226                }
3227
3228                // Handle the request events.
3229                xhr.onreadystatechange = function () {
3230                    if(xhr.readyState === 4) {
3231                        var data = xhr.responseText || '';
3232
3233                        // If no callback is given, return.
3234                        if(!callback) {
3235                            return;
3236                        }
3237
3238                        // Return an object that provides access to the data as text and JSON.
3239                        callback(xhr.status, {
3240                            text: function () {
3241                                return data;
3242                            },
3243
3244                            json: function () {
3245                                return JSON.parse(data);
3246                            }
3247                        });
3248                    }
3249                };
3250
3251                // Actually send the XHR request.
3252                xhr.send(payload);
3253            });
3254        };
3255
3256        // Define the external interface.
3257        var http = {
3258            authBasic: function (username, password) {
3259                ajax.headers['Authorization'] = 'Basic ' + base64(username + ':' + password);
3260            },
3261
3262            connect: function (url, options, callback) {
3263                return ajax('CONNECT', url, options, callback);
3264            },
3265
3266            del: function (url, options, callback) {
3267                return ajax('DELETE', url, options, callback);
3268            },
3269
3270            get: function (url, options, callback) {
3271                return ajax('GET', url, options, callback);
3272            },
3273
3274            head: function (url, options, callback) {
3275                return ajax('HEAD', url, options, callback);
3276            },
3277
3278            headers: function (headers) {
3279                ajax.headers = headers || {};
3280            },
3281
3282            isAllowed: function (url, verb, callback) {
3283                this.options(url, function (status, data) {
3284                    callback(data.text().indexOf(verb) !== -1);
3285                });
3286            },
3287
3288            options: function (url, options, callback) {
3289                return ajax('OPTIONS', url, options, callback);
3290            },
3291
3292            patch: function (url, options, callback) {
3293                return ajax('PATCH', url, options, callback);
3294            },
3295
3296            post: function (url, options, callback) {
3297                return ajax('POST', url, options, callback);
3298            },
3299
3300            put: function (url, options, callback) {
3301                return ajax('PUT', url, options, callback);
3302            },
3303
3304            trace: function (url, options, callback) {
3305                return ajax('TRACE', url, options, callback);
3306            }
3307        };
3308
3309
3310        var methode = options.type ? options.type.toLowerCase() : 'get';
3311
3312        http[methode](options.url, options, function (status, data) {
3313            if (status === 200) {
3314                options.success(data.json(), status, null);
3315            } else {
3316                options.error(data.text(), status, null);
3317            }
3318        });
3319    }
3320
3321    var _cookie = {
3322        create: function(name,value,minutes,domain) {
3323            var expires;
3324            if (minutes) {
3325                var date = new Date();
3326                date.setTime(date.getTime()+(minutes*60*1000));
3327                expires = "; expires="+date.toGMTString();
3328            }
3329            else expires = "";
3330            domain = (domain)? "domain="+domain+";" : "";
3331            document.cookie = name+"="+value+expires+";"+domain+"path=/";
3332        },
3333
3334        read: function(name) {
3335            var nameEQ = name + "=";
3336            var ca = document.cookie.split(';');
3337            for(var i=0;i < ca.length;i++) {
3338                var c = ca[i];
3339                while (c.charAt(0)==' ') c = c.substring(1,c.length);
3340                if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
3341            }
3342            return null;
3343        },
3344
3345        remove: function(name) {
3346            this.create(name,"",-1);
3347        }
3348    };
3349
3350    var cookie_noop = {
3351        create: function(name,value,minutes,domain) {},
3352        read: function(name) { return null; },
3353        remove: function(name) {}
3354    };
3355
3356
3357
3358    // move dependent functions to a container so that
3359    // they can be overriden easier in no jquery environment (node.js)
3360    var f = {
3361        extend: $ ? $.extend : _extend,
3362        each: $ ? $.each : _each,
3363        ajax: $ ? $.ajax : (typeof document !== 'undefined' ? _ajax : function() {}),
3364        cookie: typeof document !== 'undefined' ? _cookie : cookie_noop,
3365        detectLanguage: detectLanguage,
3366        escape: _escape,
3367        log: function(str) {
3368            if (o.debug && typeof console !== "undefined") console.log(str);
3369        },
3370        toLanguages: function(lng) {
3371            var languages = [];
3372            if (typeof lng === 'string' && lng.indexOf('-') > -1) {
3373                var parts = lng.split('-');
3374
3375                lng = o.lowerCaseLng ?
3376                    parts[0].toLowerCase() +  '-' + parts[1].toLowerCase() :
3377                    parts[0].toLowerCase() +  '-' + parts[1].toUpperCase();
3378
3379                if (o.load !== 'unspecific') languages.push(lng);
3380                if (o.load !== 'current') languages.push(parts[0]);
3381            } else {
3382                languages.push(lng);
3383            }
3384
3385            if (languages.indexOf(o.fallbackLng) === -1 && o.fallbackLng) languages.push(o.fallbackLng);
3386
3387            return languages;
3388        },
3389        regexEscape: function(str) {
3390            return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
3391        }
3392    };
3393    function init(options, cb) {
3394
3395        if (typeof options === 'function') {
3396            cb = options;
3397            options = {};
3398        }
3399        options = options || {};
3400
3401        // override defaults with passed in options
3402        f.extend(o, options);
3403        delete o.fixLng; /* passed in each time */
3404
3405        // create namespace object if namespace is passed in as string
3406        if (typeof o.ns == 'string') {
3407            o.ns = { namespaces: [o.ns], defaultNs: o.ns};
3408        }
3409
3410        // fallback namespaces
3411        if (typeof o.fallbackNS == 'string') {
3412            o.fallbackNS = [o.fallbackNS];
3413        }
3414
3415        // escape prefix/suffix
3416        o.interpolationPrefixEscaped = f.regexEscape(o.interpolationPrefix);
3417        o.interpolationSuffixEscaped = f.regexEscape(o.interpolationSuffix);
3418
3419        if (!o.lng) o.lng = f.detectLanguage();
3420        if (o.lng) {
3421            // set cookie with lng set (as detectLanguage will set cookie on need)
3422            if (o.useCookie) f.cookie.create(o.cookieName, o.lng, o.cookieExpirationTime, o.cookieDomain);
3423        } else {
3424            o.lng =  o.fallbackLng;
3425            if (o.useCookie) f.cookie.remove(o.cookieName);
3426        }
3427
3428        languages = f.toLanguages(o.lng);
3429        currentLng = languages[0];
3430        f.log('currentLng set to: ' + currentLng);
3431
3432        var lngTranslate = translate;
3433        if (options.fixLng) {
3434            lngTranslate = function(key, options) {
3435                options = options || {};
3436                options.lng = options.lng || lngTranslate.lng;
3437                return translate(key, options);
3438            };
3439            lngTranslate.lng = currentLng;
3440        }
3441
3442        pluralExtensions.setCurrentLng(currentLng);
3443
3444        // add JQuery extensions
3445        if ($ && o.setJqueryExt) addJqueryFunct();
3446
3447        // jQuery deferred
3448        var deferred;
3449        if ($ && $.Deferred) {
3450            deferred = $.Deferred();
3451        }
3452
3453        // return immidiatly if res are passed in
3454        if (o.resStore) {
3455            resStore = o.resStore;
3456            initialized = true;
3457            if (cb) cb(lngTranslate);
3458            if (deferred) deferred.resolve(lngTranslate);
3459            if (deferred) return deferred.promise();
3460            return;
3461        }
3462
3463        // languages to load
3464        var lngsToLoad = f.toLanguages(o.lng);
3465        if (typeof o.preload === 'string') o.preload = [o.preload];
3466        for (var i = 0, l = o.preload.length; i < l; i++) {
3467            var pres = f.toLanguages(o.preload[i]);
3468            for (var y = 0, len = pres.length; y < len; y++) {
3469                if (lngsToLoad.indexOf(pres[y]) < 0) {
3470                    lngsToLoad.push(pres[y]);
3471                }
3472            }
3473        }
3474
3475        // else load them
3476        i18n.sync.load(lngsToLoad, o, function(err, store) {
3477            resStore = store;
3478            initialized = true;
3479
3480            if (cb) cb(lngTranslate);
3481            if (deferred) deferred.resolve(lngTranslate);
3482        });
3483
3484        if (deferred) return deferred.promise();
3485    }
3486    function preload(lngs, cb) {
3487        if (typeof lngs === 'string') lngs = [lngs];
3488        for (var i = 0, l = lngs.length; i < l; i++) {
3489            if (o.preload.indexOf(lngs[i]) < 0) {
3490                o.preload.push(lngs[i]);
3491            }
3492        }
3493        return init(cb);
3494    }
3495
3496    function addResourceBundle(lng, ns, resources) {
3497        if (typeof ns !== 'string') {
3498            resources = ns;
3499            ns = o.ns.defaultNs;
3500        } else if (o.ns.namespaces.indexOf(ns) < 0) {
3501            o.ns.namespaces.push(ns);
3502        }
3503
3504        resStore[lng] = resStore[lng] || {};
3505        resStore[lng][ns] = resStore[lng][ns] || {};
3506
3507        f.extend(resStore[lng][ns], resources);
3508    }
3509
3510    function removeResourceBundle(lng, ns) {
3511        if (typeof ns !== 'string') {
3512            ns = o.ns.defaultNs;
3513        }
3514
3515        resStore[lng] = resStore[lng] || {};
3516        resStore[lng][ns] = {};
3517    }
3518
3519    function setDefaultNamespace(ns) {
3520        o.ns.defaultNs = ns;
3521    }
3522
3523    function loadNamespace(namespace, cb) {
3524        loadNamespaces([namespace], cb);
3525    }
3526
3527    function loadNamespaces(namespaces, cb) {
3528        var opts = {
3529            dynamicLoad: o.dynamicLoad,
3530            resGetPath: o.resGetPath,
3531            getAsync: o.getAsync,
3532            customLoad: o.customLoad,
3533            ns: { namespaces: namespaces, defaultNs: ''} /* new namespaces to load */
3534        };
3535
3536        // languages to load
3537        var lngsToLoad = f.toLanguages(o.lng);
3538        if (typeof o.preload === 'string') o.preload = [o.preload];
3539        for (var i = 0, l = o.preload.length; i < l; i++) {
3540            var pres = f.toLanguages(o.preload[i]);
3541            for (var y = 0, len = pres.length; y < len; y++) {
3542                if (lngsToLoad.indexOf(pres[y]) < 0) {
3543                    lngsToLoad.push(pres[y]);
3544                }
3545            }
3546        }
3547
3548        // check if we have to load
3549        var lngNeedLoad = [];
3550        for (var a = 0, lenA = lngsToLoad.length; a < lenA; a++) {
3551            var needLoad = false;
3552            var resSet = resStore[lngsToLoad[a]];
3553            if (resSet) {
3554                for (var b = 0, lenB = namespaces.length; b < lenB; b++) {
3555                    if (!resSet[namespaces[b]]) needLoad = true;
3556                }
3557            } else {
3558                needLoad = true;
3559            }
3560
3561            if (needLoad) lngNeedLoad.push(lngsToLoad[a]);
3562        }
3563
3564        if (lngNeedLoad.length) {
3565            i18n.sync._fetch(lngNeedLoad, opts, function(err, store) {
3566                var todo = namespaces.length * lngNeedLoad.length;
3567
3568                // load each file individual
3569                f.each(namespaces, function(nsIndex, nsValue) {
3570
3571                    // append namespace to namespace array
3572                    if (o.ns.namespaces.indexOf(nsValue) < 0) {
3573                        o.ns.namespaces.push(nsValue);
3574                    }
3575
3576                    f.each(lngNeedLoad, function(lngIndex, lngValue) {
3577                        resStore[lngValue] = resStore[lngValue] || {};
3578                        resStore[lngValue][nsValue] = store[lngValue][nsValue];
3579
3580                        todo--; // wait for all done befor callback
3581                        if (todo === 0 && cb) {
3582                            if (o.useLocalStorage) i18n.sync._storeLocal(resStore);
3583                            cb();
3584                        }
3585                    });
3586                });
3587            });
3588        } else {
3589            if (cb) cb();
3590        }
3591    }
3592
3593    function setLng(lng, options, cb) {
3594        if (typeof options === 'function') {
3595            cb = options;
3596            options = {};
3597        } else if (!options) {
3598            options = {};
3599        }
3600
3601        options.lng = lng;
3602        return init(options, cb);
3603    }
3604
3605    function lng() {
3606        return currentLng;
3607    }
3608    function addJqueryFunct() {
3609        // $.t shortcut
3610        $.t = $.t || translate;
3611
3612        function parse(ele, key, options) {
3613            if (key.length === 0) return;
3614
3615            var attr = 'text';
3616
3617            if (key.indexOf('[') === 0) {
3618                var parts = key.split(']');
3619                key = parts[1];
3620                attr = parts[0].substr(1, parts[0].length-1);
3621            }
3622
3623            if (key.indexOf(';') === key.length-1) {
3624                key = key.substr(0, key.length-2);
3625            }
3626
3627            var optionsToUse;
3628            if (attr === 'html') {
3629                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options;
3630                ele.html($.t(key, optionsToUse));
3631            } else if (attr === 'text') {
3632                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.text() }, options) : options;
3633                ele.text($.t(key, optionsToUse));
3634            } else if (attr === 'prepend') {
3635                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options;
3636                ele.prepend($.t(key, optionsToUse));
3637            } else if (attr === 'append') {
3638                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options;
3639                ele.append($.t(key, optionsToUse));
3640            } else {
3641                optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.attr(attr) }, options) : options;
3642                ele.attr(attr, $.t(key, optionsToUse));
3643            }
3644        }
3645
3646        function localize(ele, options) {
3647            var key = ele.attr(o.selectorAttr);
3648            if (!key && typeof key !== 'undefined' && key !== false) key = ele.text() || ele.val();
3649            if (!key) return;
3650
3651            var target = ele
3652              , targetSelector = ele.data("i18n-target");
3653            if (targetSelector) {
3654                target = ele.find(targetSelector) || ele;
3655            }
3656
3657            if (!options && o.useDataAttrOptions === true) {
3658                options = ele.data("i18n-options");
3659            }
3660            options = options || {};
3661
3662            if (key.indexOf(';') >= 0) {
3663                var keys = key.split(';');
3664
3665                $.each(keys, function(m, k) {
3666                    if (k !== '') parse(target, k, options);
3667                });
3668
3669            } else {
3670                parse(target, key, options);
3671            }
3672
3673            if (o.useDataAttrOptions === true) ele.data("i18n-options", options);
3674        }
3675
3676        // fn
3677        $.fn.i18n = function (options) {
3678            return this.each(function() {
3679                // localize element itself
3680                localize($(this), options);
3681
3682                // localize childs
3683                var elements =  $(this).find('[' + o.selectorAttr + ']');
3684                elements.each(function() {
3685                    localize($(this), options);
3686                });
3687            });
3688        };
3689    }
3690    function applyReplacement(str, replacementHash, nestedKey, options) {
3691        if (!str) return str;
3692
3693        options = options || replacementHash;
3693 // first call uses replacement hash combined with options
3694        if (str.indexOf(options.interpolationPrefix || o.interpolationPrefix) < 0) return str;
3695
3696        var prefix = options.interpolationPrefix ? f.regexEscape(options.interpolationPrefix) : o.interpolationPrefixEscaped
3697          , suffix = options.interpolationSuffix ? f.regexEscape(options.interpolationSuffix) : o.interpolationSuffixEscaped
3698          , unEscapingSuffix = 'HTML'+suffix;
3699
3700        f.each(replacementHash, function(key, value) {
3701            var nextKey = nestedKey ? nestedKey + o.keyseparator + key : key;
3702            if (typeof value === 'object' && value !== null) {
3703                str = applyReplacement(str, value, nextKey, options);
3704            } else {
3705                if (options.escapeInterpolation || o.escapeInterpolation) {
3706                    str = str.replace(new RegExp([prefix, nextKey, unEscapingSuffix].join(''), 'g'), value);
3707                    str = str.replace(new RegExp([prefix, nextKey, suffix].join(''), 'g'), f.escape(value));
3708                } else {
3709                    str = str.replace(new RegExp([prefix, nextKey, suffix].join(''), 'g'), value);
3710                }
3711                // str = options.escapeInterpolation;
3712            }
3713        });
3714        return str;
3715    }
3716
3717    // append it to functions
3718    f.applyReplacement = applyReplacement;
3719
3720    function applyReuse(translated, options) {
3721        var comma = ',';
3722        var options_open = '{';
3723        var options_close = '}';
3724
3725        var opts = f.extend({}, options);
3726        delete opts.postProcess;
3727
3728        while (translated.indexOf(o.reusePrefix) != -1) {
3729            replacementCounter++;
3730            if (replacementCounter > o.maxRecursion) { break; } // safety net for too much recursion
3731            var index_of_opening = translated.lastIndexOf(o.reusePrefix);
3732            var index_of_end_of_closing = translated.indexOf(o.reuseSuffix, index_of_opening) + o.reuseSuffix.length;
3733            var token = translated.substring(index_of_opening, index_of_end_of_closing);
3734            var token_without_symbols = token.replace(o.reusePrefix, '').replace(o.reuseSuffix, '');
3735
3736
3737            if (token_without_symbols.indexOf(comma) != -1) {
3738                var index_of_token_end_of_closing = token_without_symbols.indexOf(comma);
3739                if (token_without_symbols.indexOf(options_open, index_of_token_end_of_closing) != -1 && token_without_symbols.indexOf(options_close, index_of_token_end_of_closing) != -1) {
3740                    var index_of_opts_opening = token_without_symbols.indexOf(options_open, index_of_token_end_of_closing);
3741                    var index_of_opts_end_of_closing = token_without_symbols.indexOf(options_close, index_of_opts_opening) + options_close.length;
3742                    try {
3743                        opts = f.extend(opts, JSON.parse(token_without_symbols.substring(index_of_opts_opening, index_of_opts_end_of_closing)));
3744                        token_without_symbols = token_without_symbols.substring(0, index_of_token_end_of_closing);
3745                    } catch (e) {
3746                    }
3747                }
3748            }
3749
3750            var translated_token = _translate(token_without_symbols, opts);
3751            translated = translated.replace(token, translated_token);
3752        }
3753        return translated;
3754    }
3755
3756    function hasContext(options) {
3757        return (options.context && (typeof options.context == 'string' || typeof options.context == 'number'));
3758    }
3759
3760    function needsPlural(options) {
3761        return (options.count !== undefined && typeof options.count != 'string' && options.count !== 1);
3762    }
3763
3764    function exists(key, options) {
3765        options = options || {};
3766
3767        var notFound = _getDefaultValue(key, options)
3768            , found = _find(key, options);
3769
3770        return found !== undefined || found === notFound;
3771    }
3772
3773    function translate(key, options) {
3774        options = options || {};
3775
3776        if (!initialized) {
3777            f.log('i18next not finished initialization. you might have called t function before loading resources finished.')
3778            return options.defaultValue || '';
3779        };
3780        replacementCounter = 0;
3781        return _translate.apply(null, arguments);
3782    }
3783
3784    function _getDefaultValue(key, options) {
3785        return (options.defaultValue !== undefined) ? options.defaultValue : key;
3786    }
3787
3788    function _injectSprintfProcessor() {
3789
3790        var values = [];
3791
3792        // mh: build array from second argument onwards
3793        for (var i = 1; i < arguments.length; i++) {
3794            values.push(arguments[i]);
3795        }
3796
3797        return {
3798            postProcess: 'sprintf',
3799            sprintf:     values
3800        };
3801    }
3802
3803    function _translate(potentialKeys, options) {
3804        if (typeof options == 'string') {
3805            if (o.shortcutFunction === 'sprintf') {
3806                // mh: gettext like sprintf syntax found, automatically create sprintf processor
3807                options = _injectSprintfProcessor.apply(null, arguments);
3808            } else if (o.shortcutFunction === 'defaultValue') {
3809                options = {
3810                    defaultValue: options
3811                }
3812            }
3813        } else {
3814            options = options || {};
3815        }
3816
3817        if (potentialKeys === undefined || potentialKeys === null) return '';
3818
3819        if (typeof potentialKeys == 'string') {
3820            potentialKeys = [potentialKeys];
3821        }
3822
3823        var key = potentialKeys[0];
3824
3825        if (potentialKeys.length > 1) {
3826            for (var i = 0; i < potentialKeys.length; i++) {
3827                key = potentialKeys[i];
3828                if (exists(key)) {
3829                    break;
3830                }
3831            }
3832        }
3833
3834        var notFound = _getDefaultValue(key, options)
3835            , found = _find(key, options)
3836            , lngs = options.lng ? f.toLanguages(options.lng) : languages
3837            , ns = options.ns || o.ns.defaultNs
3838            , parts;
3839
3840        // split ns and key
3841        if (key.indexOf(o.nsseparator) > -1) {
3842            parts = key.split(o.nsseparator);
3843            ns = parts[0];
3844            key = parts[1];
3845        }
3846
3847        if (found === undefined && o.sendMissing) {
3848            if (options.lng) {
3849                sync.postMissing(lngs[0], ns, key, notFound, lngs);
3850            } else {
3851                sync.postMissing(o.lng, ns, key, notFound, lngs);
3852            }
3853        }
3854
3855        var postProcessor = options.postProcess || o.postProcess;
3856        if (found !== undefined && postProcessor) {
3857            if (postProcessors[postProcessor]) {
3858                found = postProcessors[postProcessor](found, key, options);
3859            }
3860        }
3861
3862        // process notFound if function exists
3863        var splitNotFound = notFound;
3864        if (notFound.indexOf(o.nsseparator) > -1) {
3865            parts = notFound.split(o.nsseparator);
3866            splitNotFound = parts[1];
3867        }
3868        if (splitNotFound === key && o.parseMissingKey) {
3869            notFound = o.parseMissingKey(notFound);
3870        }
3871
3872        if (found === undefined) {
3873            notFound = applyReplacement(notFound, options);
3874            notFound = applyReuse(notFound, options);
3875
3876            if (postProcessor && postProcessors[postProcessor]) {
3877                var val = _getDefaultValue(key, options);
3878                found = postProcessors[postProcessor](val, key, options);
3879            }
3880        }
3881
3882        return (found !== undefined) ? found : notFound;
3883    }
3884
3885    function _find(key, options) {
3886        options = options || {};
3887
3888        var optionWithoutCount, translated
3889            , notFound = _getDefaultValue(key, options)
3890            , lngs = languages;
3891
3892        if (!resStore) { return notFound; } // no resStore to translate from
3893
3894        if (options.lng) {
3895            lngs = f.toLanguages(options.lng);
3896
3897            if (!resStore[lngs[0]]) {
3898                var oldAsync = o.getAsync;
3899                o.getAsync = false;
3900
3901                i18n.sync.load(lngs, o, function(err, store) {
3902                    f.extend(resStore, store);
3903                    o.getAsync = oldAsync;
3904                });
3905            }
3906        }
3907
3908        var ns = options.ns || o.ns.defaultNs;
3909        if (key.indexOf(o.nsseparator) > -1) {
3910            var parts = key.split(o.nsseparator);
3911            ns = parts[0];
3912            key = parts[1];
3913        }
3914
3915        if (hasContext(options)) {
3916            optionWithoutCount = f.extend({}, options);
3917            delete optionWithoutCount.context;
3918            optionWithoutCount.defaultValue = o.contextNotFound;
3919
3920            var contextKey = ns + o.nsseparator + key + '_' + options.context;
3921
3922            translated = translate(contextKey, optionWithoutCount);
3923            if (translated != o.contextNotFound) {
3924                return applyReplacement(translated, { context: options.context }); // apply replacement for context only
3925            } // else continue translation with original/nonContext key
3926        }
3927
3928        if (needsPlural(options)) {
3929            optionWithoutCount = f.extend({}, options);
3930            delete optionWithoutCount.count;
3931            optionWithoutCount.defaultValue = o.pluralNotFound;
3932
3933            var pluralKey = ns + o.nsseparator + key + o.pluralSuffix;
3934            var pluralExtension = pluralExtensions.get(lngs[0], options.count);
3935            if (pluralExtension >= 0) {
3936                pluralKey = pluralKey + '_' + pluralExtension;
3937            } else if (pluralExtension === 1) {
3938                pluralKey = ns + o.nsseparator + key; // singular
3939            }
3940
3941            translated = translate(pluralKey, optionWithoutCount);
3942            if (translated != o.pluralNotFound) {
3943                return applyReplacement(translated, {
3944                    count: options.count,
3945                    interpolationPrefix: options.interpolationPrefix,
3946                    interpolationSuffix: options.interpolationSuffix
3947                }); // apply replacement for count only
3948            } // else continue translation with original/singular key
3949        }
3950
3951        var found;
3952        var keys = key.split(o.keyseparator);
3953        for (var i = 0, len = lngs.length; i < len; i++ ) {
3954            if (found !== undefined) break;
3955
3956            var l = lngs[i];
3957
3958            var x = 0;
3959            var value = resStore[l] && resStore[l][ns];
3960            while (keys[x]) {
3961                value = value && value[keys[x]];
3962                x++;
3963            }
3964            if (value !== undefined) {
3965                var valueType = Object.prototype.toString.apply(value);
3966                if (typeof value === 'string') {
3967                    value = applyReplacement(value, options);
3968                    value = applyReuse(value, options);
3969                } else if (valueType === '[object Array]' && !o.returnObjectTrees && !options.returnObjectTrees) {
3970                    value = value.join('\n');
3971                    value = applyReplacement(value, options);
3972                    value = applyReuse(value, options);
3973                } else if (value === null && o.fallbackOnNull === true) {
3974                    value = undefined;
3975                } else if (value !== null) {
3976                    if (!o.returnObjectTrees && !options.returnObjectTrees) {
3977                        if (o.objectTreeKeyHandler && typeof o.objectTreeKeyHandler == 'function') {
3978                            value = o.objectTreeKeyHandler(key, value, l, ns, options);
3979                        } else {
3980                            value = 'key \'' + ns + ':' + key + ' (' + l + ')\' ' +
3981                                'returned an object instead of string.';
3982                            f.log(value);
3983                        }
3984                    } else if (valueType !== '[object Number]' && valueType !== '[object Function]' && valueType !== '[object RegExp]') {
3985                        var copy = (valueType === '[object Array]') ? [] : {}; // apply child translation on a copy
3986                        f.each(value, function(m) {
3987                            copy[m] = _translate(ns + o.nsseparator + key + o.keyseparator + m, options);
3988                        });
3989                        value = copy;
3990                    }
3991                }
3992
3993                if (typeof value === 'string' && value.trim() === '' && o.fallbackOnEmpty === true)
3994                    value = undefined;
3995
3996                found = value;
3997            }
3998        }
3999
4000        if (found === undefined && !options.isFallbackLookup && (
4000o.fallbackToDefaultNS === true || (o.fallbackNS && o.fallbackNS.length > 0))) {
4001            // set flag for fallback lookup - avoid recursion
4002            options.isFallbackLookup = true;
4003
4004            if (o.fallbackNS.length) {
4005
4006                for (var y = 0, lenY = o.fallbackNS.length; y < lenY; y++) {
4007                    found = _find(o.fallbackNS[y] + o.nsseparator + key, options);
4008
4009                    if (found) {
4010                        /* compare value without namespace */
4011                        var foundValue = found.indexOf(o.nsseparator) > -1 ? found.split(o.nsseparator)[1] : found
4012                          , notFoundValue = notFound.indexOf(o.nsseparator) > -1 ? notFound.split(o.nsseparator)[1] : notFound;
4013
4014                        if (foundValue !== notFoundValue) break;
4015                    }
4016                }
4017            } else {
4018                found = _find(key, options); // fallback to default NS
4019            }
4020        }
4021
4022        return found;
4023    }
4024    function detectLanguage() {
4025        var detectedLng;
4026
4027        // get from qs
4028        var qsParm = [];
4029        if (typeof window !== 'undefined') {
4030            (function() {
4031                var query = window.location.search.substring(1);
4032                var parms = query.split('&');
4033                for (var i=0; i<parms.length; i++) {
4034                    var pos = parms[i].indexOf('=');
4035                    if (pos > 0) {
4036                        var key = parms[i].substring(0,pos);
4037                        var val = parms[i].substring(pos+1);
4038                        qsParm[key] = val;
4039                    }
4040                }
4041            })();
4042            if (qsParm[o.detectLngQS]) {
4043                detectedLng = qsParm[o.detectLngQS];
4044            }
4045        }
4046
4047        // get from cookie
4048        if (!detectedLng && typeof document !== 'undefined' && o.useCookie ) {
4049            var c = f.cookie.read(o.cookieName);
4050            if (c) detectedLng = c;
4051        }
4052
4053        // get from navigator
4054        if (!detectedLng && typeof navigator !== 'undefined') {
4055            detectedLng =  (navigator.language) ? navigator.language : navigator.userLanguage;
4056        }
4057
4058        return detectedLng;
4059    }
4060    var sync = {
4061
4062        load: function(lngs, options, cb) {
4063            if (options.useLocalStorage) {
4064                sync._loadLocal(lngs, options, function(err, store) {
4065                    var missingLngs = [];
4066                    for (var i = 0, len = lngs.length; i < len; i++) {
4067                        if (!store[lngs[i]]) missingLngs.push(lngs[i]);
4068                    }
4069
4070                    if (missingLngs.length > 0) {
4071                        sync._fetch(missingLngs, options, function(err, fetched) {
4072                            f.extend(store, fetched);
4073                            sync._storeLocal(fetched);
4074
4075                            cb(null, store);
4076                        });
4077                    } else {
4078                        cb(null, store);
4079                    }
4080                });
4081            } else {
4082                sync._fetch(lngs, options, function(err, store){
4083                    cb(null, store);
4084                });
4085            }
4086        },
4087
4088        _loadLocal: function(lngs, options, cb) {
4089            var store = {}
4090              , nowMS = new Date().getTime();
4091
4092            if(window.localStorage) {
4093
4094                var todo = lngs.length;
4095
4096                f.each(lngs, function(key, lng) {
4097                    var local = window.localStorage.getItem('res_' + lng);
4098
4099                    if (local) {
4100                        local = JSON.parse(local);
4101
4102                        if (local.i18nStamp && local.i18nStamp + options.localStorageExpirationTime > nowMS) {
4103                            store[lng] = local;
4104                        }
4105                    }
4106
4107                    todo--; // wait for all done befor callback
4108                    if (todo === 0) cb(null, store);
4109                });
4110            }
4111        },
4112
4113        _storeLocal: function(store) {
4114            if(window.localStorage) {
4115                for (var m in store) {
4116                    store[m].i18nStamp = new Date().getTime();
4117                    window.localStorage.setItem('res_' + m, JSON.stringify(store[m]));
4118                }
4119            }
4120            return;
4121        },
4122
4123        _fetch: function(lngs, options, cb) {
4124            var ns = options.ns
4125              , store = {};
4126
4127            if (!options.dynamicLoad) {
4128                var todo = ns.namespaces.length * lngs.length
4129                  , errors;
4130
4131                // load each file individual
4132                f.each(ns.namespaces, function(nsIndex, nsValue) {
4133                    f.each(lngs, function(lngIndex, lngValue) {
4134
4135                        // Call this once our translation has returned.
4136                        var loadComplete = function(err, data) {
4137                            if (err) {
4138                                errors = errors || [];
4139                                errors.push(err);
4140                            }
4141                            store[lngValue] = store[lngValue] || {};
4142                            store[lngValue][nsValue] = data;
4143
4144                            todo--; // wait for all done befor callback
4145                            if (todo === 0) cb(errors, store);
4146                        };
4147
4148                        if(typeof options.customLoad == 'function'){
4149                            // Use the specified custom callback.
4150                            options.customLoad(lngValue, nsValue, options, loadComplete);
4151                        } else {
4152                            //~ // Use our inbuilt sync.
4153                            sync._fetchOne(lngValue, nsValue, options, loadComplete);
4154                        }
4155                    });
4156                });
4157            } else {
4158                // Call this once our translation has returned.
4159                var loadComplete = function(err, data) {
4160                    cb(null, data);
4161                };
4162
4163                if(typeof options.customLoad == 'function'){
4164                    // Use the specified custom callback.
4165                    options.customLoad(lngs, ns.namespaces, options, loadComplete);
4166                } else {
4167                    var url = applyReplacement(options.resGetPath, { lng: lngs.join('+'), ns: ns.namespaces.join('+') });
4168                    // load all needed stuff once
4169                    f.ajax({
4170                        url: url,
4171                        success: function(data, status, xhr) {
4172                            f.log('loaded: ' + url);
4173                            loadComplete(null, data);
4174                        },
4175                        error : function(xhr, status, error) {
4176                            f.log('failed loading: ' + url);
4177                            loadComplete('failed loading resource.json error: ' + error);
4178                        },
4179                        dataType: "json",
4180                        async : options.getAsync
4181                    });
4182                }
4183            }
4184        },
4185
4186        _fetchOne: function(lng, ns, options, done) {
4187            var url = applyReplacement(options.resGetPath, { lng: lng, ns: ns });
4188            f.ajax({
4189                url: url,
4190                success: function(data, status, xhr) {
4191                    f.log('loaded: ' + url);
4192                    done(null, data);
4193                },
4194                error : function(xhr, status, error) {
4195                    if (error.status == 200) {
4196                        // file loaded but invalid json, stop waste time !
4197                        f.log('There is a typo in: ' + url);
4198                    } else if (error.status == 404) {
4199                        f.log('Does not exist: ' + url);
4200                    } else {
4201                        f.log(error.status + ' when loading ' + url);
4202                    }
4203
4204                    done(error, {});
4205                },
4206                dataType: "json",
4207                async : options.getAsync
4208            });
4209        },
4210
4211        postMissing: function(lng, ns, key, defaultValue, lngs) {
4212            var payload = {};
4213            payload[key] = defaultValue;
4214
4215            var urls = [];
4216
4217            if (o.sendMissingTo === 'fallback' && o.fallbackLng !== false) {
4218                urls.push({lng: o.fallbackLng, url: applyReplacement(o.resPostPath, { lng: o.fallbackLng, ns: ns })});
4219            } else if (o.sendMissingTo === 'current' || (o.sendMissingTo === 'fallback' && o.fallbackLng === false) ) {
4220                urls.push({lng: lng, url: applyReplacement(o.resPostPath, { lng: lng, ns: ns })});
4221            } else if (o.sendMissingTo === 'all') {
4222                for (var i = 0, l = lngs.length; i < l; i++) {
4223                    urls.push({lng: lngs[i], url: applyReplacement(o.resPostPath, { lng: lngs[i], ns: ns })});
4224                }
4225            }
4226
4227            for (var y = 0, len = urls.length; y < len; y++) {
4228                var item = urls[y];
4229                f.ajax({
4230                    url: item.url,
4231                    type: o.sendType,
4232                    data: payload,
4233                    success: function(data, status, xhr) {
4234                        f.log('posted missing key \'' + key + '\' to: ' + item.url);
4235
4236                        // add key to resStore
4237                        var keys = key.split('.');
4238                        var x = 0;
4239                        var value = resStore[item.lng][ns];
4240                        while (keys[x]) {
4241                            if (x === keys.length - 1) {
4242                                value = value[keys[x]] = defaultValue;
4243                            } else {
4244                                value = value[keys[x]] = value[keys[x]] || {};
4245                            }
4246                            x++;
4247                        }
4248                    },
4249                    error : function(xhr, status, error) {
4250                        f.log('failed posting missing key \'' + key + '\' to: ' + item.url);
4251                    },
4252                    dataType: "json",
4253                    async : o.postAsync
4254                });
4255            }
4256        }
4257    };
4258    // definition http://translate.sourceforge.net/wiki/l10n/pluralforms
4259    var pluralExtensions = {
4260
4261        rules: {
4262            "ach": {
4263                "name": "Acholi",
4264                "numbers": [
4265                    1,
4266                    2
4267                ],
4268                "plurals": function(n) { return Number(n > 1); }
4269            },
4270            "af": {
4271                "name": "Afrikaans",
4272                "numbers": [
4273                    1,
4274                    2
4275                ],
4276                "plurals": function(n) { return Number(n != 1); }
4277            },
4278            "ak": {
4279                "name": "Akan",
4280                "numbers": [
4281                    1,
4282                    2
4283                ],
4284                "plurals": function(n) { return Number(n > 1); }
4285            },
4286            "am": {
4287                "name": "Amharic",
4288                "numbers": [
4289                    1,
4290                    2
4291                ],
4292                "plurals": function(n) { return Number(n > 1); }
4293            },
4294            "an": {
4295                "name": "Aragonese",
4296                "numbers": [
4297                    1,
4298                    2
4299                ],
4300                "plurals": function(n) { return Number(n != 1); }
4301            },
4302            "ar": {
4303                "name": "Arabic",
4304                "numbers": [
4305                    0,
4306                    1,
4307                    2,
4308                    3,
4309                    11,
4310                    100
4311                ],
4312                "plurals": function(n) { return Number(n===0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); }
4313            },
4314            "arn": {
4315                "name": "Mapudungun",
4316                "numbers": [
4317                    1,
4318                    2
4319                ],
4320                "plurals": function(n) { return Number(n > 1); }
4321            },
4322            "ast": {
4323                "name": "Asturian",
4324                "numbers": [
4325                    1,
4326                    2
4327                ],
4328                "plurals": function(n) { return Number(n != 1); }
4329            },
4330            "ay": {
4331                "name": "Aymar\u00e1",
4332                "numbers": [
4333                    1
4334                ],
4335                "plurals": function(n) { return 0; }
4336            },
4337            "az": {
4338                "name": "Azerbaijani",
4339                "numbers": [
4340                    1,
4341                    2
4342                ],
4343                "plurals": function(n) { return Number(n != 1); }
4344            },
4345            "be": {
4346                "name": "Belarusian",
4347                "numbers": [
4348                    1,
4349                    2,
4350                    5
4351                ],
4352                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
4353            },
4354            "bg": {
4355                "name": "Bulgarian",
4356                "numbers": [
4357                    1,
4358                    2
4359                ],
4360                "plurals": function(n) { return Number(n != 1); }
4361            },
4362            "bn": {
4363                "name": "Bengali",
4364                "numbers": [
4365                    1,
4366                    2
4367                ],
4368                "plurals": function(n) { return Number(n != 1); }
4369            },
4370            "bo": {
4371                "name": "Tibetan",
4372                "numbers": [
4373                    1
4374                ],
4375                "plurals": function(n) { return 0; }
4376            },
4377            "br": {
4378                "name": "Breton",
4379                "numbers": [
4380                    1,
4381                    2
4382                ],
4383                "plurals": function(n) { return Number(n > 1); }
4384            },
4385            "bs": {
4386                "name": "Bosnian",
4387                "numbers": [
4388                    1,
4389                    2,
4390                    5
4391                ],
4392                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
4393            },
4394            "ca": {
4395                "name": "Catalan",
4396                "numbers": [
4397                    1,
4398                    2
4399                ],
4400                "plurals": function(n) { return Number(n != 1); }
4401            },
4402            "cgg": {
4403                "name": "Chiga",
4404                "numbers": [
4405                    1
4406                ],
4407                "plurals": function(n) { return 0; }
4408            },
4409            "cs": {
4410                "name": "Czech",
4411                "numbers": [
4412                    1,
4413                    2,
4414                    5
4415                ],
4416                "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); }
4417            },
4418            "csb": {
4419                "name": "Kashubian",
4420                "numbers": [
4421                    1,
4422                    2,
4423                    5
4424                ],
4425                "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
4426            },
4427            "cy": {
4428                "name": "Welsh",
4429                "numbers": [
4430                    1,
4431                    2,
4432                    3,
4433                    8
4434                ],
4435                "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3); }
4436            },
4437            "da": {
4438                "name": "Danish",
4439                "numbers": [
4440                    1,
4441                    2
4442                ],
4443                "plurals": function(n) { return Number(n != 1); }
4444            },
4445            "de": {
4446                "name": "German",
4447                "numbers": [
4448                    1,
4449                    2
4450                ],
4451                "plurals": function(n) { return Number(n != 1); }
4452            },
4453            "dz": {
4454                "name": "Dzongkha",
4455                "numbers": [
4456                    1
4457                ],
4458                "plurals": function(n) { return 0; }
4459            },
4460            "el": {
4461                "name": "Greek",
4462                "numbers": [
4463                    1,
4464                    2
4465                ],
4466                "plurals": function(n) { return Number(n != 1); }
4467            },
4468            "en": {
4469                "name": "English",
4470                "numbers": [
4471                    1,
4472                    2
4473                ],
4474                "plurals": function(n) { return Number(n != 1); }
4475            },
4476            "eo": {
4477                "name": "Esperanto",
4478                "numbers": [
4479                    1,
4480                    2
4481                ],
4482                "plurals": function(n) { return Number(n != 1); }
4483            },
4484            "es": {
4485                "name": "Spanish",
4486                "numbers": [
4487                    1,
4488                    2
4489                ],
4490                "plurals": function(n) { return Number(n != 1); }
4491            },
4492            "es_ar": {
4493                "name": "Argentinean Spanish",
4494                "numbers": [
4495                    1,
4496                    2
4497                ],
4498                "plurals": function(n) { return Number(n != 1); }
4499            },
4500            "et": {
4501                "name": "Estonian",
4502                "numbers": [
4503                    1,
4504                    2
4505                ],
4506                "plurals": function(n) { return Number(n != 1); }
4507            },
4508            "eu": {
4509                "name": "Basque",
4510                "numbers": [
4511                    1,
4512                    2
4513                ],
4514                "plurals": function(n) { return Number(n != 1); }
4515            },
4516            "fa": {
4517                "name": "Persian",
4518                "numbers": [
4519                    1
4520                ],
4521                "plurals": function(n) { return 0; }
4522            },
4523            "fi": {
4524                "name": "Finnish",
4525                "numbers": [
4526                    1,
4527                    2
4528                ],
4529                "plurals": function(n) { return Number(n != 1); }
4530            },
4531            "fil": {
4532                "name": "Filipino",
4533                "numbers": [
4534                    1,
4535                    2
4536                ],
4537                "plurals": function(n) { return Number(n > 1); }
4538            },
4539            "fo": {
4540                "name": "Faroese",
4541                "numbers": [
4542                    1,
4543                    2
4544                ],
4545                "plurals": function(n) { return Number(n != 1); }
4546            },
4547            "fr": {
4548                "name": "French",
4549                "numbers": [
4550                    1,
4551                    2
4552                ],
4553                "plurals": function(n) { return Number(n > 1); }
4554            },
4555            "fur": {
4556                "name": "Friulian",
4557                "numbers": [
4558                    1,
4559                    2
4560                ],
4561                "plurals": function(n) { return Number(n != 1); }
4562            },
4563            "fy": {
4564                "name": "Frisian",
4565                "numbers": [
4566                    1,
4567                    2
4568                ],
4569                "plurals": function(n) { return Number(n != 1); }
4570            },
4571            "ga": {
4572                "name": "Irish",
4573                "numbers": [
4574                    1,
4575                    2,
4576                    3,
4577                    7,
4578                    11
4579                ],
4580                "plurals": function(n) { return Number(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4) ;}
4581            },
4582            "gd": {
4583                "name": "Scottish Gaelic",
4584                "numbers": [
4585                    1,
4586                    2,
4587                    3,
4588                    20
4589                ],
4590                "plurals": function(n) { return Number((n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3); }
4591            },
4592            "gl": {
4593                "name": "Galician",
4594                "numbers": [
4595                    1,
4596                    2
4597                ],
4598                "plurals": function(n) { return Number(n != 1); }
4599            },
4600            "gu": {
4601                "name": "Gujarati",
4602                "numbers": [
4603                    1,
4604                    2
4605                ],
4606                "plurals": function(n) { return Number(n != 1); }
4607            },
4608            "gun": {
4609                "name": "Gun",
4610                "numbers": [
4611                    1,
4612                    2
4613                ],
4614                "plurals": function(n) { return Number(n > 1); }
4615            },
4616            "ha": {
4617                "name": "Hausa",
4618                "numbers": [
4619                    1,
4620                    2
4621                ],
4622                "plurals": function(n) { return Number(n != 1); }
4623            },
4624            "he": {
4625                "name": "Hebrew",
4626                "numbers": [
4627                    1,
4628                    2
4629                ],
4630                "plurals": function(n) { return Number(n != 1); }
4631            },
4632            "hi": {
4633                "name": "Hindi",
4634                "numbers": [
4635                    1,
4636                    2
4637                ],
4638                "plurals": function(n) { return Number(n != 1); }
4639            },
4640            "hr": {
4641                "name": "Croatian",
4642                "numbers": [
4643                    1,
4644                    2,
4645                    5
4646                ],
4647                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
4648            },
4649            "hu": {
4650                "name": "Hungarian",
4651                "numbers": [
4652                    1,
4653                    2
4654                ],
4655                "plurals": function(n) { return Number(n != 1); }
4656            },
4657            "hy": {
4658                "name": "Armenian",
4659                "numbers": [
4660                    1,
4661                    2
4662                ],
4663                "plurals": function(n) { return Number(n != 1); }
4664            },
4665  
4665          "ia": {
4666                "name": "Interlingua",
4667                "numbers": [
4668                    1,
4669                    2
4670                ],
4671                "plurals": function(n) { return Number(n != 1); }
4672            },
4673            "id": {
4674                "name": "Indonesian",
4675                "numbers": [
4676                    1
4677                ],
4678                "plurals": function(n) { return 0; }
4679            },
4680            "is": {
4681                "name": "Icelandic",
4682                "numbers": [
4683                    1,
4684                    2
4685                ],
4686                "plurals": function(n) { return Number(n%10!=1 || n%100==11); }
4687            },
4688            "it": {
4689                "name": "Italian",
4690                "numbers": [
4691                    1,
4692                    2
4693                ],
4694                "plurals": function(n) { return Number(n != 1); }
4695            },
4696            "ja": {
4697                "name": "Japanese",
4698                "numbers": [
4699                    1
4700                ],
4701                "plurals": function(n) { return 0; }
4702            },
4703            "jbo": {
4704                "name": "Lojban",
4705                "numbers": [
4706                    1
4707                ],
4708                "plurals": function(n) { return 0; }
4709            },
4710            "jv": {
4711                "name": "Javanese",
4712                "numbers": [
4713                    0,
4714                    1
4715                ],
4716                "plurals": function(n) { return Number(n !== 0); }
4717            },
4718            "ka": {
4719                "name": "Georgian",
4720                "numbers": [
4721                    1
4722                ],
4723                "plurals": function(n) { return 0; }
4724            },
4725            "kk": {
4726                "name": "Kazakh",
4727                "numbers": [
4728                    1
4729                ],
4730                "plurals": function(n) { return 0; }
4731            },
4732            "km": {
4733                "name": "Khmer",
4734                "numbers": [
4735                    1
4736                ],
4737                "plurals": function(n) { return 0; }
4738            },
4739            "kn": {
4740                "name": "Kannada",
4741                "numbers": [
4742                    1,
4743                    2
4744                ],
4745                "plurals": function(n) { return Number(n != 1); }
4746            },
4747            "ko": {
4748                "name": "Korean",
4749                "numbers": [
4750                    1
4751                ],
4752                "plurals": function(n) { return 0; }
4753            },
4754            "ku": {
4755                "name": "Kurdish",
4756                "numbers": [
4757                    1,
4758                    2
4759                ],
4760                "plurals": function(n) { return Number(n != 1); }
4761            },
4762            "kw": {
4763                "name": "Cornish",
4764                "numbers": [
4765                    1,
4766                    2,
4767                    3,
4768                    4
4769                ],
4770                "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3); }
4771            },
4772            "ky": {
4773                "name": "Kyrgyz",
4774                "numbers": [
4775                    1
4776                ],
4777                "plurals": function(n) { return 0; }
4778            },
4779            "lb": {
4780                "name": "Letzeburgesch",
4781                "numbers": [
4782                    1,
4783                    2
4784                ],
4785                "plurals": function(n) { return Number(n != 1); }
4786            },
4787            "ln": {
4788                "name": "Lingala",
4789                "numbers": [
4790                    1,
4791                    2
4792                ],
4793                "plurals": function(n) { return Number(n > 1); }
4794            },
4795            "lo": {
4796                "name": "Lao",
4797                "numbers": [
4798                    1
4799                ],
4800                "plurals": function(n) { return 0; }
4801            },
4802            "lt": {
4803                "name": "Lithuanian",
4804                "numbers": [
4805                    1,
4806                    2,
4807                    10
4808                ],
4809                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); }
4810            },
4811            "lv": {
4812                "name": "Latvian",
4813                "numbers": [
4814                    0,
4815                    1,
4816                    2
4817                ],
4818                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n !== 0 ? 1 : 2); }
4819            },
4820            "mai": {
4821                "name": "Maithili",
4822                "numbers": [
4823                    1,
4824                    2
4825                ],
4826                "plurals": function(n) { return Number(n != 1); }
4827            },
4828            "mfe": {
4829                "name": "Mauritian Creole",
4830                "numbers": [
4831                    1,
4832                    2
4833                ],
4834                "plurals": function(n) { return Number(n > 1); }
4835            },
4836            "mg": {
4837                "name": "Malagasy",
4838                "numbers": [
4839                    1,
4840                    2
4841                ],
4842                "plurals": function(n) { return Number(n > 1); }
4843            },
4844            "mi": {
4845                "name": "Maori",
4846                "numbers": [
4847                    1,
4848                    2
4849                ],
4850                "plurals": function(n) { return Number(n > 1); }
4851            },
4852            "mk": {
4853                "name": "Macedonian",
4854                "numbers": [
4855                    1,
4856                    2
4857                ],
4858                "plurals": function(n) { return Number(n==1 || n%10==1 ? 0 : 1); }
4859            },
4860            "ml": {
4861                "name": "Malayalam",
4862                "numbers": [
4863                    1,
4864                    2
4865                ],
4866                "plurals": function(n) { return Number(n != 1); }
4867            },
4868            "mn": {
4869                "name": "Mongolian",
4870                "numbers": [
4871                    1,
4872                    2
4873                ],
4874                "plurals": function(n) { return Number(n != 1); }
4875            },
4876            "mnk": {
4877                "name": "Mandinka",
4878                "numbers": [
4879                    0,
4880                    1,
4881                    2
4882                ],
4883                "plurals": function(n) { return Number(0 ? 0 : n==1 ? 1 : 2); }
4884            },
4885            "mr": {
4886                "name": "Marathi",
4887                "numbers": [
4888                    1,
4889                    2
4890                ],
4891                "plurals": function(n) { return Number(n != 1); }
4892            },
4893            "ms": {
4894                "name": "Malay",
4895                "numbers": [
4896                    1
4897                ],
4898                "plurals": function(n) { return 0; }
4899            },
4900            "mt": {
4901                "name": "Maltese",
4902                "numbers": [
4903                    1,
4904                    2,
4905                    11,
4906                    20
4907                ],
4908                "plurals": function(n) { return Number(n==1 ? 0 : n===0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3); }
4909            },
4910            "nah": {
4911                "name": "Nahuatl",
4912                "numbers": [
4913                    1,
4914                    2
4915                ],
4916                "plurals": function(n) { return Number(n != 1); }
4917            },
4918            "nap": {
4919                "name": "Neapolitan",
4920                "numbers": [
4921                    1,
4922                    2
4923                ],
4924                "plurals": function(n) { return Number(n != 1); }
4925            },
4926            "nb": {
4927                "name": "Norwegian Bokmal",
4928                "numbers": [
4929                    1,
4930                    2
4931                ],
4932                "plurals": function(n) { return Number(n != 1); }
4933            },
4934            "ne": {
4935                "name": "Nepali",
4936                "numbers": [
4937                    1,
4938                    2
4939                ],
4940                "plurals": function(n) { return Number(n != 1); }
4941            },
4942            "nl": {
4943                "name": "Dutch",
4944                "numbers": [
4945                    1,
4946                    2
4947                ],
4948                "plurals": function(n) { return Number(n != 1); }
4949            },
4950            "nn": {
4951                "name": "Norwegian Nynorsk",
4952                "numbers": [
4953                    1,
4954                    2
4955                ],
4956                "plurals": function(n) { return Number(n != 1); }
4957            },
4958            "no": {
4959                "name": "Norwegian",
4960                "numbers": [
4961                    1,
4962                    2
4963                ],
4964                "plurals": function(n) { return Number(n != 1); }
4965            },
4966            "nso": {
4967                "name": "Northern Sotho",
4968                "numbers": [
4969                    1,
4970                    2
4971                ],
4972                "plurals": function(n) { return Number(n != 1); }
4973            },
4974            "oc": {
4975                "name": "Occitan",
4976                "numbers": [
4977                    1,
4978                    2
4979                ],
4980                "plurals": function(n) { return Number(n > 1); }
4981            },
4982            "or": {
4983                "name": "Oriya",
4984                "numbers": [
4985                    2,
4986                    1
4987                ],
4988                "plurals": function(n) { return Number(n != 1); }
4989            },
4990            "pa": {
4991                "name": "Punjabi",
4992                "numbers": [
4993                    1,
4994                    2
4995                ],
4996                "plurals": function(n) { return Number(n != 1); }
4997            },
4998            "pap": {
4999                "name": "Papiamento",
5000                "numbers": [
5001                    1,
5002                    2
5003                ],
5004                "plurals": function(n) { return Number(n != 1); }
5005            },
5006            "pl": {
5007                "name": "Polish",
5008                "numbers": [
5009                    1,
5010                    2,
5011                    5
5012                ],
5013                "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
5014            },
5015            "pms": {
5016                "name": "Piemontese",
5017                "numbers": [
5018                    1,
5019                    2
5020                ],
5021                "plurals": function(n) { return Number(n != 1); }
5022            },
5023            "ps": {
5024                "name": "Pashto",
5025                "numbers": [
5026                    1,
5027                    2
5028                ],
5029                "plurals": function(n) { return Number(n != 1); }
5030            },
5031            "pt": {
5032                "name": "Portuguese",
5033                "numbers": [
5034                    1,
5035                    2
5036                ],
5037                "plurals": function(n) { return Number(n != 1); }
5038            },
5039            "pt_br": {
5040                "name": "Brazilian Portuguese",
5041                "numbers": [
5042                    1,
5043                    2
5044                ],
5045                "plurals": function(n) { return Number(n != 1); }
5046            },
5047            "rm": {
5048                "name": "Romansh",
5049                "numbers": [
5050                    1,
5051                    2
5052                ],
5053                "plurals": function(n) { return Number(n != 1); }
5054            },
5055            "ro": {
5056                "name": "Romanian",
5057                "numbers": [
5058                    1,
5059                    2,
5060                    20
5061                ],
5062                "plurals": function(n) { return Number(n==1 ? 0 : (n===0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); }
5063            },
5064            "ru": {
5065                "name": "Russian",
5066                "numbers": [
5067                    1,
5068                    2,
5069                    5
5070                ],
5071                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
5072            },
5073            "sah": {
5074                "name": "Yakut",
5075                "numbers": [
5076                    1
5077                ],
5078                "plurals": function(n) { return 0; }
5079            },
5080            "sco": {
5081                "name": "Scots",
5082                "numbers": [
5083                    1,
5084                    2
5085                ],
5086                "plurals": function(n) { return Number(n != 1); }
5087            },
5088            "se": {
5089                "name": "Northern Sami",
5090                "numbers": [
5091                    1,
5092                    2
5093                ],
5094                "plurals": function(n) { return Number(n != 1); }
5095            },
5096            "si": {
5097                "name": "Sinhala",
5098                "numbers": [
5099                    1,
5100                    2
5101                ],
5102                "plurals": function(n) { return Number(n != 1); }
5103            },
5104            "sk": {
5105                "name": "Slovak",
5106                "numbers": [
5107                    1,
5108                    2,
5109                    5
5110                ],
5111                "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); }
5112            },
5113            "sl": {
5114                "name": "Slovenian",
5115                "numbers": [
5116                    5,
5117                    1,
5118                    2,
5119                    3
5120                ],
5121                "plurals": function(n) { return Number(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); }
5122            },
5123            "so": {
5124                "name": "Somali",
5125                "numbers": [
5126                    1,
5127                    2
5128                ],
5129                "plurals": function(n) { return Number(n != 1); }
5130            },
5131            "son": {
5132                "name": "Songhay",
5133                "numbers": [
5134                    1,
5135                    2
5136                ],
5137                "plurals": function(n) { return Number(n != 1); }
5138            },
5139            "sq": {
5140                "name": "Albanian",
5141                "numbers": [
5142                    1,
5143                    2
5144                ],
5145                "plurals": function(n) { return Number(n != 1); }
5146            },
5147            "sr": {
5148                "name": "Serbian",
5149                "numbers": [
5150                    1,
5151                    2,
5152                    5
5153                ],
5154                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
5155            },
5156            "su": {
5157                "name": "Sundanese",
5158                "numbers": [
5159                    1
5160                ],
5161                "plurals": function(n) { return 0; }
5162            },
5163            "sv": {
5164                "name": "Swedish",
5165                "numbers": [
5166                    1,
5167                    2
5168                ],
5169                "plurals": function(n) { return Number(n != 1); }
5170            },
5171            "sw": {
5172                "name": "Swahili",
5173                "numbers": [
5174                    1,
5175                    2
5176                ],
5177                "plurals": function(n) { return Number(n != 1); }
5178            },
5179            "ta": {
5180                "name": "Tamil",
5181                "numbers": [
5182                    1,
5183                    2
5184                ],
5185                "plurals": function(n) { return Number(n != 1); }
5186            },
5187            "te": {
5188                "name": "Telugu",
5189                "numbers": [
5190                    1,
5191                    2
5192                ],
5193                "plurals": function(n) { return Number(n != 1); }
5194            },
5195            "tg": {
5196                "name": "Tajik",
5197                "numbers": [
5198                    1,
5199                    2
5200                ],
5201                "plurals": function(n) { return Number(n > 1); }
5202            },
5203            "th": {
5204                "name": "Thai",
5205                "numbers": [
5206                    1
5207                ],
5208                "plurals": function(n) { return 0; }
5209            },
5210            "ti": {
5211                "name": "Tigrinya",
5212                "numbers": [
5213                    1,
5214                    2
5215                ],
5216                "plurals": function(n) { return Number(n > 1); }
5217            },
5218            "tk": {
5219                "name": "Turkmen",
5220                "numbers": [
5221                    1,
5222                    2
5223                ],
5224                "plurals": function(n) { return Number(n != 1); }
5225            },
5226            "tr": {
5227                "name": "Turkish",
5228                "numbers": [
5229                    1,
5230                    2
5231                ],
5232                "plurals": function(n) { return Number(n > 1); }
5233            },
5234            "tt": {
5235                "name": "Tatar",
5236                "numbers": [
5237                    1
5238                ],
5239                "plurals": function(n) { return 0; }
5240            },
5241            "ug": {
5242                "name": "Uyghur",
5243                "numbers": [
5244                    1
5245                ],
5246                "plurals": function(n) { return 0; }
5247            },
5248            "uk": {
5249                "name": "Ukrainian",
5250                "numbers": [
5251                    1,
5252                    2,
5253                    5
5254                ],
5255                "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
5256            },
5257            "ur": {
5258                "name": "Urdu",
5259                "numbers": [
5260                    1,
5261                    2
5262                ],
5263                "plurals": function(n) { return Number(n != 1); }
5264            },
5265            "uz": {
5266                "name": "Uzbek",
5267                "numbers": [
5268                    1,
5269                    2
5270                ],
5271                "plurals": function(n) { return Number(n > 1); }
5272            },
5273            "vi": {
5274                "name": "Vietnamese",
5275                "numbers": [
5276                    1
5277                ],
5278                "plurals": function(n) { return 0; }
5279            },
5280            "wa": {
5281                "name": "Walloon",
5282                "numbers": [
5283                    1,
5284                    2
5285                ],
5286                "plurals": function(n) { return Number(n > 1); }
5287            },
5288            "wo": {
5289                "name": "Wolof",
5290                "numbers": [
5291                    1
5292                ],
5293                "plurals": function(n) { return 0; }
5294            },
5295            "yo": {
5296                "name": "Yoruba",
5297                "numbers": [
5298                    1,
5299                    2
5300                ],
5301                "plurals": function(n) { return Number(n != 1); }
5302            },
5303            "zh": {
5304                "name": "Chinese",
5305                "numbers": [
5306                    1
5307                ],
5308                "plurals": function(n) { return 0; }
5309            }
5310        },
5311
5312        // for demonstration only sl and ar is added but you can add your own pluralExtensions
5313        addRule: function(lng, obj) {
5314            pluralExtensions.rules[lng] = obj;
5315        },
5316
5317        setCurrentLng: function(lng) {
5318            if (!pluralExtensions.currentRule || pluralExtensions.currentRule.lng !== lng) {
5319                var parts = lng.split('-');
5320
5321                pluralExtensions.currentRule = {
5322                    lng: lng,
5323                    rule: pluralExtensions.rules[parts[0]]
5324                };
5325            }
5326        },
5327
5328        get: function(lng, count) {
5329            var parts = lng.split('-');
5330
5331            function getResult(l, c) {
5332                var ext;
5333                if (pluralExtensions.currentRule && pluralExtensions.currentRule.lng === lng) {
5334                    ext = pluralExtensions.currentRule.rule;
5335                } else {
5336                    ext = pluralExtensions.rules[l];
5337                }
5338                if (ext) {
5339                    var i = ext.plurals(c);
5340                    var number = ext.numbers[i];
5341                    if (ext.numbers.length === 2 && ext.numbers[0] === 1) {
5342                        if (number === 2) {
5343                            number = -1; // regular plural
5344                        } else if (number === 1) {
5345                            number = 1; // singular
5346                        }
5347                    }//console.log(count + '-' + number);
5348                    return number;
5349                } else {
5350                    return c === 1 ? '1' : '-1';
5351                }
5352            }
5353
5354            return getResult(parts[0], count);
5355        }
5356
5357    };
5358    var postProcessors = {};
5359    var addPostProcessor = function(name, fc) {
5360        postProcessors[name] = fc;
5361    };
5362    // sprintf support
5363    var sprintf = (function() {
5364        function get_type(variable) {
5365            return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
5366        }
5367        function str_repeat(input, multiplier) {
5368            for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
5369            return output.join('');
5370        }
5371
5372        var str_format = function() {
5373            if (!str_format.cache.hasOwnProperty(arguments[0])) {
5374                str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
5375            }
5376            return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
5377        };
5378
5379        str_format.format = function(parse_tree, argv) {
5380            var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
5381            for (i = 0; i < tree_length; i++) {
5382                node_type = get_type(parse_tree[i]);
5383                if (node_type === 'string') {
5384                    output.push(parse_tree[i]);
5385                }
5386                else if (node_type === 'array') {
5387                    match = parse_tree[i]; // convenience purposes only
5388                    if (match[2]) { // keyword argument
5389                        arg = argv[cursor];
5390                        for (k = 0; k < match[2].length; k++) {
5391                            if (!arg.hasOwnProperty(match[2][k])) {
5392                                throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
5393                            }
5394                            arg = arg[match[2][k]];
5395                        }
5396                    }
5397                    else if (match[1]) { // positional argument (explicit)
5398                        arg = argv[match[1]];
5399                    }
5400                    else { // positional argument (implicit)
5401                        arg = argv[cursor++];
5402                    }
5403
5404                    if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
5405                        throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
5406                    }
5407                    switch (match[8]) {
5408                        case 'b': arg = arg.toString(2); break;
5409                        case 'c': arg = String.fromCharCode(arg); break;
5410                        case 'd': arg = parseInt(arg, 10); break;
5411                        case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
5412                        case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
5413                        case 'o': arg = arg.toString(8); break;
5414                        case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
5415                        case 'u': arg = Math.abs(arg); break;
5416                        case 'x': arg = arg.toString(16); break;
5417                        case 'X': arg = arg.toString(16).toUpperCase(); break;
5418                    }
5419                    arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
5420                    pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
5421                    pad_length = match[6] - String(arg).length;
5422                    pad = match[6] ? str_repeat(pad_character, pad_length) : '';
5423                    output.push(match[5] ? arg + pad : pad + arg);
5424                }
5425            }
5426            return output.join('');
5427        };
5428
5429        str_format.cache = {};
5430
5431        str_format.parse = function(fmt) {
5432            var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
5433            while (_fmt) {
5434                if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
5435                    parse_tree.push(match[0]);
5436                }
5437                else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
5438                    parse_tree.push('%');
5439                }
5440                else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
5441                    if (match[2]) {
5442                        arg_names |= 1;
5443                        var field_list = [], replacement_field = match[2], field_match = [];
5444                        if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
5445                            field_list.push(field_match[1]);
5446                            while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
5447                                if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
5448                                    field_list.push(field_match[1]);
5449                                }
5450                                else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
5451                                    field_list.push(field_match[1]);
5452                                }
5453                                else {
5454                                    throw('[sprintf] huh?');
5455                                }
5456                            }
5457                        }
5458                        else {
5459                            throw('[sprintf] huh?');
5460                        }
5461                        match[2] = field_list;
5462                    }
5463                    else {
5464                        arg_names |= 2;
5465                    }
5466                    if (arg_names === 3) {
5467                        throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
5468                    }
5469                    parse_tree.push(match);
5470                }
5471                else {
5472                    throw('[sprintf] huh?');
5473                }
5474                _fmt = _fmt.substring(match[0].length);
5475            }
5476            return parse_tree;
5477        };
5478
5479        return str_format;
5480    })();
5481
5482    var vsprintf = function(fmt, argv) {
5483        argv.unshift(fmt);
5484        return sprintf.apply(null, argv);
5485    };
5486
5487    addPostProcessor("sprintf", function(val, key, opts) {
5488        if (!opts.sprintf) return val;
5489
5490        if (Object.prototype.toString.apply(opts.sprintf) === '[object Array]') {
5491            return vsprintf(val, opts.sprintf);
5492        } else if (typeof opts.sprintf === 'object') {
5493            return sprintf(val, opts.sprintf);
5494        }
5495
5496        return val;
5497    });
5498    // public api interface
5499    i18n.init = init;
5500    i18n.setLng = setLng;
5501    i18n.preload = preload;
5502    i18n.addResourceBundle = addResourceBundle;
5503    i18n.removeResourceBundle = removeResourceBundle;
5504    i18n.loadNamespace = loadNamespace;
5505    i18n.loadNamespaces = loadNamespaces;
5506    i18n.setDefaultNamespace = setDefaultNamespace;
5507    i18n.t = translate;
5508    i18n.translate = translate;
5509    i18n.exists = exists;
5510    i18n.detectLanguage = f.detectLanguage;
5511    i18n.pluralExtensions = pluralExtensions;
5512    i18n.sync = sync;
5513    i18n.functions = f;
5514    i18n.lng = lng;
5515    i18n.addPostProcessor = addPostProcessor;
5516    i18n.options = o;
5517
5518})();
5519
5520
5521
5522/*********
5523* ../app/js/lib/jsdiff.js
5524**********/
5525(function() {/*
5526 * Javascript Diff Algorithm
5527 *  By John Resig (http://ejohn.org/)
5528 *  Modified by Chu Alan "sprite"
5529 *
5530 * Released under the MIT license.
5531 *
5532 * More Info:
5533 *  http://ejohn.org/projects/javascript-diff-algorithm/
5534 */
5535
5536function escape(s) {
5537    var n = s;
5538    n = n.replace(/&/g, "&amp;");
5539    n = n.replace(/</g, "&lt;");
5540    n = n.replace(/>/g, "&gt;");
5541    n = n.replace(/"/g, "&quot;");
5542
5543    return n;
5544}
5545
5546function diffString( o, n ) {
5547  o = o.replace(/\s+$/, '');
5548  n = n.replace(/\s+$/, '');
5549
5550  var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) );
5551  var str = "";
5552
5553  var oSpace = o.match(/\s+/g);
5554  if (oSpace == null) {
5555    oSpace = ["\n"];
5556  } else {
5557    oSpace.push("\n");
5558  }
5559  var nSpace = n.match(/\s+/g);
5560  if (nSpace == null) {
5561    nSpace = ["\n"];
5562  } else {
5563    nSpace.push("\n");
5564  }
5565
5566  if (out.n.length == 0) {
5567      for (var i = 0; i < out.o.length; i++) {
5568        str += '<del>' + escape(out.o[i]) + oSpace[i] + "</del>";
5569      }
5570  } else {
5571    if (out.n[0].text == null) {
5572      for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
5573        str += '<del>' + escape(out.o[n]) + oSpace[n] + "</del>";
5574      }
5575    }
5576
5577    for ( var i = 0; i < out.n.length; i++ ) {
5578      if (out.n[i].text == null) {
5579        str += '<ins>' + escape(out.n[i]) + nSpace[i] + "</ins>";
5580      } else {
5581        var pre = "";
5582
5583        for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
5584          pre += '<del>' + escape(out.o[n]) + oSpace[n] + "</del>";
5585        }
5586        str += " " + out.n[i].text + nSpace[i] + pre;
5587      }
5588    }
5589  }
5590  
5591  return str;
5592}
5593
5594function randomColor() {
5595    return "rgb(" + (Math.random() * 100) + "%, " + 
5596                    (Math.random() * 100) + "%, " + 
5597                    (Math.random() * 100) + "%)";
5598}
5599function diffString2( o, n ) {
5600  o = o.replace(/\s+$/, '');
5601  n = n.replace(/\s+$/, '');
5602
5603  var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) );
5604
5605  var oSpace = o.match(/\s+/g);
5606  if (oSpace == null) {
5607    oSpace = ["\n"];
5608  } else {
5609    oSpace.push("\n");
5610  }
5611  var nSpace = n.match(/\s+/g);
5612  if (nSpace == null) {
5613    nSpace = ["\n"];
5614  } else {
5615    nSpace.push("\n");
5616  }
5617
5618  var os = "";
5619  var colors = new Array();
5620  for (var i = 0; i < out.o.length; i++) {
5621      colors[i] = randomColor();
5622
5623      if (out.o[i].text != null) {
5624          os += '<span style="background-color: ' +colors[i]+ '">' + 
5625                escape(out.o[i].text) + oSpace[i] + "</span>";
5626      } else {
5627          os += "<del>" + escape(out.o[i]) + oSpace[i] + "</del>";
5628      }
5629  }
5630
5631  var ns = "";
5632  for (var i = 0; i < out.n.length; i++) {
5633      if (out.n[i].text != null) {
5634          ns += '<span style="background-color: ' +colors[out.n[i].row]+ '">' + 
5635                escape(out.n[i].text) + nSpace[i] + "</span>";
5636      } else {
5637          ns += "<ins>" + escape(out.n[i]) + nSpace[i] + "</ins>";
5638      }
5639  }
5640
5641  return { o : os , n : ns };
5642}
5643
5644function diff( o, n ) {
5645  var ns = new Object();
5646  var os = new Object();
5647  
5648  for ( var i = 0; i < n.length; i++ ) {
5649    if ( ns[ n[i] ] == null )
5650      ns[ n[i] ] = { rows: new Array(), o: null };
5651    ns[ n[i] ].rows.push( i );
5652  }
5653  
5654  for ( var i = 0; i < o.length; i++ ) {
5655    if ( os[ o[i] ] == null )
5656      os[ o[i] ] = { rows: new Array(), n: null };
5657    os[ o[i] ].rows.push( i );
5658  }
5659  
5660  for ( var i in ns ) {
5661    if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1 ) {
5662      n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] };
5663      o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] };
5664    }
5665  }
5666  
5667  for ( var i = 0; i < n.length - 1; i++ ) {
5668    if ( n[i].text != null && n[i+1].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 
5669         n[i+1] == o[ n[i].row + 1 ] ) {
5670      n[i+1] = { text: n[i+1], row: n[i].row + 1 };
5671      o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };
5672    }
5673  }
5674  
5675  for ( var i = n.length - 1; i > 0; i-- ) {
5676    if ( n[i].text != null && n[i-1].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 
5677         n[i-1] == o[ n[i].row - 1 ] ) {
5678      n[i-1] = { text: n[i-1], row: n[i].row - 1 };
5679      o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 };
5680    }
5681  }
5682  
5683  return { o: o, n: n };
5684}
5685
5686window.diffString = diffString;
5687
5688})();
5689
5690
5691/*********
5692* ../app/js/resources/en.js
5693**********/
5694sofia.resources['en'] = {
5695	"translation": {
5696		"name": "English",
5697		"menu": {
5698			"labels": {
5699				"about": "About",
5700				"addwindow": "Add Window",
5701				"settings": "Settings",
5702				"feedback": "Feedback",
5703				"options": "Options"
5704			},
5705			"feedback": {
5706				"name": "Name",
5707				"email": "Email",
5708				"comments": "Your Comments",
5709				"feature": "Feature Suggestion",
5710				"bug": "Bug Report",
5711				"other": "Other",
5712				"send": "Send",
5713				"thankyou": "Thank you for your feedback"
5714			},
5715			"search": {
5716				"placeholder": "Search",
5717				"suggestversion": "Open <strong>{0}</strong> in new window",
5718				"suggestreference": "Go to <strong>{0}</strong> in {1}",
5719				"suggestsearch": "Search \"<strong>{0}</strong>\" in {1}"
5720			},
5721			"config": {
5722				"font": "Style",
5723				"settings": "Settings",
5724				"tools": "Tools"
5725			},
5726			"reset": "restore",
5727			"themes": {
5728				"default": "Normal",
5729				"sepia": "Sepia",
5730				"dark": "Low Light"
5731			}
5732		},
5733		"plugins": {
5734			"visualfilters": {
5735				"button": "Visual Filters",
5736				"title": "Visual Filters",
5737				"newfilter": "New Filter",
5738				"strongsnumber": "Strong's #",
5739				"morphology": "Morphology",
5740				"style": "Style"
5741			},
5742			"eng2p": {
5743				"button": "English 2nd Person Plural",
5744				"title": "English Second Person Plural",
5745				"description": "Modern English does not distinguish between <em>
5745you singular</em> and <em>you plural</em>, so this plugin uses regional English equivalents to represent the Hebrew and Greek forms."
5746
5747			},
5748			"lemmapopup": {
5749				"findalloccurrences": "Find all occurrences (approximately __count__)"
5750			}
5751		},
5752		"windows": {
5753			"deafbible": {
5754				"label": "Deaf Bible"
5755			},
5756			"videobible": {
5757				"label": "Video Bible"
5758			},
5759			"bible": {
5760				"label": "Bible",
5761				"recentlyused": "Recently Used",
5762				"filter": "Filter...",
5763				"ot": "Old Testament",
5764				"nt": "New Testament",
5765				"dc": "Deuterocanonical Books",
5766				"more": "More",
5767				"less": "Less",
5768				"default": "Featured",
5769				"languages": "Language",
5770				"countries": "Country",
5771				"lemma":"Greek/Hebrew Popups",
5772				"audio":"Audio available"
5773			},
5774			"commentary": {
5775				"label": "Commentary"
5776			},
5777			"map": {
5778				"label": "Map",
5779				"placeholder": "Search..."
5780			},
5781			"search": {
5782				"label": "Search",
5783				"placeholder": "Search",
5784				"button": "Search",
5785				"results": "Results",
5786				"verses": "verses",
5787				"options": "Search Options"
5788			},
5789			"parallel": {
5790				"label": "Parallels",
5791				"loading": "Loading...",
5792				"showall": "Show All",
5793				"hideall": "Hide All"
5794			},
5795			"media": {
5796				"label": "Media"
5797			},
5798			"notes": {
5799				"label": "Notes"
5800			},
5801			"audio": {
5802				"label":"Audio",
5803				"options": "Audio Options",
5804				"synctext": "Sync Text (beta)",
5805				"autoplay": "Autoplay Next",
5806				"drama": "Dramatized",
5807				"nondrama": "Spoken"
5808			},
5809			"comparison": {
5810				"label": "Comparison"
5811			},
5812			"stats": {
5813				"label": "Statistics",
5814				"frequentwords":"Frequent Words"
5815			}
5816		},
5817		"names": {
5818			"en": "English",
5819			"fr": "anglais",
5820			"es": "Inglés",
5821			"de": "Englisch",
5822			"ru": "английский",
5823			"hi": "अंग्रेजी",
5824			"ar": "الإنجليزية",
5825			"af": "Engels",
5826			"bn": "ইংরেজি",
5827			"id": "Bahasa Inggris",
5828			"sw": "Kiingereza",
5829			"tr": "ingilizce",
5830			"ja": "英語",
5831			"zh-TW": "英語",
5832			"zh-CN": "英语"
5833		}
5834	}
5835}
5836
5837
5838
5839/*********
5840* ../app/js/resources/fr.js
5841**********/
5842sofia.resources['fr'] = {
5843	"translation": {
5844		"name": "Français",
5845		"menu": {
5846			"search": {
5847				"placeholder": "Recherche"
5848			},
5849			"config": {
5850				"font": "Style",
5851				"settings": "Paramètres",
5852				"tools": "Outils"
5853			},
5854			"reset": "remettre",
5855			"themes": {
5856				"default": "Normal",
5857				"sepia": "Sépia",
5858				"dark": "Low Light"
5859			},
5860			"labels": {
5861				"about": "Sur",
5862				"addwindow": "Ajouter fenêtre",
5863				"settings": "Paramètres",
5864				"feedback": "Réaction",
5865				"options": "Options"
5866			},
5867			"feedback": {
5868				"name": "Nom",
5869				"email": "Email",
5870				"comments": "Vos commentaires",
5871				"feature": "Caractéristique Suggestion",
5872				"bug": "Rapport de Bug",
5873				"other": "Autre",
5874				"send": "Envoyer",
5875				"thankyou": "Merci pour vos commentaires"
5876			}
5877		},
5878		"plugins": {
5879			"visualfilters": {
5880				"button": "Filtres visuels",
5881				"title": "Filtres visuels",
5882				"newfilter": "Nouveau filtre",
5883				"strongsnumber": "De Strong #",
5884				"morphology": "Morphologie",
5885				"style": "Style"
5886			},
5887			"eng2p": {
5888				"button": "Anglais 2ème personne pluriel",
5889				"title": "Anglais Deuxième personne pluriel"
5890			},
5891			"lemmapopup": {
5892				"findalloccurrences": "Trouver toutes les occurrences (environ __count__)"
5893			}
5894		},
5895		"windows": {
5896			"bible": {
5897				"label": "Bible",
5898				"filter": "Filtrer ...",
5899				"ot": "Ancien Testament",
5900				"nt": "Le Nouveau Testament",
5901				"dc": "Deutérocanoniques",
5902				"more": "Plus",
5903				"less": "Moins",
5904				"recentlyused": "Récemment utilisés",
5905				"languages": "Langues",
5906				"countries": "Pays"
5907			},
5908			"commentary": {
5909				"label": "Commentaire"
5910			},
5911			"map": {
5912				"label": "Cartes",
5913				"placeholder": "Recherche ..."
5914			},
5915			"search": {
5916				"label": "Recherche",
5917				"placeholder": "Recherche",
5918				"button": "Recherche",
5919				"results": "Résultats",
5920				"verses": "versets",
5921				"options": "options de recherche"
5922			},
5923			"media": {
5924				"label": "Médias"
5925			},
5926			"notes": {
5927				"label": "Remarques"
5928			},
5929			"audio": {
5930				"options": "Options audio",
5931				"synctext": "Synchronisation du texte (beta)",
5932				"autoplay": "Autoplay Suivant",
5933				"drama": "Drame",
5934				"nondrama": "Non Drame"
5935			},
5936			"parallel": {
5937				"label": "Parallels",
5938				"loading": "Chargement ...",
5939				"showall": "Tout afficher",
5940				"hideall": "Masquer tout"
5941			},
5942			"comparison": {
5943				"label": "Comparaison"
5944			}
5945		},
5946		"names": {
5947			"en": "French"
5948		}
5949	}
5950}
5951
5952
5953
5954/*********
5955* ../app/js/resources/de.js
5956**********/
5957sofia.resources['de'] = {
5958	"translation": {
5959		"name": "Deutsch",
5960		"menu": {
5961			"search": {
5962				"placeholder": "Suche"
5963			},
5964			"config": {
5965				"font": "Schriftart",
5966				"settings": "Einstellungen",
5967				"tools": "Werkzeuge"
5968			},
5969			"reset": "zurücksetzen",
5970			"labels": {
5971				"about": "Über",
5972				"addwindow": "Neues Fenster",
5973				"settings": "Einstellungen",
5974				"feedback": "Rückmeldung",
5975				"options": "Optionen"
5976			},
5977			"feedback": {
5978				"name": "Name",
5979				"email": "E-Mail",
5980				"comments": "Ihre Kommentare",
5981				"feature": "Feature-Vorschlag",
5982				"bug": "Fehlerhinweis",
5983				"other": "Sonstiges",
5984				"send": "Absenden",
5985				"thankyou": "Vielen Dank für Ihre Rückmeldung"
5986			},
5987			"themes": {
5988				"default": "Normal",
5989				"sepia": "Sepia",
5990				"dark": "Wenig Licht"
5991			}
5992		},
5993		"plugins": {
5994			"visualfilters": {
5995				"button": "Visuelle Filter",
5996				"title": "Visuelle Filter",
5997				"newfilter": "Neuer Filter",
5998				"strongsnumber": "Strong #",
5999				"morphology": "Morphologie",
6000				"style": "Stil"
6001			},
6002			"eng2p": {
6003				"button": "2. Person Plural Englisch",
6004				"title": "Englisch Zweite Person Plural"
6005			},
6006			"lemmapopup": {
6007				"findalloccurrences": "Alle Vorkommen (etwa __count__)"
6008			}
6009		},
6010		"windows": {
6011			"deafbible": {
6012				"label": "Bibel für Taube"
6013			},
6014			"videobible": {
6015				"label": "Video-Bibel"
6016			},
6017			"bible": {
6018				"label": "Bibel",
6019				"filter": "Filter ...",
6020				"ot": "Altes Testament",
6021				"nt": "Neues Testament",
6022				"dc": "Deuterokanonische Bücher",
6023				"more": "Mehr",
6024				"less": "Weniger",
6025				"recentlyused": "Zuletzt verwendet",
6026				"languages": "Sprachen",
6027				"countries": "Länder",
6028				"lemma":"Griechisch/Hebräisch-Popups",
6029				"audio":"Audio verfügbar"
6030			},
6031			"commentary": {
6032				"label": "Kommentar"
6033			},
6034			"map": {
6035				"label": "Karten",
6036				"placeholder": "Suche ..."
6037			},
6038			"search": {
6039				"label": "Suche",
6040				"placeholder": "Suche",
6041				"button": "Suche",
6042				"results": "Ergebnisse",
6043				"verses": "Verse",
6044				"options": "Suchoptionen"
6045			},
6046			"media": {
6047				"label": "Medien"
6048			},
6049			"notes": {
6050				"label": "Notizen"
6051			},
6052			"audio": {
6053				"options": "Audio-Optionen",
6054				"synctext": "Text synchron halten (beta)",
6055				"autoplay": "Automatisch nächstes spielen",
6056				"drama": "Dramatisiert",
6057				"nondrama": "Vorgelesen"
6058			},
6059			"parallel": {
6060				"label": "Parallelstellen",
6061				"loading": "Wird geladen ...",
6062				"showall": "Alle anzeigen",
6063				"hideall": "Alle ausblenden"
6064			},
6065			"comparison": {
6066				"label": "Vergleich"
6067			},
6068			"stats": {
6069				"label": "Statistiken",
6070				"frequentwords":"Häufige Wörter"
6071			}
6072		},
6073		"names": {
6074			"en": "German"
6075		}
6076	}
6077}
6078
6079
6080
6081/*********
6082* ../app/js/resources/es.js
6083**********/
6084sofia.resources['es'] = {
6085	"translation": {
6086		"name": "español",
6087		"menu": {
6088			"search": {
6089				"placeholder": "Búsqueda"
6090			},
6091			"config": {
6092				"font": "Fuente",
6093				"settings": "Configuración",
6094				"tools": "Instrumentos"
6095			},
6096			"reset": "reajustar",
6097			"labels": {
6098				"about": "Acerca de",
6099				"addwindow": "Añadir Ventana",
6100				"settings": "Ajustes",
6101				"feedback": "Realimentación",
6102				"options": "Opciones"
6103			},
6104			"feedback": {
6105				"name": "Nombre",
6106				"email": "Correo electrónico",
6107				"comments": "Sus comentarios",
6108				"feature": "Característica Sugerencia",
6109				"bug": "Informar de un error",
6110				"other": "Otro",
6111				"send": "Enviar",
6112				"thankyou": "Gracias por su colaboración"
6113			},
6114			"themes": {
6115				"default": "Normal",
6116				"sepia": "Sepia",
6117				"dark": "Luz baja"
6118			}
6119		},
6120		"plugins": {
6121			"visualfilters": {
6122				"button": "Visual Filtros",
6123				"title": "Visual Filtros",
6124				"newfilter": "Nuevo filtro",
6125				"strongsnumber": "Strong #",
6126				"morphology": "Morfología",
6127				"style": "Estilo"
6128			},
6129			"eng2p": {
6130				"button": "Inglés segunda persona del plural",
6131				"title": "Inglés Segunda Persona del Plural"
6132			},
6133			"lemmapopup": {
6134				"findalloccurrences": "Buscar todas las ocurrencias (aproximadamente __count__)"
6135			}
6136		},
6137		"windows": {
6138			"bible": {
6139				"label": "Biblia",
6140				"filter": "Filtro ...",
6141				"ot": "Antiguo Testamento",
6142				"nt": "Nuevo Testamento",
6143				"dc": "Libros Deuterocanónicos",
6144				"more": "Más",
6145				"less": "Menos",
6146				"recentlyused": "Usados ​​recientemente",
6147				"languages": "Idiomas",
6148				"countries": "Países"
6149			},
6150			"commentary": {
6151				"label": "Comentario"
6152			},
6153			"map": {
6154				"label": "Mapas",
6155				"placeholder": "Buscar ..."
6156			},
6157			"search": {
6158				"label": "Búsqueda",
6159				"placeholder": "Búsqueda",
6160				"button": "Búsqueda",
6161				"results": "Resultados",
6162				"verses": "versos",
6163				"options": "Opciones de Búsqueda"
6164			},
6165			"media": {
6166				"label": "Medios de comunicación"
6167			},
6168			"notes": {
6169				"label": "Notas"
6170			},
6171			"audio": {
6172				"options": "Opciones de audio",
6173				"synctext": "Sincronizar texto (beta)",
6174				"autoplay": "Reproducción automática Siguiente",
6175				"drama": "Drama",
6176				"nondrama": "No drama"
6177			},
6178			"parallel": {
6179				"label": "Paralelas",
6180				"loading": "Cargando ...",
6181				"showall": "Mostrar todo",
6182				"hideall": "Ocultar todo"
6183			},
6184			"comparison": {
6185				"label": "Comparación"
6186			}
6187		},
6188		"names": {
6189			"en": "Spanish"
6190		}
6191	}
6192}
6193
6194
6195
6196/*********
6197* ../app/js/resources/ru.js
6198**********/
6199sofia.resources['ru'] = {
6200	"translation": {
6201		"name": "русский",
6202		"menu": {
6203			"search": {
6204				"placeholder": "Поиск"
6205			},
6206			"config": {
6207				"font": "Шрифт",
6208				"settings": "Настройки",
6209				"tools": "Инструментарий"
6210			},
6211			"reset": "сброс",
6212			"themes": {
6213				"default": "Нормальный",
6214				"sepia": "Сепия",
6215				"dark": "Низкая освещенность"
6216			},
6217			"labels": {
6218				"about": "О",
6219				"addwindow": "Добавить окно",
6220				"settings": "Настройки",
6221				"feedback": "Обратная связь",
6222				"options": "Опции"
6223			},
6224			"feedback": {
6225				"name": "Имя",
6226				"email": "Email",
6227				"comments": "Ваши комментарии",
6228				"feature": "Особенность Предложение",
6229				"bug": "Сообщение об ошибке",
6230				"other": "Другой",
6231				"send": "Послать",
6232				"thankyou": "Спасибо за Ваш отзыв"
6233			}
6234		},
6235		"plugins": {
6236			"visualfilters": {
6237				"button": "Визуальные Фильтры",
6238				"title": "Визуальные Фильтры",
6239				"newfilter": "Новый фильтр",
6240				"strongsnumber": "# Стронга",
6241				"morphology": "Морфология",
6242				"style": "Стиль"
6243			},
6244			"eng2p": {
6245				"button": "Английский 2-го лица множественного числа",
6246				"title": "Английский Второе Лицо Множественное"
6247			},
6248			"lemmapopup": {
6249				"findalloccurrences": "Найти все вхождения (примерно __count__)"
6250			}
6251		},
6252		"windows": {
6253			"bible": {
6254				"label": "Библия",
6255				"filter": "Фильтр ...",
6256				"ot": "Ветхий Завет",
6257				"nt": "Новый Завет",
6258				"dc": "Второканонические книги",
6259				"more": "Более",
6260				"less": "Меньше",
6261				"recentlyused": "Недавно использованные",
6262				"languages": "Языки",
6263				"countries": "Страны"
6264			},
6265			"commentary": {
6266				"label": "Комментарий"
6267			},
6268			"map": {
6269				"label": "Карты",
6270				"placeholder": "Поиск ..."
6271			},
6272			"search": {
6273				"label": "Поиск",
6274				"placeholder": "Поиск",
6275				"button": "Поиск",
6276				"results": "Результаты",
6277				"verses": "вирши",
6278				"options": "Параметры поиска"
6279			},
6280			"media": {
6281				"label": "Средства массовой информации"
6282			},
6283			"notes": {
6284				"label": "Примечания"
6285			},
6286			"audio": {
6287				"options": "Audio Options",
6288				"synctext": "Синхронизация текст (бета-версия)",
6289				"autoplay": "Автозапуск Следующая",
6290				"drama": "Драма",
6291				"nondrama": "Номера для Драма"
6292			},
6293			"parallel": {
6294				"label": "Parallels",
6295				"loading": "Loading ...",
6296				"showall": "Показать все",
6297				"hideall": "Скрыть все"
6298			},
6299			"comparison": {
6300				"label": "Сравнение"
6301			}
6302		},
6303		"names": {
6304			"en": "Russian"
6305		}
6306	}
6307}
6308
6309
6310
6311/*********
6312* ../app/js/resources/hi.js
6313**********/
6314sofia.resources['hi'] = {
6315	"translation": {
6316		"name": "हिन्दी",
6317		"menu": {
6318			"search": {
6319				"placeholder": "खोज"
6320			},
6321			"config": {
6322				"font": "फ़ॉन्ट",
6323				"settings": "सेटिंग",
6324				"tools": "उपकरण"
6325			},
6326			"tools": {
6327				"visualfilters": "विजुअल फ़िल्टर",
6328				"eng2p": "अंग्रेजी 2 व्यक्ति बहुवचन"
6329			},
6330			"reset": "रीसेट",
6331			"themes": {
6332				"default": "साधारण",
6333				"sepia": "एक प्रकार की मछली",
6334				"dark": "कम रोशनी"
6335			},
6336			"labels": {
6337				"about": "तकरीबन",
6338				"addwindow": "खिड़की जोड़ें",
6339				"settings": "सेटिंग्स",
6340				"feedback": "प्रतिक्रिया",
6341				"options": "विकल्प"
6342			},
6343			"feedback": {
6344				"name": "नाम",
6345				"email": "ईमेल",
6346				"comments": "आपकी टिप्पणी",
6347				"feature": "सुविधा सुझाव",
6348				"bug": "बग रिपोर्ट",
6349				"other": "अन्य",
6350				"send": "भेजो",
6351				"thankyou": "आपकी प्रतिक्रिया के लिए धन्यवाद"
6352			}
6353		},
6354		"plugins": {
6355			"visualfilters": {
6356				"button": "विजुअल फ़िल्टर",
6357				"title": "विजुअल फ़िल्टर",
6358				"newfilter": "नया फ़िल्टर",
6359				"strongsnumber": "मजबूत #",
6360				"morphology": "आकृति विज्ञान",
6361				"style": "शैली"
6362			},
6363			"eng2p": {
6364				"button": "अंग्रेजी 2 व्यक्ति बहुवचन",
6365				"title": "अंग्रेजी 2 व्यक्ति बहुवचन"
6366			},
6367			"lemmapopup": {
6368				"findalloccurrences": "सभी घटनाओं का पता (लगभग __count__)"
6369			}
6370		},
6371		"windows": {
6372			"bible": {
6373				"label": "बाइबल",
6374				"filter": "तक...",
6375				"ot": "ओल्ड टेस्टामेंट",
6376				"nt": "न्यू टेस्टामेंट",
6377				"dc": "Deuterocanonical Books",
6378				"more": "अधिक",
6379				"less": "कम",
6380				"recentlyused": "हाल ही में प्रयुक्त",
6381				"languages": "भाषाएँ",
6382				"countries": "देश"
6383			},
6384			"commentary": {
6385				"label": "टीका"
6386			},
6387			"map": {
6388				"label": "मानचित्र",
6389				"placeholder": "खोज..."
6390			},
6391			"search": {
6392				"label": "खोज",
6393				"placeholder": "खोज",
6394				"button": "खोज",
6395				"results": "परिणाम",
6396				"verses": "छंद",
6397				"options": "खोज विकल्प"
6398			},
6399			"media": {
6400				"label": "मीडिया"
6401			},
6402			"notes": {
6403				"label": "नोट्स"
6404			},
6405			"audio": {
6406				"options": "ऑडियो विकल्प",
6407				"synctext": "सिंक पाठ ( बीटा )",
6408				"autoplay": "ऑटोप्ले अगला",
6409				"drama": "ड्रामा",
6410				"nondrama": "गैर ड्रामा"
6411			},
6412			"parallel": {
6413				"label": "समानताएं",
6414				"loading": "लोडिंग ...",
6415				"showall": "सब दिखाएं",
6416				"hideall": "सभी छुपाएं"
6417			},
6418			"comparison": {
6419				"label": "तुलना"
6420			}
6421		},
6422		"names": {
6423			"en": "Hindi"
6424		}
6425	}
6426}
6427
6428
6429
6430/*********
6431* ../app/js/resources/ar.js
6432**********/
6433sofia.resources['ar'] = {
6434	"translation": {
6435		"name": "العربية",
6436		"menu": {
6437			"search": {
6438				"placeholder": "بحث"
6439			},
6440			"config": {
6441				"font": "الخط",
6442				"settings": "إعدادات",
6443				"tools": "أدوات"
6444			},
6445			"reset": "إعادة ضبط",
6446			"themes": {
6447				"default": "افتراضي",
6448				"sepia": "بني داكن",
6449				"dark": "خلفيفة غامقة"
6450			},
6451			"labels": {
6452				"about": "حول",
6453				"addwindow": "إضافة النافذة",
6454				"settings": "الإعدادات",
6455				"feedback": "ردود الفعل",
6456				"options": "خيارات"
6457			},
6458			"feedback": {
6459				"name": "اسم",
6460				"email": "البريد الإلكتروني",
6461				"comments": "تحديثاتك",
6462				"feature": "ميزة اقتراح",
6463				"bug": "تقرير علة",
6464				"other": "آخر",
6465				"send": "إرسال",
6466				"thankyou": "شكرا لملاحظاتك"
6467			}
6468		},
6469		"plugins": {
6470			"visualfilters": {
6471				"button": "مرشحات بصرية",
6472				"title": "مرشحات بصرية",
6473				"newfilter": "مرشح جديد",
6474				"strongsnumber": "رقم فهرس سترونج",
6475				"morphology": "لغويات",
6476				"style": "أسلوب"
6477			},
6478			"eng2p": {
6479				"button": "استخدام كلمة أنت للجمع في الإنجليزية",
6480				"title": "استخدام كلمة أنت للجمع في الإنجليزية"
6481			},
6482			"lemmapopup": {
6483				"findalloccurrences": "بحث كل الحالات"
6484			}
6485		},
6486		"windows": {
6487			"bible": {
6488				"label": "الكتاب المقدس",
6489				"filter": "ترشيح القائمة",
6490				"ot": "العهد القديم",
6491				"nt": "العهد الجديد",
6492				"dc": "الأسفار القانونية",
6493				"more": "أكثر",
6494				"less": "أقل",
6495				"recentlyused": "المستخدمة مؤخرا",
6496				"languages": "اللغات",
6497				"countries": "الدول"
6498			},
6499			"commentary": {
6500				"label": "تفاسير"
6501			},
6502			"map": {
6503				"label": "خرائط",
6504				"placeholder": "بحث"
6505			},
6506			"search": {
6507				"label": "بحث",
6508				"placeholder": "بحث",
6509				"button": "بحث",
6510				"results": "النتائج",
6511				"verses": "الآيات",
6512				"options": "خيارات أكثر للبحث"
6513			},
6514			"media": {
6515				"label": "سمعيات وبصريات"
6516			},
6517			"notes": {
6518				"label": "ملاحظات"
6519			},
6520			"audio": {
6521				"options": "خيارات الصوت",
6522				"synctext": "تحريك النص مع الصوت",
6523				"autoplay": "تشغيل الأصحاح التالي تلقائياً",
6524				"drama": "قراءة درامية",
6525				"nondrama": "قراءة غير درامية"
6526			},
6527			"parallel": {
6528				"label": "المتوازيات",
6529				"loading": "تحميل ...",
6530				"showall": "عرض جميع",
6531				"hideall": "إخفاء الكل"
6532			},
6533			"comparison": {
6534				"label": "مقارنة"
6535			}
6536		},
6537		"names": {
6538			"en": "Arabic"
6539		}
6540	}
6541}
6542
6543
6544
6545/*********
6546* ../app/js/resources/af.js
6547**********/
6548sofia.resources['af'] = {
6549	"translation": {
6550		"name": "Afrikaans",
6551		"menu": {
6552			"search": {
6553				"placeholder": "Soek"
6554			},
6555			"config": {
6556				"font": "Skrif",
6557				"settings": "Instellings",
6558				"tools": "Gereedskap"
6559			},
6560			"reset": "herstel",
6561			"themes": {
6562				"default": "Normale",
6563				"sepia": "Sepia",
6564				"dark": "Lae lig"
6565			},
6566			"labels": {
6567				"about": "Oor",
6568				"addwindow": "Voeg Venster",
6569				"settings": "Instellings",
6570				"feedback": "Terugvoer",
6571				"options": "Opsies"
6572			},
6573			"feedback": {
6574				"name": "Naam",
6575				"email": "E-pos",
6576				"comments": "Jou kommentaar",
6577				"feature": "Funksie Voorstelle",
6578				"bug": "Bug Rapporteer",
6579				"other": "Ander",
6580				"send": "Stuur",
6581				"thankyou": "Dankie vir jou terugvoer"
6582			}
6583		},
6584		"plugins": {
6585			"visualfilters": {
6586				"button": "Visuele Filters",
6587				"title": "Visuele Filters",
6588				"newfilter": "Nuwe weergawe",
6589				"strongsnumber": "Strong&#39;s #",
6590				"morphology": "Morfologie",
6591				"style": "Style"
6592			},
6593			"eng2p": {
6594				"button": "Engels 2de persoon meervoud",
6595				"title": "Engels Tweede persoon meervoud"
6596			},
6597			"lemmapopup": {
6598				"findalloccurrences": "Vind alle gebeurtenisse (ongeveer __count__)"
6599			}
6600		},
6601		"windows": {
6602			"bible": {
6603				"label": "Bybel",
6604				"filter": "Filter ...",
6605				"ot": "Ou Testament",
6606				"nt": "Nuwe Testament",
6607				"dc": "Deuterokanonieke Boeke",
6608				"more": "Meer",
6609				"less": "Minder",
6610				"recentlyused": "Onlangs gebruik",
6611				"languages": "Tale",
6612				"countries": "Lande"
6613			},
6614			"commentary": {
6615				"label": "Kommentaar"
6616			},
6617			"map": {
6618				"label": "Kaarte",
6619				"placeholder": "Soek ..."
6620			},
6621			"search": {
6622				"label": "Soek",
6623				"placeholder": "Soek",
6624				"button": "Soek",
6625				"results": "Resultate",
6626				"verses": "verse",
6627				"options": "Soek opsies"
6628			},
6629			"media": {
6630				"label": "Media"
6631			},
6632			"notes": {
6633				"label": "Notes"
6634			},
6635			"audio": {
6636				"options": "Audio Options",
6637				"synctext": "Sinkroniseer teks (beta)",
6638				"autoplay": "Vakansie speel Volgende",
6639				"drama": "Drama",
6640				"nondrama": "Nie-Drama"
6641			},
6642			"parallel": {
6643				"label": "Parallelle",
6644				"loading": "Laai ...",
6645				"showall": "Wys Alle",
6646				"hideall": "Verberg Alle"
6647			},
6648			"comparison": {
6649				"label": "Vergelyking"
6650			}
6651		},
6652		"names": {
6653			"en": "Afrikaans"
6654		}
6655	}
6656}
6657
6658
6659
6660/*********
6661* ../app/js/resources/bn.js
6662**********/
6663sofia.resources['bn'] = {
6664	"translation": {
6665		"name": "বাংলা",
6666		"menu": {
6667			"search": {
6668				"placeholder": "অনুসন্ধান"
6669			},
6670			"config": {
6671				"font": "ফন্ট",
6672				"settings": "সেটিংস",
6673				"tools": "সরঞ্জাম"
6674			},
6675			"reset": "রিসেট করুন",
6676			"themes": {
6677				"default": "স্বাভাবিক",
6678				"sepia": "ছবি আঁকার একধরনের কালচে - বাদামী রং",
6679				"dark": "কম আলো"
6680			},
6681			"labels": {
6682				"about": "প্রায়",
6683				"addwindow": "যোগ উইন্ডো",
6684				"settings": "সেটিংস",
6685				"feedback": "প্রতিক্রিয়া",
6686				"options": "বিকল্প"
6687			},
6688			"feedback": {
6689				"name": "নাম",
6690				"email": "ইমেল",
6691				"comments": "আপনার মন্তব্য",
6692				"feature": "বৈশিষ্ট্য পরামর্শ",
6693				"bug": "বাগ রিপোর্ট",
6694				"other": "অন্যান্য",
6695				"send": "পাঠান",
6696				"thankyou": "আপনার প্রতিক্রিয়ার জন্য ধন্যবাদ"
6697			}
6698		},
6699		"plugins": {
6700			"visualfilters": {
6701				"button": "ভিসুয়াল ফিল্টার",
6702				"title": "ভিসুয়াল ফিল্টার",
6703				"newfilter": "নতুন ফিল্টার",
6704				"strongsnumber": "এর স্ট্রং #",
6705				"morphology": "অঙ্গসংস্থানবিদ্যা",
6706				"style": "শৈলী"
6707			},
6708			"eng2p": {
6709				"button": "ইংরেজি 2nd ব্যক্তি বহুবচন",
6710				"title": "ইংরেজি দ্বিতীয় ব্যক্তি বহুবচন"
6711			},
6712			"lemmapopup": {
6713				"findalloccurrences": "সব ঘটনার খুঁজুন (আনুমানিক __count__)"
6714			}
6715		},
6716		"windows": {
6717			"bible": {
6718				"label": "বাইবেল",
6719				"filter": "ফিল্টার ...",
6720				"ot": "ওল্ড টেস্টামেন্ট",
6721				"nt": "নববিধান",
6722				"dc": "Deuterocanonical বই",
6723				"more": "অধিক",
6724				"less": "কম",
6725				"recentlyused": "সম্প্রতি ব্যবহৃত",
6726				"languages": "ভাষা",
6727				"countries": "দেশ সমূহ"
6728			},
6729			"commentary": {
6730				"label": "ভাষ্য"
6731			},
6732			"map": {
6733				"label": "মানচিত্র",
6734				"placeholder": "অনুসন্ধান ..."
6735			},
6736			"search": {
6737				"label": "অনুসন্ধান",
6738				"placeholder": "অনুসন্ধান",
6739				"button": "অনুসন্ধান",
6740				"results": "ফলাফল",
6741				"verses": "আয়াত",
6742				"options": "অনুসন্ধান বিকল্প"
6743			},
6744			"media": {
6745				"label": "মিডিয়া"
6746			},
6747			"notes": {
6748				"label": "নোট"
6749			},
6750			"audio": {
6751				"options": "অডিও বিকল্প",
6752				"synctext": "সিঙ্ক টেক্সট (বিটা)",
6753				"autoplay": "অটোপ্লে পরবর্তী",
6754				"drama": "নাটক",
6755				"nondrama": "অ নাটক"
6756			},
6757			"parallel": {
6758				"label": "সমান্তরাল",
6759				"loading": "লোড হচ্ছে ...",
6760				"showall": "সমস্ত দেখান",
6761				"hideall": "সমস্ত লুকান"
6762			},
6763			"comparison": {
6764				"label": "তুলনা"
6765			}
6766		},
6767		"names": {
6768			"en": "Bengali"
6769		}
6770	}
6771}
6772
6773
6774
6775/*********
6776* ../app/js/resources/id.js
6777**********/
6778sofia.resources['id'] = {
6779	"translation": {
6780		"name": "Bahasa Indonesia",
6781		"menu": {
6782			"search": {
6783				"placeholder": "Pencarian"
6784			},
6785			"config": {
6786				"font": "Fon",
6787				"settings": "Pengaturan",
6788				"tools": "Alat"
6789			},
6790			"reset": "ulang",
6791			"themes": {
6792				"default": "Normal",
6793				"sepia": "Warna coklat tua",
6794				"dark": "Cahaya Rendah"
6795			},
6796			"labels": {
6797				"about": "Tentang",
6798				"addwindow": "Tambahkan Jendela",
6799				"settings": "Pengaturan",
6800				"feedback": "Umpan Balik",
6801				"options": "Pilihan"
6802			},
6803			"feedback": {
6804				"name": "Nama",
6805				"email": "Email",
6806				"comments": "Komentar Anda",
6807				"feature": "Fitur Saran",
6808				"bug": "Bug Report",
6809				"other": "Yang Lainnya",
6810				"send": "Mengirim",
6811				"thankyou": "Terima kasih atas tanggapan Anda"
6812			}
6813		},
6814		"plugins": {
6815			"visualfilters": {
6816				"button": "Visual Filter",
6817				"title": "Visual Filter",
6818				"newfilter": "Filter baru",
6819				"strongsnumber": "Strong #",
6820				"morphology": "Morfologi",
6821				"style": "Gaya"
6822			},
6823			"eng2p": {
6824				"button": "English 2 Orang Plural",
6825				"title": "English Kedua Orang Plural"
6826			},
6827			"lemmapopup": {
6828				"findalloccurrences": "Cari semua kejadian (sekitar __count__)"
6829			}
6830		},
6831		"windows": {
6832			"bible": {
6833				"label": "Alkitab",
6834				"filter": "Filter ...",
6835				"ot": "Perjanjian Lama",
6836				"nt": "Perjanjian Baru",
6837				"dc": "Deuterokanonika Books",
6838				"more": "Lebih",
6839				"less": "Kurang",
6840				"recentlyused": "Baru Digunakan",
6841				"languages": "Bahasa",
6842				"countries": "Negara"
6843			},
6844			"commentary": {
6845				"label": "Komentar"
6846			},
6847			"map": {
6848				"label": "Peta",
6849				"placeholder": "Cari ..."
6850			},
6851			"search": {
6852				"label": "Pencarian",
6853				"placeholder": "Pencarian",
6854				"button": "Pencarian",
6855				"results": "Hasil",
6856				"verses": "ayat",
6857				"options": "Opsi pencarian"
6858			},
6859			"media": {
6860				"label": "Media"
6861			},
6862			"notes": {
6863				"label": "Catatan"
6864			},
6865			"audio": {
6866				"options": "Audio Pilihan",
6867				"synctext": "Sync Text (beta)",
6868				"autoplay": "Autoplay Berikutnya",
6869				"drama": "Drama",
6870				"nondrama": "Non-Drama"
6871			},
6872			"parallel": {
6873				"label": "Parallels",
6874				"loading": "Loading ...",
6875				"showall": "Tampilkan Semua",
6876				"hideall": "Sembunyikan Semua"
6877			},
6878			"comparison": {
6879				"label": "Perbandingan"
6880			}
6881		},
6882		"names": {
6883			"en": "Indonesian"
6884		}
6885	}
6886}
6887
6888
6889
6890/*********
6891* ../app/js/resources/sw.js
6892**********/
6893sofia.resources['sw'] = {
6894	"translation": {
6895		"name": "Kiswahili",
6896		"menu": {
6897			"search": {
6898				"placeholder": "Search"
6899			},
6900			"config": {
6901				"font": "Font",
6902				"settings": "Mazingira",
6903				"tools": "Vyombo vya"
6904			},
6905			"reset": "upya",
6906			"themes": {
6907				"default": "Kawaida",
6908				"sepia": "Sepia",
6909				"dark": "Low Mwanga"
6910			},
6911			"labels": {
6912				"about": "Kuhusu",
6913				"addwindow": "Kuongeza Window",
6914				"settings": "Mipangilio",
6915				"feedback": "Maoni",
6916				"options": "Options"
6917			},
6918			"feedback": {
6919				"name": "Jina",
6920				"email": "Email",
6921				"comments": "Maoni yako",
6922				"feature": "Feature Maoni",
6923				"bug": "Bug Ripoti",
6924				"other": "Nyingine",
6925				"send": "Kutuma",
6926				"thankyou": "Asante sana kwa maoni yako"
6927			}
6928		},
6929		"plugins": {
6930			"visualfilters": {
6931				"button": "Visual Filters",
6932				"title": "Visual Filters",
6933				"newfilter": "New Filter",
6934				"strongsnumber": "Nguvu ya #",
6935				"morphology": "Maumbile",
6936				"style": "Mtindo"
6937			},
6938			"eng2p": {
6939				"button": "English 2 Mtu vyama vingi",
6940				"title": "Kiingereza Pili Mtu vyama vingi"
6941			},
6942			"lemmapopup": {
6943				"findalloccurrences": "Kupata matukio yote (takriban __count__)"
6944			}
6945		},
6946		"windows": {
6947			"bible": {
6948				"label": "Biblia",
6949				"filter": "Filter ...",
6950				"ot": "Agano la Kale",
6951				"nt": "New Testament",
6952				"dc": "Deuterocanonical vitabu",
6953				"more": "Zaidi",
6954				"less": "Chini ya",
6955				"recentlyused": "Kutumika hivi karibuni",
6956				"languages": "Lugha",
6957				"countries": "Nchi"
6958			},
6959			"commentary": {
6960				"label": "Ufafanuzi"
6961			},
6962			"map": {
6963				"label": "Ramani",
6964				"placeholder": "Search ..."
6965			},
6966			"search": {
6967				"label": "Search",
6968				"placeholder": "Search",
6969				"button": "Search",
6970				"results": "Matokeo",
6971				"verses": "mistari",
6972				"options": "Search Options"
6973			},
6974			"media": {
6975				"label": "Vyombo vya habari"
6976			},
6977			"notes": {
6978				"label": "Maelezo"
6979			},
6980			"audio": {
6981				"options": "Chaguzi Audio",
6982				"synctext": "Sync Nakala (beta)",
6983				"autoplay": "Autoplay Ifwatayo",
6984				"drama": "Mchezo wa kuigiza",
6985				"nondrama": "Mashirika yasiyo ya Drama"
6986			},
6987			"parallel": {
6988				"label": "Sambamba",
6989				"loading": "Loading ...",
6990				"showall": "Kuonyesha wote",
6991				"hideall": "Ficha All"
6992			},
6993			"comparison": {
6994				"label": "Kulinganisha"
6995			}
6996		},
6997		"names": {
6998			"en": "Swahili"
6999		}
7000	}
7001}
7002
7003
7004
7005/*********
7006* ../app/js/resources/tr.js
7007**********/
7008sofia.resources['tr'] = {
7009	"translation": {
7010		"name": "Türkçe",
7011		"menu": {
7012			"search": {
7013				"placeholder": "Arama"
7014			},
7015			"config": {
7016				"font": "Stil",
7017				"settings": "Ayarlar",
7018				"tools": "Araçlar"
7019			},
7020			"reset": "sıfırla",
7021			"themes": {
7022				"default": "Açık",
7023				"sepia": "Sepya",
7024				"dark": "Koyu"
7025			},
7026			"labels": {
7027				"about": "Hakkında",
7028				"addwindow": "Pencere ekle",
7029				"settings": "Ayarlar",
7030				"feedback": "Geribesleme",
7031				"options": "Seçenekler"
7032			},
7033			"feedback": {
7034				"name": "Isim",
7035				"email": "E-posta",
7036				"comments": "Yorumlarınız",
7037				"feature": "Özellik Öneri",
7038				"bug": "Hata Raporu",
7039				"other": "Diğer",
7040				"send": "Göndermek",
7041				"thankyou": "Görüşleriniz için teşekkür ederiz"
7042			}
7043		},
7044		"plugins": {
7045			"visualfilters": {
7046				"button": "Görsel Filtreler",
7047				"title": "Görsel Filtreler",
7048				"newfilter": "Yeni Filtre",
7049				"strongsnumber": "Strong #",
7050				"morphology": "Biçimbilgisi",
7051				"style": "Stil"
7052			},
7053			"eng2p": {
7054				"button": "İngilizce 2. Kişi Çoğul",
7055				"title": "İngilizce İkinci Kişi Çoğul"
7056			},
7057			"lemmapopup": {
7058				"findalloccurrences": "Tüm örneği bul (yaklaşık __count__)"
7059			}
7060		},
7061		"windows": {
7062			"bible": {
7063				"label": "Katsal Kitap",
7064				"filter": "Filtre ...",
7065				"ot": "Eski Antlaşma",
7066				"nt": "Yeni Antlaşma",
7067				"dc": "Apokrif",
7068				"more": "Daha fazla",
7069				"less": "Daha az",
7070				"recentlyused": "Son Kullanılan",
7071				"languages": "Diller",
7072				"countries": "Ülkeler"
7073			},
7074			"commentary": {
7075				"label": "Açıklamalar"
7076			},
7077			"map": {
7078				"label": "Haritalar",
7079				"placeholder": "Arama..."
7080			},
7081			"search": {
7082				"label": "Arama",
7083				"placeholder": "Arama",
7084				"button": "Arama",
7085				"results": "Sonuçlar",
7086				"verses": "ayetler",
7087				"options": "Arama Seçenekleri"
7088			},
7089			"parallel": {
7090				"label": "Paralellik",
7091				"loading": "Yükleniyor...",
7092				"showall": "Hepsi Göster",
7093				"hideall": "Hepsi Gizle"
7094			},
7095			"media": {
7096				"label": "Medya"
7097			},
7098			"notes": {
7099				"label": "Notlar"
7100			},
7101			"audio": {
7102				"options": "Ses Seçenekleri",
7103				"synctext": "Sütün Eşleşme (beta)",
7104				"autoplay": "Otomatik Yürüt",
7105				"drama": "Dramatize Edilmiş",
7106				"nondrama": "Tekil Konuşma"
7107			},
7108			"comparison": {
7109				"label": "Karşılaştırma"
7110			}
7111		},
7112		"names": {
7113			"en": "Turkish",
7114			"de": "Türkisch",
7115			"zh-TW": "英語",
7116			"zh-CN": "英语",
7117			"es": "Turco",
7118			"fr": "turc"
7119		}
7120	}
7121}
7122
7123
7124
7125/*********
7126* ../app/js/resources/ja.js
7127**********/
7128sofia.resources['ja'] = {
7129	"translation": {
7130		"name": "日本語",
7131		"menu": {
7132			"search": {
7133				"placeholder": "検索"
7134			},
7135			"config": {
7136				"font": "フォント",
7137				"settings": "[設定]",
7138				"tools": "ツール"
7139			},
7140			"reset": "リセット",
7141			"themes": {
7142				"default": "通常の",
7143				"sepia": "セピア",
7144				"dark": "微光"
7145			},
7146			"labels": {
7147				"about": "約",
7148				"addwindow": "ウィンドウを追加",
7149				"settings": "[設定]",
7150				"feedback": "フィードバック",
7151				"options": "オプション"
7152			},
7153			"feedback": {
7154				"name": "名前",
7155				"email": "メール",
7156				"comments": "あなたのコメント",
7157				"feature": "フィーチャー提案",
7158				"bug": "バグレポート",
7159				"other": "その他",
7160				"send": "送信する",
7161				"thankyou": "ご協力ありがとうございました"
7162			}
7163		},
7164		"plugins": {
7165			"visualfilters": {
7166				"button": "視覚的なフィルタ",
7167				"title": "視覚的なフィルタ",
7168				"newfilter": "新しいフィルタ",
7169				"strongsnumber": "の強い#",
7170				"morphology": "形態学",
7171				"style": "スタイル"
7172			},
7173			"eng2p": {
7174				"button": "英語第二人は複数",
7175				"title": "英語二人目は複数"
7176			},
7177			"lemmapopup": {
7178				"findalloccurrences": "すべて検索(約__count__)"
7179			}
7180		},
7181		"windows": {
7182			"bible": {
7183				"label": "聖書",
7184				"filter": "フィルタ...",
7185				"ot": "旧約聖書",
7186				"nt": "新約聖書",
7187				"dc": "第二正典",
7188				"more": "もっと",
7189				"less": "レス",
7190				"recentlyused": "最近使用された",
7191				"languages": "言語",
7192				"countries": "国"
7193			},
7194			"commentary": {
7195				"label": "解説"
7196			},
7197			"map": {
7198				"label": "地図",
7199				"placeholder": "検索..."
7200			},
7201			"search": {
7202				"label": "検索",
7203				"placeholder": "検索",
7204				"button": "検索",
7205				"results": "結果",
7206				"verses": "è©©",
7207				"options": "検索オプション"
7208			},
7209			"media": {
7210				"label": "メディア"
7211			},
7212			"notes": {
7213				"label": "注釈"
7214			},
7215			"audio": {
7216				"options": "オーディオオプション",
7217				"synctext": "同期テキスト(ベータ版)",
7218				"autoplay": "自動再生次",
7219				"drama": "ドラマ",
7220				"nondrama": "非ドラマ"
7221			},
7222			"parallel": {
7223				"label": "Parallelsの",
7224				"loading": "読み込んでいます...",
7225				"showall": "すべて表示",
7226				"hideall": "すべて非表示にする"
7227			},
7228			"comparison": {
7229				"label": "比較"
7230			}
7231		},
7232		"names": {
7233			"en": "Japanese"
7234		}
7235	}
7236}
7237
7238
7239
7240/*********
7241* ../app/js/resources/zh-CN.js
7242**********/
7243sofia.resources['zh-CN'] = {
7244	"translation": {
7245		"name": "中文",
7246		"menu": {
7247			"search": {
7248				"placeholder": "搜索"
7249			},
7250			"config": {
7251				"font": "字体",
7252				"settings": "设置",
7253				"tools": "工具"
7254			},
7255			"reset": "复位"
7256		},
7257		"plugins": {
7258			"visualfilters": {
7259				"button": "视觉过滤器",
7260				"title": "视觉过滤器"
7261			},
7262			"eng2p": {
7263				"button": "英语第二人称复数",
7264				"title": "英语第二人称复数"
7265			}
7266		},
7267		"windows": {
7268			"bible": {
7269				"label": "圣经",
7270				"filter": "过滤器...",
7271				"ot": "旧约",
7272				"nt": "新约圣经",
7273				"dc": "经书"
7274			},
7275			"commentary": {
7276				"label": "解说"
7277			},
7278			"map": {
7279				"label": "地图",
7280				"placeholder": "搜索..."
7281			},
7282			"search": {
7283				"label": "搜索",
7284				"placeholder": "搜索",
7285				"button": "搜索",
7286				"results": "结果",
7287				"verses": "诗"
7288			},
7289			"media": {
7290				"label": "媒体"
7291			},
7292			"notes": {
7293				"label": "注意"
7294			},
7295			"audio": {
7296				"options": "音频选项",
7297				"synctext": "同步文本(测试版)",
7298				"autoplay": "自动播放下一个",
7299				"drama": "戏剧",
7300				"nondrama": "非戏剧"
7301			}
7302		},
7303		"names": {
7304			"en": "Chinese - Simplified"
7305		}
7306	}
7307}
7308
7309
7310
7311/*********
7312* ../app/js/resources/zh-TW.js
7313**********/
7314sofia.resources['zh-TW'] = {
7315	"translation": {
7316		"name": "中文(繁體)",
7317		"menu": {
7318			"search": {
7319				"placeholder": "搜索"
7320			},
7321			"config": {
7322				"font": "字形",
7323				"settings": "設置",
7324				"tools": "工具"
7325			},
7326			"reset": "重置",
7327			"themes": {
7328				"default": "正常",
7329				"sepia": "烏賊",
7330				"dark": "弱光"
7331			},
7332			"labels": {
7333				"about": "大約",
7334				"addwindow": "添加窗口",
7335				"settings": "設置",
7336				"feedback": "信息反饋",
7337				"options": "選項"
7338			},
7339			"feedback": {
7340				"name": "名字",
7341				"email": "電子郵件",
7342				"comments": "您的意見",
7343				"feature": "功能建議",
7344				"bug": "錯誤報告",
7345				"other": "其他",
7346				"send": "發送",
7347				"thankyou": "感謝您的反饋"
7348			}
7349		},
7350		"plugins": {
7351			"visualfilters": {
7352				"button": "視覺過濾器",
7353				"title": "視覺過濾器",
7354				"newfilter": "新的過濾器",
7355				"strongsnumber": "強烈的#",
7356				"morphology": "形態學",
7357				"style": "風格"
7358			},
7359			"eng2p": {
7360				"button": "英語第二人稱複數",
7361				"title": "英語第二人稱複數"
7362			},
7363			"lemmapopup": {
7364				"findalloccurrences": "查找所有出現(約__count__)"
7365			}
7366		},
7367		"windows": {
7368			"bible": {
7369				"label": "聖經",
7370				"filter": "過濾...",
7371				"ot": "舊約全書",
7372				"nt": "新約全書",
7373				"dc": "經書",
7374				"more": "更多",
7375				"less": "少",
7376				"recentlyused": "最近使用",
7377				"languages": "語言",
7378				"countries": "國家"
7379			},
7380			"commentary": {
7381				"label": "評論"
7382			},
7383			"map": {
7384				"label": "地圖",
7385				"placeholder": "搜索..."
7386			},
7387			"search": {
7388				"label": "搜索",
7389				"placeholder": "搜索",
7390				"button": "搜索",
7391				"results": "結果",
7392				"verses": "經文",
7393				"options": "搜索選項"
7394			},
7395			"media": {
7396				"label": "媒體"
7397			},
7398			"notes": {
7399				"label": "筆記"
7400			},
7401			"audio": {
7402				"options": "音頻選項",
7403				"synctext": "同步文本(測試版)",
7404				"autoplay": "自動播放下一頁",
7405				"drama": "戲劇",
7406				"nondrama": "非戲劇"
7407			},
7408			"parallel": {
7409				"label": "相似之處",
7410				"loading": "加載中...",
7411				"showall": "顯示全部",
7412				"hideall": "全部隱藏"
7413			},
7414			"comparison": {
7415				"label": "對照"
7416			}
7417		},
7418		"names": {
7419			"en": "Chinese - Traditional"
7420		}
7421	}
7422}
7423
7424
7425
7426/*********
7427* ../app/js/common/languagedata.js
7428**********/
7429window.data = window.data || {};
7430
7431data.language = {
7432
7433	rtl: ['ar','arz','arc','dv','fa','ha','he','heb','khw','ks','ku','ps','ur','yi'],
7434
7435	isRTL: function(lang) {
7436		return data.language.rtl.indexOf(lang) > -1;
7437	},
7438
7439	codes:  {
7440		ara: 'Afar',
7441		abk: 'Abkhazian',
7442		ace: 'Achinese',
7443		ach: 'Acoli',
7444		ada: 'Adangme',
7445		ady: 'Adyghe; Adygei',
7446		afa: 'Afro-Asiatic languages',
7447		afh: 'Afrihili',
7448		afr: 'Afrikaans',
7449		ain: 'Ainu',
7450		aka: 'Akan',
7451		akk: 'Akkadian',
7452		alb: 'Albanian',
7453		ale: 'Aleut',
7454		alg: 'Algonquian languages',
7455		alt: 'Southern Altai',
7456		amh: 'Amharic',
7457		anp: 'Angika',
7458		apa: 'Apache languages',
7459		ara: 'Arabic',
7460		arg: 'Aragonese',
7461		arm: 'Armenian',
7462		arn: 'Mapudungun',
7463		arp: 'Arapaho',
7464		arw: 'Arawak',
7465		asm: 'Assamese',
7466		ast: 'Asturian',
7467		ath: 'Athapascan languages',
7468		aus: 'Australian languages',
7469		ava: 'Avaric',
7470		ave: 'Avestan',
7471		awa: 'Awadhi',
7472		aym: 'Aymara',
7473		aze: 'Azerbaijani',
7474		bad: 'Banda languages',
7475		bai: 'Bamileke languages',
7476		bak: 'Bashkir',
7477		bal: 'Baluchi',
7478		bam: 'Bambara',
7479		ban: 'Balinese',
7480		baq: 'Basque',
7481		bas: 'Basa',
7482		bat: 'Baltic languages',
7483		bej: 'Beja; Bedawiyet',
7484		bel: 'Belarusian',
7485		bem: 'Bemba',
7486		ben: 'Bengali',
7487		ber: 'Berber languages',
7488		bho: 'Bhojpuri',
7489		bih: 'Bihari languages',
7490		bik: 'Bikol',
7491		bin: 'Bini; Edo',
7492		bis: 'Bislama',
7493		bla: 'Siksika',
7494		bnt: 'Bantu languages',
7495		tib: 'Tibetan',
7496		bos: 'Bosnian',
7497		bra: 'Braj',
7498		bre: 'Breton',
7499		btk: 'Batak languages',
7500		bua: 'Buriat',
7501		bug: 'Buginese',
7502		bul: 'Bulgarian',
7503		bur: 'Burmese',
7504		byn: 'Blin',
7505		cad: 'Caddo',
7506		cai: 'Central American Indian languages',
7507		car: 'Galibi Carib',
7508		cat: 'Catalan; Valencian',
7509		cau: 'Caucasian languages',
7510		ceb: 'Cebuano',
7511		cel: 'Celtic languages',
7512		cze: 'Czech',
7513		cha: 'Chamorro',
7514		chb: 'Chibcha',
7515		che: 'Chechen',
7516		chg: 'Chagatai',
7517		chi: 'Chinese',
7518		chk: 'Chuukese',
7519		chm: 'Mari',
7520		chn: 'Chinook jargon',
7521		cho: 'Choctaw',
7522		chp: 'Chipewyan; Dene Suline',
7523		chr: 'Cherokee',
7524		chu: 'Old Slavonic',
7525		chv: 'Chuvash',
7526		chy: 'Cheyenne',
7527		cmc: 'Chamic languages',
7528		cop: 'Coptic',
7529		cor: 'Cornish',
7530		cos: 'Corsican',
7531		cre: 'Cree',
7532		crh: 'Crimean Tatar',
7533		crp: 'Creoles and pidgins',
7534		csb: 'Kashubian',
7535		cus: 'Cushitic languages',
7536		wel: 'Welsh',
7537		cze: 'Czech',
7538		dak: 'Dakota',
7539		dan: 'Danish',
7540		dar: 'Dargwa',
7541		day: 'Land Dayak languages',
7542		del: 'Delaware',
7543		den: 'Slave',
7544		ger: 'German',
7545		dgr: 'Dogrib',
7546		din: 'Dinka',
7547		div: 'Divehi',
7548		doi: 'Dogri',
7549		dra: 'Dravidian languages',
7550		dsb: 'Lower Sorbian',
7551		dua: 'Duala',
7552		dum: 'Dutch',
7553		dut: 'Dutch; Flemish',
7554		dyu: 'Dyula',
7555		dzo: 'Dzongkha',
7556		efi: 'Efik',
7557		eka: 'Ekajuk',
7558		gre: 'Greek',
7559		elx: 'Elamite',
7560		eng: 'English',
7561		engn: 'English Commentaries',
7562		epo: 'Esperanto',
7563		est: 'Estonian',
7564		baq: 'Basque',
7565		ewe: 'Ewe',
7566		ewo: 'Ewondo',
7567		fan: 'Fang',
7568		fao: 'Faroese',
7569		per: 'Persian',
7570		fat: 'Fanti',
7571		fij: 'Fijian',
7572		fil: 'Filipino; Pilipino',
7573		fin: 'Finnish',
7574		fiu: 'Finno-Ugrian languages',
7575		fon: 'Fon',
7576		fre: 'French',
7577		frr: 'Northern Frisian',
7578		frs: 'Eastern Frisian',
7579		fry: 'Western Frisian',
7580		ful: 'Fulah',
7581		fur: 'Friulian',
7582		gaa: 'Ga',
7583		gay: 'Gayo',
7584		gba: 'Gbaya',
7585		gem: 'Germanic languages',
7586		geo: 'Georgian',
7587		gez: 'Geez',
7588		gil: 'Gilbertese',
7589		gla: 'Gaelic',
7590		gle: 'Irish',
7591		glg: 'Galician',
7592		glv: 'Manx',
7593		gon: 'Gondi',
7594		gor: 'Gorontalo',
7595		got: 'Gothic',
7596		grb: 'Grebo',
7597		gre: 'Greek',
7598		grn: 'Guarani',
7599		gsw: 'Swiss German',
7600		guj: 'Gujarati',
7601		gwi: 'Gwich\'in',
7602		hai: 'Haida',
7603		hat: 'Haitian',
7604		hau: 'Hausa',
7605		haw: 'Hawaiian',
7606		heb: 'Hebrew',
7607		her: 'Herero',
7608		hil: 'Hiligaynon',
7609		him: 'Himachali languages; Western Pahari languages',
7610		hin: 'Hindi',
7611		hit: 'Hittite',
7612		hmn: 'Hmong; Mong',
7613		hmo: 'Hiri Motu',
7614		hrv: 'Croatian',
7615		hsb: 'Upper Sorbian',
7616		hun: 'Hungarian',
7617		hup: 'Hupa',
7618		arm: 'Armenian',
7619		iba: 'Iban',
7620		ibo: 'Igbo',
7621		ice: 'Icelandic',
7622		ido: 'Ido',
7623		iii: 'Sichuan Yi',
7624		ijo: 'Ijo languages',
7625		iku: 'Inuktitut',
7626		ile: 'Interlingue; Occidental',
7627		ilo: 'Iloko',
7628		inc: 'Indic languages',
7629		ind: 'Indonesian',
7630		ine: 'Indo-European languages',
7631		inh: 'Ingush',
7632		ipk: 'Inupiaq',
7633		ira: 'Iranian languages',
7634		iro: 'Iroquoian languages',
7635		ice: 'Icelandic',
7636		ita: 'Italian',
7637		jav: 'Javanese',
7638		jbo: 'Lojban',
7639		jpn: 'Japanese',
7640		jpr: 'Judeo-Persian',
7641		jrb: 'Judeo-Arabic',
7642		kaa: 'Kara-Kalpak',
7643		kab: 'Kabyle',
7644		kac: 'Kachin; Jingpho',
7645		kal: 'Kalaallisut; Greenlandic',
7646		kam: 'Kamba',
7647		kan: 'Kannada',
7648		kar: 'Karen languages',
7649		kas: 'Kashmiri',
7650		geo: 'Georgian',
7651		kau: 'Kanuri',
7652		kaw: 'Kawi',
7653		kaz: 'Kazakh',
7654		kbd: 'Kabardian',
7655		kha: 'Khasi',
7656		khi: 'Khoisan languages',
7657		khm: 'Central Khmer',
7658		kho: 'Khotanese; Sakan',
7659		kik: 'Kikuyu; Gikuyu',
7660		kin: 'Kinyarwanda',
7661		kir: 'Kirghiz; Kyrgyz',
7662		kmb: 'Kimbundu',
7663		kok: 'Konkani',
7664		kom: 'Komi',
7665		kon: 'Kongo',
7666		kor: 'Korean',
7667		kos: 'Kosraean',
7668		kpe: 'Kpelle',
7669		krc: 'Karachay-Balkar',
7670		krl: 'Karelian',
7671		kro: 'Kru languages',
7672		kru: 'Kurukh',
7673		kua: 'Kuanyama',
7674		kum: 'Kumyk',
7675		kur: 'Kurdish',
7676		kut: 'Kutenai',
7677		lad: 'Ladino',
7678		lah: 'Lahnda',
7679		lam: 'Lamba',
7680		lao: 'Lao',
7681		lat: 'Latin',
7682		lav: 'Latvian',
7683		lez: 'Lezghian',
7684		lim: 'Limburgan',
7685		lin: 'Lingala',
7686		lit: 'Lithuanian',
7687		lol: 'Mongo',
7688		loz: 'Lozi',
7689		ltz: 'Luxembourgish; Letzeburgesch',
7690		lua: 'Luba-Lulua',
7691		lub: 'Luba-Katanga',
7692		lug: 'Ganda',
7693		lui: 'Luiseno',
7694		lun: 'Lunda',
7695		luo: 'Luo (Kenya and Tanzania)',
7696		lus: 'Lushai',
7697		mac: 'Macedonian',
7698		mad: 'Madurese',
7699		mag: 'Magahi',
7700		mah: 'Marshallese',
7701		mai: 'Maithili',
7702		mak: 'Makasar',
7703		mal: 'Malayalam',
7704		man: 'Mandingo',
7705		mao: 'Maori',
7706		map: 'Austronesian languages',
7707		mar: 'Marathi',
7708		mas: 'Masai',
7709		may: 'Malay',
7710		mdf: 'Moksha',
7711		mdr: 'Mandar',
7712		men: 'Mende',
7713		mic: 'Mi\'kmaq; Micmac',
7714		min: 'Minangkabau',
7715		mac: 'Macedonian',
7716		mkh: 'Mon-Khmer languages',
7717		mlg: 'Malagasy',
7718		mlt: 'Maltese',
7719		mnc: 'Manchu',
7720		mni: 'Manipuri',
7721		mno: 'Manobo languages',
7722		moh: 'Mohawk',
7723		mon: 'Mongolian',
7724		mos: 'Mossi',
7725		mao: 'Maori',
7726		may: 'Malay',
7727		mul: 'Multiple languages',
7728		mun: 'Munda languages',
7729		mus: 'Creek',
7730		mwl: 'Mirandese',
7731		mwr: 'Marwari',
7732		bur: 'Burmese',
7733		myn: 'Mayan languages',
7734		myv: 'Erzya',
7735		nah: 'Nahuatl languages',
7736		nai: 'North American Indian languages',
7737		nap: 'Neapolitan',
7738		nau: 'Nauru',
7739		nav: 'Navajo; Navaho',
7740		nbl: 'Ndebele South',
7741		nde: 'Ndebele North',
7742		ndo: 'Ndonga',
7743		nep: 'Nepali',
7744		nia: 'Nias',
7745		nic: 'Niger-Kordofanian languages',
7746		niu: 'Niuean',
7747		dut: 'Dutch',
7748		nno: 'Norwegian Nynorsk',
7749		nob: 'Bokmål, Norwegian',
7750		nog: 'Nogai',
7751		nor: 'Norwegian',
7752		nqo: 'N\'Ko',
7753		nso: 'Pedi; Sepedi; Northern Sotho',
7754		nub: 'Nubian languages',
7755		nwc: 'Classical Newari; Old Newari; Classical Nepal Bhasa',
7756		nya: 'Chichewa; Chewa; Nyanja',
7757		nym: 'Nyamwezi',
7758		nyn: 'Nyankole',
7759		nyo: 'Nyoro',
7760		nzi: 'Nzima',
7761		oci: 'Occitan (post 1500)',
7762		oji: 'Ojibwa',
7763		ori: 'Oriya',
7764		orm: 'Oromo',
7765		osa: 'Osage',
7766		oss: 'Ossetian; Ossetic',
7767		ota: 'Turkish, Ottoman (1500-1928)',
7768		oto: 'Otomian languages',
7769		paa: 'Papuan languages',
7770		pag: 'Pangasinan',
7771		pal: 'Pahlavi',
7772		pam: 'Pampanga; Kapampangan',
7773		pan: 'Panjabi; Punjabi',
7774		pap: 'Papiamento',
7775		pau: 'Palauan',
7776		per: 'Persian',
7777		phi: 'Philippine languages',
7778		phn: 'Phoenician',
7779		pli: 'Pali',
7780		pol: 'Polish',
7781		pon: 'Pohnpeian',
7782		por: 'Portuguese',
7783		pra: 'Prakrit languages',
7784		pus: 'Pashto',
7785		que: 'Quechua',
7786		raj: 'Rajasthani',
7787		rap: 'Rapanui',
7788		rar: 'Rarotongan; Cook Islands Maori',
7789		roa: 'Romance languages',
7790		roh: 'Romansh',
7791		rom: 'Romany',
7792		rum: 'Romanian',
7793		run: 'Rundi',
7794		rus: 'Russian',
7795		sad: 'Sandawe',
7796		sag: 'Sango',
7797		sah: 'Yakut',
7798		sal: 'Salishan languages',
7799		sam: 'Samaritan Aramaic',
7800		san: 'Sanskrit',
7801		sas: 'Sasak',
7802		sat: 'Santali',
7803		scn: 'Sicilian',
7804		sco: 'Scots',
7805		sel: 'Selkup',
7806		sgn: 'Sign Languages',
7807		shn: 'Shan',
7808		sid: 'Sidamo',
7809		sin: 'Sinhala; Sinhalese',
7810		sio: 'Siouan languages',
7811		sit: 'Sino-Tibetan languages',
7812		sla: 'Slavic languages',
7813		slo: 'Slovak',
7814		sma: 'Southern Sami',
7815		sme: 'Northern Sami',
7816		smi: 'Sami languages',
7817		smj: 'Lule Sami',
7818		smn: 'Inari Sami',
7819		smo: 'Samoan',
7820		sms: 'Skolt Sami',
7821		sna: 'Shona',
7822		snd: 'Sindhi',
7823		snk: 'Soninke',
7824		sog: 'Sogdian',
7825		som: 'Somali',
7826		son: 'Songhai languages',
7827		sot: 'Sotho, Southern',
7828		spa: 'Spanish',
7829		alb: 'Albanian',
7830		srd: 'Sardinian',
7831		srn: 'Sranan Tongo',
7832		srp: 'Serbian',
7833		srr: 'Serer',
7834		ssa: 'Nilo-Saharan languages',
7835		ssw: 'Swati',
7836		suk: 'Sukuma',
7837		sun: 'Sundanese',
7838		sus: 'Susu',
7839		sux: 'Sumerian',
7840		swa: 'Swahili',
7841		swe: 'Swedish',
7842		syc: 'Classical Syriac',
7843		syr: 'Syriac',
7844		tah: 'Tahitian',
7845		tai: 'Tai languages',
7846		tam: 'Tamil',
7847		tat: 'Tatar',
7848		tel: 'Telugu',
7849		tem: 'Timne',
7850		ter: 'Tereno',
7851		tet: 'Tetum',
7852		tgk: 'Tajik',
7853		tgl: 'Tagalog',
7854		tha: 'Thai',
7855		tib: 'Tibetan',
7856		tig: 'Tigre',
7857		tir: 'Tigrinya',
7858		tiv: 'Tiv',
7859		tkl: 'Tokelau',
7860		tlh: 'Klingon',
7861		tli: 'Tlingit',
7862		tmh: 'Tamashek',
7863		tog: 'Tonga',
7864		ton: 'Tonga',
7865		tpi: 'Tok Pisin',
7866		tsi: 'Tsimshian',
7867		tsn: 'Tswana',
7868		tso: 'Tsonga',
7869		tuk: 'Turkmen',
7870		tum: 'Tumbuka',
7871		tup: 'Tupi languages',
7872		tur: 'Turkish',
7873		tut: 'Altaic languages',
7874		tvl: 'Tuvalu',
7875		twi: 'Twi',
7876		tyv: 'Tuvinian',
7877		udm: 'Udmurt',
7878		uga: 'Ugaritic',
7879		uig: 'Uighur; Uyghur',
7880		ukr: 'Ukrainian',
7881		umb: 'Umbundu',
7882		und: 'Undetermined',
7883		urd: 'Urdu',
7884		uzb: 'Uzbek',
7885		vai: 'Vai',
7886		ven: 'Venda',
7887		vie: 'Vietnamese',
7888		vol: 'Volapük',
7889		vot: 'Votic',
7890		wak: 'Wakashan languages',
7891		wal: 'Wolaitta; Wolaytta',
7892		war: 'Waray',
7893		was: 'Washo',
7894		wel: 'Welsh',
7895		wen: 'Sorbian languages',
7896		wln: 'Walloon',
7897		wol: 'Wolof',
7898		xal: 'Kalmyk; Oirat',
7899		xho: 'Xhosa',
7900		yao: 'Yao',
7901		yap: 'Yapese',
7902		yid: 'Yiddish',
7903		yor: 'Yoruba',
7904		ypk: 'Yupik languages',
7905		zap: 'Zapotec',
7906		zbl: 'Blissymbols; Blissymbolics; Bliss',
7907		zen: 'Zenaga',
7908		zgh: 'Standard Moroccan Tamazight',
7909		zha: 'Zhuang',
7910		chi: 'Chinese Simplified',
7911		zho: 'Chinese Traditional',
7912		znd: 'Zande languages',
7913		zul: 'Zulu',
7914		zun: 'Zuni'
7915	}
7916};
7917
7918
7919
7920/*********
7921* ../app/js/common/countriesdata.js
7922**********/
7923sofia.countries =  [
7924{"name":"Afghanistan","alpha-2":"AF","alpha-3":"AFG"},
7925{"name":"Åland Islands","alpha-2":"AX","alpha-3":"ALA"},
7926{"name":"Albania","alpha-2":"AL","alpha-3":"ALB"},
7927{"name":"Algeria","alpha-2":"DZ","alpha-3":"DZA"},
7928{"name":"American Samoa","alpha-2":"AS","alpha-3":"ASM"},
7929{"name":"Andorra","alpha-2":"AD","alpha-3":"AND"},
7930{"name":"Angola","alpha-2":"AO","alpha-3":"AGO"},
7931{"name":"Anguilla","alpha-2":"AI","alpha-3":"AIA"},
7932{"name":"Antarctica","alpha-2":"AQ","alpha-3":"ATA"},
7933{"name":"Antigua and Barbuda","alpha-2":"AG","alpha-3":"ATG"},
7934{"name":"Argentina","alpha-2":"AR","alpha-3":"ARG"},
7935{"name":"Armenia","alpha-2":"AM","alpha-3":"ARM"},
7936{"name":"Aruba","alpha-2":"AW","alpha-3":"ABW"},
7937{"name":"Australia","alpha-2":"AU","alpha-3":"AUS"},
7938{"name":"Austria","alpha-2":"AT","alpha-3":"AUT"},
7939{"name":"Azerbaijan","alpha-2":"AZ","alpha-3":"AZE"},
7940{"name":"Bahamas","alpha-2":"BS","alpha-3":"BHS"},
7941{"name":"Bahrain","alpha-2":"BH","alpha-3":"BHR"},
7942{"name":"Bangladesh","alpha-2":"BD","alpha-3":"BGD"},
7943{"name":"Barbados","alpha-2":"BB","alpha-3":"BRB"},
7944{"name":"Belarus","alpha-2":"BY","alpha-3":"BLR"},
7945{"name":"Belgium","alpha-2":"BE","alpha-3":"BEL"},
7946{"name":"Belize","alpha-2":"BZ","alpha-3":"BLZ"},
7947{"name":"Benin","alpha-2":"BJ","alpha-3":"BEN"},
7948{"name":"Bermuda","alpha-2":"BM","alpha-3":"BMU"},
7949{"name":"Bhutan","alpha-2":"BT","alpha-3":"BTN"},
7950{"name":"Bolivia, Plurinational State of","alpha-2":"BO","alpha-3":"BOL"},
7951{"name":"Bonaire, Sint Eustatius and Saba","alpha-2":"BQ","alpha-3":"BES"},
7952{"name":"Bosnia and Herzegovina","alpha-2":"BA","alpha-3":"BIH"},
7953{"name":"Botswana","alpha-2":"BW","alpha-3":"BWA"},
7954{"name":"Bouvet Island","alpha-2":"BV","alpha-3":"BVT"},
7955{"name":"Brazil","alpha-2":"BR","alpha-3":"BRA"},
7956{"name":"British Indian Ocean Territory","alpha-2":"IO","alpha-3":"IOT"},
7957{"name":"Brunei Darussalam","alpha-2":"BN","alpha-3":"BRN"},
7958{"name":"Bulgaria","alpha-2":"BG","alpha-3":"BGR"},
7959{"name":"Burkina Faso","alpha-2":"BF","alpha-3":"BFA"},
7960{"name":"Burundi","alpha-2":"BI","alpha-3":"BDI"},
7961{"name":"Cambodia","alpha-2":"KH","alpha-3":"KHM"},
7962{"name":"Cameroon","alpha-2":"CM","alpha-3":"CMR"},
7963{"name":"Canada","alpha-2":"CA","alpha-3":"CAN"},
7964{"name":"Cape Verde","alpha-2":"CV","alpha-3":"CPV"},
7965{"name":"Cayman Islands","alpha-2":"KY","alpha-3":"CYM"},
7966{"name":"Central African Republic","alpha-2":"CF","alpha-3":"CAF"},
7967{"name":"Chad","alpha-2":"TD","alpha-3":"TCD"},
7968{"name":"Chile","alpha-2":"CL","alpha-3":"CHL"},
7969{"name":"China","alpha-2":"CN","alpha-3":"CHN"},
7970{"name":"Christmas Island","alpha-2":"CX","alpha-3":"CXR"},
7971{"name":"Cocos (Keeling) Islands","alpha-2":"CC","alpha-3":"CCK"},
7972{"name":"Colombia","alpha-2":"CO","alpha-3":"COL"},
7973{"name":"Comoros","alpha-2":"KM","alpha-3":"COM"},
7974{"name":"Congo","alpha-2":"CG","alpha-3":"COG"},
7975{"name":"Congo, the Democratic Republic of the","alpha-2":"CD","alpha-3":"COD"},
7976{"name":"Cook Islands","alpha-2":"CK","alpha-3":"COK"},
7977{"name":"Costa Rica","alpha-2":"CR","alpha-3":"CRI"},
7978{"name":"Côte d'Ivoire","alpha-2":"CI","alpha-3":"CIV"},
7979{"name":"Croatia","alpha-2":"HR","alpha-3":"HRV"},
7980{"name":"Cuba","alpha-2":"CU","alpha-3":"CUB"},
7981{"name":"Curaçao","alpha-2":"CW","alpha-3":"CUW"},
7982{"name":"Cyprus","alpha-2":"CY","alpha-3":"CYP"},
7983{"name":"Czech Republic","alpha-2":"CZ","alpha-3":"CZE"},
7984{"name":"Denmark","alpha-2":"DK","alpha-3":"DNK"},
7985{"name":"Djibouti","alpha-2":"DJ","alpha-3":"DJI"},
7986{"name":"Dominica","alpha-2":"DM","alpha-3":"DMA"},
7987{"name":"Dominican Republic","alpha-2":"DO","alpha-3":"DOM"},
7988{"name":"Ecuador","alpha-2":"EC","alpha-3":"ECU"},
7989{"name":"Egypt","alpha-2":"EG","alpha-3":"EGY"},
7990{"name":"El Salvador","alpha-2":"SV","alpha-3":"SLV"},
7991{"name":"Equatorial Guinea","alpha-2":"GQ","alpha-3":"GNQ"},
7992{"name":"Eritrea","alpha-2":"ER","alpha-3":"ERI"},
7993{"name":"Estonia","alpha-2":"EE","alpha-3":"EST"},
7994{"name":"Ethiopia","alpha-2":"ET","alpha-3":"ETH"},
7995{"name":"Falkland Islands (Malvinas)","alpha-2":"FK","alpha-3":"FLK"},
7996{"name":"Faroe Islands","alpha-2":"FO","alpha-3":"FRO"},
7997{"name":"Fiji","alpha-2":"FJ","alpha-3":"FJI"},
7998{"name":"Finland","alpha-2":"FI","alpha-3":"FIN"},
7999{"name":"France","alpha-2":"FR","alpha-3":"FRA"},
8000{"name":"French Guiana","alpha-2":"GF","alpha-3":"GUF"},
8001{"name":"French Polynesia","alpha-2":"PF","alpha-3":"PYF"},
8002{"name":"French Southern Territories","alpha-2":"TF","alpha-3":"ATF"},
8003{"name":"Gabon","alpha-2":"GA","alpha-3":"GAB"},
8004{"name":"Gambia","alpha-2":"GM","alpha-3":"GMB"},
8005{"name":"Georgia","alpha-2":"GE","alpha-3":"GEO"},
8006{"name":"Germany","alpha-2":"DE","alpha-3":"DEU"},
8007{"name":"Ghana","alpha-2":"GH","alpha-3":"GHA"},
8008{"name":"Gibraltar","alpha-2":"GI","alpha-3":"GIB"},
8009{"name":"Greece","alpha-2":"GR","alpha-3":"GRC"},
8010{"name":"Greenland","alpha-2":"GL","alpha-3":"GRL"},
8011{"name":"Grenada","alpha-2":"GD","alpha-3":"GRD"},
8012{"name":"Guadeloupe","alpha-2":"GP","alpha-3":"GLP"},
8013{"name":"Guam","alpha-2":"GU","alpha-3":"GUM"},
8014{"name":"Guatemala","alpha-2":"GT","alpha-3":"GTM"},
8015{"name":"Guernsey","alpha-2":"GG","alpha-3":"GGY"},
8016{"name":"Guinea","alpha-2":"GN","alpha-3":"GIN"},
8017{"name":"Guinea-Bissau","alpha-2":"GW","alpha-3":"GNB"},
8018{"name":"Guyana","alpha-2":"GY","alpha-3":"GUY"},
8019{"name":"Haiti","alpha-2":"HT","alpha-3":"HTI"},
8020{"name":"Heard Island and McDonald Islands","alpha-2":"HM","alpha-3":"HMD"},
8021{"name":"Holy See (Vatican City State)","alpha-2":"VA","alpha-3":"VAT"},
8022{"name":"Honduras","alpha-2":"HN","alpha-3":"HND"},
8023{"name":"Hong Kong","alpha-2":"HK","alpha-3":"HKG"},
8024{"name":"Hungary","alpha-2":"HU","alpha-3":"HUN"},
8025{"name":"Iceland","alpha-2":"IS","alpha-3":"ISL"},
8026{"name":"India","alpha-2":"IN","alpha-3":"IND"},
8027{"name":"Indonesia","alpha-2":"ID","alpha-3":"IDN"},
8028{"name":"Iran, Islamic Republic of","alpha-2":"IR","alpha-3":"IRN"},
8029{"name":"Iraq","alpha-2":"IQ","alpha-3":"IRQ"},
8030{"name":"Ireland","alpha-2":"IE","alpha-3":"IRL"},
8031{"name":"Isle of Man","alpha-2":"IM","alpha-3":"IMN"},
8032{"name":"Israel","alpha-2":"IL","alpha-3":"ISR"},
8033{"name":"Italy","alpha-2":"IT","alpha-3":"ITA"},
8034{"name":"Jamaica","alpha-2":"JM","alpha-3":"JAM"},
8035{"name":"Japan","alpha-2":"JP","alpha-3":"JPN"},
8036{"name":"Jersey","alpha-2":"JE","alpha-3":"JEY"},
8037{"name":"Jordan","alpha-2":"JO","alpha-3":"JOR"},
8038{"name":"Kazakhstan","alpha-2":"KZ","alpha-3":"KAZ"},
8039{"name":"Kenya","alpha-2":"KE","alpha-3":"KEN"},
8040{"name":"Kiribati","alpha-2":"KI","alpha-3":"KIR"},
8041{"name":"Korea, Democratic People's Republic of","alpha-2":"KP","alpha-3":"PRK"},
8042{"name":"Korea, Republic of","alpha-2":"KR","alpha-3":"KOR"},
8043{"name":"Kuwait","alpha-2":"KW","alpha-3":"KWT"},
8044{"name":"Kyrgyzstan","alpha-2":"KG","alpha-3":"KGZ"},
8045{"name":"Lao People's Democratic Republic","alpha-2":"LA","alpha-3":"LAO"},
8046{"name":"Latvia","alpha-2":"LV","alpha-3":"LVA"},
8047{"name":"Lebanon","alpha-2":"LB","alpha-3":"LBN"},
8048{"name":"Lesotho","alpha-2":"LS","alpha-3":"LSO"},
8049{"name":"Liberia","alpha-2":"LR","alpha-3":"LBR"},
8050{"name":"Libya","alpha-2":"LY","alpha-3":"LBY"},
8051{"name":"Liechtenstein","alpha-2":"LI","alpha-3":"LIE"},
8052{"name":"Lithuania","alpha-2":"LT","alpha-3":"LTU"},
8053{"name":"Luxembourg","alpha-2":"LU","alpha-3":"LUX"},
8054{"name":"Macao","alpha-2":"MO","alpha-3":"MAC"},
8055{"name":"Macedonia, the former Yugoslav Republic of","alpha-2":"MK","alpha-3":"MKD"},
8056{"name":"Madagascar","alpha-2":"MG","alpha-3":"MDG"},
8057{"name":"Malawi","alpha-2":"MW","alpha-3":"MWI"},
8058{"name":"Malaysia","alpha-2":"MY","alpha-3":"MYS"},
8059{"name":"Maldives","alpha-2":"MV","alpha-3":"MDV"},
8060{"name":"Mali","alpha-2":"ML","alpha-3":"MLI"},
8061{"name":"Malta","alpha-2":"MT","alpha-3":"MLT"},
8062{"name":"Marshall Islands","alpha-2":"MH","alpha-3":"MHL"},
8063{"name":"Martinique","alpha-2":"MQ","alpha-3":"MTQ"},
8064{"name":"Mauritania","alpha-2":"MR","alpha-3":"MRT"},
8065{"name":"Mauritius","alpha-2":"MU","alpha-3":"MUS"},
8066{"name":"Mayotte","alpha-2":"YT","alpha-3":"MYT"},
8067{"name":"Mexico","alpha-2":"MX","alpha-3":"MEX"},
8068{"name":"Micronesia, Federated States of","alpha-2":"FM","alpha-3":"FSM"},
8069{"name":"Moldova, Republic of","alpha-2":"MD","alpha-3":"MDA"},
8070{"name":"Monaco","alpha-2":"MC","alpha-3":"MCO"},
8071{"name":"Mongolia","alpha-2":"MN","alpha-3":"MNG"},
8072{"name":"Montenegro","alpha-2":"ME","alpha-3":"MNE"},
8073{"name":"Montserrat","alpha-2":"MS","alpha-3":"MSR"},
8074{"name":"Morocco","alpha-2":"MA","alpha-3":"MAR"},
8075{"name":"Mozambique","alpha-2":"MZ","alpha-3":"MOZ"},
8076{"name":"Myanmar","alpha-2":"MM","alpha-3":"MMR"},
8077{"name":"Namibia","alpha-2":"NA","alpha-3":"NAM"},
8078{"name":"Nauru","alpha-2":"NR","alpha-3":"NRU"},
8079{"name":"Nepal","alpha-2":"NP","alpha-3":"NPL"},
8080{"name":"Netherlands","alpha-2":"NL","alpha-3":"NLD"},
8081{"name":"New Caledonia","alpha-2":"NC","alpha-3":"NCL"},
8082{"name":"New Zealand","alpha-2":"NZ","alpha-3":"NZL"},
8083{"name":"Nicaragua","alpha-2":"NI","alpha-3":"NIC"},
8084{"name":"Niger","alpha-2":"NE","alpha-3":"NER"},
8085{"name":"Nigeria","alpha-2":"NG","alpha-3":"NGA"},
8086{"name":"Niue","alpha-2":"NU","alpha-3":"NIU"},
8087{"name":"Norfolk Island","alpha-2":"NF","alpha-3":"NFK"},
8088{"name":"Northern Mariana Islands","alpha-2":"MP","alpha-3":"MNP"},
8089{"name":"Norway","alpha-2":"NO","alpha-3":"NOR"},
8090{"name":"Oman","alpha-2":"OM","alpha-3":"OMN"},
8091{"name":"Pakistan","alpha-2":"PK","alpha-3":"PAK"},
8092{"name":"Palau","alpha-2":"PW","alpha-3":"PLW"},
8093{"name":"Palestine, State of","alpha-2":"PS","alpha-3":"PSE"},
8094{"name":"Panama","alpha-2":"PA","alpha-3":"PAN"},
8095{"name":"Papua New Guinea","alpha-2":"PG","alpha-3":"PNG"},
8096{"name":"Paraguay","alpha-2":"PY","alpha-3":"PRY"},
8097{"name":"Peru","alpha-2":"PE","alpha-3":"PER"},
8098{"name":"Philippines","alpha-2":"PH","alpha-3":"PHL"},
8099{"name":"Pitcairn","alpha-2":"PN","alpha-3":"PCN"},
8100{"name":"Poland","alpha-2":"PL","alpha-3":"POL"},
8101{"name":"Portugal","alpha-2":"PT","alpha-3":"PRT"},
8102{"name":"Puerto Rico","alpha-2":"PR","alpha-3":"PRI"},
8103{"name":"Qatar","alpha-2":"QA","alpha-3":"QAT"},
8104{"name":"Réunion","alpha-2":"RE","alpha-3":"REU"},
8105{"name":"Romania","alpha-2":"RO","alpha-3":"ROU"},
8106{"name":"Russian Federation","alpha-2":"RU","alpha-3":"RUS"},
8107{"name":"Rwanda","alpha-2":"RW","alpha-3":"RWA"},
8108{"name":"Saint Barthélemy","alpha-2":"BL","alpha-3":"BLM"},
8109{"name":"Saint Helena, Ascension and Tristan da Cunha","alpha-2":"SH","alpha-3":"SHN"},
8110{"name":"Saint Kitts and Nevis","alpha-2":"KN","alpha-3":"KNA"},
8111{"name":"Saint Lucia","alpha-2":"LC","alpha-3":"LCA"},
8112{"name":"Saint Martin (French part)","alpha-2":"MF","alpha-3":"MAF"},
8113{"name":"Saint Pierre and Miquelon","alpha-2":"PM","alpha-3":"SPM"},
8114{"name":"Saint Vincent and the Grenadines","alpha-2":"VC","alpha-3":"VCT"},
8115{"name":"Samoa","alpha-2":"WS","alpha-3":"WSM"},
8116{"name":"San Marino","alpha-2":"SM","alpha-3":"SMR"},
8117{"name":"Sao Tome and Principe","alpha-2":"ST","alpha-3":"STP"},
8118{"name":"Saudi Arabia","alpha-2":"SA","alpha-3":"SAU"},
8119{"name":"Senegal","alpha-2":"SN","alpha-3":"SEN"},
8120{"name":"Serbia","alpha-2":"RS","alpha-3":"SRB"},
8121{"name":"Seychelles","alpha-2":"SC","alpha-3":"SYC"},
8122{"name":"Sierra Leone","alpha-2":"SL","alpha-3":"SLE"},
8123{"name":"Singapore","alpha-2":"SG","alpha-3":"SGP"},
8124{"name":"Sint Maarten (Dutch part)","alpha-2":"SX","alpha-3":"SXM"},
8125{"name":"Slovakia","alpha-2":"SK","alpha-3":"SVK"},
8126{"name":"Slovenia","alpha-2":"SI","alpha-3":"SVN"},
8127{"name":"Solomon Islands","alpha-2":"SB","alpha-3":"SLB"},
8128{"name":"Somalia","alpha-2":"SO","alpha-3":"SOM"},
8129{"name":"South Africa","alpha-2":"ZA","alpha-3":"ZAF"},
8130{"name":"South Georgia and the South Sandwich Islands","alpha-2":"GS","alpha-3":"SGS"},
8131{"name":"South Sudan","alpha-2":"SS","alpha-3":"SSD"},
8132{"name":"Spain","alpha-2":"ES","alpha-3":"ESP"},
8133{"name":"Sri Lanka","alpha-2":"LK","alpha-3":"LKA"},
8134{"name":"Sudan","alpha-2":"SD","alpha-3":"SDN"},
8135{"name":"Suriname","alpha-2":"SR","alpha-3":"SUR"},
8136{"name":"Svalbard and Jan Mayen","alpha-2":"SJ","alpha-3":"SJM"},
8137{"name":"Swaziland","alpha-2":"SZ","alpha-3":"SWZ"},
8138{"name":"Sweden","alpha-2":"SE","alpha-3":"SWE"},
8139{"name":"Switzerland","alpha-2":"CH","alpha-3":"CHE"},
8140{"name":"Syrian Arab Republic","alpha-2":"SY","alpha-3":"SYR"},
8141{"name":"Taiwan, Province of China","alpha-2":"TW","alpha-3":"TWN"},
8142{"name":"Tajikistan","alpha-2":"TJ","alpha-3":"TJK"},
8143{"name":"Tanzania, United Republic of","alpha-2":"TZ","alpha-3":"TZA"},
8144{"name":"Thailand","alpha-2":"TH","alpha-3":"THA"},
8145{"name":"Timor-Leste","alpha-2":"TL","alpha-3":"TLS"},
8146{"name":"Togo","alpha-2":"TG","alpha-3":"TGO"},
8147{"name":"Tokelau","alpha-2":"TK","alpha-3":"TKL"},
8148{"name":"Tonga","alpha-2":"TO","alpha-3":"TON"},
8149{"name":"Trinidad and Tobago","alpha-2":"TT","alpha-3":"TTO"},
8150{"name":"Tunisia","alpha-2":"TN","alpha-3":"TUN"},
8151{"name":"Turkey","alpha-2":"TR","alpha-3":"TUR"},
8152{"name":"Turkmenistan","alpha-2":"TM","alpha-3":"TKM"},
8153{"name":"Turks and Caicos Islands","alpha-2":"TC","alpha-3":"TCA"},
8154{"name":"Tuvalu","alpha-2":"TV","alpha-3":"TUV"},
8155{"name":"Uganda","alpha-2":"UG","alpha-3":"UGA"},
8156{"name":"Ukraine","alpha-2":"UA","alpha-3":"UKR"},
8157{"name":"United Arab Emirates","alpha-2":"AE","alpha-3":"ARE"},
8158{"name":"United Kingdom","alpha-2":"GB","alpha-3":"GBR"},
8159{"name":"United States","alpha-2":"US","alpha-3":"USA"},
8160{"name":"United States Minor Outlying Islands","alpha-2":"UM","alpha-3":"UMI"},
8161{"name":"Uruguay","alpha-2":"UY","alpha-3":"URY"},
8162{"name":"Uzbekistan","alpha-2":"UZ","alpha-3":"UZB"},
8163{"name":"Vanuatu","alpha-2":"VU","alpha-3":"VUT"},
8164{"name":"Venezuela, Bolivarian Republic of","alpha-2":"VE","alpha-3":"VEN"},
8165{"name":"Viet Nam","alpha-2":"VN","alpha-3":"VNM"},
8166{"name":"Virgin Islands, British","alpha-2":"VG","alpha-3":"VGB"},
8167{"name":"Virgin Islands, U.S.","alpha-2":"VI","alpha-3":"VIR"},
8168{"name":"Wallis and Futuna","alpha-2":"WF","alpha-3":"WLF"},
8169{"name":"Western Sahara","alpha-2":"EH","alpha-3":"ESH"},
8170{"name":"Yemen","alpha-2":"YE","alpha-3":"YEM"},
8171{"name":"Zambia","alpha-2":"ZM","alpha-3":"ZMB"},
8172{"name":"Zimbabwe","alpha-2":"ZW","alpha-3":"ZWE"}
8173];
8174
8175
8176
8177/*********
8178* ../app/js/common/ajax.js
8179**********/
8180sofia.ajax = function(params) {
8181
8182	var $params = $.extend(true, {}, params);
8183
8184	if ($params.url.indexOf('http') != 0) {
8185
8186		$params.url =
8187				// base URL
8188				sofia.config.baseContentUrl +
8189				// API settings
8190				(sofia.config.baseContentApiPath != '' ? sofia.config.baseContentApiPath + '?key=' + sofia.config.baseContentApiKey + '&action=' : '') +
8191				// then plain URL
8192				$params.url;
8193
8194		// add type overrides if possible
8195		if ($params.dataType == 'json') {
8196			$params.beforeSend = function(xhr){
8197				if (xhr.overrideMimeType){
8198					xhr.overrideMimeType('application/' + (sofia.config.baseContentApiPath != '' ? 'javascript' : 'json'));
8199				}
8200			}
8201		}
8202
8203		// change setting
8204		if (sofia.config.baseContentApiPath != '') {
8205			$params.dataType = 'jsonp';
8206		}
8207	}
8208
8209	$params.success = function(data) {
8210
8211		if (params.success) {
8212
8213			// convert back to text
8214			if ((params.dataType == 'text' || params.dataType == 'html') && sofia.config.baseContentApiPath != '') {
8215				data = data.text;
8216			}
8217
8218			params.success(data);
8219		}
8220	};
8221
8222	$params.error = function() {
8223
8224		if (params.error) {
8225			params.error.apply(null, arguments);
8226		}
8227
8228	};
8229
8230	// run through jQuery
8231	$.ajax($params);
8232};
8233
8234
8235
8236/*********
8237* ../app/js/common/appsettings.js
8238**********/
8239/******************
8240ReaderSettings
8241*******************/
8242var AppSettings = (function() {
8243	var storage = {};
8244
8245    function testlocalStorage() {
8246        try {
8247            window.localStorage.setItem('1', '2');
8248            if (localStorage.getItem('1') !== '2') {
8249                return false;
8250            }
8251            window.localStorage.removeItem('1');
8252            return localStorage.getItem('1') !== '2';
8253        } catch(e) {
8254            return false;
8255        }
8256    }
8257
8258	if (testlocalStorage()) {
8259		storage = window.localStorage;
8260	}
8261
8262	// create me
8263	function getValue(key, defaultValue) {
8264		key = sofia.config.settingsPrefix + key;
8265
8266		// console.log('getValue', key, defaultValue);
8267
8268		var returnValue = {},
8269			storedValue = null;
8270
8271
8272		// put all default values on the value object
8273		for (var objkey in defaultValue) {
8274			returnValue[objkey] = defaultValue[objkey];
8275		}
8276
8277		//console.log('default', returnValue);
8278
8279		storedValue = storage[key];
8280
8281		//console.log('storedValue', 'key:' + key, storedValue);
8282
8283		if (storedValue == null) {
8284			return returnValue;
8285		} else {
8286			try {
8287				storedValue = JSON.parse(storedValue);
8288			} catch (ex) {
8289
8290			}
8291		}
8292
8293		//console.log('storedValue', storedValue);
8294
8295		for (var objkey in storedValue) {
8296			returnValue[objkey] = storedValue[objkey];
8297		}
8298
8299		//console.log('combined', returnValue);
8300
8301		return returnValue;
8302	}
8303
8304	function setValue(key, value) {
8305
8306		key = sofia.config.settingsPrefix + key;
8307
8308		// console.log('STORE', 'key:' + key, value);
8309		storage[key] = JSON.stringify(value);
8310	}
8311
8312	/* From QuirksMode */
8313	function getCookieValue(name) {
8314		var nameEQ = name + "=";
8315		var ca = document.cookie.split(';');
8316		for(var i = 0; i < ca.length; i++) {
8317			var c = ca[i];
8318			while (c.charAt(0) == ' ') c = c.substring(1, c.length);
8319			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
8320		}
8321		return null;
8322	}
8323
8324	return {
8325		getValue: getValue,
8326		setValue: setValue,
8327		getCookieValue: getCookieValue
8328	}
8329
8330})();
8331
8332
8333
8334/*********
8335* ../app/js/common/eventlistener.js
8336**********/
8337var EventEmitter = {
8338	events: [],
8339
8340	addEventListener: function (eventName, callback, bubble) {
8341		this.events[eventName] = this.events[eventName] || [];
8342		this.events[eventName].push(callback);
8343	},
8344	removeEventListener: function (eventName, callback) {
8345		if (!eventName) { this.events = {}; return true; }
8346		var callbacks = this.events[eventName];
8347		if (!callbacks) return true;
8348		if (!callback) { this.events[eventName] = []; return true; }
8349		for (i = 0; i < callbacks.length; i++) {
8350			if (callbacks[i] === callback) {
8351				this.events[eventName].splice(i, 1);
8352				return true;
8353			}
8354		}
8355		return false;
8356	},
8357	dispatchEvent: function (eventName) {
8358		var i,
8359			args,
8360			callbacks = this.events[eventName];
8361
8362		if (callbacks) {
8363			args = Array.prototype.slice.call(arguments, 1);
8364			for (i = 0; i < callbacks.length; i++) {
8365				callbacks[i].apply(null, args);
8366			}
8367		}
8368	},
8369
8370	clearListeners: function() {
8371		this.events = [];
8372	},
8373
8374	// jQuery like names
8375	on: function(eventName, callback, bubble) {
8376		this.addEventListener.call(this, eventName, callback, bubble);
8377	},
8378	trigger: function(eventName, value) {
8379		this.dispatchEvent.call(this, eventName, value);
8380	}
8381};
8382
8383
8384
8385/*********
8386* ../app/js/common/timer.js
8387**********/
8388
8389var Timer = function(callback, seconds) {
8390
8391	var timeoutValue = null;
8392	function start() {
8393		if (timeoutValue == null) {
8394			clear();
8395
8396			timeoutValue = setTimeout(function() {
8397				callback();
8398				clear();
8399			}, seconds);
8400		}
8401	}
8402	function clear() {
8403		if (timeoutValue != null) {
8404			clearTimeout(timeoutValue);
8405			timeoutValue = null;
8406		}
8407	}
8408
8409	return {
8410		start: start,
8411		clear: clear
8412	}
8413};
8414
8415
8416
8417/*********
8418* ../app/js/common/stringutility.js
8419**********/
8420var stringUtility = {
8421
8422	// get from URL
8423	parseQuerystring: function (querystring) {
8424
8425		querystring = querystring || window.location.search.substring(1);
8426
8427		// remove any preceding url and split
8428		querystring = querystring.substring(querystring.indexOf('?')+1).split('&');
8429		var params = {}, pair, d = decodeURIComponent;
8430		// march and parse
8431		for (var i = querystring.length - 1; i >= 0; i--) {
8432		pair = querystring[i].split('=');
8433		params[d(pair[0])] = d(pair[1]);
8434		}
8435
8436		return params;
8437	}
8438};
8439
8440
8441
8442/*********
8443* ../app/js/common/detection.js
8444**********/
8445var Detection = (function() {
8446	var detection = {};
8447
8448	detection.hasTouch = 'ontouchend' in document;
8449
8450	/*
8451	detection.hasFlash = (function() {
8452		if (navigator.plugins != null && navigator.plugins.length > 0){
8453			return navigator.plugins["Shockwave Flash"] && true;
8454		}
8455		if (~navigator.userAgent.toLowerCase().indexOf("webtv")){
8456			return true;
8457		}
8458		if ((~navigator.appVersion.indexOf("MSIE") || ~navigator.appVersion.indexOf("Trident")) && !~navigator.userAgent.indexOf("Opera")){
8459			try {
8460				return new ActiveXObject("ShockwaveFlash.ShockwaveFlash") && true;
8461			} catch(e) {}
8462		}
8463		return false;
8464	})();
8465	*/
8466
8467
8468	return detection;
8469})();
8470
8471if (!Array.prototype.filter) {
8472  Array.prototype.filter = function(fun /*, thisp */) {
8473	"use strict";
8474
8475	if (this === void 0 || this === null)
8476	  throw new TypeError();
8477
8478	var t = Object(this);
8479	var len = t.length >>> 0;
8480	if (typeof fun !== "function")
8481	  throw new TypeError();
8482
8483	var res = [];
8484	var thisp = arguments[1];
8485	for (var i = 0; i < len; i++) {
8486	  if (i in t) {
8487		var val = t[i]; // in case fun mutates this
8488		if (fun.call(thisp, val, i, t))
8489		  res.push(val);
8490	  }
8491	}
8492
8493	return res;
8494  };
8495}
8496
8497if (!Array.prototype.indexOf) {
8498	Array.prototype.indexOf = function(obj, start) {
8499		for (var i = (start || 0), j = this.length; i < j; i++) {
8500			if (this[i] === obj) {
8501				return i;
8502			}
8503		}
8504		return -1;
8505	};
8506}
8507
8508if(typeof String.prototype.trim !== 'function') {
8509	String.prototype.trim = function() {
8510		return this.replace(/^\s+|\s+$/g, '');
8511	};
8512};
8513
8514
8515
8516/*********
8517* ../app/js/common/navigation.js
8518**********/
8519/* temporarily stores locations when settings are changed */
8520var PlaceKeeper = (function() {
8521
8522	var currentWindow = null,
8523		currentData = null;
8524
8525	function storePlace() {
8526		currentData = getFirstLocation();
8527	}
8528
8529	function getFirstLocation() {
8530		currentWindow = (sofia.app.windowManager) ?
8531							sofia.app.windowManager.getWindows().filter(function(w) { return w.className == 'BibleWindow'})[0] :
8532							null;
8533
8534		var data = (currentWindow != null) ? currentWindow.getData() : null;
8535
8536		return data;
8537	}
8538
8539	function restorePlace() {
8540		if (currentWindow != null) {
8541			//
8542			//firstWindow.trigger('message', {type:'globalmessage', messagetype: 'nav', })
8543
8544			currentWindow.trigger('globalmessage', {
8545				type: 'globalmessage',
8546				target: currentWindow,
8547				data: {
8548					messagetype: 'nav',
8549					type: 'bible',
8550					locationInfo: currentData // firstWindow.getData()
8551				}
8552			});
8553		}
8554
8555	}
8556
8557	return {
8558		storePlace: storePlace,
8559		restorePlace: restorePlace,
8560		getFirstLocation: getFirstLocation
8561	}
8562
8563})();
8564
8565/* stores locations for back/foward buttons on browser */
8566var TextNavigation = (function() {
8567
8568	var locations = [],
8569		locationIndex = -1;
8570
8571	$(window).on('popstate',  handleBrowserNavigation);
8572
8573	function handleBrowserNavigation(e) {
8574		console.log('handleNavigation', e, e.state);
8575
8576		if (e.originalEvent && e.originalEvent.state != null && typeof e.originalEvent.state.locationid != 'undefined') {
8577
8578			var newlocationid =  e.originalEvent.state.locationid,
8579				type = '';
8580
8581
8582			if (locationIndex-1 > -1 && locations[locationIndex-1] == newlocationid) {
8583				locationIndex--;
8584				type = 'back';
8585			} else if (locationIndex+1 < locations.length && locations[locationIndex+1] == newlocationid) {
8586				locationIndex++;
8587				type = 'forward';
8588			}
8589
8590			setLocation(newlocationid);
8591
8592			textNavigation.trigger('locationchange', {type: type});
8593		}
8594
8595	}
8596
8597
8598	// upon startup, store the first place
8599	function firstState(locationid) {
8600		console.log('firststate',locationid);
8601
8602		locations.push(locationid);
8603		locationIndex = 0;
8604
8605		window.history.replaceState({"locationid": locationid}, null, window.location.href);
8606	}
8607
8608	// each additional change
8609	function locationChange(locationid, type) {
8610
8611		// slice off anything after this one
8612		while (locationIndex > locations.length-1) {
8613			locations.pop();
8614		}
8615
8616		locations.push(locationid);
8617		locationIndex++;
8618
8619		window.history.pushState({"locationid": locationid}, null, window.location.href);
8620
8621
8622		textNavigation.trigger('locationchange', {type: type});
8623	}
8624
8625
8626	function setLocation(locationid) {
8627		// break off and be sure to add the trailing verse 1 (JN1_1)
8628		var fragmentid = locationid.indexOf('_') > -1 ? locationid : locationid + '_1',
8629			sectionid = fragmentid.split('_')[0];
8630
8631		sofia.app.handleGlobalMessage({
8632			type: 'globalmessage',
8633			target: this,
8634			data: {
8635				messagetype:'nav',
8636				type: 'bible',
8637				locationInfo: {
8638					fragmentid: fragmentid,
8639					sectionid: sectionid,
8640					offset: 0
8641				}
8642			}
8643		});
8644	}
8645
8646	function back() {
8647
8648		window.history.go(-1);
8649
8650		/*
8651		if (locationIndex > 0) {
8652			locationIndex--;
8653			var locationid = locations[locationIndex];
8654			setLocation(locationid);
8655		}
8656		*/
8657	}
8658
8659	function forward() {
8660
8661		window.history.go(1);
8662
8663
8664		/*
8665		if (locationIndex < locations.length-1) {
8666			// get this location
8667			locationIndex++;
8668			var locationid = locations[locationIndex];
8669			setLocation(locationid);
8670		}
8671		*/
8672	}
8673
8674	function getLocations() {
8675		return locations;
8676	}
8677
8678	function getLocationIndex() {
8679		return locationIndex;
8680	}
8681
8682
8683	var textNavigation =  {
8684		locationChange: locationChange,
8685		firstState: firstState,
8686		forward: forward,
8687		back: back,
8688		getLocationIndex: getLocationIndex,
8689		getLocations: getLocations
8690	}
8691
8692	textNavigation = $.extend(true, textNavigation, EventEmitter);
8693
8694	return textNavigation;
8695})();
8696
8697
8698
8699/*********
8700* ../app/js/common/iso2iana.js
8701**********/
8702var iso2iana = {
8703  // These are languages for which at least some browsers have hyphenation
8704  // dictionaries for listed under ISO-639-1 codes for which ISO-639-2 codes
8705  // are used in Bible modules and other data we process.
8706  data: {
8707    "afr": "af",
8708    "arz": "ar",
8709    "bul": "bg",
8710    "cat": "ca",
8711    "cym": "cy",
8712    "dan": "da",
8713    "dut": "nl",
8714    "eng": "en",
8715    "epo": "eo",
8716    "est": "et",
8717    "fin": "fi",
8718    "fra": "fr",
8719    "fre": "fr",
8720    "glg": "gl",
8721    "grc": "el",
8722    "grk": "el",
8723    "heb": "he",
8724    "hun": "hu",
8725    "ice": "is",
8726    "ina": "ia",
8727    "isl": "is",
8728    "ita": "it",
8729    "lat": "la",
8730    "lit": "lt",
8731    "mon": "mn",
8732    "nld": "nl",
8733    "nno": "nn",
8734    "nob": "nb",
8735    "por": "pt",
8736    "rus": "ru",
8737    "slv": "sl",
8738    "spa": "es",
8739    "swe": "sv",
8740    "tur": "tr",
8741    "ukr": "uk",
8742    "wel": "cy"
8743  },
8744
8745  // Return the language code needed for browsers to properly process lang=
8746  // attributes. If we don't recognize a mapping, just return the original
8747  convert: function(iso) {
8748    var iana =  (typeof this.data[iso] === 'undefined') ? iso : this.data[iso];
8749    return iana;
8750  }
8751};
8752
8753
8754
8755/*********
8756* ../app/js/common/clickoff.js
8757**********/
8758var ClickOff = {
8759
8760	clickoffid: '',
8761	clickTargets:[],
8762
8763	getClickTargets: function() {
8764		return this.clickTargets;
8765	},
8766
8767	setClickTargets: function(clickTargets) {
8768		this.clickTargets = clickTargets;
8769		this.clearOffClickTimer();
8770	},
8771
8772	docClick: function(e) {
8773
8774		var target = $(e.target),
8775			clickedOnThis = false;
8776
8777		// go through all nested clicked elements
8778		while (target != null && target.length > 0) {
8779
8780			for (var i=0, il=this.clickTargets.length; i<il; i++) {
8781				var clickTarget = this.clickTargets[i];
8782
8783				if (target[0] == clickTarget[0]) {
8784					clickedOnThis = true;
8785					break;
8786				}
8787			}
8788
8789			if (clickedOnThis) {
8790				break;
8791			}
8792
8793			target = target.parent();
8794		}
8795
8796		console.log('clickoff', this.clickoffid, 'doc.click', clickedOnThis);
8797
8798		if (!clickedOnThis) {
8799			//e.preventDefault();
8800
8801			//this.startOffClickTimer();
8802			this.trigger('offclick');
8803
8804			//$(document).off('click', this.docClick);
8805			this.unbindDocClick();
8806
8807			//return false;
8808		}
8809	},
8810
8811	offClickTimeout: null,
8812
8813	startOffClickTimer: function () {
8814		var t = this;
8815
8816		this.clearOffClickTimer();
8817		this.offClickTimeout = setTimeout(function() {
8818			if (t.trigger) {
8819				t.trigger('offclick');
8820			}
8821		}, 50);
8822	},
8823
8824	clearOffClickTimer: function () {
8825		if (this.offClickTimeout != null) {
8826			clearTimeout(this.offClickTimeout);
8827			this.offClickTimeout = null;
8828		}
8829	},
8830
8831	onshow: function() {
8832		console.log('clickoff', this.clickoffid, 'onshow');
8833
8834		this.bindDocClick();
8835	},
8836
8837	onhide: function() {
8838		console.log('clickoff', this.clickoffid, 'onhide');
8839
8840		this.clearOffClickTimer();
8841		this.unbindDocClick();
8842	},
8843
8844	bindDocClick: function() {
8845		this.unbindDocClick();
8846
8847		var proxy = this.proxy = $.proxy( this, 'docClick' );
8848
8849		$(document).on('click.' + this.clickoffid, proxy);
8850	},
8851
8852	unbindDocClick: function() {
8853		if (this.proxy) {
8854			$(document).off('click.' + this.clickoffid, this.proxy);
8855		}
8856	}
8857
8858};
8859
8860
8861
8862/*********
8863* ../app/js/bible/bible.data.js
8864**********/
8865/**
8866 * Bible namespace, Bible chapter/verse statistics, Book names and abbreviations
8867 *
8868 * @license MIT/GLPv2
8869 * @author John Dyer (http://j.hn/)
8870 */
8871
8872if (typeof window.bible == 'undefined')
8873	window.bible = {};
8874
8875/**
8876 * @description ID with chapter/verse statistics and English names
8877 */
8878bible.BOOK_DATA = {
8879"FR":{"name":"Front matter","sortOrder":0,"shortCode":"FR","usfm":"FRT","osis":"Preface",
8880    "chapters":[null],
8881    "names":{"eng":["Front matter"]}},
8882"IN":{"name":"Introduction","sortOrder":1,"shortCode":"IN","usfm":"INT","osis":"Intro",
8883    "chapters":[null],
8884    "names":{"eng":["Introduction"]}},
8885"GN":{"name":"Genesis","sortOrder":2,"shortCode":"GN","usfm":"GEN","osis":"Gen","section":"penta",
8886    "chapters":[31,25,24,26,32,22,24,22,29,32,32,20,18,24,21,16,27,33,38,18,34,24,20,67,34,35,46,22,35,43,55,32,20,31,29,43,36,30,23,23,57,38,34,34,28,34,31,22,33,26],
8887    "names":{"eng":["Genesis","Ge","Gen"]}},
8888"EX":{"name":"Exodus","sortOrder":3,"shortCode":"EX","usfm":"EXO","osis":"Exod","section":"penta",
8889    "chapters":[22,25,22,31,23,30,25,32,35,29,10,51,22,31,27,36,16,27,25,26,36,31,33,18,40,37,21,43,46,38,18,35,23,35,35,38,29,31,43,38],
8890    "names":{"eng":["Exodus","Ex","Exo"]}},
8891"LV":{"name":"Leviticus","sortOrder":4,"shortCode":"LV","usfm":"LEV","osis":"Lev","section":"penta",
8892    "chapters":[17,16,17,35,19,30,38,36,24,20,47,8,59,57,33,34,16,30,37,27,24,33,44,23,55,46,34],
8893    "names":{"eng":["Leviticus","Le","Lev"]}},
8894"NU":{"name":"Numbers","sortOrder":5,"shortCode":"NU","usfm":"NUM","osis":"Num","section":"penta",
8895    "chapters":[54,34,51,49,31,27,89,26,23,36,35,16,33,45,41,50,13,32,22,29,35,41,30,25,18,65,23,31,40,16,54,42,56,29,34,13],
8896    "names":{"eng":["Numbers","Nu","Num"]}},
8897"DT":{"name":"Deuteronomy","sortOrder":6,"shortCode":"DT","usfm":"DEU","osis":"Deut","section":"penta",
8898    "chapters":[46,37,29,49,33,25,26,20,29,22,32,32,18,29,23,22,20,22,21,20,23,30,25,22,19,19,26,68,29,20,30,52,29,12],
8899    "names":{"eng":["Deuteronomy","Dt","Deut","Deu","De"]}},
8900"JS":{"name":"Joshua","sortOrder":7,"shortCode":"JS","usfm":"JOS","osis":"Josh","section":"hist",
8901    "chapters":[18,24,17,24,15,27,26,35,27,43,23,24,33,15,63,10,18,28,51,9,45,34,16,33],
8902    "names":{"eng":["Joshua","Js","Jos","Jos","Josh"]}},
8903"JG":{"name":"Judges","sortOrder":8,"shortCode":"JG","usfm":"JDG","osis":"Judg","section":"hist",
8904    "chapters":[36,23,31,24,31,40,25,35,57,18,40,15,25,20,20,31,13,31,30,48,25],
8905    "names":{"eng":["Judges","Jg","Jdg","Jdgs"]}},
8906"RT":{"name":"Ruth","sortOrder":9,"shortCode":"RT","usfm":"RUT","osis":"Ruth","section":"hist",
8907    "chapters":[22,23,18,22],
8908    "names":{"eng":["Ruth","Ru","Rut"]}},
8909"S1":{"name":"The First Book of Samuel","sortOrder":10,"shortCode":"S1","usfm":"1SA","osis":"1Sam","section":"hist",
8910    "chapters":[28,36,21,22,12,21,17,22,27,27,15,25,23,52,35,23,58,30,24,42,15,23,29,22,44,25,12,25,11,31,13],
8911    "names":{"eng":["1 Samuel","1S","1 Sam","1Sam","1 Sa","1Sa","I Samuel","I Sam","I Sa"]}},
8912"S2":{"name":"The Second Book of Samuel","sortOrder":11,"shortCode":"S2","usfm":"2SA","osis":"2Sam","section":"hist",
8913    "chapters":[27,32,39,12,25,23,29,18,13,19,27,31,39,33,37,23,29,33,43,26,22,51,39,25],
8914    "names":{"eng":["2 Samuel","2S","2 Sam","2Sam","2 Sa","2Sa","II Samuel","II Sam","II Sa","IIS"]}},
8915"K1":{"name":"The First Book of Kings","sortOrder":12,"shortCode":"K1","usfm":"1KI","osis":"1Kgs","section":"hist",
8916    "chapters":[53,46,28,34,18,38,51,66,28,29,43,33,34,31,34,34,24,46,21,43,29,53],
8917    "names":{"eng":["1 Kings","1K","1 Kin","1Kin","1 Ki","IK","1Ki","I Kings","I Kin","I Ki"]}},
8918"K2":{"name":"The Second Book of Kings","sortOrder":13,"shortCode":"K2","usfm":"2KI","osis":"2Kgs","section":"hist",
8919    "chapters":[18,25,27,44,27,33,20,29,37,36,21,21,25,29,38,20,41,37,37,21,26,20,37,20,30],
8920    "names":{"eng":["2 Kings","2K","2 Kin","2Kin","2 Ki","IIK","2Ki","II Kings","II Kin","II Ki"]}},
8921"R1":{"name":"The First Book of Chronicles","sortOrder":14,"shortCode":"R1","usfm":"1CH","osis":"1Chr","section":"hist",
8922    "chapters":[54,55,24,43,26,81,40,40,44,14,47,40,14,17,29,43,27,17,19,8,30,19,32,31,31,32,34,21,30],
8923    "names":{"eng":["1 Chronicles","1Ch","1 Chr","1Chr","1 Ch","ICh","I Chronicles","I Chr","I Ch"]}},
8924"R2":{"name":"The Second Book of Chronicles","sortOrder":15,"shortCode":"R2","usfm":"2CH","osis":"2Chr","section":"hist",
8925    "chapters":[17,18,17,22,14,42,22,18,31,19,23,16,22,15,19,14,19,34,11,37,20,12,21,27,28,23,9,27,36,27,21,33,25,33,27,23],
8926    "names":{"eng":["2 Chronicles","2Ch","2 Chr","2 Chr","2Chr","2 Ch","IICh","II Chronicles","II Chr","II Ch"]}},
8927"ER":{"name":"Ezra","sortOrder":16,"shortCode":"ER","usfm":"EZR","osis":"Ezra","section":"hist",
8928    "chapters":[11,70,13,24,17,22,28,36,15,44],
8929    "names":{"eng":["Ezra","Ezr"]}},
8930"NH":{"name":"Nehemiah","sortOrder":17,"shortCode":"NH","usfm":"NEH","osis":"Neh","section":"hist",
8931    "chapters":[11,20,32,23,19,19,73,18,38,39,36,47,31],
8932    "names":{"eng":["Nehemiah","Ne","Neh","Neh","Ne"]}},
8933"ET":{"name":"Esther","sortOrder":18,"shortCode":"ET","usfm":"EST","osis":"Esth","section":"hist",
8934    "chapters":[22,23,15,17,14,14,10,17,32,3],
8935    "names":{"eng":["Esther","Es","Est","Esth"]}},
8936"JB":{"name":"Job","sortOrder":19,"shortCode":"JB","usfm":"JOB","osis":"Job","section":"poet",
8937    "chapters":[22,13,26,21,27,30,21,22,35,22,20,25,28,22,35,22,16,21,29,29,34,30,17,25,6,14,23,28,25,31,40,22,33,37,16,33,24,41,30,24,34,17],
8938    "names":{"eng":["Job","Jb","Job"]}},
8939"PS":{"name":"Psalms","sortOrder":20,"shortCode":"PS","usfm":"PSA","osis":"Ps","section":"poet",
8940    "chapters":[6,12,8,8,12,10,17,9,20,18,7,8,6,7,5,11,15,50,14,9,13,31,6,10,22,12,14,9,11,12,24,11,22,22,28,12,40,22,13,17,13,11,5,26,17,11,9,14,20,23,19,9,6,7,23,13,11,11,17,12,8,12,11,10,13,20,7,35,36,5,24,20,28,23,10,12,20,72,13,19,16,8,18,12,13,17,7,18,52,17,16,15,5,23,11,13,12,9,9,5,8,28,22,35,45,48,43,13,31,7,10,10,9,8,18,19,2,29,176,7,8,9,4,8,5,6,5,6,8,8,3,18,3,3,21,26,9,8,24,13,10,7,12,15,21,10,20,14,9,6],
8941    "names":{"eng":["Psalm","Ps","Psa"]}},
8942"PR":{"name":"Proverbs","sortOrder":21,"shortCode":"PR","usfm":"PRO","osis":"Prov","section":"poet",
8943    "chapters":[33,22,35,27,23,35,27,36,18,32,31,28,25,35,33,33,28,24,29,30,31,29,35,34,28,28,27,28,27,33,31],
8944    "names":{"eng":["Proverbs","Pr","Prov","Pro"]}},
8945"EC":{"name":"Ecclesiastes","sortOrder":22,"shortCode":"EC","usfm":"ECC","osis":"Eccl","section":"poet",
8946    "chapters":[18,26,22,16,20,12,29,17,18,20,10,14],
8947    "names":{"eng":["Ecclesiastes","Ec","Ecc","Qohelet"]}},
8948"SS":{"name":"Song of Solomon","sortOrder":23,"shortCode":"SS","usfm":"SNG","osis":"Song","section":"poet",
8949    "chapters":[17,17,11,16,16,13,13,14],
8950    "names":{"eng":["Song of Songs","So","Sos","Song of Solomon","SOS","SongOfSongs","SongofSolomon","Canticle of Canticles"]}},
8951"IS":{"name":"Isaiah","sortOrder":24,"shortCode":"IS","usfm":"ISA","osis":"Isa","section":"majorp",
8952    "chapters":[31,22,26,6,30,13,25,22,21,34,16,6,22,32,9,14,14,7,25,6,17,25,18,23,12,21,13,29,24,33,9,20,24,17,10,22,38,22,8,31,29,25,28,28,25,13,15,22,26,11,23,15,12,17,13,12,21,14,21,22,11,12,19,12,25,24],
8953    "names":{"eng":["Isaiah","Is","Isa"]}},
8954"JR":{"name":"Jeremiah","sortOrder":25,"shortCode":"JR","usfm":"JER","osis":"Jer","section":"majorp",
8955    "chapters":[19,37,25,31,31,30,34,22,26,25,23,17,27,22,21,21,27,23,15,18,14,30,40,10,38,24,22,17,32,24,40,44,26,22,19,32,21,28,18,16,18,22,13,30,5,28,7,47,39,46,64,34],
8956    "names":{"eng":["Jeremiah","Je","Jer"]}},
8957"LM":{"name":"Lamentations","sortOrder":26,"shortCode":"LM","usfm":"LAM","osis":"Lam","section":"poet",
8958    "chapters":[22,22,66,22,22],
8959    "names":{"eng":["Lamentations","La","Lam","Lament"]}},
8960"EK":{"name":"Ezekiel","sortOrder":27,"shortCode":"EK","usfm":"EZK","osis":"Ezek","section":"majorp",
8961    "chapters":[28,10,27,17,17,14,27,18,11,22,25,28,23,23,8,63,24,32,14,49,32,31,49,27,17,21,36,26,21,26,18,32,33,31,15,38,28,23,29,49,26,20,27,31,25,24,23,35],
8962    "names":{"eng":["Ezekiel","Ek","Ezek","Eze"]}},
8963"DN":{"name":"Daniel","sortOrder":28,"shortCode":"DN","usfm":"DAN","osis":"Dan","section":"majorp",
8964    "chapters":[21,49,30,37,31,28,28,27,27,21,45,13],
8965    "names":{"eng":["Daniel","Da","Dan","Dl","Dnl"]}},
8966"HS":{"name":"Hosea","sortOrder":29,"shortCode":"HS","usfm":"HOS","osis":"Hos","section":"minorp",
8967    "chapters":[11,23,5,19,15,11,16,14,17,15,12,14,16,9],
8968    "names":{"eng":["Hosea","Ho","Hos"]}},
8969"JL":{"name":"Joel","sortOrder":30,"shortCode":"JL","usfm":"JOL","osis":"Joel","section":"minorp",
8970    "chapters":[20,32,21],
8971    "names":{"eng":["Joel","Jl","Joel","Joe"]}},
8972"AM":{"name":"Amos","sortOrder":31,"shortCode":"AM","usfm":"AMO","osis":"Amos","section":"minorp",
8973    "chapters":[15,16,15,13,27,14,17,14,15],
8974    "names":{"eng":["Amos","Am","Amos","Amo"]}},
8975"OB":{"name":"Obadiah","sortOrder":32,"shortCode":"OB","usfm":"OBA","osis":"Obad","section":"minorp",
8976    "chapters":[21],
8977    "names":{"eng":["Obadiah","Ob","Oba","Obd","Odbh"]}},
8978"JH":{"name":"Jonah","sortOrder":33,"shortCode":"JH","usfm":"JON","osis":"Jonah","section":"minorp",
8979    "chapters":[17,10,10,11],
8980    "names":{"eng":["Jonah","Jh","Jon","Jnh"]}},
8981"MC":{"name":"Micah","sortOrder":34,"shortCode":"MC","usfm":"MIC","osis":"Mic","section":"minorp",
8982    "chapters":[16,13,12,13,15,16,20],
8983    "names":{"eng":["Micah","Mi","Mic"]}},
8984"NM":{"name":"Nahum","sortOrder":35,"shortCode":"NM","usfm":"NAM","osis":"Nah","section":"minorp",
8985    "chapters":[15,13,19],
8986    "names":{"eng":["Nahum","Na","Nah","Nah","Na"]}},
8987"HK":{"name":"Habakkuk","sortOrder":36,"shortCode":"HK","usfm":"HAB","osis":"Hab","section":"minorp",
8988    "chapters":[17,20,19],
8989    "names":{"eng":["Habakkuk","Hb","Hab","Hk","Habk"]}},
8990"ZP":{"name":"Zephaniah","sortOrder":37,"shortCode":"ZP","usfm":"ZEP","osis":"Zeph","section":"minorp",
8991    "chapters":[18,15,20],
8992    "names":{"eng":["Zephaniah","Zp","Zep","Zeph"]}},
8993"HG":{"name":"Haggai","sortOrder":38,"shortCode":"HG","usfm":"HAG","osis":"Hag","section":"minorp",
8994    "chapters":[15,23],
8995    "names":{"eng":["Haggai","Ha","Hag","Hagg"]}},
8996"ZC":{"name":"Zechariah","sortOrder":39,"shortCode":"ZC","usfm":"ZEC","osis":"Zech","section":"minorp",
8997    "chapters":[21,13,10,14,11,15,14,23,17,12,17,14,9,21],
8998    "names":{"eng":["Zechariah","Zc","Zech","Zec"]}},
8999"ML":{"name":"Malachi","sortOrder":40,"shortCode":"ML","usfm":"MAL","osis":"Mal","section":"minorp",
9000    "chapters":[14,17,18,6],
9001    "names":{"eng":["Malachi","Ml","Mal","Mlc"]}},
9002"TB":{"name":"Tobit","sortOrder":41,"shortCode":"TB","usfm":"TOB","osis":"Tob",
9003    "chapters":[22,14,17,21,22,17,18],
9004    "names":{"eng":["Tobit"]}},
9005"JT":{"name":"Judith","sortOrder":42,"shortCode":"JT","usfm":"JDT","osis":"Jdt",
9006    "chapters":[16,28,10,15,24,21,32,36,14,23,23,20,20,19,13,25],
9007    "names":{"eng":["Judith"]}},
9008"EG":{"name":"Esther (Greek)","sortOrder":43,"shortCode":"EG","usfm":"ESG","osis":"EsthGr",
9009    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9010    "names":{"eng":["Esther (Greek)"]}},
9011"AE":{"name":"Additions to Esther","sortOrder":44,"shortCode":"AE","usfm":"ADE","osis":"AddEsth",
9012    "chapters":[],
9013    "names":{"eng":["Additions to Esther"]}},
9014"WS":{"name":"Wisdom of Solomon","sortOrder":45,"shortCode":"WS","usfm":"WIS","osis":"Wis",
9015    "chapters":[],
9016    "names":{"eng":["Wisdom","Wisdom of Solomon"]}},
9017"SR":{"name":"Sirach","sortOrder":46,"shortCode":"SR","usfm":"SIR","osis":"Sir",
9018    "chapters":[],
9019    "names":{"eng":["Sirach","Ecclesiasticus"]}},
9020"BR":{"name":"Baruch","sortOrder":47,"shortCode":"BR","usfm":"BAR","osis":"Bar",
9021    "chapters":[],
9022    "names":{"eng":["Baruch"]}},
9023"LJ":{"name":"Letter of Jeremiah","sortOrder":48,"shortCode":"LJ","usfm":"LJE","osis":"EpJer",
9024    "chapters":[],
9025    "names":{"eng":["Letter of Jeremiah"]}},
9026"PA":{"name":"Song of the Three Children","sortOrder":49,"shortCode":"PA","usfm":"S3Y","osis":"PrAzar",
9027    "chapters":[],
9028    "names":{"eng":["Prayer of Azariah"]}},
9029"SN":{"name":"Susanna","sortOrder":50,"shortCode":"SN","usfm":"SUS","osis":"Sus",
9030    "chapters":[],
9031    "names":{"eng":["Susanna"]}},
9032"BL":{"name":"Bel and the Dragon","sortOrder":51,"shortCode":"BL","usfm":"BEL","osis":"Bel",
9033    "chapters":[],
9034    "names":{"eng":["Bel and the Dragon"]}},
9035"M1":{"name":"1 Maccabees","sortOrder":52,"shortCode":"M1","usfm":"1MA","osis":"1Macc",
9036    "chapters":[],
9037    "names":{"eng":["1 Maccabees"]}},
9038"M2":{"name":"2 Maccabees","sortOrder":53,"shortCode":"M2","usfm":"2MA","osis":"2Macc",
9039    "chapters":[],
9040    "names":{"eng":["2 Maccabees"]}},
9041"E1":{"name":"1 Esdras","sortOrder":54,"shortCode":"E1","usfm":"1ES","osis":"1Esd",
9042    "chapters":[],
9043    "names":{"eng":["1 Esdras"]}},
9044"PX":{"name":"Psalm 151","sortOrder":56,"shortCode":"PX","usfm":"PS2","osis":"AddPs",
9045    "chapters":[null],
9046    "names":{"eng":["Psalm 151"]}},
9047"PN":{"name":"Prayer of Manasseh","sortOrder":56,"shortCode":"PN","usfm":"MAN","osis":"PrMan",
9048    "chapters":[null],
9049    "names":{"eng":["Prayer of Manasseh"]}},
9050"M3":{"name":"3 Maccabees","sortOrder":57,"shortCode":"M3","usfm":"3MA","osis":"3Macc",
9051    "chapters":[],
9052    "names":{"eng":["3 Maccabees"]}},
9053"E2":{"name":"2 Esdras","sortOrder":58,"shortCode":"E2","usfm":"2ES","osis":"2Esd",
9054    "chapters":[],
9055    "names":{"eng":["2 Esdras","5 Ezra"]}},
9056"M4":{"name":"4 Maccabees","sortOrder":59,"shortCode":"M4","usfm":"4MA","osis":"4Macc",
9057    "chapters":[],
9058    "names":{"eng":["4 Maccabees"]}},
9059"OS":{"name":"Odes of Solomon","sortOrder":60,"shortCode":"OS","usfm":"ODS","osis":"OdesSol",
9060    "chapters":[],
9061    "names":{"eng":["Odes of Solomon"]}},
9062"SP":{"name":"Psalms of Solomon","sortOrder":61,"shortCode":"SP","usfm":"PSS","osis":"PssSol",
9063    "chapters":[],
9064    "names":{"eng":["Psalms of Solomon"]}},
9065"LL":{"name":"Epistle to the Laodiceans","sortOrder":62,"shortCode":"LL","usfm":"EPL","osis":"EpLao",
9066    "chapters":[],
9067    "names":{"eng":["Epistle to the Laodiceans"]}},
9068"N1":{"name":"Ethiopic Apocalypse of Enoch","sortOrder":63,"shortCode":"N1","usfm":"1EN","osis":"1En",
9069    "chapters":[],
9070    "names":{"eng":["Ethiopic Apocalypse of Enoch"]}},
9071"JE":{"name":"Jubilees","sortOrder":64,"shortCode":"JE","usfm":"JUB","osis":"Jub",
9072    "chapters":[],
9073    "names":{"eng":["Jubilees"]}},
9074"AD":{"name":"Additions to Daniel","sortOrder":65,"shortCode":"AD","usfm":"DNT","osis":"AddDan",
9075    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9076    "names":{"eng":["Additions to Daniel"]}},
9077"DG":{"name":"Daniel (Greek)","sortOrder":66,"shortCode":"DG","usfm":"DAG","osis":"DanGr",
9078    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null],
9079    "names":{"eng":["Daniel (Greek)"]}},
9080"MT":{"name":"Matthew","sortOrder":70,"shortCode":"MT","usfm":"MAT","osis":"Matt","section":"gospel",
9081    "chapters":[25,23,17,25,48,34,29,34,38,42,30,50,58,36,39,28,27,35,30,34,46,46,39,51,46,75,66,20],
9082    "names":{"eng":["Matthew","Mt","Matt","Mat"]}},
9083"MK":{"name":"Mark","sortOrder":71,"shortCode":"MK","usfm":"MRK","osis":"Mark","section":"gospel",
9084    "chapters":[45,28,35,41,43,56,37,38,50,52,33,44,37,72,47,20],
9085    "names":{"eng":["Mark","Mk","Mar","Mrk"]}},
9086"LK":{"name":"Luke","sortOrder":72,"shortCode":"LK","usfm":"LUK","osis":"Luke","section":"gospel",
9087    "chapters":[80,52,38,44,39,49,50,56,62,42,54,59,35,35,32,31,37,43,48,47,38,71,56,53],
9088    "names":{"eng":["Luke","Lk","Luk","Lu"]}},
9089"JN":{"name":"John","sortOrder":73,"shortCode":"JN","usfm":"JHN","osis":"John","section":"gospel",
9090    "chapters":[51,25,36,54,47,71,53,59,41,42,57,50,38,31,27,33,26,40,42,31,25],
9091    "names":{"eng":["John","Jn","Joh","Jo"]}},
9092"AC":{"name":"Acts","sortOrder":74,"shortCode":"AC","usfm":"ACT","osis":"Acts","section":"acts",
9093    "chapters":[26,47,26,37,42,15,60,40,43,48,30,25,52,28,41,40,34,28,41,38,40,30,35,27,27,32,44,31],
9094    "names":{"eng":["Acts","Ac","Act"]}},
9095"RM":{"name":"Romans","sortOrder":75,"shortCode":"RM","usfm":"ROM","osis":"Rom","section":"paul",
9096    "chapters":[32,29,31,25,21,23,25,39,33,21,36,21,14,23,33,27],
9097    "names":{"eng":["Romans","Ro","Rom","Rmn","Rmns"]}},
9098"C1":{"name":"Paul's First Letter to the Corinthians","sortOrder":76,"shortCode":"C1","usfm":"1CO","osis":"1Cor","section":"paul",
9099    "chapters":[31,16,23,21,13,20,40,13,27,33,34,31,13,40,58,24],
9100    "names":{"eng":["1 Corinthians","1Co","1 Cor","1Cor","ICo","1 Co","1Co","I Corinthians","I Cor","I Co"]}},
9101"C2":{"name":"Paul's Second Lettor to the Corinthians","sortOrder":77,"shortCode":"C2","usfm":"2CO","osis":"2Cor","section":"paul",
9102    "chapters":[24,17,18,18,21,18,16,24,15,18,33,21,14],
9103    "names":{"eng":["2 Corinthians","2Co","2 Cor","2Cor","IICo","2 Co","2Co","II Corinthians","II Cor","II Co"]}},
9104"GL":{"name":"Paul's Letter to the Galatians","sortOrder":78,"shortCode":"GL","usfm":"GAL","osis":"Gal","section":"paul",
9105    "chapters":[24,21,29,31,26,18],
9106    "names":{"eng":["Galatians","Ga","Gal","Gltns"]}},
9107"EP":{"name":"Paul's Letter to the Ephesians","sortOrder":79,"shortCode":"EP","usfm":"EPH","osis":"Eph","section":"paul",
9108    "chapters":[23,22,21,32,33,24],
9109    "names":{"eng":["Ephesians","Ep","Eph","Ephn"]}},
9110"PP":{"name":"Paul's Letter to the Philippians","sortOrder":80,"shortCode":"PP","usfm":"PHP","osis":"Phil","section":"paul",
9111    "chapters":[30,30,21,23],
9112    "names":{"eng":["Philippians","Pp","Phi","Phil","Phi"]}},
9113"CL":{"name":"Paul's Letter to the Colossians","sortOrder":81,"shortCode":"CL","usfm":"COL","osis":"Col","section":"paul",
9114    "chapters":[29,23,25,18],
9115    "names":{"eng":["Colossians","Co","Col","Colo","Cln","Clns"]}},
9116"H1":{"name":"Paul's First Letter to the Thessalonians","sortOrder":82,"shortCode":"H1","usfm":"1TH","osis":"1Thess","
9116section":"paul",
9117    "chapters":[10,20,13,18,28],
9118    "names":{"eng":["1 Thessalonians","1Th","1 Thess","1Thess","ITh","1 Thes","1Thes","1 The","1The","1 Th","1Th","I Thessalonians","I Thess","I The","I Th"]}},
9119"H2":{"name":"Paul's Second Letter to the Thessalonians","sortOrder":83,"shortCode":"H2","usfm":"2TH","osis":"2Thess","section":"paul",
9120    "chapters":[12,17,18],
9121    "names":{"eng":["2 Thessalonians","2Th","2 Thess","2 Thess","2Thess","IITh","2 Thes","2Thes","2 The","2The","2 Th","2Th","II Thessalonians","II Thess","II The","II Th"]}},
9122"T1":{"name":"Paul's First Letter to Timothy","sortOrder":84,"shortCode":"T1","usfm":"1TI","osis":"1Tim","section":"paul",
9123    "chapters":[20,15,16,16,25,21],
9124    "names":{"eng":["1 Timothy","1Ti","1 Tim","1Tim","1 Ti","ITi","1Ti","I Timothy","I Tim","I Ti"]}},
9125"T2":{"name":"Paul's Second Letter to Timothy","sortOrder":85,"shortCode":"T2","usfm":"2TI","osis":"2Tim","section":"paul",
9126    "chapters":[18,26,17,22],
9127    "names":{"eng":["2 Timothy","2Ti","2 Tim","2 Tim","2Tim","2 Ti","IITi","2Ti","II Timothy","II Tim","II Ti"]}},
9128"TT":{"name":"Paul's Letter to Titus","sortOrder":86,"shortCode":"TT","usfm":"TIT","osis":"Titus","section":"paul",
9129    "chapters":[16,15,15],
9130    "names":{"eng":["Titus","Ti","Tit","Tt","Ts"]}},
9131"PM":{"name":"Paul's Letter to Philemon","sortOrder":87,"shortCode":"PM","usfm":"PHM","osis":"Phlm","section":"paul",
9132    "chapters":[25],
9133    "names":{"eng":["Philemon","Pm","Phile","Phile","Philm","Pm"]}},
9134"HB":{"name":"The Letter to the Hebrews","sortOrder":88,"shortCode":"HB","usfm":"HEB","osis":"Heb","section":"general",
9135    "chapters":[14,18,19,16,14,20,28,13,28,39,40,29,25],
9136    "names":{"eng":["Hebrews","He","Heb","Hw"]}},
9137"JM":{"name":"The Letter from James","sortOrder":89,"shortCode":"JM","usfm":"JAS","osis":"Jas","section":"general",
9138    "chapters":[27,26,18,17,20],
9139    "names":{"eng":["James","Jm","Jam","Jas","Ja"]}},
9140"P1":{"name":"The First Letter from Peter","sortOrder":90,"shortCode":"P1","usfm":"1PE","osis":"1Pet","section":"general",
9141    "chapters":[25,25,22,19,14],
9142    "names":{"eng":["1 Peter","1P","1 Pet","1Pet","IPe","1P","I Peter","I Pet","I Pe"]}},
9143"P2":{"name":"The Second Letter from Peter","sortOrder":91,"shortCode":"P2","usfm":"2PE","osis":"2Pet","section":"general",
9144    "chapters":[21,22,18],
9145    "names":{"eng":["2 Peter","2P","2 Pet","2Pet","2Pe","IIP","II Peter","II Pet","II Pe"]}},
9146"J1":{"name":"John's First Letter","sortOrder":92,"shortCode":"J1","usfm":"1JN","osis":"1John","section":"general",
9147    "chapters":[10,29,24,21,21],
9148    "names":{"eng":["1 John","1J","1 Jn","1Jn","1 Jo","IJo","I John","I Jo","I Jn"]}},
9149"J2":{"name":"John's Second Letter","sortOrder":93,"shortCode":"J2","usfm":"2JN","osis":"2John","section":"general",
9150    "chapters":[13],
9151    "names":{"eng":["2 John","2J","2 Jn","2Jn","2 Jo","IIJo","II John","II Jo","II Jn"]}},
9152"J3":{"name":"John's Third Letter","sortOrder":94,"shortCode":"J3","usfm":"3JN","osis":"3John","section":"general",
9153    "chapters":[14],
9154    "names":{"eng":["3 John","3J","3 Jn","3 Jn","3Jn","3 Jo","IIIJo","III John","III Jo","III Jn"]}},
9155"JD":{"name":"Jude's Letter","sortOrder":95,"shortCode":"JD","usfm":"JUD","osis":"Jude","section":"general",
9156    "chapters":[25],
9157    "names":{"eng":["Jude","Jude","Jude"]}},
9158"RV":{"name":"The Revelation to John","sortOrder":96,"shortCode":"RV","usfm":"REV","osis":"Rev","section":"rev",
9159    "chapters":[20,29,22,11,14,17,17,13,21,11,19,17,18,20,8,21,18,24,21,15,27,20],
9160    "names":{"eng":["Revelation","Re","Rev","Rvltn"]}},
9161"BK":{"name":"Back matter","sortOrder":97,"shortCode":"BK","usfm":"BAK","osis":"Back",
9162    "chapters":[null],
9163    "names":{"eng":["Back matter"]}},
9164"OH":{"name":"Other","sortOrder":98,"shortCode":"OH","usfm":"OTH","osis":"Other",
9165    "chapters":[null],
9166    "names":{"eng":["Other"]}},
9167"XA":{"name":"XXA","sortOrder":99,"shortCode":"XA","usfm":"XXA","osis":"XXA",
9168    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9169    "names":{"eng":["XXA"]}},
9170"XB":{"name":"XXB","sortOrder":100,"shortCode":"XB","usfm":"XXB","osis":"XXB",
9171    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9172    "names":{"eng":["XXB"]}},
9173"XC":{"name":"XXC","sortOrder":101,"shortCode":"XC","usfm":"XXC","osis":"XXC",
9174    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9175    "names":{"eng":["XXC"]}},
9176"XD":{"name":"XXD","sortOrder":102,"shortCode":"XD","usfm":"XXD","osis":"XXD",
9177    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9178    "names":{"eng":["XXD"]}},
9179"XE":{"name":"XXE","sortOrder":103,"shortCode":"XE","usfm":"XXE","osis":"XXE",
9180    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9181    "names":{"eng":["XXE"]}},
9182"XF":{"name":"XXF","sortOrder":104,"shortCode":"XF","usfm":"XXF","osis":"XXF",
9183    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9184    "names":{"eng":["XXF"]}},
9185"XG":{"name":"XXG","sortOrder":105,"shortCode":"XG","usfm":"XXG","osis":"XXG",
9186    "chapters":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
9187    "names":{"eng":["XXG"]}},
9188"GS":{"name":"Glossary","sortOrder":106,"shortCode":"GS","usfm":"GLO","osis":"Glossary",
9189    "chapters":[null],
9190    "names":{"eng":["Glossary"]}},
9191"CN":{"name":"Concordance","sortOrder":107,"shortCode":"CN","usfm":"CNC","osis":"Conc",
9192    "chapters":[null],
9193    "names":{"eng":["Concordance"]}},
9194"TX":{"name":"Topical Index","sortOrder":108,"shortCode":"TX","usfm":"TDX","osis":"Topic",
9195    "chapters":[null],
9196    "names":{"eng":["Topical Index"]}},
9197"NX":{"name":"Names Index","sortOrder":109,"shortCode":"NX","usfm":"NDX","osis":"Name",
9198    "chapters":[null],
9199    "names":{"eng":["Names Index"]}}
9200}
9201
9202bible.EXTRA_MATTER = ["FR","IN","BK","OH","XA","XB","XC","XD","XE","XF","XG","GS","CN","TX","NX"];
9203
9204/**
9205 * @description Default order of Old Testament books
9206 */
9207bible.OT_BOOKS_OSIS = ["Gen","Exod","Lev","Num","Deut","Josh","Judg","Ruth","1Sam","2Sam","1Kgs","2Kgs","1Chr","2Chr","Ezra","Neh","Esth","Job","Ps","Prov","Eccl","Song","Isa","Jer","Lam","Ezek","Dan","Hos","Joel","Amos","Obad","Jonah","Mic","Nah","Hab","Zeph","Hag","Zech","Mal"];
9208
9209bible.OT_BOOKS_USFM = ["GEN","EXO","LEV","NUM","DEU","JOS","JDG","RUT","1SA","2SA","1KI","2KI","1CH","2CH","EZR","NEH","EST","JOB","PSA","PRO","ECC","SNG","ISA","JER","LAM","EZK","DAN","HOS","JOL","AMO","OBA","JON","MIC","NAM","HAB","ZEP","HAG","ZEC","MAL"];
9210
9211bible.OT_BOOKS =
9212["GN","EX","LV","NU","DT","JS","JG","RT","S1","S2","K1","K2","R1","R2","ER","NH","ET","JB","PS","PR","EC","SS","IS","JR","LM","EK","DN","HS","JL","AM","OB","JH","MC","NM","HK","ZP","HG","ZC","ML"];
9213
9214
9215/**
9216 * @description Default order of New Testament books
9217 */
9218bible.NT_BOOKS_OSIS = ["Matt","Mark","Luke","John","Acts","Rom","1Cor","2Cor","Gal","Eph","Phil","Col","1Thess","2Thess","1Tim","2Tim","Titus","Phlm","Heb","Jas","1Pet","2Pet","1John","2John","3John","Jude","Rev"];
9219
9220bible.NT_BOOKS_USFM = ["MAT","MRK","LUK","JHN","ACT","ROM","1CO","2CO","GAL","EPH","PHP","COL","1TH","2TH","1TI","2TI","TIT","PHM","HEB","JAS","1PE","2PE","1JN","2JN","3JN","JUD","REV"];
9221
9222bible.NT_BOOKS = ["MT","MK","LK","JN","AC","RM","C1","C2","GL","EP","PP","CL","H1","H2","T1","T2","TT","PM","HB","JM","P1","P2","J1","J2","J3","JD","RV"];
9223
9224/**
9225 * @description Default order of Apocryphal books
9226 */
9227bible.AP_BOOKS_OSIS = ["Tob","Jdt","EsthGr","AddEsth","Wis","Sir","Bar","EpJer","PrAzar","Sus","Bel","1Macc","2Macc","3Macc","4Macc","1Esd","2Esd","Ps151"];
9228
9229bible.AP_BOOKS_USFM =
9230["TOB","JDT","ESG","ADE","WIS","SIR","BAR","LJE","PA","SUS","BEL","1MA","2MA","1ES"]
9231
9232bible.AP_BOOKS =
9233["TB","JT","ED","AE","WS","SR","BR","LJ","S3Y","SN","BL","M1","M2","E1"]
9234
9235
9236/**
9237 * @description Default order of books
9238 */
9239bible.DEFAULT_BIBLE = bible.OT_BOOKS.concat(bible.NT_BOOKS);
9240bible.DEFAULT_BIBLE_OSIS = bible.OT_BOOKS_OSIS.concat(bible.NT_BOOKS_OSIS);
9241bible.DEFAULT_BIBLE_USFM = bible.OT_BOOKS_USFM.concat(bible.NT_BOOKS_USFM);
9242
9243/**
9244 * @description Default order of Bible's with Apocryphal books
9245 */
9246bible.APOCRYPHAL_BIBLE = bible.OT_BOOKS.concat(bible.AP_BOOKS, bible.NT_BOOKS);
9247bible.APOCRYPHAL_BIBLE_USFM = bible.OT_BOOKS_USFM.concat(bible.AP_BOOKS_USFM, bible.NT_BOOKS_USFM);
9248
9249
9250/**
9251 * @description Adds names in bulk for a language (each version contains their own list of book names)
9252 */
9253bible.addNames = function(lang, bookList, namesData) {
9254	for (var i in bookList) {
9255		var dbsCode = bookList[i],
9256			bookInfo = bible.BOOK_DATA[ dbsCode ],
9257			names = namesData[i];
9258
9259		if (typeof bookInfo != 'undefined') {
9260
9261			if (typeof names === 'string') {
9262				names = [names];
9263			}
9264
9265			if (!bookInfo.names[lang]) {
9266				bookInfo.names[lang] = [];
9267			}
9268
9269			// bookInfo.names[lang] = names;
9270			bookInfo.names[lang].splice(bookInfo.names[lang].length-1, 0, names);
9271
9272		}
9273	}
9274}
9275
9276
9277/**
9278 * @description Used in selecting a chapter since not all Bibles use digits 0-9.
9279 */
9280bible.numbers = {
9281	"default":["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70","71","72","73","74","75","76","77","78","79","80","81","82","83","84","85","86","87","88","89","90","91","92","93","94","95","96","97","98","99","100","101","102","103","104","105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127","128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","144","145","146","147","148","149","150"]
9282}
9283
9284
9285
9286/*********
9287* ../app/js/bible/bible.reference.js
9288**********/
9289/**
9290 * Bible reference parser
9291 *
9292 * @author John Dyer (http://j.hn/)
9293 */
9294
9295
9296// Depends on bible.data.js
9297
9298bible.parseReference = function (textReference, language) {
9299
9300	var
9301		bookIndex = -1,
9302		chapter1 = -1,
9303		verse1 = -1,
9304		chapter2 = -1,
9305		verse2 = -1,
9306		input = new String(textReference),
9307		i,
9308		lang,
9309		bookid,
9310		matchingbookid = null,
9311		matchingLanguage = null,
9312		afterRange = false,
9313		afterSeparator = false,
9314		startedNumber = false,
9315		currentNumber = '',
9316		name,
9317		possibleMatch,
9318		shortCodeRegex = /^\w{2}\d{1,3}(_\d{1,3})?$/;
9319
9320	// is short code format (GN2 || GN2_1)
9321	//bible.shortCodeRegex.lastIndex = 0;
9322	if (shortCodeRegex.test(input)) {
9323
9324		var parts = input.split('_'),
9325			bookChapter = parts[0];
9326
9327
9328		bookid = bookChapter.substring(0,2).toUpperCase();
9329		chapter1 = parseInt(bookChapter.substring(2), 10);
9330
9331		if (parts.length > 1) {
9332			verse1 = parseInt(parts[1], 10);
9333		}
9334
9335		return bible.Reference(bookid, chapter1, verse1, chapter2, verse2, language);
9336	}
9337
9338	// check books for DBS, OSIS, USFM
9339
9340
9341	// go through all books and test all names
9342	for (bookid in bible.BOOK_DATA) {
9343
9344		// match id?
9345		possibleMatch = input.substring(0, Math.floor(bookid.length, input.length)).toLowerCase();
9346		var nextIsSeparator = input.length > possibleMatch.length ? /(\d|\.|\s)/.test(input.substr(possibleMatch.length, 1)) : false;
9347		if (possibleMatch == bookid.toLowerCase() && nextIsSeparator) {
9348			matchingbookid = bookid;
9349			input = input.substring(bookid.length);
9350			//matchingLanguage = 'eng';
9351			break;
9352		}
9353
9354		// if no direct match on OSIS id, then go through names in each language
9355		for (lang in bible.BOOK_DATA[bookid].names) {
9356
9357			// test each name starting with the full name, then short code, then abbreviation, then alternates
9358			for (var i=0, il=bible.BOOK_DATA[bookid].names[lang].length; i<il; i++) {
9359
9360				name = new String(bible.BOOK_DATA[bookid].names[lang][i]).toLowerCase();
9361				possibleMatch = input.substring(0, Math.floor(name.length, input.length)).toLowerCase();
9362
9363				if (possibleMatch == name) {
9364					matchingbookid = bookid;
9365					matchingLanguage = lang;
9366					input = input.substring(name.length);
9367					break; // out of names
9368				}
9369			}
9370
9371			if (matchingbookid != null)
9372				break;	// out of languages
9373
9374		}
9375		if (matchingbookid != null)
9376			break; // out of books
9377	}
9378
9379	if (matchingbookid  == null)
9380		return null;
9381
9382	// pull of _10_10 => 10_10
9383	if (input.substring(0,1) == '_') {
9384		input = input.substring(1)
9385	}
9386
9387	for (i = 0; i < input.length; i++) {
9388		var c = input.charAt(i);
9389
9390		if (c == ' ' || isNaN(c)) {
9391			if (!startedNumber)
9392				continue;
9393
9394			if (c == '-') {
9395				afterRange = true;
9396				afterSeparator = false;
9397			} else if (c == ':' || c == ',' || c == '.' || c == '_') {
9398				afterSeparator = true;
9399			} else {
9400				// ignore
9401			}
9402
9403			// reset
9404			currentNumber = '';
9405			startedNumber = false;
9406
9407		} else {
9408			startedNumber = true;
9409			currentNumber += c;
9410
9411			if (afterSeparator) {
9412				if (afterRange) {
9413					verse2 = parseInt(currentNumber);
9414				} else { // 1:1
9415					verse1 = parseInt(currentNumber);
9416				}
9417			} else {
9418				if (afterRange) {
9419					chapter2 = parseInt(currentNumber);
9420				} else { // 1
9421					chapter1 = parseInt(currentNumber);
9422				}
9423			}
9424		}
9425	}
9426
9427	// reassign 1:1-2
9428	if (chapter1 > 0 && verse1 > 0 && chapter2 > 0 && verse2 <= 0) {
9429		verse2 = chapter2;
9430		chapter2 = chapter1;
9431	}
9432	// fix 1-2:5
9433	if (chapter1 > 0 && verse1 <= 0 && chapter2 > 0 && verse2 > 0) {
9434		verse1 = 1;
9435	}
9436
9437	// just book
9438	if (bookIndex > -1 && chapter1 <= 0 && verse1 <= 0 && chapter2 <= 0 && verse2 <= 0) {
9439		chapter1 = 1;
9440		//verse1 = 1;
9441	}
9442
9443	// validate max chapter
9444	if (chapter1 == -1) {
9445		chapter1 = 1;
9446	} else if (bible.BOOK_DATA[matchingbookid].chapters && bible.BOOK_DATA[matchingbookid].chapters.length > 0 && chapter1 > bible.BOOK_DATA[matchingbookid].chapters.length) {
9447		chapter1 = bible.BOOK_DATA[matchingbookid].chapters.length;
9448		if (verse1 > 0)
9449			verse1 = 1;
9450	}
9451
9452	// validate max verse
9453	/*
9454	if (verse1 == -1) {
9455	verse1 = 1;
9456	} else
9457	*/
9458	if (bible.BOOK_DATA[matchingbookid].chapters && bible.BOOK_DATA[matchingbookid].chapters.length > 0 && verse1 > bible.BOOK_DATA[matchingbookid].chapters[chapter1 - 1]) {
9459		verse1 = bible.BOOK_DATA[matchingbookid].chapters[chapter1 - 1];
9460	}
9461	if (verse2 <= verse1) {
9462		chapter2 = -1;
9463		verse2 = -1;
9464	}
9465
9466	// finalize
9467	return bible.Reference(matchingbookid, chapter1, verse1, chapter2, verse2, language);
9468
9469}
9470
9471bible.Reference = function () {
9472
9473	var
9474		_bookid = -1,
9475		_chapter1 = -1,
9476		_verse1 = -1,
9477		_chapter2 = -1,
9478		_verse2 = -1,
9479		_language = 'eng';
9480
9481	if (arguments.length == 1 && typeof arguments[0] == 'string') { // a string that needs to be parsed
9482		return bible.parseReference(arguments[0]);
9483
9484	} else if (arguments.length == 2 && typeof arguments[0] == 'string' && typeof arguments[1] == 'string') { // verse, lang
9485		return bible.parseReference(arguments[0], arguments[1]);
9486
9487	} else if (arguments.length >= 2 && typeof arguments[0] == 'string' && typeof arguments[1] == 'number'){
9488		_bookid = arguments[0];
9489		_chapter1 = arguments[1];
9490		if (arguments.length >= 3) _verse1 = arguments[2];
9491		if (arguments.length >= 4) _chapter2 = arguments[3];
9492		if (arguments.length >= 5) _verse2 = arguments[4];
9493		if (arguments.length >= 6) _language = arguments[5];
9494	} else {
9495		return null;
9496	}
9497
9498	function padLeft(input, length, s) {
9499		while (input.length < length)
9500			input = s + input;
9501		return input;
9502	}
9503
9504	var refObject = {
9505		bookid: _bookid,
9506		chapter: _chapter1,
9507		verse: _verse1,
9508		chapter1: _chapter1,
9509		verse1: _verse1,
9510		chapter2: _chapter2,
9511		verse2: _verse2,
9512		language: _language,
9513		bookList: bible.DEFAULT_BIBLE,
9514
9515		isValid: function () {
9516			return (typeof _bookid != 'undefined' && _bookid != null && _chapter1 > 0);
9517		},
9518
9519		chapterAndVerse: function (cvSeparator, vvSeparator, ccSeparator) {
9520			cvSeparator = cvSeparator || ':';
9521			vvSeparator = vvSeparator || '-';
9522			ccSeparator = ccSeparator || '-';
9523
9524			if (this.chapter1 > 0 && this.verse1 <= 0 && this.chapter2 <= 0 && this.verse2 <= 0) // John 1
9525				return this.chapter1;
9526			else if (this.chapter1 > 0 && this.verse1 > 0 && this.chapter2 <= 0 && this.verse2 <= 0) // John 1:1
9527				return this.chapter1 + cvSeparator + this.verse1;
9528			else if (this.chapter1 > 0 && this.verse1 > 0 && this.chapter2 <= 0 && this.verse2 > 0) // John 1:1-5
9529				return this.chapter1 + cvSeparator + this.verse1 + vvSeparator + this.verse2;
9530			else if (this.chapter1 > 0 && this.verse1 <= 0 && this.chapter2 > 0 && this.verse2 <= 0) // John 1-2
9531				return this.chapter1 + ccSeparator + this.chapter2;
9532			else if (this.chapter1 > 0 && this.verse1 > 0 && this.chapter2 > 0 && this.verse2 > 0) // John 1:1-2:2
9533				return this.chapter1 + cvSeparator + this.verse1 + ccSeparator + ((this.chapter1 != this.chapter2) ? this.chapter2 + cvSeparator : '') + this.verse2;
9534			else
9535				return 'unknown';
9536		},
9537
9538		toFormat: function(format) {
9539
9540			function padLeft(nr, n, str){
9541			    return Array(n-String(nr).length+1).join(str||'0')+nr;
9542			}
9543
9544
9545			var t = this,
9546				output = format,
9547				bookInfo = bible.BOOK_DATA[this.bookid],
9548				flags = {
9549					// book number
9550					'I': function() {
9551						return bookInfo.sortOrder.toString();
9552					},
9553					'II': function() {
9554						return padLeft(bookInfo.sortOrder.toString(), 2);
9555					},
9556					'III': function() {
9557						return padLeft(bookInfo.sortOrder.toString(), 3);
9558					},
9559					// book USFM
9560					'UUU': function() {
9561						return bookInfo.usfm.toUpperCase();
9562					},
9563					'uuu': function() {
9564						return bookInfo.usfm.toLowerCase();
9565					},
9566					'Uuu': function() {
9567						return bookInfo.usfm.substring(0,1).toUpperCase() + bookInfo.usfm.substring(1).toLowerCase();
9568					},
9569					// DBS format
9570					'DD': function() {
9571						return bookInfo.shortCode.toUpperCase();
9572					},
9573					'dd': function() {
9574						return bookInfo.shortCode.toLowerCase();
9575					},
9576
9577					// Name
9578					'NNN': function() {
9579						var bookName = '',
9580							bookNames = bookInfo.names[t.language];
9581
9582						if (typeof bookNames != 'undefined') {
9583							bookName = bookNames[0];
9584						} else {
9585							bookName = bookInfo.names['eng'][0]
9586						}
9587
9588						return bookName;
9589					},
9590					'N': function() {
9591						return bookInfo.usfm.substring(0,1).toUpperCase() + bookInfo.usfm.substring(1).toLowerCase();
9592					},
9593
9594					// chapter
9595					'C': function() {
9596						return t.chapter1.toString();
9597					},
9598					'CC': function() {
9599						return padLeft(t.chapter1.toString(), 2);
9600					},
9601					'CCC': function() {
9602						return padLeft(t.chapter1.toString(), 3);
9603					},
9604					// verse
9605					'V': function() {
9606						return t.verse1.toString();
9607					},
9608					'VV': function() {
9609						return padLeft(t.verse1.toString(), 2);
9610					},
9611					'VVV': function() {
9612						return padLeft(t.verse1.toString(), 3);
9613					},
9614					'##': function() {
9615						return t.chapterAndVerse();
9616					}
9617				};
9618
9619				// copy number/chapter/verse
9620				flags['i'] = flags['I'];
9621				flags['ii'] = flags['II'];
9622				flags['iii'] = flags['III'];
9623				flags['c'] = flags['C'];
9624				flags['cc'] = flags['CC'];
9625				flags['ccc'] = flags['CCC'];
9626				flags['v'] = flags['V'];
9627				flags['vv'] = flags['VV'];
9628				flags['vvv'] = flags['VVV'];
9629
9630
9631			// create and sort keys
9632			var keys = Object.keys(flags);
9633			keys = keys.sort(function(b, a) {
9634				if (a.length > b.length) {
9635					return 1;
9636				} else if (a.length < b.length) {
9637					return -1;
9638				} else {
9639					return 0;
9640				}
9641			});
9642
9643			// do replacement
9644			for (var i in keys) {
9645				var key = keys[i];
9646				output = output.replace(new RegExp(key, 'g'), flags[key]());
9647			}
9648
9649			return output;
9650		},
9651
9652		toString: function () {
9653			if (this.bookid == null) return "invalid";
9654
9655			var bookName = '',
9656				bookNames = bible.BOOK_DATA[this.bookid].names[this.language];
9657
9658			if (typeof bookNames != 'undefined') {
9659				bookName = bookNames[0];
9660			} else {
9661				bookName = bible.BOOK_DATA[this.bookid].names['eng'][0]
9662			}
9663
9664			return bookName + ' ' + this.chapterAndVerse();
9665		},
9666
9667		toSection: function () {
9668			if (this.bookid == null) return "invalid";
9669
9670			return this.bookid + '' + this.chapter1 + (this.verse1 > 0 ? '_' + this.verse1 : '');
9671		},
9672
9673/*
9674		toOsis: function () {
9675			if (this.bookid == null) return "invalid";
9676
9677			return this.bookid + '.' + this.chapter1 + (this.verse1 > 0 ? '.' + this.verse1 : '');
9678		},
9679
9680		toOsisChapter: function () {
9681			if (this.bookid == null) return "invalid";
9682
9683			return this.bookid + '.' + this.chapter1;
9684		},
9685
9686		toOsisVerse: function () {
9687			if (this.bookid == null) return "invalid";
9688
9689			return this.bookid + '.' + this.chapter1 + '.' + (this.verse1 > 0 ? this.verse1 : '0');
9690		},
9691*/
9692
9693		prevChapter: function () {
9694			this.verse1 = 1;
9695			this.chapter2 = -1;
9696			this.verse2 = -1;
9697			if (this.chapter1 == 1 && this.bookList.indexOf(this.bookid) == 0) {
9698				return null;
9699			} else {
9700				if (this.chapter1 == 1) {
9701					// get the previous book
9702					this.bookid = this.bookList[this.bookList.indexOf(this.bookid)-1];
9703
9704					// get the last chapter in this book
9705					this.chapter = this.chapter1 = bible.BOOK_DATA[this.bookid].chapters.length;
9706				} else {
9707					// just go back a chapter
9708					this.chapter = this.chapter1 = this.chapter1 - 1;
9709				}
9710
9711			}
9712
9713			// return the object ()
9714			return this;
9715		},
9716
9717		nextChapter: function () {
9718			this.verse1 = 1;
9719			this.chapter2 = -1;
9720			this.verse2 = -1;
9721
9722			// check for the last chapter in the last book
9723			if (this.bookList[this.bookid] == this.bookList.length-1 && bible.BOOK_DATA[this.bookid].chapters.length == this.chapter1) {
9724				return null;
9725			} else {
9726
9727				if (this.chapter1 < bible.BOOK_DATA[this.bookid].chapters.length) {
9728					// just go up one chapter
9729					this.chapter = this.chapter1 = this.chapter1+1;
9730
9731				} else if (this.bookList.indexOf(this.bookid) < this.bookList.length-1) {
9732					// go to the next book, first chapter
9733					this.bookid = this.bookList[this.bookList.indexOf(this.bookid)+1];
9734					this.chapter = this.chapter1 = 1;
9735				}
9736
9737			}
9738
9739			return this;
9740		},
9741
9742		isFirstChapter: function () {
9743			return (this.chapter1 == 1 && this.bookList.indexOf(this.bookid) == 0);
9744		},
9745
9746		isLastChapter: function () {
9747			return (this.bookList[this.bookid] == this.bookList.length-1 && bible.BOOK_DATA[this.bookid].chapters.length == this.chapter1);
9748		}
9749	};
9750
9751	return refObject;
9752};
9753bible.utility = {};
9754
9755
9756
9757/*********
9758* ../app/js/bible/morphology.js
9759**********/
9760/* language tools */
9761if (typeof(window.bible) == 'undefined')
9762	window.bible = {};
9763
9764
9765bible.morphology = {};
9766
9767// http://wiki.logos.com/Morphology_Codes
9768//
9769bible.morphology['robinson'] = {
9770
9771	format: function(morph) {
9772
9773		var firstDash = morph.indexOf('-'),
9774			partOfSpeechKey = (firstDash > -1) ? morph.substring(0, firstDash) : morph,
9775			partOfSpeech = this.partsOfSpeech[partOfSpeechKey.toUpperCase()],
9776
9777			parsingInfo = (firstDash > -1) ? morph.substring(firstDash+1) : '',
9778			formattedParsing = '';
9779
9780		switch (partOfSpeechKey.toUpperCase()) {
9781			case 'T':
9782			case 'N':
9783			case 'R':
9784			case 'C':
9785			case 'D':
9786			case 'K':
9787			case 'I':
9788			case 'X':
9789			case 'Q':
9790			case 'F':
9791			case 'S':
9792				var c = this.nounCases[parsingInfo.substring(0, 1)];
9793				var n = this.wordNumber[parsingInfo.substring(1, 2)];
9794				var g = this.wordGender[parsingInfo.substring(2, 3)];
9795				formattedParsing = c + ((n) ? ', ' + n + ((g) ? ', ' + g : '') : '');
9796				break;
9797
9798			case 'P':
9799
9800				var firstLetter = parsingInfo.substr(0,1);
9801				if (firstLetter == '1' || firstLetter == '2') {
9802
9803					var p = this.wordPerson[parsingInfo.substring(0, 1)];
9804					var c = this.nounCases[parsingInfo.substring(1, 2)];
9805					var n = this.wordNumber[parsingInfo.substring(2, 3)];
9806					formattedParsing = p + ((c) ? ', ' + c + ((n) ? ', ' + n : '') : '');
9807				} else {
9808					var c = this.nounCases[parsingInfo.substring(0, 1)];
9809					var n = this.wordNumber[parsingInfo.substring(1, 2)];
9810					var g = this.wordGender[parsingInfo.substring(2, 3)];
9811					formattedParsing = c + ((n) ? ', ' + n + ((g) ? ', ' + g : '') : '');
9812				}
9813
9814				break;
9815
9816			case 'A':
9817				var c = this.nounCases[parsingInfo.substring(0, 1)];
9818				var n = this.wordNumber[parsingInfo.substring(1, 2)];
9819				formattedParsing =   c + ', ' + n;
9820				break;
9821
9822			case 'PRT':
9823
9824				formattedParsing = this.particleTypes[parsingInfo];
9825				break;
9826
9827			case 'V':
9828				var t = '';
9829				var rem = ''
9830				if (parsingInfo.substring(0, 1) == '2') {
9831					t = this.verbTenses[parsingInfo.substring(0, 2)];
9832					rem = parsingInfo.substring(2);
9833				} else {
9834					t = this.verbTenses[parsingInfo.substring(0, 1)];
9835					rem = parsingInfo.substring(1);
9836				}
9837				var v = this.verbVoices[rem.substring(0, 1)];
9838				var m = this.verbMoods[rem.substring(1, 2)];
9839
9840				if (rem.length == 2) {
9841					formattedParsing = t + ', ' + v + ', ' + m;
9842
9843				} else if (rem.length == 5) {
9844					var p = this.wordPerson[rem.substring(3, 4)];
9845					var n = this.wordNumber[rem.substring(4, 5)];
9846					formattedParsing =  t + ', ' + v + ', ' + m + ', ' + p + ', ' + n;
9847
9848				} else if (rem.length == 6) {
9849					var c = this.nounCases[rem.substring(3, 4)];
9850					var n = this.wordNumber[rem.substring(4, 5)];
9851					var g = this.wordGender[rem.substring(5, 6)];
9852
9853					formattedParsing =  t + ', ' + v + ', ' + m + ', ' + c + ', ' + n + ', ' + g;
9854				}
9855				break;
9856
9857				//m = this.verbMoods[parsingInfo.substring(2+offset,3+offset)];
9858			default:
9859				formattedParsing =  parsingInfo;
9860
9861		}
9862
9863		return (typeof partOfSpeech != 'undefined' ? partOfSpeech + (formattedParsing != '' ? ': ' : '') : '') + formattedParsing;
9864
9865	},
9866
9867	partsOfSpeech: {
9868		N: 'noun',
9869		A: 'adjective',
9870		T: 'article',
9871		V: 'verb',
9872		P: 'personal pronoun',
9873		R: 'relative pronoun',
9874		C: 'reciprocal pronoun',
9875		D: 'demonstrative pronoun',
9876		K: 'correlative pronoun',
9877		I: 'interrogative pronoun',
9878		X: 'indefinite pronoun',
9879		Q: 'correlative or interrogative pronoun',
9880		F: 'reflexive pronoun',
9881		S: 'posessive pronoun',
9882		ADV: 'adverb',
9883		CONJ: 'conjunction',
9884		COND: 'cond',
9885		PRT: 'particle',
9886		PREP: 'preposition',
9887		INJ: 'interjection',
9888		ARAM: 'aramaic',
9889		HEB: 'hebrew'
9890	},
9891
9892	getPartofSpeech: function(partOfSpeechKey) {
9893		var full = this.partsOfSpeech[partOfSpeechKey.toUpperCase()];
9894
9895		return (full != null) ? full : '?';
9896	},
9897
9898	nounCases: {
9899		'N': 'nominative',
9900		'V': 'vocative',
9901		'G': 'genitive',
9902		'D': 'dative',
9903		'A': 'accusative',
9904		'P': 'proper name'
9905	},
9906
9907	wordNumber: {
9908		'S': 'singular',
9909		'P': 'plural'
9910	},
9911
9912	wordGender: {
9913		'M': 'masculine',
9914		'F': 'feminine',
9915		'N': 'neuter'
9916	},
9917
9918	wordPerson: {
9919		'1': '1st',
9920		'2': '2nd',
9921		'3': '3rd'
9922	},
9923
9924	verbTenses: {
9925		'P': 'present',
9926		'I': 'imperfect',
9927		'F': 'future',
9928		'2F': 'second future',
9929		'A': 'aorist',
9930		'2A': 'second aorist',
9931		'R': 'perfect',
9932		'2R': 'second perfect',
9933		'L': 'pluperfect',
9934		'2L': 'second pluperfect',
9935		'X': 'no tense stated'
9936	},
9937
9938	verbVoices: {
9939		'A': 'active',
9940		'M': 'middle',
9941		'P': 'passive',
9942		'E': 'middle or passive',
9943		'D': 'middle deponent',
9944		'O': 'passive deponent',
9945		'N': 'middle or passive deponent',
9946		'Q': 'impersonal active',
9947		'X': 'no voice'
9948	},
9949
9950	verbMoods: {
9951		'I': 'indicative',
9952		'S': 'subjunctive',
9953		'O': 'optative',
9954		'M': 'imperative',
9955		'N': 'infinitive',
9956		'P': 'participle',
9957		'R': 'imperative participle'
9958	},
9959
9960	particleTypes: {
9961		'I': 'interogative',
9962		'N': 'negative'
9963	}
9964};
9965bible.morphology['Greek'] = bible.morphology['robinson'];
9966
9967
9968
9969// http://openscriptures.github.io/morphhb/parsing/HebrewMorphologyCodes.html
9970bible.morphology['OSHB'] = {
9971	format: function(morph) {
9972		var languageKey = morph.substr(0,1),
9973			language = this.languages[languageKey],
9974			morphParts = morph.substr(1).split('/'),
9975			formattedOutput = '';
9976
9977		for (var i=0, il=morphParts.length; i<il; i++) {
9978			var m = morphParts[i],
9979				partOfSpeechKey = m.substr(0,1),
9980				partOfSpeech = this.partsOfSpeech[partOfSpeechKey],
9981				morphCodes = m.length > 0 ? m.substr(1) : '',
9982
9983				partsList = [],
9984				details = [];
9985
9986			switch (partOfSpeechKey) {
9987
9988				case 'A':
9989					partsList = ['adjectiveTypes', 'gender', 'number', 'state'];
9990
9991					break;
9992				case 'C':
9993				case 'D':
9994					// nothing
9995
9996					break;
9997				case 'N':
9998
9999					partsList = ['nounTypes', 'gender', 'number', 'state'];
10000
10001					break;
10002				case 'P':
10003
10004					partsList = ['pronounTypes', 'person', 'gender', 'number'];
10005
10006					break;
10007				case 'R':
10008					partsList = ['prepositionTypes'];
10009
10010					break;
10011				case 'S':
10012
10013					partsList = ['suffixTypes', 'person', 'gender', 'number'];
10014
10015					break;
10016				case 'T':
10017
10018					partsList = ['particleTypes'];
10019
10020					break;
10021				case 'V':
10022
10023					partsList = [language.toLowerCase() + 'VerbStems', 'verbConjugationTypes', 'person', 'gender', 'number', 'state'];
10024
10025					break;
10026
10027			}
10028
10029			for (var j=0, jl=partsList.length; j<jl; j++) {
10030				if (morphCodes.length > j) {
10031					details.push( this[ partsList[j] ][ morphCodes.substr(j, 1) ] );
10032				}
10033			}
10034
10035
10036
10037			formattedOutput += (i>0 ? '; ' : '') + partOfSpeech + (details.length > 0 ? ': ' + details.join(', ') : '');
10038		}
10039
10040		return formattedOutput;
10041	},
10042
10043	partsOfSpeech: {
10044
10045		'A':'adjective',
10046		'C':'conjunction',
10047		'D':'adverb',
10048		'N':'noun',
10049		'P':'pronoun',
10050		'R':'preposition',
10051		'S':'suffix',
10052		'T':'particle',
10053		'V':'verb'
10054
10055	},
10056
10057	hebrewVerbStems: {
10058
10059		'q': 'qal',
10060		'N': 'niphal',
10061		'p': 'piel',
10062		'P': 'pual',
10063		'h': 'hiphil',
10064		'H': 'hophal',
10065		't': 'hithpael',
10066		'o': 'polel',
10067		'O': 'polal',
10068		'r': 'hithpolel',
10069		'm': 'poel',
10070		'M': 'poal',
10071		'k': 'palel',
10072		'K': 'pulal',
10073
10074		'Q': 'qal passive',
10075		'l': 'pilpel',
10076		'L': 'polpal',
10077		'f': 'hithpalpel',
10078		'D': 'nithpael',
10079		'j': 'pealal',
10080		'i': 'pilel',
10081		'u': 'hothpaal',
10082		'c': 'tiphil',
10083		'v': 'hishtaphel',
10084		'w': 'nithpalel',
10085		'y': 'nithpoel',
10086		'z': 'hithpoel'
10087	},
10088
10089	aramaicVerbStems: {
10090
10091		'q': 'peal',
10092		'Q': 'peil',
10093		'u': 'hithpeel',
10094		'p': 'pael',
10095		'P': 'ithpaal',
10096		'M': 'hithpaal',
10097		'a': 'aphel',
10098		'h': 'haphel',
10099		's': 'saphel',
10100		'e': 'shaphel',
10101		'H': 'hophal',
10102		'i': 'ithpeel',
10103		't': 'hishtaphel',
10104		'v': 'ishtaphel',
10105		'w': 'hithaphel',
10106		'o': 'polel',
10107		'z': 'ithpoel',
10108		'r': 'hithpolel',
10109		'f': 'hithpalpel',
10110		'b': 'hephal',
10111		'c': 'tiphel',
10112		'm': 'poel',
10113		'l': 'palpel',
10114		'L': 'ithpalpel',
10115		'O': 'ithpolel',
10116		'G': 'ittaphal'
10117
10118	},
10119
10120	verbConjugationTypes: {
10121		'p': 'perfect (qatal)',
10122		'q': 'sequential perfect (weqatal)',
10123		'i': 'imperfect (yiqtol)',
10124		'w': 'sequential imperfect (wayyiqtol)',
10125		'h': 'cohortative',
10126		'j': 'jussive',
10127		'v': 'imperative',
10128		'r': 'participle active',
10129		's': 'participle passive',
10130		'a': 'infinitive absolute',
10131		'c': 'infinitive construct'
10132	},
10133
10134	adjectiveTypes: {
10135		'a': 'adjective',
10136		'c': 'cardinal number',
10137		'g': 'gentilic',
10138		'o': 'ordinal number'
10139	},
10140
10141	nounTypes: {
10142		'c': 'common',
10143		'g': 'gentilic',
10144		'p': 'proper name'
10145	},
10146
10147	pronounTypes: {
10148		'd': 'demonstrative',
10149		'f': 'indefinite',
10150		'i': 'interrogative',
10151		'p': 'personal',
10152		'r': 'relative'
10153	},
10154
10155	prepositionTypes: {
10156		'd': 'definite article'
10157	},
10158
10159	suffixTypes: {
10160		'd': 'directional he',
10161		'h': 'paragogic he',
10162		'n': 'paragogic nun',
10163		'p': 'pronominal'
10164	},
10165
10166	particleTypes: {
10167		'a': 'affirmation',
10168		'd': 'definite article',
10169		'e': 'exhortation',
10170		'i': 'interrogative',
10171		'j': 'interjection',
10172		'm': 'demonstrative',
10173		'n': 'negative',
10174		'o': 'direct object marker',
10175		'r': 'relative'
10176	},
10177
10178	person: {
10179		'1': 'first',
10180		'2': 'second',
10181		'3': 'third'
10182	},
10183
10184	gender: {
10185		'b': 'both (noun)',
10186		'c': 'common (verb)',
10187		'f': 'feminine',
10188		'm': 'masculine'
10189	},
10190
10191	number: {
10192		'd': 'dual',
10193		'p': 'plural',
10194		's': 'singular'
10195	},
10196
10197	state: {
10198		'a': 'absolute',
10199		'c': 'construct',
10200		'd': 'determined'
10201	},
10202
10203	languages: {
10204		'H': 'Hebrew',
10205		'A': 'Aramaic'
10206	}
10207};
10208
10209bible.morphology['Hebrew'] = bible.morphology['OSHB'];
10210
10211
10212
10213/*********
10214* ../app/js/texts/textloader.js
10215**********/
10216sofia.textproviders = {};
10217
10218TextLoader = (function() {
10219
10220	var
10221		textInfoDataIsLoading = false,
10222
10223		textInfoLoadingCallbacks = [],
10224
10225		textInfoDataIsLoaded = false,
10226
10227		// simple data from manifests that includes name, lang
10228		textInfoData = [],
10229
10230		// full data with chapter arrays, etc.
10231		textData = {},
10232
10233		cachedTexts = {};
10234
10235	function loadSection(textInfo, sectionid, successCallback, errorCallback) {
10236
10237		// double check
10238		if (sectionid == 'null' || sectionid == null) {
10239			return;
10240		}
10241
10242		var textid = '';
10243
10244		if (textInfo != null && typeof textInfo == 'string') {
10245			textid = textInfo;
10246
10247			getText(textid, function(textInfo) {
10248				loadSection(textInfo, sectionid, successCallback, errorCallback);
10249			});
10250			return;
10251
10252		} else {
10253			textid = textInfo.id;
10254
10255			// sometimes the sections aren't yet known, so we'll check for them and hope for the best :)
10256			if (textInfo.sections && textInfo.sections.length > 0 && textInfo.sections.indexOf(sectionid) == -1) {
10257				sectionid = textInfo.sections[0];
10258			}
10259		}
10260
10261		// send analytics for loading
10262		if (sofia.analytics && sofia.analytics.record) {
10263			sofia.analytics.record('load', textInfo.id, sectionid);
10264		}
10265
10266		// use stored text if present
10267		if (typeof cachedTexts[textid] == 'undefined') {
10268			cachedTexts[textid] = {};
10269		}
10270		if (typeof cachedTexts[textid][sectionid] != 'undefined') {
10271			successCallback ( $(cachedTexts[textid][sectionid])  );
10272			return;
10273		}
10274
10275
10276		// load from provider
10277		sofia.textproviders[textInfo.providerName].loadSection(textid, sectionid, function(html) {
10278
10279			// store
10280			cachedTexts[textid][sectionid] = html;
10281
10282			// send
10283			successCallback ( $(cachedTexts[textid][sectionid])  );
10284		});
10285	}
10286
10287
10288	function getTextid(input) {
10289
10290		var parts = input.split(':'),
10291			textid = (parts.length > 1) ? parts[1] : parts[0];
10292
10293		return textid;
10294
10295	}
10296
10297	function getProviderName(input) {
10298
10299		// if not loaded, get it from provider
10300		var
10301			parts = input.split(':'),
10302			textid = parts.length > 1 ? parts[1] : parts[0],
10303			providerName = parts.length > 1 ? parts[0] : '';
10304
10305		if (providerName == '') {
10306			var textInfo = textInfoData.filter(function(info) {
10307					return info.id == textid;
10308				})[0];
10309
10310			if (textInfo && typeof textInfo.providerName != 'undefined') {
10311				providerName = textInfo.providerName;
10312			} else {
10313				providerName = 'local'; // ???
10314			}
10315		}
10316
10317		return providerName;
10318	}
10319
10320	function getProviderId(input) {
10321
10322		// assume we have the full providerid
10323		if (input.indexOf(':') > -1) {
10324			return input;
10325		} else {
10326			// assume we only have the textid
10327			var textid = input,
10328				textInfo = textInfoData.filter(function(info) {
10329					return info.id == textid;
10330				})[0];
10331
10332			return textInfo.providerid;
10333		}
10334	}
10335
10336
10337	function getText(textid, callback, errorCallback) {
10338
10339		// if already loaded, then send it right back
10340		var t = this,
10341			textinfo = textData[textid];
10342
10343		if (typeof textinfo != 'undefined') {
10344			if (typeof callback != 'undefined') {
10345				callback(textinfo);
10346			}
10347			return textinfo;
10348		}
10349
10350		var providerName = getProviderName(textid),
10351			textid = getTextid(textid);
10352
10353
10354		sofia.textproviders[providerName].getTextInfo(textid, function(data) {
10355
10356			var initialInfo = textInfoData[textid];
10357			data = $.extend({}, initialInfo, data, true);
10358
10359
10360			processText(data, providerName)
10361			//data.providerName = providerName;
10362
10363
10364
10365
10366
10367			// store
10368			textData[data.id] = data;
10369
10370			// set names
10371			if (data.divisionNames) {
10372				bible.addNames(data.lang, data.divisions, data.divisionNames);
10373			}
10374
10375			// send back
10376			callback(data);
10377
10378		}, errorCallback);
10379
10380	}
10381
10382	function loadTexts(callback) {
10383		if (textInfoDataIsLoaded) {
10384			callback(textInfoData);
10385		} else {
10386			loadTextsManifest(callback);
10387		}
10388	}
10389
10390	function loadTextsManifest(callback) {
10391
10392		if (callback) {
10393			textInfoLoadingCallbacks.push(callback);
10394		}
10395
10396		if (textInfoDataIsLoading) {
10397			return;
10398		}
10399
10400		textInfoDataIsLoading = true;
10401
10402		var providerKeys = Object.keys(sofia.textproviders),
10403			currentProviderIndex = 0;
10404
10405		function loadNextProvider() {
10406			if (currentProviderIndex < providerKeys.length) {
10407
10408				var providerName = providerKeys[currentProviderIndex]
10409
10410				sofia.textproviders[providerName].getTextManifest(function(data) {
10411
10412					if (data && data != null) {
10413
10414						// append to array from previous provider
10415						textInfoData = textInfoData.concat(data);
10416					}
10417
10418					currentProviderIndex++;
10419					loadNextProvider();
10420				});
10421
10422
10423			} else {
10424				textInfoDataIsLoading = false;
10425				textInfoDataIsLoaded = true;
10426
10427
10428				while (textInfoLoadingCallbacks.length > 0) {
10429
10430					var cb = textInfoLoadingCallbacks.pop();
10431					cb(textInfoData);
10432
10433				}
10434			}
10435		}
10436
10437		loadNextProvider();
10438
10439	}
10440
10441	function processTexts(text_array, providerName) {
10442		// add providerName, providerid, and id to each one
10443		for (var i=0, il=text_array.length; i<il; i++) {
10444
10445			var text = text_array[i];
10446
10447			processText(text, providerName);
10448		}
10449	}
10450
10451	function processText(text, providerName) {
10452		// remove any provider info from the id
10453		if (text.id.split(':').length > 1) {
10454			text.id = text.id.split(':')[1];
10455		}
10456
10457		text.providerName = providerName;
10458		text.providerid = providerName + ':' + text.id;
10459
10460		// TEMP for DBS
10461		if (typeof text.country != 'undefined' && typeof text.countries == 'undefined' && text.country != text.langName && text.country != text.langNameEnglish) {
10462			text.countries = [];
10463
10464			var possibleCountryMatches = sofia.countries.filter(function(c) {
10465				return c.name.indexOf(text.country) == 0;
10466			});
10467
10468			if (possibleCountryMatches.length > 0) {
10469				text.countries.push( possibleCountryMatches[0]['alpha-2'] );
10470			}
10471		}
10472	}
10473
10474	function startSearch(textid, divisions, searchTerms, onSearchLoad, onSearchIndexComplete, onSearchComplete) {
10475
10476		var providerName = getProviderName(textid);
10477
10478		sofia.textproviders[providerName].startSearch(textid, divisions, searchTerms, onSearchLoad, onSearchIndexComplete, onSearchComplete);
10479
10480	}
10481
10482	function getTextInfoData() {
10483		return textInfoData;
10484	}
10485
10486	// when the document is ready, start loading texts from providers
10487	$(function() {
10488		loadTextsManifest();
10489	});
10490
10491	var ext = {
10492		getText: getText,
10493		loadTexts: loadTexts,
10494		textData: textData,
10495		getTextInfoData: getTextInfoData,
10496		loadSection: loadSection,
10497		startSearch: startSearch,
10498		processTexts: processTexts,
10499		processText: processText
10500	}
10501
10502	return ext;
10503})();
10504
10505
10506
10507/*********
10508* ../app/js/texts/textprovider-local.js
10509**********/
10510
10511sofia.textproviders['local'] = (function() {
10512
10513	var providerName = 'local',
10514		fullName = '',
10515		textData = {};
10516
10517	function getTextManifest(callback) {
10518		var textsUrl = 'content/texts/' + sofia.config.textsIndexPath;
10519
10520		sofia.ajax({
10521			url: textsUrl,
10522			dataType: 'json',
10523			cache: false,
10524			success: function(data) {
10525
10526				var textInfoData = data.textInfoData;
10527
10528				//text_data = TextLoader.processTexts(textInfoData, providerName);
10529
10530				callback(textInfoData);
10531			},
10532			error: function(jqXHR, textStatus, errorThrown) {
10533				//console.log('error loading texts.json', jqXHR, textStatus, errorThrown);
10534				//console.log(textStatus);
10535
10536				var modal = new MovableWindow(600,250, 'Texts Error');
10537				//modal.size(500, 200).center();
10538
10539				modal.body.css({background: '#000', color: '#fff' }).html(
10540					'<div style="padding: 20px;">' +
10541						'<p>Problem loading <code>' + sofia.config.baseContentUrl + textsUrl + '</code></p>' +
10542						'<p>Status: ' + textStatus + '</p>'+
10543						'<p>Error: ' + errorThrown + '</p>'+
10544					'</div>'
10545				);
10546				modal.show().center();
10547
10548			}
10549		});
10550	}
10551
10552	function getTextInfo(textid, callback, errorCallback) {
10553
10554		if (typeof textData[textid] != 'undefined') {
10555			callback(textData[textid]);
10556			return;
10557		}
10558
10559		// load it!
10560		var infoUrl = 'content/texts/' + textid + '/info.json';
10561
10562		sofia.ajax({
10563			url: infoUrl,
10564			dataType: 'json',
10565			success: function(data) {
10566				textData[textid] = data;
10567				callback(data);
10568			},
10569			error: function(error) {
10570
10571				console.log("ERROR TextInfoLoader.getText", infoUrl);
10572
10573				if (errorCallback) {
10574					errorCallback(error);
10575				}
10576			}
10577		});
10578	}
10579
10580	function loadSection(textid, sectionid, callback, errorCallback) {
10581
10582		getTextInfo(textid, function(textInfo) {
10583
10584			var url = 'content/texts/' + textid + '/' + sectionid + '.html';
10585
10586			sofia.ajax({
10587				dataType: 'text',
10588				url: url,
10589				success: function(data) {
10590
10591					// text to treat this like JSON or text/html
10592					var text = data,
10593						// split at the closing head tag to prevent problems with loading head material
10594						main = $( text.indexOf('</head>') > -1 ? text.split('</head>')[1] : text ),
10595						content = main.filter('.section'),
10596						footnotes = main.filter('.footnotes'),
10597						notes = footnotes.find('.footnote');
10598
10599					// move notes into place
10600					if (notes.length > 0) {
10601						notes.each(function() {
10602							var footnote = $(this),
10603								noteid = footnote.find('a').attr('href'),
10604								footnotetext = footnote.find('.text'),
10605								noteintext = content.find(noteid);
10606
10607							//console.log(noteid, noteintext);
10608
10609							noteintext.append(footnotetext);
10610
10611						});
10612					}
10613
10614					content.attr('data-textid', textid);
10615					content.attr('data-lang3', textInfo.lang);
10616
10617					// FIX title after chapter number
10618					var c = content.find('.c'),
10619						afterc = c.next();
10620					if (afterc.hasClass('s')) {
10621						c.before(afterc);
10622					}
10623
10624					// FIX verse numbers inside verse
10625					content.find('.v-num').each(function() {
10626						var vnum = $(this),
10627							v = vnum.closest('.v');
10628
10629						if (v.length > 0) {
10630							v.before(vnum);
10631						}
10632
10633					});
10634
10635					var html = content.wrapAll('<div></div>').parent().html();
10636
10637					callback(html);
10638
10639				}, error: function(jqXHR, textStatus, errorThrown) {
10640					if (errorCallback) {
10641						errorCallback(textid, sectionid);
10642					}
10643
10644					//console.log('error', textStatus, errorThrown, jqXHR );
10645				}
10646			});
10647		});
10648
10649	}
10650
10651	function startSearch(textid, divisions, text, onSearchLoad, onSearchIndexComplete, onSearchComplete) {
10652
10653		var textSearch = new TextSearch();
10654
10655		textSearch.on('load', onSearchLoad);
10656		textSearch.on('indexcomplete', onSearchIndexComplete);
10657		textSearch.on('complete', onSearchComplete);
10658
10659		textSearch.start(textid, divisions, text);
10660
10661	}
10662
10663	return {
10664		getTextManifest: getTextManifest,
10665		getTextInfo: getTextInfo,
10666		loadSection: loadSection,
10667		startSearch: startSearch,
10668		fullName: fullName
10669	}
10670
10671})();
10672
10673/*
10674sofia.textproviders['example'] = (function() {
10675
10676	function getTextManifest (callback) {
10677		callback({});
10678	}
10679
10680	function getTextInfo(textid, callback) {
10681		callback({});
10682	}
10683
10684	function loadSection(textid, sectionid, callback) {
10685		callback(null);
10686	}
10687
10688	return {
10689		getTextManifest: getTextManifest,
10690		getTextInfo: getTextInfo,
10691		loadSection: loadSection
10692	}
10693
10694})();
10695*/
10696
10697
10698
10699/*********
10700* ../app/js/texts/textprovider-dbs.js
10701**********/
10702sofia.config = $.extend(sofia.config, {
10703	dbsEnabled: true,
10704	dbsKey: '',
10705	dbsBase: 'https://api.dbp4.org/',	
10706	dbsIncludeList: [],
10707	dbsExcludeList: [],
10708});
10709
10710
10711
10712sofia.textproviders['dbs'] = (function() {
10713
10714	var text_data = [],
10715		text_data_is_loaded = false,
10716		text_data_is_loading = false,
10717		text_data_callbacks = [],
10718
10719		providerName = 'dbs',
10720		fullName = 'Digital Bible Society';
10721
10722	function getTextManifest (callback) {
10723
10724		// check for offline use
10725		if (!sofia.config.enableOnlineSources || !sofia.config.dbsEnabled || sofia.config.dbsKey == '' ) {
10726			callback(null);
10727			return;
10728		}
10729
10730		// if loaded immediately callback
10731		if (text_data_is_loaded) {
10732
10733			callback(text_data);
10734
10735		} else {
10736
10737			// store callback
10738			text_data_callbacks.push(callback);
10739
10740			// don't continue
10741			if (text_data_is_loading) {
10742				return;
10743			}
10744
10745			text_data_is_loading = true;
10746
10747			$.ajax({
10748				url: sofia.config.dbsBase + 'bibles?v=4&key=' + sofia.config.dbsKey,
10749				/*
10750				dataType: 'jsonp',
10751				beforeSend:  function(xhr){
10752					if (xhr.overrideMimeType) {
10753						xhr.overrideMimeType('application/javascript');
10754					}
10755				},
10756				*/
10757				dataType: 'json',
10758				cache: false,
10759				success: function(data) {
10760
10761					if (data == null || data.data == null) {
10762						finish();
10763						return;
10764					}
10765
10766					// convert to older DBS version
10767	/*
10768	{
10769		"id":"eng-CEV",
10770		"name":"Contemporary English Version (US Version)",
10771		"nameEnglish":"",
10772		"abbr":"CEV",
10773		"lang":"eng",
10774		"langName":"English",
10775		"langNameEnglish":"English",
10776		"dir":"ltr",
10777		"type":"bible",
10778		"absid":"eng-CEV",
10779		"absAudio":"NONE"
10780	},
10781	*/
10782					text_data = [];
10783					for (var id in data.data) {
10784
10785						var dbs = data.data[id];
10786
10787						// skip not in include list
10788						if (sofia.config.dbsIncludeList.length > 0 && sofia.config.dbsIncludeList.indexOf(dbs.abbr) == -1) {
10789							continue;
10790						}
10791						
10792						// skip in exclude list
10793						if (sofia.config.dbsExcludeList.length > 0 && sofia.config.dbsExcludeList.indexOf(dbs.abbr) > -1) {
10794							continue;
10795						}						
10796						
10797						// keep this one!
10798						if (dbs.name != null) {
10799
10800							// find the plain text id
10801							var text_plain = dbs.filesets['dbp-prod'].filter(function(fileset) { return fileset.type == "text_plain";});
10802
10803							var sofiabible = {
10804								type: 'bible',
10805								id: dbs.abbr,
10806								name: dbs.name,
10807								nameEnglish: dbs.vname,
10808								abbr: dbs.abbr.replace(/ENG/,''),
10809								lang: dbs.iso,
10810								langName: dbs.language,
10811								langNameEnglish: dbs.language,
10812								dbfilsPlainTextId: text_plain.length > 0 ? text_plain[0].id : '',
10813								dbs: dbs
10814							};
10815							text_data.push(sofiabible);
10816						}
10817					}
10818
10819					TextLoader.processTexts(text_data, providerName);
10820
10821					finish();
10822				},
10823				error: function(jqXHR, textStatus, errorThrown) {
10824
10825					text_data = null;
10826					finish();
10827				}
10828			});
10829		}
10830	}
10831
10832	function finish() {
10833		text_data_is_loading = false;
10834		text_data_is_loaded = true;
10835
10836		while (text_data_callbacks.length > 0) {
10837			var cb = text_data_callbacks.pop();
10838			cb(text_data);
10839		}
10840	}
10841
10842	function getProviderid(textid) {
10843		var parts = textid.split(':'),
10844			fullid = providerName + ':' + (parts.length > 1 ? parts[1] : parts[0]);
10845
10846		return fullid;
10847	}
10848
10849	function getTextInfo(textid, callback) {
10850
10851		if (!text_data_is_loaded) {
10852
10853			getTextManifest (function() {
10854				getTextInfo(textid, callback);
10855			});
10856			return;
10857		}
10858
10859		var providerid = getProviderid(textid);
10860
10861		// get initial data
10862		var info = text_data.filter(function(text) {
10863			return text.providerid == providerid;
10864		})[0];
10865
10866		if (typeof info.divisions == 'undefined' || info.divisions.length == 0) {
10867
10868			$.ajax({
10869				url: sofia.config.dbsBase + 'bibles/' + info.id + '?v=4&key=' + sofia.config.dbsKey ,
10870				dataType: 'json',
10871				/*
10872				dataType: 'jsonp',
10873				beforeSend:  function(xhr){
10874					if (xhr.overrideMimeType) {
10875						xhr.overrideMimeType('application/javascript');
10876					}
10877				},
10878				*/
10879				cache: false,
10880				success: function(data) {
10881
10882
10883					// transfer books and chapters
10884					/*
10885					divisions: ['GN','EX']
10886					sections: ['GN1','GN2']
10887					*/
10888
10889					info.divisions = [];
10890					info.sections = [];
10891					info.divisionNames = [];
10892
10893					// book list (divisions)
10894					for (var i=0, il=data.data.books.length; i<il; i++) {
10895						var bookinfo = data.data.books[i],
10896							usfmCode = bookinfo.book_id,
10897							bookIndex = bible.APOCRYPHAL_BIBLE_USFM.indexOf(usfmCode),
10898							dbsCode = bible.APOCRYPHAL_BIBLE[bookIndex];
10899
10900						if (typeof dbsCode == 'undefined') {
10901							console.warn(bookinfo, usfmCode);
10902						}
10903
10904						info.divisions.push(dbsCode);
10905						info.divisionNames.push(bookinfo.name);
10906
10907						// chapter list (sections)
10908						for (var j=0, jl=bookinfo.chapters.length; j<jl; j++) {
10909
10910							info.sections.push(dbsCode + (j+1));
10911
10912						}
10913
10914					}
10915
10916
10917					console.log('converted DBS' + info.id);
10918					console.log(info);
10919
10920
10921
10922					//$.extend(info, data);
10923					callback(info);
10924
10925				},
10926				error: function(jqXHR, textStatus, errorThrown) {
10927					callback(null);
10928				}
10929
10930			});
10931
10932		} else {
10933
10934			callback(info);
10935		}
10936
10937	}
10938
10939
10940	function getTextInfoSync(textid) {
10941
10942		var providerid = getProviderid(textid);
10943
10944		// get initial data
10945		var info = text_data.filter(function(text) {
10946			return text.providerid == providerid;
10947		})[0];
10948
10949		return info;
10950	}
10951
10952
10953	function loadSection(textid, sectionid, callback) {
10954
10955		loadSectionText(textid, sectionid, callback);
10956
10957	}
10958
10959
10960	function loadSectionText(textid, sectionid, callback) {
10961
10962		var textinfo = getTextInfoSync(textid),
10963
10964			lang3 = textinfo.lang,
10965			lang = iso2iana.convert(lang3),
10966			dir = (textinfo.dir && (textinfo.dir == 'ltr' || textinfo.dir == 'rtl')) ? textinfo.dir : data.language.isRTL(lang) ? 'rtl' : 'ltr',
10967
10968
10969			bookid = dbsBookCode = sectionid.substring(0,2),
10970			usfm = bible.BOOK_DATA[dbsBookCode].usfm,
10971			chapterNum = sectionid.substring(2),
10972
10973			sectionIndex = textinfo.sections.indexOf(sectionid),
10974			previd = sectionIndex > 0 ? textinfo.sections[sectionIndex-1] : null,
10975			nextid = sectionIndex < textinfo.sections.length ? textinfo.sections[sectionIndex+1] : null;
10976
10977
10978		// TEXT VERSION
10979
10980
10981		$.ajax({
10982			url: sofia.config.dbsBase + 'bibles/filesets/' + textinfo.dbsPlainTextId + '/' + usfm + '/' + chapterNum + '?v=4&key=' + sofia.config.dbsKey ,
10983			dataType: 'json',
10984			/*
10985			dataType: 'jsonp',
10986			beforeSend:  function(xhr){
10987				if (xhr.overrideMimeType) {
10988					xhr.overrideMimeType('application/javascript');
10989				}
10990			},
10991			*/
10992			cache: false,
10993			success: function(data) {
10994
10995				// create HTML
10996				var html = [];
10997
10998				//<div class="section chapter AC AC1 eng_kjv eng" dir="ltr" lang="en" data-id="AC1" data-nextid="AC2" data-previd="JN21">
10999				html.push('<div class="section chapter ' + textid + ' ' + bookid + ' ' + sectionid + ' ' + iso2iana.convert(lang) + ' " ' +
11000							' data-textid="' + textid + '"' +
11001							' data-id="' + sectionid + '"' +
11002							' data-nextid="' + nextid + '"' +
11003							' data-previd="' + previd + '"' +
11004							' lang="' + lang + '"' +
11005							' data-lang3="' + lang + '"' +
11006							' dir="' + dir + '"' +
11007							'>');
11008
11009
11010				if (chapterNum == '1') {
11011					html.push('<div class="mt">' + textinfo.divisionNames[textinfo.divisions.indexOf(bookid)] + '</div>');
11012				}
11013
11014				html.push('<div class="c">' + chapterNum + '</div>');
11015
11016				html.push('<div class="p">');
11017				for (var i=0, il=data.data.length; i<il; i++ ) {
11018					var verse = data.data[i],
11019						text = verse.verse_text,
11020						vnum = verse.verse_start,
11021						vid = sectionid + '_' + vnum;
11022
11023					html.push(' <span class="v-num v-' + vnum + '">' + vnum + '</span><span class="v ' + vid + '" data-id="' + vid + '">
11023' + text + '</span>');
11024
11025				}
11026
11027				html.push('</div>'); // p
11028				html.push('</div>'); // section
11029
11030
11031
11032				callback(html.join(''));
11033
11034
11035			},
11036			error: function(jqXHR, textStatus, errorThrown) {
11037				callback(null);
11038			}
11039
11040		});
11041	}
11042
11043	function startSearch(textid, divisions, text, onSearchLoad, onSearchIndexComplete, onSearchComplete) {
11044
11045		var e = {
11046			type:'complete',
11047			target: this,
11048			data: {
11049				results: [],
11050				searchIndexesData: [], // not needed for SearchWindow
11051				searchTermsRegExp: SearchTools.createSearchTerms(text, false),
11052				isLemmaSearch: false
11053			}
11054		};
11055
11056		doSearch(textid, divisions, text, e, function() {
11057
11058			onSearchComplete(e);
11059
11060		});
11061	}
11062	function doSearch(textid, divisions, text, e, callback) {
11063
11064		var textinfo = getTextInfoSync(textid);
11065
11066		$.ajax({
11067			/*
11068			beforeSend: function(xhr){
11069				if (xhr.overrideMimeType){
11070					xhr.overrideMimeType("application/javascript");
11071				}
11072			},
11073			dataType: 'jsonp',
11074			*/
11075			dataType: 'json',
11076
11077			// One giant call seems faster, than doing all the books individually?
11078			url: sofia.config.dbsBase + 'text/search?v=4&key=' + sofia.config.dbsKey + '&fileset_id=' + textid + '&query=' + text.replace(/\s/gi, '+') + '&limit=2000',
11079			success: function(data) {
11080
11081				for (var i=0, il=data.data.length; i<il; i++) {
11082					var verse = data.data[i],
11083						dbsBookCode = bible.DEFAULT_BIBLE[ bible.DEFAULT_BIBLE_USFM.indexOf(verse.book_id) ],
11084						fragmentid = dbsBookCode + verse.chapter + '_' + verse.verse_start,
11085						hasMatch = e.data.searchTermsRegExp[0].test(verse.verse_text);
11086
11087					if (hasMatch && (divisions.length == 0 || divisions.indexOf(dbsBookCode) > -1)) {
11088						e.data.results.push({
11089							fragmentid: fragmentid,
11090							html: highlightWords(verse.verse_text, e.data.searchTermsRegExp)
11091						});
11092					}
11093				}
11094
11095				callback(data);
11096			}
11097		});
11098	}
11099
11100	function highlightWords(text, searchTermsRegExp) {
11101
11102		var processedHtml = text;
11103
11104		for (var j=0, jl=searchTermsRegExp.length; j<jl; j++) {
11105
11106			searchTermsRegExp[j].lastIndex = 0;
11107
11108			// surround the word with a highlight
11109			processedHtml = processedHtml.replace(searchTermsRegExp[j], function(match) {
11110				return '<span class="highlight">' + match + '</span>';
11111			});
11112		}
11113
11114		return processedHtml;
11115	}
11116
11117
11118
11119	return {
11120		getTextManifest: getTextManifest,
11121		getTextInfo: getTextInfo,
11122		loadSection: loadSection,
11123		startSearch: startSearch,
11124		fullName: fullName
11125	}
11126
11127})();
11128
11129
11130
11131/*********
11132* ../app/js/texts/search.js
11133**********/
11134// truth AND Love
11135// 1. load indexes
11136// 2. find verses with both terms
11137// 3. reduce to chapters
11138// 4. load chapters
11139// 5. extract verses
11140
11141
11142// truth OR love
11143// 1. load indexes
11144// 2. merge lists of verses into canonical order
11145// 3. reduce to chapters
11146// 4. load chapters
11147// 5. extract verses
11148
11149
11150// "in truth"
11151// 1. load indexes
11152// 2. ...
11153// 3. reduce to chapters
11154// 4. load chapters
11155// 5. extract verses
11156
11157
11158// truth love
11159// 1. NO INDEX
11160// 2. load every chapter
11161// 3. use regexp to find verses with words
11162
11163var singleWordLanguages = ['cht','chs','chi','zho','cmn', 'jpn', 'kor'];
11164
11165TextSearch = function() {
11166
11167	var
11168		baseContentPath = sofia.config.baseContentUrl + 'content/' + 'texts/',
11169		isSearching = false,
11170		canceled = false,
11171		searchText = '',
11172		searchTextid = '',
11173		searchDivisions = [],
11174		//isAsciiRegExp = new RegExp('^[\040-\176]*$', 'gi'),
11175		isLemmaRegExp = /[GgHh]\d{1,6}/g,
11176
11177		isLemmaSearch = false,
11178		startTime = null,
11179
11180		searchTermsRegExp = [],
11181		searchIndexLoader = new SearchIndexLoader(),
11182		searchIndexesData = [],
11183		searchIndexesCurrentIndex = 0,
11184
11185		searchType = 'AND',
11186
11187		searchFinalResults = [];
11188		;
11189
11190	searchIndexLoader.on('complete', indexesLoaded);
11191
11192	function start(textid, divisions, text) {
11193
11194		//console.log('TextSearch.start', text, textid);
11195
11196		if (isSearching) {
11197			//console.log('already started ... return');
11198			return false;
11199		}
11200		isSearching = true;
11201
11202		// store variables
11203		searchText = text.trim();
11204		searchTextid = textid;
11205		searchDivisions = divisions;
11206		textInfo = TextLoader.getText(searchTextid);
11207
11208
11209		// reset
11210		canceled = false;
11211		startTime = new Date();
11212		searchFinalResults = [];
11213		searchTermsRegExp = [];
11214		searchIndexesData = []
11215		searchIndexesCurrentIndex = 0;
11216		searchType = /\bOR\b/gi.test(text) ? 'OR' : 'AND';
11217
11218		//createSearchTerms();
11219		isLemmaSearch = isLemmaRegExp.test(searchText);
11220		searchTermsRegExp = SearchTools.createSearchTerms(text, isLemmaSearch);
11221
11222		if (sofia.config.serverSearchPath != '' && (window.location.protocol != 'file:' || sofia.config.baseContentUrl != '')) {
11223
11224			startServerSearch(textInfo, searchDivisions, searchText, isLemmaSearch);
11225		} else {
11226			// load indexes
11227			searchIndexLoader.loadIndexes(textInfo, searchDivisions, searchText, isLemmaSearch);
11228		}
11229
11230		return true;
11231	}
11232
11233	function startServerSearch(textInfo, searchDivisions, searchText, isLemmaSearch) {
11234
11235		$.ajax({
11236			dataType: 'jsonp',
11237			url: sofia.config.baseContentUrl + sofia.config.serverSearchPath,
11238			data: {
11239				textid: textInfo.id,
11240				search: searchText.toLowerCase(),
11241				divisions: searchDivisions.join(','),
11242				date: (new Date()).toString()
11243			},
11244			success: function(data) {
11245
11246				// create results
11247				if (data && data.results) {
11248
11249					// STEMs?
11250					if (typeof data.stem_words != 'undefinded' && data.stem_words.length > 0) {
11251
11252						searchType = 'OR';
11253						// redo search terms
11254						searchTermsRegExp = [];
11255
11256						for (var i=0, il=data.stem_words.length; i<il; i++) {
11257							searchTermsRegExp.push( new XRegExp('\\b(' + data.stem_words[i] + ')\\b', 'gi') );
11258						}
11259
11260					}
11261
11262
11263
11264					for (var i=0, il=data.results.length; i<il; i++) {
11265						var result = data.results[i],
11266							fragmentid = Object.keys(result)[0],
11267							html = result[fragmentid];
11268
11269
11270						var result = findMatchesInVerse(html);
11271
11272						if (result.foundMatch) {
11273							searchFinalResults.push({fragmentid: fragmentid, html: result.html});
11274						} else {
11275							console.log('no match', html);
11276						}
11277
11278						// add to results
11279						//searchFinalResults.push({fragmentid: fragmentid, html: html});
11280					}
11281
11282
11283					ext.trigger('complete', {type: 'complete', target:this, data: {results: searchFinalResults, searchIndexesData: searchIndexesData, searchTermsRegExp: searchTermsRegExp, isLemmaSearch: isLemmaSearch}});
11284
11285
11286				} else {
11287
11288					ext.trigger('complete', {type: 'complete', target: this, data: {results: null, searchIndexesData: searchIndexesData, searchTermsRegExp: searchTermsRegExp, isLemmaSearch: isLemmaSearch}});
11289
11290
11291				}
11292
11293
11294
11295				isSearching = false;
11296			},
11297			error: function(a,b,c,d) {
11298				console.log('error:serverSearch', a,b,c,d);
11299				//reset()
11300			}
11301		})
11302	}
11303
11304	// fires after indexer loader is done
11305	function indexesLoaded(e) {
11306		//console.log('searchIndexLoader:complete', e.data);
11307
11308		if (e.data.loadedIndexes.length == 0) {
11309
11310			// BRUTE FORCE?
11311			//console.log('BRUTE FORCE');
11312
11313			// create "index" of all verses?
11314			searchIndexesData = [];
11315			for (var i=0, il = textInfo.sections.length; i<il; i++) {
11316				var sectionid = textInfo.sections[i],
11317					dbsBookCode = sectionid.substr(0,2),
11318					chapterNumber = parseInt(sectionid.substr(2), 10),
11319					sectionData = {
11320						sectionid: sectionid,
11321						fragmentids: []
11322					};
11323
11324				for (v=1; v<=bible.BOOK_DATA[dbsBookCode].chapters[chapterNumber-1]; v++ ) {
11325					sectionData.fragmentids.push(sectionid + '_' + v);
11326				}
11327
11328
11329				searchIndexesData.push(sectionData);
11330			}
11331
11332			loadNextSectionid();
11333
11334
11335
11336			/*
11337			isSearching = false;
11338			ext.trigger('indexerror', {type: 'indexerror', target:this, data: {results: searchFinalResults}});
11339			*/
11340
11341		} else {
11342			ext.trigger('indexcomplete', {type: 'indexcomplete', target:this, data: {searchIndexesData: e.data.loadedResults }});
11343
11344			// begin loading
11345
11346			if (e.data.stemInfo && e.data.stemInfo.length > 0) {
11347
11348				//if (typeof data.stem_words != 'undefinded' ) {
11349
11350					searchType = 'OR';
11351					// redo search terms
11352					searchTermsRegExp = [];
11353
11354					for (var si=0, sil=e.data.stemInfo.length; si <sil; si++) {
11355						var stemWords = e.data.stemInfo[si].words;
11356
11357						for (var i=0, il=stemWords.length; i<il; i++) {
11358							searchTermsRegExp.push( new XRegExp('\\b(' + stemWords[i] + ')\\b', 'gi') );
11359						}
11360
11361					}
11362				//}
11363			}
11364
11365
11366			searchIndexesData = e.data.loadedResults;
11367			searchIndexesCurrentIndex = -1;
11368
11369			//console.log('start loading indexes', searchIndexesData.length, searchIndexesCurrentIndex);
11370			loadNextSectionid();
11371		}
11372
11373	}
11374
11375	function loadNextSectionid() {
11376		searchIndexesCurrentIndex++;
11377
11378		////console.log('loadNextSectionid', searchIndexesData.length, searchIndexesCurrentIndex);
11379
11380		if (searchIndexesCurrentIndex > searchIndexesData.length) {
11381
11382			//console.log('OVER');
11383
11384			isSearching = false;
11385
11386		} else if (searchIndexesCurrentIndex == searchIndexesData.length) {
11387			// DONE!
11388
11389			//console.log('textSearch:complete');
11390
11391			ext.trigger('complete', {type: 'complete', target:this, data: {results: searchFinalResults, searchIndexesData: searchIndexesData, searchTermsRegExp: searchTermsRegExp, isLemmaSearch: isLemmaSearch}});
11392
11393			isSearching = false;
11394
11395		} else {
11396			var sectionData = searchIndexesData[searchIndexesCurrentIndex],
11397				sectionid = sectionData ? sectionData.sectionid : null,
11398				fragmentids = sectionData ? sectionData.fragmentids : null;
11399				;// url = baseContentPath + textInfo.id + '/' + sectionid + '.json';
11400
11401			if (!sectionData) {
11402				loadNextSectionid();
11403				return;
11404			}
11405
11406			ext.trigger('load', {type: 'load', target:this, data: {sectionid: sectionid, index: searchIndexesCurrentIndex, total: searchIndexesData.length}});
11407
11408			TextLoader.loadSection(textInfo, sectionid, function(content) {
11409
11410				for (var i=0, il=fragmentids.length; i<il; i++) {
11411					var
11412						fragmentid = fragmentids[i],
11413						fragmentNode = content.find('.' + fragmentid).clone(),
11414
11415						// assuming a single node
11416						//html = fragmentNode.html();
11417
11418						html = '';
11419
11420					// remove notes
11421					fragmentNode.find('.note, .cf, .v-num, .verse-num').remove();
11422
11423					// concat verses split over multiple <span class="v"> nodes (paragraphs)
11424					fragmentNode.each(function(i,el) {
11425						html += $(el).html() + ' ';
11426					});
11427
11428					if (fragmentNode.length > 0) {
11429
11430						var result = findMatchesInVerse(html);
11431
11432						if (result.foundMatch) {
11433							searchFinalResults.push({fragmentid: fragmentid, html: result.html});
11434						}
11435					}
11436				}
11437
11438
11439				// DEBUG!!
11440				//setTimeout(function() {
11441					loadNextSectionid();
11442				//}, 10);
11443
11444
11445			}, function(error) {
11446
11447				//console.log('searchindex:error');
11448
11449				loadNextSectionid();
11450			});
11451
11452		}
11453	}
11454
11455
11456	function findMatchesInVerse(html) {
11457		var processedHtml = html,
11458			foundMatch = false,
11459			regMatches = new Array(searchTermsRegExp.length);
11460
11461		for (var j=0, jl=searchTermsRegExp.length; j<jl; j++) {
11462
11463			searchTermsRegExp[j].lastIndex = 0;
11464
11465			if (isLemmaSearch) {
11466
11467				// add the 'highlight' class to the <l> node
11468				processedHtml = processedHtml.replace(searchTermsRegExp[j], function(match) {
11469					regMatches[j] = true;
11470					foundMatch = true;
11471					return match + ' class="highlight" ';
11472				});
11473
11474			} else {
11475
11476				// surround the word with a highlight
11477				processedHtml = processedHtml.replace(searchTermsRegExp[j], function(match) {
11478					regMatches[j] = true;
11479					foundMatch = true;
11480					return '<span class="highlight">' + match + '</span>';
11481				});
11482
11483			}
11484		}
11485
11486		if (searchType == 'AND') {
11487			var foundAll = true;
11488			for (var j=0, jl=regMatches.length; j<jl; j++) {
11489				if (regMatches[j] !== true) {
11490					foundAll = false;
11491					break;
11492				}
11493			}
11494			foundMatch = foundAll;
11495
11496		}
11497
11498		return {html: processedHtml, foundMatch: foundMatch};
11499	}
11500
11501
11502
11503
11504	var ext = {
11505		start: start,
11506		findMatchesInVerse: findMatchesInVerse
11507	};
11508	ext = $.extend(true, ext, EventEmitter);
11509
11510	return ext;
11511};
11512
11513SearchTools = {
11514
11515	isAsciiRegExp: new RegExp('^[\040-\176]*$', 'gi'),
11516
11517	isLemmaRegExp: /[GgHh]\d{1,6}/g,
11518
11519	createSearchTerms: function (searchText, isLemmaSearch) {
11520		var searchTermsRegExp = [];
11521
11522		if (isLemmaSearch) {
11523
11524			var strongNumbers = searchText.split(' ');
11525
11526			for (var i=0, il=strongNumbers.length; i<il; i++) {
11527
11528				var part = strongNumbers[i];
11529
11530				searchTermsRegExp.push( new RegExp('s=("|\')(\\w\\d{1,4}[a-z]?\\s)?' + '(G|H)?' + part.substr(1) + '[a-z]?(\\s\\w\\d{1,4}[a-z]?)?("|\')', 'gi') );
11531
11532			}
11533
11534		} else {
11535
11536
11537
11538			// check for quoted search "jesus christ"
11539			if (searchText.substring(0,1) == '"' && searchText.substring(searchText.length-1) == '"') {
11540
11541				var withoutQuotes =  searchText.substring(1,searchText.length-1);
11542				withoutQuotes = withoutQuotes.replace(/\s/g,'(\\s?(<(.|\\n)*?>)?\\s?)?');
11543
11544				searchTermsRegExp.push( new XRegExp('\\b(' + withoutQuotes + ')\\b', 'gi') );
11545
11546			} else {
11547
11548				// ASCII characters have predictable word boundaries (space ' ' = \b)
11549				SearchTools.isAsciiRegExp.lastIndex = 0;
11550
11551				if (SearchTools.isAsciiRegExp.test( searchText )) {
11552
11553					// for non-quoted searches, use "AND" search
11554					var andSearchParts = searchText.split(/\s+AND\s+|\s+/gi);
11555
11556					// filter for duplicate words
11557					andSearchParts = $.unique(andSearchParts);
11558
11559					for (var i=0, il=andSearchParts.length; i<il; i++) {
11560
11561						var part = andSearchParts[i],
11562							partRegex = new XRegExp('\\b(' + part + ')\\b', 'gi');
11563
11564						searchTermsRegExp.push( partRegex );
11565					}
11566
11567				} else {
11568
11569					var words = SearchTools.splitWords(searchText);
11570
11571					for (var j=0, jl=words.length; j<jl; j++) {
11572
11573						searchTermsRegExp.push( new XRegExp(words[j], 'gi') );
11574
11575					}
11576				}
11577			}
11578		}
11579
11580		return searchTermsRegExp;
11581	},
11582
11583	splitWords: function(input) {
11584
11585		var
11586			removeRegChars = ['\\', '^', '$', '.', '|', '?', '*', '+', '(', ')', '[', ']', '{', '}'];
11587			otherRemoveChars = [
11588				// roman
11589				',',';', '!', '-', '–', '―', '—', '~', ':', '"','/', "'s", '’s', "'", '‘', '’', '“', '”', '¿', '<', '>', '&',
11590				// chinese
11591				'。', ':', ',', '”', '“', ')', '(', '~', '「', '」'
11592			],
11593			punctuation = [].concat(removeRegChars).concat(otherRemoveChars),
11594			innerWordExceptions = ["'", '’', '-'],
11595			words = [],
11596			word = '';
11597
11598		function addWord() {
11599			if (word != '')	{
11600				words.push(word);
11601			}
11602			word = '';
11603		}
11604
11605		// formalize string
11606		input = new String(input);
11607
11608		// yes or no on apostrophes
11609		input = input.replace(/(['’]s)/gi, '');
11610
11611	    for (var i = 0, il = input.length; i<il; i++) {
11612			var	letter = input.charAt(i),
11613				charCode = input.charCodeAt(i),
11614				isFirstChar = (i==0);
11615				isLastChar = (i==il-1),
11616				isPunctuation = punctuation.indexOf(letter) > -1,
11617				isWhitespace = letter == ' ',
11618				isLetter = !(isWhitespace || isPunctuation);
11619
11620			// if this is a letter
11621			if ( isLetter ) {
11622
11623				 word += letter;
11624
11625				// If this is a Chinese/Japanese/Korean ideograph, it is a word by itself. No separator is needed.
11626				if (((charCode >= 0x4E00) && (charCode <= 0x9FFF)) || ((charCode >= 0x3400) && (charCode <= 0x4DFF)) || ((charCode >= 0x20000) && (charCode <= 0x2A6DF))) {
11627
11628	                addWord(); // Technically, some ideographs combine to make a compound word, but concordance/search will work without that refinement, possibly with extra hits.
11629	            }
11630
11631			} else if (!isFirstChar && !isLastChar && innerWordExceptions.indexOf(letter) > -1 && punctuation.indexOf(input[i-1]) == -1 && punctuation.indexOf(input[i+1]) == -1) {
11632
11633				word += letter;
11634
11635			} else {
11636				// it was punctuation!
11637				addWord();
11638			}
11639
11640			////console.log(letter, charCode);
11641	    }
11642
11643		addWord();
11644
11645		words = $.unique(words);
11646
11647		return words;
11648	},
11649
11650	HASHSIZE: 20,
11651
11652	hashWord: function(word) {
11653	    var hash = 0;
11654	    for (i = 0; i < word.length; i++) {
11655	        hash += word.charCodeAt(i);
11656	        hash %= SearchTools.HASHSIZE;
11657	   }
11658	   return hash;
11659	}
11660};
11661
11662SearchIndexLoader = function() {
11663
11664	var
11665		baseContentPath = sofia.config.baseContentUrl + 'content/' + 'texts/',
11666		textInfo = null,
11667		searchTerms = [],
11668		searchTermsIndex = -1,
11669		isLemmaSearch = false,
11670		isStemEnabled = true,
11671		stemmingData = {},
11672		stemInfo = [],
11673		searchDivisions = [],
11674		// initial load: [{term:'light': occurrences: ['GN1_2', 'GN2_5']}, {term: 'love': ['JN3']}
11675		loadedIndexes = [],
11676		// final: [{sectionid:'GN1', fragmentids: ['GN1_2', 'GN1_3']}, {sectionid:'GN2', fragmentids: ['GN2_4']} }
11677		loadedResults = [],
11678		searchType = 'AND'; // OR
11679
11680	// START
11681	function loadIndexes(newTextInfo, divisions, searchText, isLemma) {
11682
11683		isLemmaSearch = isLemma;
11684		textInfo = newTextInfo;
11685		searchDivisions = divisions;
11686
11687		searchTerms = SearchTools.splitWords(searchText);
11688
11689		searchTermsIndex = -1;
11690		loadedIndexes = [];
11691		loadedResults = [];
11692		stemInfo = [];
11693		stemmingData = isLemmaSearch ? null : {};
11694
11695		searchType = /\bOR\b/gi.test(searchText) ? 'OR' : 'AND';
11696
11697		if (isStemEnabled && !isLemmaSearch) {
11698			loadStemmingData();
11699		} else {
11700			loadNextIndex();
11701		}
11702	}
11703
11704	function loadStemmingData() {
11705
11706		var stemUrl = baseContentPath + textInfo.id + '/index/stems.json';
11707
11708		// attempt to load in index
11709		$.ajax({
11710			beforeSend: function(xhr){
11711				if (xhr.overrideMimeType){
11712					xhr.overrideMimeType("application/json");
11713				}
11714			},
11715
11716			dataType: 'json',
11717			url: stemUrl,
11718			success: function(data) {
11719				console.log('Loaded Stems!', textInfo.id);
11720
11721				stemmingData = data;
11722
11723				loadNextIndex();
11724			},
11725			error: function() {
11726				// set it to null in order to check later
11727				stemmingData = null;
11728
11729				console.log('No stem data for', textInfo.id);
11730				loadNextIndex();
11731			}
11732		});
11733	}
11734
11735
11736	function loadNextIndex() {
11737
11738		// starts at -1, so this will make the first one 0
11739		searchTermsIndex++;
11740
11741		if (searchTermsIndex < searchTerms.length) {
11742
11743			loadSearchTermIndex( searchTerms[searchTermsIndex] );
11744
11745		} else {
11746
11747			// if we've done all the indexes, then it's time to start combining them
11748			//combineIndexes();
11749			processIndexes();
11750		}
11751
11752	}
11753
11754	function loadSearchTermIndex(searchTerm) {
11755
11756		var indexUrl = '',
11757			key = '',
11758			hash = '',
11759			stem = '';
11760
11761		if (isLemmaSearch) {
11762			key = searchTerm.toUpperCase();
11763			var letter = key.substr(0,1),
11764				firstNumber = searchTerm.length >= 5 ? searchTerm.substr(1,1) : '0';
11765
11766			indexUrl = baseContentPath + textInfo.id + '/indexlemma/_' + letter.toUpperCase() + firstNumber + '000' + '.json';
11767
11768		} else {
11769			key = searchTerm.toLowerCase();
11770
11771			if (isStemEnabled && stemmingData != null) {
11772				stem = stemmingData[key],
11773				hash = SearchTools.hashWord(stem);
11774				indexUrl = baseContentPath + textInfo.id + '/index/_stems_' + hash + '.json';
11775
11776			} else {
11777				hash = SearchTools.hashWord(key);
11778				indexUrl = baseContentPath + textInfo.id + '/index/_' + hash + '.json';
11779
11780			}
11781		}
11782
11783		if (searchTerm == 'undefined') {
11784			//console.log('STOP search. undefined term');
11785			return;
11786		}
11787
11788		//console.log('Loading Index:' + searchTerm + ',' + searchTermEncoded);
11789
11790		// attempt to load in index
11791		$.ajax({
11792			beforeSend: function(xhr){
11793				if (xhr.overrideMimeType){
11794					xhr.overrideMimeType("application/json");
11795				}
11796			},
11797
11798			dataType: 'json',
11799			url: indexUrl,
11800			success: function(data) {
11801
11802				var fragments = null;
11803
11804				//console.log(key, stem, data, data[stem]);
11805
11806				if (isStemEnabled && stemmingData != null) {
11807					fragments = data[stem].fragmentids;
11808					stemInfo.push({
11809						word: key,
11810						stem: stem,
11811						words: data[stem].words
11812					});
11813				} else {
11814					fragments = data[key];
11815				}
11816
11817				loadedIndexes.push(fragments);
11818
11819				loadNextIndex();
11820			},
11821			error: function() {
11822				//console.log('no index for: ' + searchTerm);
11823				loadNextIndex();
11824			}
11825
11826		});
11827
11828	}
11829
11830	function processIndexes() {
11831
11832		// we'll combine everything into this ['GN1_1', 'GN1_2']
11833		fragmentids = [];
11834		// then pair down to this [{sectionid:'GN1', fragmentids: ['GN1_1','GN1_2']}]
11835		loadedResults = [];
11836
11837		if (loadedIndexes.length > 0) {
11838
11839			if (searchType == 'OR') {
11840				// combine all the fragments
11841				for (var i=0, il=loadedIndexes.length; i<il; i++) {
11842					fragmentids = fragmentids.concat( loadedIndexes[i] ); // .occurrences );
11843					////console.log(loadedIndexes[i].term);
11844					////console.log(loadedIndexes[i].occurrences);
11845				}
11846
11847				// sort!
11848				fragmentids.sort(function(a, b) {
11849					// split into parts
11850					function splitFragment(fragmentid) {
11851						var parts = fragmentid.split('_'),
11852							sectionid = parts[0],
11853							sectionIndex = textInfo.sections.indexOf(sectionid),
11854							fragmentNum = parseInt(parts[1], 10),
11855							value = {
11856								sectionid: sectionid,
11857								sectionIndex: sectionIndex,
11858								fragmentNum: fragmentNum
11859							};
11860
11861						return value;
11862					}
11863
11864					var fraga = splitFragment(a),
11865						fragb = splitFragment(b);
11866
11867
11868					if (fraga.sectionIndex < fragb.sectionIndex ||
11869						(fraga.sectionIndex == fragb.sectionIndex && fraga.fragmentNum < fragb.fragmentNum) )
11870						return -1;
11871					if (fraga.sectionIndex > fragb.sectionIndex ||
11872						(fraga.sectionIndex == fragb.sectionIndex && fraga.fragmentNum > fragb.fragmentNum) )
11873						return 1;
11874					// a must be equal to b
11875					return 0;
11876				});
11877
11878
11879			} else if (searchType == 'AND') {
11880
11881				// combine arrays to only include fragments (verses) where all indexes overlap (truth AND love)
11882				var totalIndexes = loadedIndexes.length;
11883				if (totalIndexes == 1) {
11884					fragmentids = loadedIndexes[0]; // .occurrences;
11885				} else if (totalIndexes > 1) {
11886					//fragmentids = loadedIndexes[0].occurrences.filter(function(val) {
11887					fragmentids = loadedIndexes[0].filter(function(val) {
11888						var inOtherArrays = true;
11889						for (var i=1; i<totalIndexes; i++) {
11890							//if (loadedIndexes[i].occurrences.indexOf(val) == -1) {
11891							if (loadedIndexes[i].indexOf(val) == -1) {
11892								inOtherArrays = false;
11893								break;
11894							}
11895						}
11896
11897						return inOtherArrays;
11898					});
11899				}
11900			}
11901
11902			// reformat fragments into sectionids
11903			// ['JN1_1','JN1_2'] => [{sectionid: 'JN1', fragmentids: ['JN1_1','JN1_2']}]
11904			if (fragmentids) {
11905				for (var i=0, il=fragmentids.length; i<il; i++) {
11906					var fragmentid = fragmentids[i];
11907
11908					if (fragmentid != '' && fragmentid != null) {
11909
11910
11911						var	sectionid = fragmentid.split('_')[0],
11912							dbsBookCode = sectionid.substring(0,2);
11913
11914						if (searchDivisions.length == 0 || searchDivisions.indexOf(dbsBookCode) > -1) {
11915
11916							// see if we already created data for this section id
11917							var sectionidInfo = $.grep(loadedResults, function(val){ return val.sectionid == sectionid; });
11918
11919							// create new data
11920							if (sectionidInfo.length == 0) {
11921								loadedResults.push({sectionid: sectionid, fragmentids: [fragmentid]});
11922							}
11923							// add to this sectionid
11924							else {
11925								sectionidInfo[0].fragmentids.push(fragmentid);
11926							}
11927						}
11928					}
11929				}
11930			}
11931		}
11932
11933		// send up the chain
11934		ext.trigger('complete', {type:'complete', target: this, data: {
11935																	loadedIndexes: loadedIndexes,
11936																	loadedResults: loadedResults,
11937																	fragmentids: fragmentids,
11938																	stemInfo: stemInfo,
11939																}
11940								});
11941	}
11942
11943	var ext = {
11944		loadIndexes: loadIndexes
11945	};
11946	ext = $.extend(true, ext, EventEmitter);
11947
11948	return ext;
11949};
11950
11951
11952
11953/*********
11954* ../app/js/media/audiodatamanager.js
11955**********/
11956
11957if (typeof sofia != 'undefined') {
11958	sofia.audioSources = [];
11959}
11960
11961var AudioDataManager = function() {
11962
11963	function getAudioInfo(textInfo, callback) {
11964
11965		var index = 0;
11966		function doNext() {
11967
11968			var audioSource = sofia.audioSources[index];
11969			audioSource.getAudioInfo(textInfo, recieveData);
11970
11971			////console.log('-AudioSource', index, textInfo.id);
11972		}
11973		function recieveData(audioInfo) {
11974
11975			////console.log('-AudioSource.receiveData', textInfo.id);
11976
11977			// send data backup
11978			if (audioInfo != null) {
11979				////console.log('found: ', data);
11980
11981				audioInfo.audioSourceIndex = index;
11982
11983				callback(audioInfo);
11984			} else {
11985				index++;
11986
11987				if (index < sofia.audioSources.length) {
11988					// try again
11989					doNext();
11990
11991				} else {
11992					// didn't find anything
11993					callback(null);
11994				}
11995
11996			}
11997
11998		}
11999
12000		doNext();
12001	}
12002
12003	function getFragmentAudio(textInfo, audioInfo, fragmentid, audioOption, callback) {
12004
12005		// pass to the correct source using the index number assigned when getAudioInfo was called
12006		sofia.audioSources[audioInfo.audioSourceIndex].getFragmentAudio(textInfo, audioInfo, fragmentid, audioOption, callback);
12007
12008	}
12009
12010	function getNextFragment(textInfo, audioInfo, fragmentid, callback) {
12011		sofia.audioSources[audioInfo.audioSourceIndex].getNextFragment(textInfo, audioInfo, fragmentid, callback);
12012	}
12013
12014	function getPrevFragment(textInfo, audioInfo, fragmentid, callback) {
12015		sofia.audioSources[audioInfo.audioSourceIndex].getPrevFragment(textInfo, audioInfo, fragmentid, callback);
12016	}
12017
12018
12019	var audio = {
12020		getAudioInfo: getAudioInfo,
12021		getFragmentAudio: getFragmentAudio,
12022		getNextFragment: getNextFragment,
12023		getPrevFragment: getPrevFragment
12024	};
12025	return audio;
12026};
12027
12028
12029var LocalAudio = (function() {
12030
12031	function getAudioInfo(textInfo, callback) {
12032		////console.log('LocalAudio', textInfo.id);
12033
12034		var checkDirectory = textInfo.id;
12035
12036
12037		if (typeof textInfo.audioDirectory != 'undefined') {
12038
12039			// if empty it means, "Don't check anything"
12040			if (textInfo.audioDirectory == '') {
12041				callback(null);
12042				return;
12043			} else {
12044
12045				checkDirectory = textInfo.audioDirectory;
12046			}
12047		}
12048
12049		sofia.ajax({
12050			dataType: 'json',
12051			url: 'content/audio/' + checkDirectory + '/info.json',
12052			success: function(audioInfo) {
12053
12054				if (typeof audioInfo == 'undefined' ) {
12055					callback(null);
12056					return;
12057				}
12058
12059				audioInfo.type = 'local';
12060				audioInfo.directory = checkDirectory;
12061
12062				if (!audioInfo.title) {
12063					audioInfo.title = 'Local';
12064				}
12065
12066				////console.log('--LocalAudio success', textInfo.id);
12067
12068				callback(audioInfo);
12069			},
12070			error: function(e) {
12071
12072				////console.log('--LocalAudio error', textInfo.id);
12073
12074				callback(null);
12075			}
12076		});
12077	}
12078
12079	function getFragmentAudio(textInfo, audioInfo, fragmentid, audioOption, callback) {
12080
12081		var fragmentData = findFragmentData(audioInfo, fragmentid);
12082
12083		if (fragmentData == null) {
12084			callback(null);
12085			return;
12086		}
12087
12088		// yeah, we found one!
12089		var audioData = {
12090			url: sofia.config.baseContentUrl + 'content/' + 'audio/' + audioInfo.directory + '/' + fragmentData.filename + '.' + fragmentData.exts[0],
12091			id: fragmentData.index,
12092			start: fragmentData.start,
12093			end: fragmentData.end
12094		}
12095
12096		callback(audioData);
12097	}
12098
12099	function findFragmentData(audioInfo, fragmentid) {
12100		// split through all the files
12101		var verseParts = fragmentid.split('_'),
12102			sectionid = verseParts[0],
12103			verseNumber = parseInt(verseParts[1], 10),
12104			fragmentIndex = 0,
12105			fragmentData = null;
12106
12107		// look through all the ranges
12108		/*
12109		{
12110			"start": "GN1_1",
12111			"end": "GN1_31",
12112			"filename": "GN1",
12113			"exts": [
12114				"mp3"
12115			]
12116		},
12117		*/
12118		for (var i=0, il=audioInfo.fragments.length; i<il; i++) {
12119			var fragmentFileinfo = audioInfo.fragments[i],
12120				startFragmentParts = fragmentFileinfo.start.split('_'),
12121				startSectionid = startFragmentParts[0];
12122
12123			// if matching chapter then check if verse
12124			if (sectionid == startSectionid) {
12125				var startVerseNumber = parseInt(startFragmentParts[1], 10),
12126					endFragmentParts = fragmentFileinfo.end.split('_'),
12127					endVerseNumber = parseInt(endFragmentParts[1], 10);
12128
12129				if (verseNumber >= startVerseNumber && verseNumber <= endVerseNumber) {
12130					//filename = fragmentFileinfo.filename + '.' + fragmentFileinfo.exts[0];
12131
12132					fragmentIndex = i;
12133					fragmentData = fragmentFileinfo;
12134
12135					break;
12136				}
12137			}
12138		}
12139
12140		if (fragmentData != null) {
12141			fragmentData.index = fragmentIndex;
12142		}
12143
12144		return fragmentData;
12145	}
12146
12147	function getNextFragment(textInfo, audioInfo, fragmentid, callback) {
12148		var fragmentData = findFragmentData(audioInfo, fragmentid);
12149
12150		if (fragmentData == null) {
12151			callback(null);
12152			return;
12153		}
12154
12155		if (fragmentData.index < audioInfo.fragments.length-1) {
12156			var nextFragmentData = audioInfo.fragments[fragmentData.index+1];
12157
12158			callback(nextFragmentData.start);
12159		} else {
12160			callback(null);
12161		}
12162	}
12163
12164	function getPrevFragment(textInfo, audioInfo, fragmentid, callback) {
12165		var fragmentData = findFragmentData(audioInfo, fragmentid);
12166
12167		if (fragmentData == null) {
12168			callback(null);
12169			return;
12170		}
12171
12172		if (fragmentData.index > 0) {
12173			var prevFragmentData = audioInfo.fragments[fragmentData.index-1];
12174
12175			callback(prevFragmentData.start);
12176		} else {
12177			callback(null);
12178		}
12179	}
12180
12181	var audio = {
12182		getAudioInfo: getAudioInfo,
12183		getFragmentAudio: getFragmentAudio	,
12184		getNextFragment: getNextFragment,
12185		getPrevFragment: getPrevFragment
12186	};
12187	return audio;
12188})();
12189
12190
12191
12192
12193var FaithComesByHearingAudio = (function() {
12194
12195	var fcbhLocation = null,
12196		fcbhList = null,
12197		fcbhIsLoaded = false,
12198
12199		currentTextInfo = null,
12200		currentCallback = null
12201
12202
12203	function init() {
12204		$(function(){
12205			loadFcbHearingLocations();
12206		});
12207	}
12208
12209	function loadFcbHearingLocations() {
12210		$.ajax({
12211			dataType: 'jsonp',
12212			url: sofia.protocol + '//dbt.io/audio/location?v=2&reply=jsonp&key=' + sofia.config.fcbhKey,
12213			success: function(data) {
12214				fcbhLocation = data;
12215			},
12216			complete: function() {
12217				loadFcbHearingInfo();
12218			}
12219		});
12220	}
12221
12222	function loadFcbHearingInfo() {
12223		$.ajax({
12224			dataType: 'jsonp',
12225			url: sofia.protocol + '//dbt.io/library/volume?v=2&reply=jsonp&media=audio&delivery=web&key=' + sofia.config.fcbhKey,
12226			success: function(data) {
12227				fcbhList = data;
12228
12229				////console.log('FCBH', fcbhLocation, fcbhList);
12230
12231			},
12232			complete: function() {
12233				fcbhIsLoaded = true;
12234
12235				attemptGetAudioInfo();
12236			}
12237		});
12238	}
12239
12240
12241	function getAudioInfo(textInfo, callback) {
12242
12243		// store
12244		currentTextInfo = textInfo;
12245		currentCallback = callback;
12246
12247		// attempt
12248		attemptGetAudioInfo();
12249	}
12250
12251	function attemptGetAudioInfo() {
12252
12253		// check for FCBH info hardcoded in the textInfo object
12254		if (fcbhIsLoaded && currentTextInfo != null) {
12255			var fcbhKeys = ['fcbh_audio_nt','fcbh_audio_ot','fcbh_drama_nt','fcbh_drama_ot'],
12256				fcbhExists = false,
12257				audioData = {title: 'FCBH', type: 'fcbh'};
12258
12259			for (var i=0, il=fcbhKeys.length; i<il; i++) {
12260				var key = fcbhKeys[i],
12261					keyData = currentTextInfo[ key ];
12262
12263				audioData[key] = keyData;
12264
12265				if (keyData && keyData != '') {
12266					fcbhExists = true;
12267				}
12268			}
12269
12270			// favor drama
12271			if (audioData.fcbh_drama_nt != '' || audioData.fcbh_drama_ot != '') {
12272				audioData.title += ' (drama)';
12273			}
12274
12275			if (fcbhExists) {
12276				currentCallback(audioData);
12277			} else {
12278				currentCallback(null);
12279			}
12280
12281			currentTextInfo = null;
12282			currentCallback = null;
12283
12284			return;
12285		}
12286
12287
12288		// TEMP: no longer attempt FCBH detection
12289		return;
12290
12291
12292		// if not in textInfo, then we need to look it up from the FCBH data
12293		if (fcbhIsLoaded && currentTextInfo != null && currentCallback != null) {
12294
12295			var audioData = {
12296				title: 'FCBH',
12297				fcbh_audio_nt: getFbchCollection(currentTextInfo, 'non-drama', 'nt'),
12298				fcbh_audio_ot: getFbchCollection(currentTextInfo, 'non-drama', 'ot'),
12299				fcbh_drama_nt: getFbchCollection(currentTextInfo, 'drama', 'nt'),
12300				fcbh_drama_ot: getFbchCollection(currentTextInfo, 'drama', 'ot')
12301			};
12302
12303			// if we find one, send it!
12304			if (audioData.fcbh_audio_nt != null ||
12305				audioData.fcbh_audio_ot != null ||
12306				audioData.fcbh_drama_nt != null ||
12307				audioData.fcbh_drama_ot != null) {
12308
12309				currentCallback(audioData);
12310			} else {
12311
12312				currentCallback(null);
12313			}
12314
12315
12316
12317			currentTextInfo = null;
12318			currentCallback = null;
12319		}
12320	}
12321
12322	function getFbchCollection(textInfo, mediaType, testament ) {
12323
12324		var
12325			// our attempt to find a fake dam_id (real: ENGESVN2DA and ENGESVO2DA)
12326			dam_id_start = textInfo.id.replace(/[-_]/gi, '').toUpperCase(),
12327			results = [];
12328
12329
12330		// match dam_id
12331		for (var i=0, il = fcbhList.length; i<il; i++) {
12332			var fbchCollection = fcbhList[i];
12333
12334			if (fbchCollection.dam_id && fbchCollection.dam_id.indexOf(dam_id_start) > -1) {
12335				results.push(fbchCollection);
12336			}
12337		}
12338
12339		// if no dam_id match, then try language
12340		if (results.length == 0 && textInfo.lang != 'eng') {
12341			// dam_id
12342			for (var i=0, il = fcbhList.length; i<il; i++) {
12343				var fbchCollection = fcbhList[i];
12344
12345				if (fbchCollection.language_iso && fbchCollection.language_iso == textInfo.lang) {
12346					results.push(fbchCollection);
12347				}
12348			}
12349		}
12350
12351		// if no dam_id or langauge match, try family code
12352		if (results.length == 0 && textInfo.lang != 'eng') {
12353			// dam_id
12354			for (var i=0, il = fcbhList.length; i<il; i++) {
12355				var fbchCollection = fcbhList[i];
12356
12357				if (fbchCollection.language_family_iso && fbchCollection.language_family_iso == textInfo.lang) {
12358					results.push(fbchCollection);
12359				}
12360			}
12361		}
12362
12363		// filter by testament if needed
12364		if (testament != '') {
12365			var testamentResults = [];
12366
12367			for (var i=0, il = results.length; i<il; i++) {
12368				var fbchCollection = results[i];
12369
12370				if (fbchCollection.collection_code && fbchCollection.collection_code.toLowerCase() == testament) {
12371					testamentResults.push(fbchCollection);
12372				}
12373			}
12374
12375			results = testamentResults;
12376		}
12377
12378		// filter by mediaType ('drama' or 'non-drama') if possible
12379		if (mediaType != '') {
12380			var mediaTypeResults = [];
12381
12382			for (var i=0, il = results.length; i<il; i++) {
12383				var fbchCollection = results[i];
12384
12385				if (fbchCollection.media_type && fbchCollection.media_type.toLowerCase() == mediaType) {
12386					mediaTypeResults.push(fbchCollection);
12387				}
12388			}
12389
12390			if (mediaTypeResults.length > 0) {
12391				results = mediaTypeResults;
12392			}
12393		}
12394
12395		if (results.length > 0) {
12396			return results[0].dam_id;
12397		} else {
12398			return null;
12399		}
12400	}
12401
12402	function getFbchCollectionById(dam_id) {
12403		var collection = null;
12404
12405		if (fcbhList == null) {
12406			return null;
12407		}
12408
12409		for (var i=0, il = fcbhList.length; i<il; i++) {
12410			var fbchCollection = fcbhList[i];
12411
12412			if (fbchCollection.dam_id && fbchCollection.dam_id == dam_id) {
12413				collection = fbchCollection;
12414				break;
12415			}
12416		}
12417
12418		return collection;
12419	}
12420
12421	function getFragmentAudio(textInfo, audioInfo, fragmentid, audioOption, callback) {
12422		if (audioInfo == null) {
12423			callback(null);
12424			return;
12425		}
12426
12427		// find the right dam_id for this audio fragment
12428		var
12429			fragmentparts = fragmentid.split('_'),
12430			sectionid = fragmentparts[0],
12431			dbsBookCode = sectionid.substr(0,2),
12432			chapterNum = sectionid.substr(2),
12433			bookInfo = bible.BOOK_DATA[dbsBookCode],
12434			testament = bible.OT_BOOKS.indexOf(dbsBookCode) > -1 ? 'ot' :
12435						bible.NT_BOOKS.indexOf(dbsBookCode) > -1 ? 'nt' : '',
12436			dramaKey = 'fcbh_drama_' + testament,
12437			audioKey = 'fcbh_audio_' + testament,
12438			dam_id = '';
12439
12440
12441		// apocrypha!
12442		if (testament == '') {
12443			callback(null);
12444			return;
12445		}
12446
12447
12448
12449		// find correct damID
12450		if (audioOption == 'audio') {
12451
12452			if (audioInfo[audioKey] && audioInfo[audioKey] != '') {
12453				dam_id = audioInfo[audioKey];
12454			} else if (audioInfo[dramaKey] && audioInfo[dramaKey] != '') {
12455				dam_id = audioInfo[dramaKey];
12456			}
12457
12458		} else if (audioOption == 'drama') {
12459
12460			if (audioInfo[dramaKey] && audioInfo[dramaKey] != '') {
12461				dam_id = audioInfo[dramaKey];
12462			} else if (audioInfo[audioKey] && audioInfo[audioKey] != '') {
12463				dam_id = audioInfo[audioKey];
12464			}
12465		}
12466
12467		//console.log('audio preference', audioOption, dam_id);
12468
12469		var collectionInfo = getFbchCollectionById(dam_id);
12470		if (collectionInfo != null) {
12471			audioInfo.title = 'FCBH: ' + collectionInfo.version_code; //  + '-' + (collectionInfo.version_name != '' ? collectionInfo.version_name : collectionInfo.volume_name);
12472		}
12473
12474		var
12475			url = sofia.protocol + '//dbt.io/audio/path?v=2&reply=jsonp&dam_id=' + dam_id + '&book_id=' + bookInfo.osis.toLowerCase() + '&chapter_id=' + chapterNum.toString() + '&key=' + sofia.config.fcbhKey;
12476
12477		$.ajax({
12478			dataType: 'jsonp',
12479			url: url,
12480			success: function(pathData) {
12481
12482				if (fcbhLocation == null || fcbhLocation.length == 0) {
12483					callback(null);
12484					return;
12485				}
12486
12487				if (pathData.length == 0) {
12488					callback(null);
12489					return;
12490				}
12491
12492				var server = fcbhLocation[0],
12493					url = server.protocol + "://" + server.server + server.root_path + "/" + pathData[0].path,
12494					audioData = {
12495						id: sectionid,
12496						url: url,
12497						start: sectionid + '_1',
12498						end: ''
12499					};
12500
12501				if (callback) {
12502					callback(audioData);
12503				}
12504			},
12505			error: function(a,b,c,d) {
12506
12507				//console.log('FCBH location error', a,b,c,d);
12508
12509				if (callback) {
12510					callback(null);
12511				}
12512			}
12513		});
12514
12515	}
12516
12517	function getNextFragment(textInfo, audioInfo, fragmentid, callback) {
12518
12519		// next chapter!
12520		var sectionid = fragmentid.split('_')[0],
12521			reference = new bible.Reference(sectionid),
12522			nextChapter = reference.nextChapter();
12523
12524		callback( nextChapter.toSection() );
12525
12526	}
12527
12528	function getPrevFragment(textInfo, audioInfo, fragmentid, callback) {
12529
12530		// prev chapter!
12531		var sectionid = fragmentid.split('_')[0],
12532			reference = new bible.Reference(sectionid),
12533			prevChapter = reference.prevChapter();
12534
12535		callback( prevChapter.toSection() );
12536	}
12537
12538	var audio = {
12539		getAudioInfo: getAudioInfo,
12540		getFragmentAudio: getFragmentAudio,
12541		getFbchCollection: getFbchCollection,
12542		getFragmentAudio: getFragmentAudio,
12543		fcbhIsLoaded: fcbhIsLoaded,
12544		getNextFragment: getNextFragment,
12545		getPrevFragment: getPrevFragment
12546	};
12547
12548	init();
12549
12550	return audio;
12551})();
12552
12553
12554// FCBH First (March 2014)
12555sofia.initMethods.push(function() {
12556	if (typeof sofia != 'undefined') {
12557		if (sofia.config.enableOnlineSources) {
12558
12559			sofia.audioSources.push(FaithComesByHearingAudio);
12560		}
12561	}
12562
12563	if (typeof sofia != 'undefined') {
12564		sofia.audioSources.push(LocalAudio);
12565	}
12566});
12567
12568
12569
12570/*********
12571* ../app/js/media/jesusfilmapi.js
12572**********/
12573var JesusFilmMediaApi = (function() {
12574
12575	var
12576		isLoaded = false,
12577		isLoading = false,
12578		jfmLanguages = null,
12579		getPlayerCallbacks = [];
12580
12581	function load() {
12582
12583		if (isLoaded || isLoading) {
12584			return;
12585		}
12586
12587		isLoading = true;
12588		isLoaded = false;
12589
12590		$.ajax({
12591
12592			url: 'content/media/jesusfilmmedia/jesusfilmlanguages.json',
12593			success: function(data) {
12594
12595				isLoading = true;
12596				isLoaded = true;
12597
12598				jfmLanguages = data;
12599
12600				findPlayers();
12601			},
12602			error: function(a,b,c,d) {
12603				console.log('error', a,b,c,d);
12604			}
12605
12606		});
12607	}
12608
12609	// public method
12610	function getPlayer(lang, segmentNumber, callback) {
12611
12612		getPlayerCallbacks.push({lang: lang, segmentNumber: segmentNumber, callback:callback});
12613
12614		if (!isLoaded) {
12615			if (!isLoading) {
12616				load();
12617			}
12618		} else {
12619			findPlayers();
12620		}
12621	}
12622
12623	function findPlayers() {
12624
12625		// wait until the languages are loaded
12626		if (jfmLanguages == null) {
12627			load();
12628			return;
12629		}
12630
12631		// return all results
12632		while(getPlayerCallbacks.length > 0) {
12633			var callbackInfo = getPlayerCallbacks.pop();
12634
12635			findPlayer(callbackInfo.lang, callbackInfo.segmentNumber, callbackInfo.callback);
12636		}
12637	}
12638
12639	function findPlayer(lang, segmentNumber, callback) {
12640
12641
12642		var languageId = jfmLanguages[lang],
12643			iframeUrl = '';
12644
12645		if (typeof languageId == 'undefined' || languageId == null)	{
12646			languageId = 529;
12647			lang = 'eng';
12648		}
12649
12650		iframeUrl = sofia.protocol + '//api.arclight.org/videoPlayerUrl?refId=1_' + languageId + '-jf61' + segmentNumber + '-0-0&amp;apiSessionId=' + sofia.config.jfmKey + '&playerStyle=vanilla';
12651
12652		callback(iframeUrl, lang);
12653	}
12654
12655	var ext = {
12656		getPlayer: getPlayer
12657
12658	}
12659
12660	return ext;
12661})();
12662
12663
12664
12665/*********
12666* ../app/js/media/medialibrary.js
12667**********/
12668sofia.globals.mediaLargeImage = null;
12669
12670sofia.globals.mediaImageClick = function(e) {
12671	e.preventDefault();
12672
12673
12674	if (sofia.globals.mediaLargeImage != null) {
12675		sofia.globals.mediaLargeImage.fadeOut(function() {
12676			$(this).remove();
12677		});
12678	}
12679
12680	var link = $(this),
12681		url = link.attr('href'),
12682		img = link.find('img'),
12683		imgWidth = img.width(),
12684		imgHeight = img.height(),
12685		imgOffset = img.offset(),
12686		win = $(window),
12687		winHeight = win.height(),
12688		winWidth = win.width(),
12689
12690		clonedImage = $('<img src="' + img.parent('a').attr('href') + '" />')
12691
12692		//clonedImage = img
12693		//				.clone()
12694						.appendTo($('body'))
12695						.addClass('big-image')
12696						.css({top: imgOffset.top, left: imgOffset.left, width: imgWidth, height: imgHeight }),
12697
12698		newHeight = winHeight,
12699		newWidth = winHeight * imgWidth / imgHeight,
12700		newTop = 0,
12701		newLeft = winWidth/2 - newWidth/2;
12702
12703	// if the image is wider in ratio than the window
12704	if (newLeft < 0) {
12705		newWidth = winWidth;
12706		newHeight = winWidth * imgHeight / imgWidth;
12707		newLeft = 0;
12708		newTop = winHeight/2 - newHeight/2;
12709	}
12710
12711	clonedImage.on('load', function() {
12712		clonedImage.animate({
12713			top: newTop,
12714			left: newLeft,
12715			width: newWidth,
12716			height: newHeight
12717		});
12718	});
12719
12720	sofia.globals.mediaLargeImage = clonedImage;
12721
12722	if (sofia.analytics) {
12723		sofia.analytics.record('imageclick', url);
12724	}
12725
12726	clonedImage.on('click', shrinkImage);
12727
12728	function shrinkImage() {
12729		clonedImage.animate(
12730			{top: imgOffset.top, left: imgOffset.left, width: imgWidth, height: imgHeight },
12731			{complete: function() { clonedImage.remove();}
12732		});
12733
12734		// remove this listener
12735		$('body').off('click', shrinkImage);
12736	}
12737
12738	// click anywhere to remove
12739	$('body').on('click', shrinkImage);
12740
12741	return false;
12742};
12743
12744
12745// GLOBAL Video window
12746sofia.globals.videoWindow = null;
12747
12748$(function() {
12749
12750	// create window
12751	sofia.globals.videoWindow = new MovableWindow(640,360,'Video','video-player');
12752	sofia.globals.videoWindow.center();
12753	sofia.globals.videoWindow.hide();
12754
12755	sofia.globals.videoWindow.container.find('.close-button').on('click', function() {
12756
12757		// attempt to hide video
12758		var video = sofia.globals.videoWindow.container.find('video');
12759		if (video.length > 0) {
12760			video[0].pause();
12761		}
12762
12763		sofia.globals.videoWindow.body.html('');
12764	});
12765
12766});
12767
12768sofia.globals.mediaVideoClick = function(e) {
12769	e.preventDefault();
12770
12771	var link = $(this),
12772		url = link.attr('href'),
12773		title = link.attr('title');
12774
12775	sofia.globals.showVideo(url, title);
12776
12777	return false;
12778};
12779
12780sofia.globals.mediaVideoJfmClick = function(e) {
12781	e.preventDefault();
12782
12783	var link = $(this),
12784		//segmentNumber = link.attr('href'),
12785		segmentData = link.attr('data-filename'),
12786		title = link.attr('title'),
12787
12788		firstSection = $('.section:first'),
12789
12790		lang = firstSection.length > 0 ? firstSection.attr('data-lang3') : 'eng';
12791
12792
12793	JesusFilmMediaApi.getPlayer(lang, segmentData, function(iframeUrl) {
12794		sofia.globals.showIframe(iframeUrl, title);
12795	});
12796
12797	return false;
12798};
12799
12800sofia.globals.showVideo = function(videoUrl, title) {
12801
12802	sofia.globals.videoWindow.body.html('');
12803
12804	// add video
12805	var video = $('<video autoplay controls src="' + videoUrl + '" style="width:100%; height: auto;"></video>')
12806					.appendTo(sofia.globals.videoWindow.body);
12807
12808	// title?
12809	if (title) {
12810		sofia.globals.videoWindow.title.html(title);
12811	}
12812
12813	// show me!
12814	sofia.globals.videoWindow.show();
12815}
12816
12817
12818sofia.globals.showIframe = function(iframeUrl, title) {
12819
12820	sofia.globals.videoWindow.body.html('');
12821
12822	// add video
12823	var iframe = $('<iframe src="' + iframeUrl + '" allowfullscreen frameborder="0" width="' + sofia.globals.videoWindow.body.width() + '" height="' + sofia.globals.videoWindow.body.height() + '"></iframe>')
12824					.appendTo(sofia.globals.videoWindow.body);
12825
12826	// title?
12827	if (title) {
12828		sofia.globals.videoWindow.title.html(title);
12829	}
12830
12831	// show me!
12832	sofia.globals.videoWindow.show();
12833}
12834
12835
12836
12837
12838
12839var MediaLibrary = (function(){
12840
12841
12842	var
12843		currentMediaIndex = 0,
12844		mediaLibraries = null,
12845		dataIsLoaded = false;
12846
12847	function init() {
12848
12849		// load main info
12850		sofia.ajax({
12851			dataType: 'json',
12852			url: 'content/media/media.json',
12853			success: function(data) {
12854
12855				mediaLibraries = data.media;
12856
12857
12858			}, complete: function() {
12859				loadNextMediaLibrary();
12860			}
12861		});
12862
12863	}
12864
12865	function loadNextMediaLibrary() {
12866
12867		//console.log('loadNextMediaLibrary', currentMediaIndex, mediaLibraries.length);
12868
12869		if (currentMediaIndex < mediaLibraries.length) {
12870
12871			var mediaLibrary = mediaLibraries[currentMediaIndex];
12872			currentMediaIndex++;
12873
12874			sofia.ajax({
12875				dataType: 'json',
12876				url: 'content/media/' + mediaLibrary.folder + '/info.json',
12877				success: function(data) {
12878					mediaLibrary.data = data;
12879				},
12880				complete: function() {
12881					loadNextMediaLibrary();
12882				}
12883			});
12884		} else {
12885
12886			dataIsLoaded = true;
12887			sendMediaCallbacks();
12888		}
12889
12890	}
12891
12892
12893	var getMediaListCallbacks = [];
12894	function getMediaLibraries(callback) {
12895		getMediaListCallbacks.push(callback);
12896		sendMediaCallbacks();
12897	}
12898	function sendMediaCallbacks() {
12899
12900		if (!dataIsLoaded) {
12901			return;
12902		}
12903
12904		while (getMediaListCallbacks.length > 0) {
12905			var callback = getMediaListCallbacks.pop();
12906
12907			callback(mediaLibraries);
12908		}
12909	}
12910
12911	var module = {
12912		getMediaLibraries: getMediaLibraries,
12913		init: init
12914	};
12915
12916
12917	return module;
12918
12919})();
12920
12921sofia.initMethods.push(MediaLibrary.init);
12922
12923
12924
12925/*********
12926* ../app/js/ui/textchooser.js
12927**********/
12928sofia.config = $.extend(sofia.config, {
12929
12930	enableBibleSelectorTabs: false,
12931	bibleSelectorDefaultList: ['ENGNAS', 'ENGNIV']
12932
12933});
12934
12935
12936
12937/******************
12938TextChooser
12939*******************/
12940
12941var TextChooser = function() {
12942	// create me
12943	var
12944		// set by show() function
12945		container = null,
12946		text_type = null,
12947		target = null,
12948
12949		isFull = false,
12950		selectedTextInfo = null,
12951		textChooser = $('<div class="text-chooser nav-drop-list">' +
12952							'<span class="up-arrow"></span>' +
12953							'<span class="up-arrow-border"></span>' +
12954							'<div class="text-chooser-header">' +
12955								'<div class="text-chooser-selector">' +
12956									'<span class="text-chooser-default selected i18n" data-mode="default" data-i18n="[html]windows.bible.default"></span>' +
12957									'<span class="text-chooser-languages i18n" data-mode="languages" data-i18n="[html]windows.bible.languages"></span>' +
12958									'<span class="text-chooser-countries i18n" data-mode="countries" data-i18n="[html]windows.bible.countries"></span>' +
12959								'</div>' +
12960								'<input type="text" class="text-chooser-filter-text i18n" data-i18n="[placeholder]windows.bible.filter" />' +
12961								'<span class="close-button">Close</span>' +
12962							'</div>' +
12963							'<div class="text-chooser-main"></div>' +
12964						'</div>')
12965						.appendTo( $('body') )
12966						.hide(),
12967		header = textChooser.find('.text-chooser-header'),
12968		main = textChooser.find('.text-chooser-main'),
12969		listselector = textChooser.find('.text-chooser-selector'),
12970		defaultSelector = textChooser.find('.text-chooser-default'),
12971		languagesSelector = textChooser.find('.text-chooser-languages'),
12972		countriesSelector = textChooser.find('.text-chooser-countries'),
12973		filter = textChooser.find('.text-chooser-filter-text'),
12974		title = textChooser.find('.text-chooser-title'),
12975		closeBtn = textChooser.find('.close-button').hide(),
12976		allTextsVisible = false,
12977		hasDefaultTexts = false,
12978		recentlyUsedKey = 'texts-recently-used',
12979		recentlyUsed = AppSettings.getValue(recentlyUsedKey, {"recent":[]} ),
12980		list_data = null;
12981
12982	//textChooser.find('.i18n').i18n();
12983
12984	title.html("Texts");
12985
12986	closeBtn.on('click', hide);
12987
12988	if (sofia.config.enableBibleSelectorTabs && sofia.config.bibleSelectorDefaultList && sofia.config.bibleSelectorDefaultList.length > 0) {
12989
12990		listselector.on('click', 'span', function() {
12991			$(this)
12992				.addClass('selected')
12993				.siblings()
12994					.removeClass('selected');
12995
12996			filter
12997				.val('');
12998
12999			if (!Detection.hasTouch) {
13000				filter.focus();
13001			}
13002
13003			renderTexts(list_data);
13004		});
13005
13006
13007	} else {
13008		listselector.hide();
13009	}
13010
13011
13012	filter.on('keyup keypress', filterVersions);
13013
13014	filter.on('focus', function() {
13015		/*
13016		if (Detection.hasTouch) {
13017			filter.blur();
13018		}
13019		*/
13020	});
13021
13022	function filterVersions(e) {
13023
13024		// when the user presses return and there is only one version, attempt to go to that one
13025		if (e && e.which == 13) {
13026			var visibleRows = main.find('.text-chooser-row:visible, .text-chooser-row-divider:visible');
13027
13028			if (visibleRows.length == 1) {
13029
13030				visibleRows.click();
13031
13032				filter.val('');
13033				return;
13034			}
13035		}
13036
13037		var text = filter.val().toLowerCase();
13038
13039
13040
13041		if (text == '') {
13042			renderTexts(list_data);
13043			//updateRecentlyUsed();
13044		} else {
13045
13046			// filter by type
13047			var arrayOfTexts = list_data;
13048
13049			arrayOfTexts = arrayOfTexts.filter(function(t) {
13050
13051				if (text_type == 'audio') {
13052					var hasAudio = 	t.hasAudio ||
13053						typeof t.audioDirectory != 'undefined' ||
13054						(typeof t.fcbh_audio_ot != 'undefined' || typeof t.fcbh_audio_nt != 'undefined' ||
13055						 typeof t.fcbh_drama_ot != 'undefined' || typeof t.fcbh_drama_nt != 'undefined');
13056
13057					return hasAudio == true;
13058				}
13059
13060				var thisTextType = typeof t.type == 'undefined' ? 'bible' : t.type;
13061				return thisTextType == text_type;
13062			});
13063
13064			var html = [];
13065
13066			for (var i=0, il=arrayOfTexts.length; i<il; i++) {
13067				var textInfo = arrayOfTexts[i],
13068					hasMatch = 	textInfo.name.toLowerCase().indexOf(text) > -1 ||
13069								textInfo.abbr.toLowerCase().indexOf(text) > -1 ||
13070								(textInfo.langNameEnglish && textInfo.langName.toLowerCase().indexOf(text) > -1) ||
13071								(textInfo.langNameEnglish && textInfo.langNameEnglish.toLowerCase().indexOf(text) > -1);
13072
13073
13074				if (hasMatch) {
13075					html.push (
13076						createTextRow(textInfo, false, '')
13077					);
13078				}
13079
13080			}
13081
13082
13083			main.html('<table cellspacing="0">' + html.join('') + '</table>');
13084
13085		}
13086	}
13087
13088	// handle when user clicks on a text
13089	textChooser.on('click', '.text-chooser-row', function() {
13090		var row = $(this),
13091			textid = row.attr('data-id');
13092
13093		row.addClass('selected')
13094			.siblings()
13095			.removeClass('selected');
13096
13097
13098		storeRecentlyUsed(textid);
13099		updateRecentlyUsed();
13100
13101		hide();
13102
13103		TextLoader.getText(textid, function(data) {
13104
13105			selectedTextInfo = data;
13106
13107			//console.log('chooser:change:click', selectedTextInfo);
13108			ext.trigger('change', {type:'change', target: this, data: {textInfo: selectedTextInfo, target: target} });
13109
13110		});
13111
13112	});
13113
13114
13115	function storeRecentlyUsed(textInfo) {
13116
13117		if (text_type != 'bible') {
13118			return;
13119		}
13120
13121		var textid = (typeof textInfo == 'string') ? textInfo : textInfo.id;
13122
13123		// look for this version
13124		var existingVersions = recentlyUsed.recent.filter(function(t) {
13125			return t == textid;
13126		});
13127
13128		if (existingVersions.length == 0) {
13129
13130			// store recent text
13131			recentlyUsed.recent.unshift(textid);
13132
13133			// limit to 5
13134			while (recentlyUsed.recent.length > 5 ) {
13135				recentlyUsed.recent.pop();
13136			}
13137		}
13138
13139		//console.log('storeRecentlyUsed',recentlyUsed.recent.length);
13140
13141		// save
13142		AppSettings.setValue(recentlyUsedKey, recentlyUsed);
13143	}
13144
13145	function updateRecentlyUsed() {
13146
13147		if (list_data == null || text_type == null) {
13148			return;
13149		}
13150
13151		if (text_type != 'bible' || (getMode() != 'default' && getMode() != 'none')) {
13152			main.find('.text-chooser-recently-used').remove();
13153			return;
13154		}
13155
13156		// RECENTly Used
13157		if (recentlyUsed.recent.length > 0) {
13158
13159			var isDefaultText = false;
13160
13161			// find if this should be a priority text shown at the beginning
13162			//if (sofia.config.topTexts && sofia.config.topTexts.length > 0) {
13163			//	isDefaultText = true;
13164			//}
13165
13166			var recentlyUsedHtml =
13167					createHeaderRow(
13168						'',
13169						i18n.t('windows.bible.recentlyused'),
13170						'',
13171						'',
13172						'text-chooser-recently-used' + (isDefaultText ? ' is-default-text' : '')
13173					);
13174
13175			for (var i=0, il=recentlyUsed.recent.length; i<il; i++) {
13176				var textid = recentlyUsed.recent[i],
13177					textInfo = list_data.filter(function(ti) { return ti.id == textid; })[0];
13178
13179				if (textInfo) {
13180					recentlyUsedHtml +=
13181						createTextRow(textInfo, isDefaultText, 'text-chooser-recently-used' );
13182				}
13183			}
13184
13185			// remove existing
13186			main.find('.text-chooser-recently-used').remove();
13187
13188			// add update recent stuff
13189			var recentRow = $(recentlyUsedHtml);
13190			main.find('table tbody').prepend(recentRow);
13191		}
13192
13193	}
13194
13195
13196	function checkIsDefaultText(id) {
13197
13198		var isDefaultText = false,
13199			parts = id.split(':'),
13200			textid = parts.length > 1 ? parts[1] : parts[0];
13201
13202		// find if this should be a priority text shown at the beginning
13203		if (sofia.config.bibleSelectorDefaultList && sofia.config.bibleSelectorDefaultList.length > 0) {
13204
13205			for (var t=0, tl=sofia.config.bibleSelectorDefaultList.length; t<tl; t++) {
13206				if (textid == sofia.config.bibleSelectorDefaultList[t]) {
13207					isDefaultText = true;
13208					break;
13209				}
13210			}
13211
13212		} else {
13213			isDefaultText = false;
13214		}
13215
13216
13217		return isDefaultText;
13218
13219	}
13220
13221	function getMode() {
13222		if (sofia.config.enableBibleSelectorTabs) {
13223			var selectedMode = listselector.find('.selected'),
13224				mode = selectedMode.length > 0 ? selectedMode.data('mode') : 'none';
13225
13226			return mode;
13227		} else {
13228			return 'none';
13229		}
13230	}
13231
13232	function renderTexts(data) {
13233
13234		if (data == null || typeof data == 'undefined') {
13235			return;
13236		}
13237
13238		// render all the rows
13239		var html = [],
13240			arrayOfTexts = data,
13241			mode = getMode();
13242
13243		if (mode == 'languages' || mode == 'default' || mode == 'none') {
13244
13245			// filter by type
13246			arrayOfTexts = arrayOfTexts.filter(function(t) {
13247
13248				if (text_type == 'audio') {
13249					var hasAudio = t.hasAudio ||
13250						typeof t.audioDirectory != 'undefined' ||
13251						(typeof t.fcbh_audio_ot != 'undefined' || typeof t.fcbh_audio_nt != 'undefined' ||
13252						 typeof t.fcbh_drama_ot != 'undefined' || typeof t.fcbh_drama_nt != 'undefined');
13253
13254					return hasAudio === true;
13255				} else {
13256					if (t.hasText === false) {
13257						return false;
13258					}
13259				}
13260
13261				var thisTextType = typeof t.type == 'undefined' ? 'bible' : t.type;
13262
13263				return thisTextType == text_type;
13264			});
13265
13266			// find languages
13267			var languages = [];
13268			for (var index in arrayOfTexts) {
13269				var text = arrayOfTexts[index];
13270
13271				/* ORDER BY  English Name */
13272				var langKey = text.langNameEnglish;
13273				if (langKey == undefined || langKey == '') {
13274					langKey = text.langName;
13275				}
13276
13277				if (languages.indexOf(langKey) == -1) {
13278					languages.push( langKey );
13279				}
13280			}
13281
13282			// PINNED
13283			var pinnedLanguages = [];
13284			if (sofia.config.pinnedLanguages && sofia.config.pinnedLanguages.length && sofia.config.pinnedLanguages.length > 0) {
13285				console.log('finding pins');
13286
13287				for (var i=0, il  = sofia.config.pinnedLanguages.length; i<il; i++) {
13288					var pinnedLanguage = sofia.config.pinnedLanguages[i];
13289
13290					var pinnedIndex = languages.indexOf(pinnedLanguage);
13291					if (pinnedIndex > -1) {
13292
13293						if (mode == 'default' || mode == 'none') {
13294							// pull it out
13295							languages.splice(pinnedIndex, 1);
13296						}
13297
13298						// store for later
13299						pinnedLanguages.push(pinnedLanguage);
13300					}
13301				}
13302			}
13303
13304			// sort
13305			languages.sort();
13306
13307			// put it back in
13308			if (pinnedLanguages.length > 0) {
13309				languages.splice.apply(languages, [0, 0].concat(pinnedLanguages));
13310			}
13311
13312
13313			for (var index in languages) {
13314
13315				// get all the ones with this language
13316				var langName = languages[index],
13317					textsInLang = arrayOfTexts.filter(function(t) { return (t.langName == langName || t.langNameEnglish == langName) }),
13318					hasDefaultText = false,
13319					langHtml = [];
13320
13321				// sort the texts by name
13322				textsInLang = textsInLang.sort(function (a, b) {
13323					if (a.name == b.name) {
13324						return 0;
13325					} else if (a.name > b.name) {
13326						return 1;
13327					} else if (a.name < b.name) {
13328						return -1;
13329					}
13330				});
13331
13332				// create HTML for the texts
13333				for (var textIndex in textsInLang) {
13334					var text = textsInLang[textIndex],
13335						isDefaultText = checkIsDefaultText(text.id);
13336
13337					if (text_type == 'bible' ) {
13338						if (mode == 'none' || mode == 'languages' || (isDefaultText && mode == 'default')) {
13339							langHtml.push(
13340								createTextRow(
13341										text,
13342										isDefaultText,
13343										mode == 'languages' ? 'collapsed' : ''
13344								)
13345							);
13346						}
13347
13348						if (!hasDefaultText && isDefaultText) {
13349							hasDefaultText = true;
13350						}
13351					} else if (text_type == 'deafbible' ) {
13352						langHtml.push(
13353							createImageRow(
13354									text,
13355									mode == 'languages' ? 'collapsed' : ''
13356							)
13357						);
13358
13359					}
13360				}
13361
13362				if (text_type == 'bible' && (mode == 'none' || mode == 'languages' || (hasDefaultText && mode == 'default')) ) {
13363
13364					var languageDisplayTitle = '';
13365
13366					// vernacular first
13367					/*
13368					languageDisplayTitle = textsInLang[0].langName +
13369									( textsInLang[0].langName != textsInLang[0].langNameEnglish && typeof textsInLang[0].langNameEnglish != 'undefined' ? ' (' + textsInLang[0].langNameEnglish + ')' : '');
13370					*/
13371
13372					// english first
13373					var langName = textsInLang[0].langName,
13374						langNameEnglish = textsInLang[0].langNameEnglish;
13375
13376					if (langNameEnglish != '' && langNameEnglish != undefined) {
13377						languageDisplayTitle = 	langNameEnglish + (langName != langNameEnglish ? ' (' + langName + ')' : '');
13378					} else {
13379						languageDisplayTitle = langName;
13380					}
13381
13382					html.push(
13383						createHeaderRow(
13384							'',
13385							languageDisplayTitle,
13386							'',
13387							'',
13388							mode == 'languages' ? 'collapsible-language collapsed' : ''
13389						)
13390					);
13391				}
13392
13393				html.push(langHtml.join(''));
13394
13395			}
13396
13397
13398			main.html('<table cellspacing="0" class="' + (mode == 'languages' ? 'collapsible' : '') + '">' + html.join('') + '</table>');
13399
13400
13401			updateSelectedText();
13402
13403			// do this after the 'selected' so it's not in the recently used
13404			updateRecentlyUsed();
13405
13406
13407		} else if (mode == "countries") {
13408
13409			textChooser.removeClass('show-more');
13410
13411			for (var i=0, il=sofia.countries.length; i<il; i++) {
13412
13413				var countryInfo = sofia.countries[i],
13414					textsInCountry = arrayOfTexts.filter(function(t) {
13415						return typeof t.countries != 'undefined' && t.countries.indexOf(countryInfo["alpha-2"]) > -1;
13416					});
13417
13418
13419				if (textsInCountry.length > 0) {
13420					html.push(
13421						createHeaderRow(countryInfo["alpha-3"],
13422							countryInfo.name,
13423							'',
13424							'<img src="' + sofia.config.baseContentUrl + 'content/countries/' + countryInfo["alpha-2"].toLowerCase() + '.png" alt="' + countryInfo["alpha-2"] + '" />',
13425							'country collapsed')
13426
13427					);
13428
13429					// order by languages?
13430					var languagesInCountry = [];
13431
13432					for (var index in textsInCountry) {
13433						var text = textsInCountry[index];
13434
13435						/* ORDER BY  English Name */
13436						var langKey = text.langNameEnglish;
13437						if (langKey == undefined || langKey == '') {
13438							langKey = text.langName;
13439						}
13440
13441						if (languagesInCountry.indexOf(langKey) == -1) {
13442							languagesInCountry.push( langKey );
13443						}
13444					}
13445
13446					languagesInCountry.sort();
13447
13448					for (var index in languagesInCountry) {
13449
13450						// get all the ones with this language
13451						var langName = languagesInCountry[index],
13452							textsInLang = textsInCountry.filter(function(t) { return (t.langName == langName || t.langNameEnglish == langName) }),
13453							hasDefaultText = false,
13454							langHtml = [];
13455
13456
13457						// LANGUAGE
13458						var languageDisplayTitle = '';
13459
13460						// english first
13461						var langName = textsInLang[0].langName,
13462							langNameEnglish = textsInLang[0].langNameEnglish;
13463
13464						if (langNameEnglish != '' && langNameEnglish != undefined) {
13465							languageDisplayTitle = 	langNameEnglish + (langName != langNameEnglish ? ' (' + langName + ')' : '');
13466						} else {
13467							languageDisplayTitle = langName;
13468						}
13469
13470						html.push(
13471							createDividerRow(
13472								languageDisplayTitle,
13473								'collapsed'
13474							)
13475						);
13476
13477
13478						// sort the texts by name
13479						textsInLang = textsInLang.sort(function (a, b) {
13480							if (a.name == b.name) {
13481								return 0;
13482							} else if (a.name > b.name) {
13483								return 1;
13484							} else if (a.name < b.name) {
13485								return -1;
13486							}
13487						});
13488
13489
13490						// create HTML for the texts
13491						for (var textIndex in textsInLang) {
13492							var text = textsInLang[textIndex];
13493
13494							langHtml.push(
13495								createTextRow(
13496										text,
13497										false,
13498										'collapsed'
13499								)
13500							);
13501
13502							if (!hasDefaultText && isDefaultText) {
13503								hasDefaultText = true;
13504							}
13505						}
13506
13507
13508
13509
13510
13511						html.push(langHtml.join(''));
13512
13513					}
13514
13515
13516
13517					/* simple list in country */
13518					/*
13519					for (var textIndex in textsInCountry) {
13520						var text = textsInCountry[textIndex];
13521
13522						html.push(
13523							createTextRow(text, isDefaultText, 'collapsed')
13524						);
13525
13526					}
13527					*/
13528
13529				}
13530
13531			}
13532
13533			main.html('<table cellspacing="0" class="collapsible">' + html.join('') + '</table>');
13534
13535		}
13536
13537	}
13538
13539	main.on('click', '.collapsible .text-chooser-row-header', function() {
13540
13541		var header = $(this),
13542			children = header.nextUntil('.text-chooser-row-header');
13543
13544		if (header.hasClass('collapsed')) {
13545
13546			header.removeClass('collapsed');
13547			children.removeClass('collapsed');
13548
13549		} else {
13550
13551			header.addClass('collapsed');
13552			children.addClass('collapsed');
13553
13554		}
13555
13556
13557	});
13558
13559	function updateSelectedText() {
13560
13561		// find the selected text
13562		if (selectedTextInfo != null) {
13563
13564			textChooser
13565					.find('table .selected')
13566					.removeClass('selected');
13567
13568			textChooser
13569					.find('[data-id="' + selectedTextInfo.id + '"]')
13570					.addClass('selected');
13571		}
13572
13573	}
13574
13575
13576	function createTextRow(text, isDefaultText, className) {
13577		var hasAudio = 	text.hasAudio ||
13578						typeof text.audioDirectory != 'undefined' ||
13579						(typeof text.fcbh_audio_ot != 'undefined' || typeof text.fcbh_audio_nt != 'undefined' ||
13580						 typeof text.fcbh_drama_ot != 'undefined' || typeof text.fcbh_drama_nt != 'undefined'),
13581			hasLemma = text.hasLemma,
13582
13583			providerName = (typeof text.providerName != 'undefined' && text.providerName != 'local') ? text.providerName : '',
13584			providerFullName = sofia.textproviders[text.providerName] && sofia.textproviders[text.providerName].fullName ? sofia.textproviders[text.providerName].fullName : '',
13585
13586			colspan = 4 - (hasAudio ? 1 : 0) - (hasLemma ? 1 : 0) - (providerName != '' ? 1 : 0);
13587
13588		var html = '<tr class="text-chooser-row' + (isDefaultText ? ' is-default-text' : '') + (className != '' ? ' ' + className : '') + '" data-id="' + text.id + '" data-lang-name="' + text.langName + '" data-lang-name-english="' + text.langNameEnglish + '">' +
13589					'<td class="text-chooser-abbr">' + text.abbr + '</td>' +
13590					'<td class="text-chooser-name" ' + (colspan > 1 ? ' colspan="' + colspan + '"' : '') + '>
13590' +
13591						'<span>' + text.name + '</span>' +
13592					'</td>' +
13593
13594					(hasLemma === true ? '<td class="text-chooser-lemma"><span title="' + i18n.t('windows.bible.lemma') + '" data-i18n="[title]windows.bible.lemma"></span></td>' : '') +
13595					(hasAudio === true ? '<td class="text-chooser-audio"><span title="' + i18n.t('windows.bible.audio') + '" data-i18n="[title]windows.bible.audio"></span></td>' : '') +
13596					(providerName != '' ? '<td class="text-chooser-provider-' + providerName + '"><span title="' + providerFullName + '"></span></td>' : '') +
13597				'</tr>';
13598
13599		return html;
13600	}
13601
13602	function createImageRow(text, className) {
13603		var
13604			providerName = (typeof text.providerName != 'undefined' && text.providerName != 'local') ? text.providerName : '',
13605			providerFullName = sofia.textproviders[text.providerName] && sofia.textproviders[text.providerName].fullName ? sofia.textproviders[text.providerName].fullName : '',
13606			imageUrl = 'content/texts/' + text.id + '/' + text.id + '.png';
13607
13608
13609		var html = '<tr class="text-chooser-row' + (className != '' ? ' ' + className : '') + '" data-id="' + text.id + '" data-lang-name="' + text.langName + '" data-lang-name-english="' + text.langNameEnglish + '">' +
13610					'<td class="text-chooser-image">' +
13611						'<img src="' + imageUrl + '" />' +
13612					'</td>' +
13613					'<td class="text-chooser-name" >' +
13614						'<span>' + text.name + '</span>' +
13615					'</td>' +
13616				'</tr>';
13617
13618		return html;
13619	}
13620
13621
13622
13623	function createHeaderRow(id, name, englishName, additionalHtml, className) {
13624		var html = '<tr class="text-chooser-row-header' + (className != '' ? ' ' + className : '') + '" data-id="' + id + '"><td colspan="5">' +
13625					'<span class="name">' + name + '</span>' +
13626					additionalHtml +
13627					'</td></tr>';
13628
13629
13630		return html;
13631	}
13632
13633	function createDividerRow(name, className) {
13634		var html = '<tr class="text-chooser-row-divider ' + (className != '' ? ' ' + className : '') + '">' +
13635					//'<td>&nbsp;</td>' +
13636					//'<td colspan="4">' +
13637					'<td colspan="5">' +
13638						'<span class="name">' + name + '</span>' +
13639					'</td>' +
13640					'</tr>';
13641
13642
13643		return html;
13644	}
13645
13646	function toggle() {
13647
13648		if (textChooser.is(':visible') ) {
13649			hide();
13650		} else {
13651			show();
13652		}
13653
13654	}
13655
13656	function setTarget(_container, _target, _text_type) {
13657
13658		var needsToRerender = _text_type != text_type;;
13659
13660		container = _container;
13661		target = _target;
13662		text_type = _text_type;
13663
13664		ext.setClickTargets([_target, textChooser]);
13665
13666		if (needsToRerender) {
13667			renderTexts(list_data);
13668
13669			if (text_type == 'bible' && sofia.config.enableBibleSelectorTabs && sofia.config.bibleSelectorDefaultList && sofia.config.bibleSelectorDefaultList.length > 0) {
13670				listselector
13671					.find('.text-chooser-default')
13672					.addClass('selected')
13673						.siblings()
13674							.removeClass('selected');
13675
13676				listselector.show();
13677			} else {
13678
13679				listselector
13680					.find('span')
13681					.removeClass('selected');
13682
13683				listselector.hide();
13684			}
13685		}
13686
13687	}
13688
13689	function getTarget() {
13690		return target;
13691	}
13692
13693	function show() {
13694		//$('.nav-drop-list').hide();
13695
13696		//console.log('textchooser.show');
13697
13698		size();
13699		textChooser.show();
13700		ext.onshow();
13701
13702
13703		if (!list_data) {
13704			main.addClass('loading-indicator');//.html('Loading');
13705
13706			TextLoader.loadTexts(function(data) {
13707				list_data = data;
13708
13709				// check for countries
13710				if (sofia.config.enableCountrySelector) {
13711					var hasCountries = list_data.filter(function(c) { return typeof c.countries != 'undefined' && c.countries.length > 0; }).length > 0;
13712					if (!hasCountries) {
13713						listselector.hide();
13714					}
13715				}
13716
13717				//console.log('TextChoose.show(), load render');
13718				main.removeClass('loading-indicator');
13719				renderTexts(list_data);
13720
13721			});
13722		} else {
13723			main.removeClass('loading-indicator');
13724		}
13725
13726		size();
13727
13728		if (filter.val() != '') {
13729			filter.val('');
13730			filterVersions();
13731		}
13732
13733		if (!Detection.hasTouch) {
13734			filter.focus();
13735		}
13736
13737	}
13738
13739	function hide() {
13740		textChooser.hide();
13741		ext.onhide();
13742	}
13743
13744	function setTextInfo(text) {
13745		selectedTextInfo = text;
13746
13747		storeRecentlyUsed(selectedTextInfo);
13748		updateRecentlyUsed();
13749
13750		updateSelectedText();
13751	}
13752
13753	function getTextInfo() {
13754		return selectedTextInfo;
13755	}
13756
13757	function size(width,height) {
13758
13759		if (target == null || container == null) {
13760			return;
13761		}
13762
13763		//clearOffClickTimer();
13764
13765		if (isFull) {
13766
13767			// cover the container area
13768			if (!(width && height)) {
13769				width = container.width();
13770				height = container.height();
13771			}
13772
13773			textChooser
13774				.width(width)
13775				.height(height)
13776				.css({top: container.offset().top,left: container.offset().left});
13777
13778			main
13779				.width(width)
13780				.height(height - header.outerHeight());
13781
13782		} else {
13783
13784			// reasonable size!
13785			var targetOffset = target.offset(),
13786				targetOuterHeight = target.outerHeight(),
13787				win = $(window),
13788				selectorWidth = textChooser.outerWidth(),
13789
13790				top = targetOffset.top + targetOuterHeight + 10,
13791				left = targetOffset.left,
13792				winHeight = win.height() - 40,
13793				winWidth = win.width(),
13794				maxHeight = winHeight - top;
13795
13796			if (winWidth < left + selectorWidth) {
13797				left = winWidth - selectorWidth;
13798				if (left < 0) {
13799					left = 0;
13800				}
13801			}
13802
13803
13804			textChooser
13805				.outerHeight(maxHeight)
13806				.css({top: top,left: left});
13807
13808			main
13809				.outerHeight(maxHeight - header.outerHeight());
13810
13811
13812			// UP ARROW
13813			var upArrowLeft = targetOffset.left - left + 20;
13814
13815			textChooser.find('.up-arrow, .up-arrow-border')
13816				.css({left: upArrowLeft});
13817
13818		}
13819
13820	}
13821
13822	function isVisible() {
13823		return textChooser.is(':visible');
13824	}
13825
13826	function node() {
13827		return textChooser;
13828	}
13829
13830	function close() {
13831		//textChooser.remove();
13832		//ext.clearListeners();
13833	}
13834
13835	var ext = {
13836		setTarget: setTarget,
13837		getTarget: getTarget,
13838		show: show,
13839		hide: hide,
13840		toggle: toggle,
13841		isVisible: isVisible,
13842		node: node,
13843		getTextInfo: getTextInfo,
13844		setTextInfo: setTextInfo,
13845		renderTexts: renderTexts,
13846		size: size,
13847		close: close
13848	};
13849	ext = $.extend(true, ext, EventEmitter);
13850	ext = $.extend(true, ext, ClickOff);
13851	ext.clickoffid = 'version picker';
13852	ext.on('offclick', function() {
13853		hide();
13854	});
13855
13856	return ext;
13857
13858};
13859
13860sofia.initMethods.push(function() {
13861	sofia.globalTextChooser = new TextChooser();
13862});
13863
13864
13865
13866/*********
13867* ../app/js/ui/textnavigator.js
13868**********/
13869/******************
13870Text Navigator
13871*******************/
13872
13873var TextNavigator = function() {
13874	// create me
13875
13876	var
13877		container = null,
13878		target = null,
13879		isFull = false,
13880		changer = $('<div class="text-navigator nav-drop-list">' +
13881							'<span class="up-arrow"></span>' +
13882							'<span class="up-arrow-border"></span>' +
13883						'<div class="text-navigator-header">' +
13884							'<span class="text-navigator-title">&nbsp;</span>' +
13885							'<span class="text-navigator-back">Back</span>' +
13886							'<span class="text-navigator-close">Close</span>' +
13887						'</div>' +
13888						'<div class="text-navigator-divisions"></div>' +
13889						//'<div class="text-navigator-sections"></div>' +
13890 					'</div>'
13891					)
13892					//.css({height: $(window).height(), width: $(window).width()})
13893					.appendTo( $('body') )
13894					.hide(),
13895		header = changer.find('.text-navigator-header'),
13896		title = changer.find('.text-navigator-title'),
13897		back = changer.find('.text-navigator-back').hide(),
13898		closeBtn = changer.find('.text-navigator-close').hide(),
13899		fullname = $('<div class="text-navigator-fullname"></div>').appendTo( $('body') ).hide(),
13900		textInfo = null,
13901
13902		fullBookMode = false;
13903
13904	if (!Detection.hasTouch) {
13905		changer.on('mouseover', '.text-navigator-division', function() {
13906
13907			if (!fullBookMode) {
13908
13909				var node = $(this),
13910					name = node.attr('data-name');
13911
13912				fullname
13913					.html(name)
13914					.css({
13915						backgroundColor: node.css('backgroundColor'),
13916						top: (parseInt(node.offset().top, 10)-1) + 'px',
13917						left: node.offset().left,
13918					})
13919					.show();
13920
13921				fullname.lastNode = node;
13922			}
13923
13924		})
13925		.on('mouseout', '.text-navigator-division', function() {
13926			fullname.hide();
13927		});
13928	}
13929
13930	// pointer-events: none for modern browsers;
13931	fullname.on('click', function() {
13932		if (fullname.lastNode) {
13933			fullname.lastNode.trigger('click');
13934			fullname.hide();
13935		}
13936
13937	});
13938
13939	closeBtn.on('click', function() {
13940		hide();
13941	});
13942
13943	function hide() {
13944		changer.hide();
13945		fullname.hide();
13946		ext.onhide();
13947	}
13948
13949	function toggle() {
13950
13951		if (changer.is(':visible')) {
13952			hide();
13953		} else {
13954			show();
13955		}
13956
13957	}
13958
13959	function show() {
13960
13961		//$('.nav-drop-list').hide();
13962
13963		if (textInfo == null) {
13964			console.warn('chooser has not textInfo!');
13965			return;
13966		}
13967
13968		title.html( textInfo.name );
13969
13970		// reset width
13971		size();
13972		changer.show();
13973		ext.onshow();
13974		size();
13975
13976		// remove all selections
13977		changer.find('.selected').removeClass('selected');
13978		changer.find('.text-navigator-divisions').scrollTop(0);
13979
13980		switch (textInfo.type.toLowerCase()) {
13981			case 'bible':
13982			case 'deafbible':
13983			case 'videobible':
13984			case 'commentary':
13985
13986				var textInputValue = target.val(),
13987					biblereference = new bible.Reference(textInputValue),
13988					fragmentid = (biblereference) ? biblereference.toSection() : null;
13989
13990				renderDivisions();
13991				changer.find('.text-navigator-divisions').show().attr('dir', textInfo.dir).attr('lang', textInfo.lang);
13992				//changer.find('.text-navigator-sections').hide();
13993
13994				if (fragmentid) {
13995					var parts = fragmentid.split('_'),
13996						sectionid = parts[0],
13997						divisionid = sectionid.substring(0,2),
13998						chapter = sectionid.substring(2);
13999
14000					var divisionNode = changer.find('.divisionid-' + divisionid).addClass('selected');
14001					// scroll to it
14002
14003					if (divisionNode.length > 0) {
14004						var offset = divisionNode.position();
14005
14006						changer.find('.text-navigator-divisions').scrollTop(offset.top-40);
14007
14008						renderSections(false);
14009
14010						divisionNode.find('.section-' + sectionid).addClass('selected');
14011					}
14012
14013
14014
14015				}
14016
14017				break;
14018			case 'book':
14019				renderSections();
14020				changer.find('.text-navigator-divisions').hide();
14021				changer.find('.text-navigator-sections').show().attr('dir', textInfo.dir).attr('lang', textInfo.lang);
14022				break;
14023
14024		}
14025	}
14026
14027	function getBookSectionClass(bookid) {
14028		return bible.BOOK_DATA[bookid].section;
14029	}
14030
14031	// divisions = Bible books
14032	function renderDivisions() {
14033		//console.log('renderDivisions', textInfo);
14034
14035		// render books
14036		var html = [];
14037
14038		var hasPrintedOt = false,
14039			hasPrintedNt = false,
14040			hasPrintedAp = false;
14041
14042		fullBookMode = !(textInfo.divisionAbbreviations || textInfo.lang == 'eng') ;
14043		fullBookMode = true;
14044
14045		//console.log(fullBookMode, textInfo.divisionAbbreviations , textInfo.divabbr);
14046
14047		if (fullBookMode) {
14048			changer.find('.text-navigator-divisions').addClass('text-navigator-divisions-full');
14049		} else {
14050			changer.find('.text-navigator-divisions').removeClass('text-navigator-divisions-full');
14051		}
14052
14053
14054		for (var i=0, il= textInfo.divisions.length ; i<il; i++) {
14055
14056			var divisionid = textInfo.divisions[i],
14057				divisionName = (textInfo.divisionNames) ?
14058									textInfo.divisionNames[i] : null,
14059				divisionAbbr = (textInfo.divisionAbbreviations) ?
14060									textInfo.divisionAbbreviations[i] : null,
14061				displayName = fullBookMode ? divisionName :
14062											divisionAbbr != null ?
14063												divisionAbbr.replace(/\s/i,'').substring(0,3) :
14064												divisionName.replace(/\s/i,'').substring(0,3),
14065				book = bible.BOOK_DATA[divisionid];
14066
14067			if (typeof book == 'undefined') {
14068				continue;
14069			}
14070
14071			if (bible.OT_BOOKS.indexOf(divisionid) > -1 && !hasPrintedOt) {
14072				html.push('<div class="text-navigator-division-header">' + i18n.t('windows.bible.ot') + '</div>');
14073				hasPrintedOt = true;
14074			}
14075			if (bible.NT_BOOKS.indexOf(divisionid) > -1 && !hasPrintedNt) {
14076				html.push('<div class="text-navigator-division-header">' + i18n.t('windows.bible.nt') + '</div>');
14077				hasPrintedNt = true;
14078			}
14079			/*
14080			if (bible.AP_BOOKS.indexOf(divisionid) > -1 && !hasPrintedAp) {
14081				html.push('<div class="text-navigator-division-header">' + i18n.t('windows.bible.dc') + '</div>');
14082				hasPrintedAp = true;
14083			}
14084			*/
14085
14086
14087			/*
14088			var num_of_chapters = 0;
14089			for (var j=0, jl=textInfo.sections.length; j<jl; j++) {
14090				if (textInfo.sections[j].substring(0,2) == divisionid) {
14091					num_of_chapters++;
14092				}
14093			}
14094			html.push('<div class="text-navigator-division divisionid-' + divisionid + ' division-section-' + getBookSectionClass(divisionid) + '" data-id="' + divisionid + '" data-chapters="' + num_of_chapters + '" data-name="' + divisionName + '"><span>
14094' + displayName + '</span></div>');
14095			*/
14096
14097			var chapters = textInfo.sections.filter(function(c) {
14098				return c.substring(0,2) == divisionid;
14099			});
14100
14101			html.push('<div class="text-navigator-division divisionid-' + divisionid + ' division-section-' + getBookSectionClass(divisionid) + '" data-id="' + divisionid + '" data-chapters="' + chapters.join(',') + '" data-name="' + divisionName + '"><span>' + displayName + '</span></div>');
14102		} //
14103
14104		changer.find('.text-navigator-divisions').html(html).show();
14105		changer.find('.text-navigator-sections').remove();
14106
14107	}
14108
14109
14110	// click a division (Bible book)
14111	changer.on('click', '.text-navigator-division', function() {
14112
14113		var divisionNode = $(this);
14114
14115		if (divisionNode.hasClass('selected')) {
14116			divisionNode.find('.text-navigator-sections').slideUp({complete: function() {
14117				divisionNode.removeClass('selected');
14118			}});
14119
14120			return;
14121		}
14122
14123		divisionNode
14124			.addClass('selected')
14125			.siblings()
14126				.removeClass('selected');
14127
14128		fullname.hide();
14129
14130		// hide previous book's chapter
14131		var divisions = changer.find('.text-navigator-divisions'),
14132			positionBefore = divisionNode.position(),
14133			scrollTopBefore = divisions.scrollTop();
14134
14135		// close the existing set of sections
14136		changer.find('.text-navigator-sections').remove();
14137
14138		var positionAfter = divisionNode.position();
14139
14140		if (positionBefore.top > positionAfter.top) {
14141			var newScrollTop = scrollTopBefore - (positionBefore.top - positionAfter.top);// + header.outerHeight(true);
14142
14143			divisions.scrollTop(newScrollTop);
14144		}
14145
14146		renderSections(true);
14147	});
14148
14149
14150	// sections = bible chapters
14151	function renderSections(animated) {
14152
14153		console.log('renderSections', textInfo.type);
14154
14155		var html = [];
14156
14157		switch (textInfo.type.toLowerCase()) {
14158			case 'bible':
14159			case 'deafbible':
14160			case 'videobible':
14161
14162			case 'commentary':
14163				// print out chapters
14164				var selected_division = changer.find('.text-navigator-division.selected'),
14165					isLast = selected_division.next().length == 0,
14166					divisionid = selected_division.attr('data-id'),
14167					divisionname = selected_division.attr('data-name'),
14168					//num_of_chapters = parseInt(selected_division.attr('data-chapters'), 10),
14169					chapters = selected_division.attr('data-chapters').split(','),
14170					numbers = typeof textInfo.numbers != 'undefined' ? textInfo.numbers : bible.numbers.default;
14171
14172				title.html( divisionname );
14173
14174				console.log('chapters',chapters);
14175
14176				for (var chapter=0; chapter<chapters.length; chapter++) {
14177					var dbsChapterCode = chapters[chapter],
14178						chapterNumber = parseInt(dbsChapterCode.substring(2));
14179
14180					html.push('<span class="text-navigator-section section-' + dbsChapterCode + '" data-id="' + dbsChapterCode + '">' + numbers[chapterNumber].toString() + '</span>');
14181				}
14182
14183				var sectionNodes = $('<div class="text-navigator-sections" style="display:none;">' + html.join('') + '</div>');
14184
14185				selected_division.find('span').after( sectionNodes );
14186
14187
14188				if (animated === true && !isLast) {
14189					sectionNodes.slideDown();
14190				} else {
14191					sectionNodes.show();
14192
14193					if (isLast) {
14194						var divisions = changer.find('.text-navigator-divisions');
14195						divisions.scrollTop( divisions.scrollTop() + 500 );
14196					}
14197				}
14198
14199				break;
14200			case 'book':
14201
14202				//console.log('text', textInfo.sections);
14203
14204				// flat list of sections (i.e. pages)
14205				for (var i=0, il= textInfo.sections.length ; i<il; i++) {
14206					var sectionid = textInfo.sections[i];
14207					html.push('<span class="text-navigator-section" data-id="' + sectionid + '">' + sectionid.replace('P','') + '</span>');
14208				}
14209
14210
14211				break;
14212		}
14213
14214/*
14215		changer.find('.text-navigator-divisions').hide();
14216		changer.find('.text-navigator-sections')
14217				.html(html)
14218				.attr('dir', textInfo.dir)
14219				.attr('lang', textInfo.lang)
14220				.show();
14221*/
14222	}
14223
14224
14225
14226	// click a chapter
14227	changer.on('click', '.text-navigator-section', function() {
14228		var sectionid = $(this)
14229							.addClass('selected')
14230							.attr('data-id');
14231
14232		console.log('navigator selected', sectionid);
14233		ext.trigger('change', {type:'change', target: this, data: {sectionid: sectionid, target: target}});
14234
14235		//navigation_changed_callback(sectionid);
14236
14237		changer.hide();
14238	});
14239
14240	function size(width,height) {
14241
14242		if (isFull) {
14243
14244			if (!(width && height)) {
14245				width = container.width();
14246				height = container.height();
14247			}
14248
14249			changer
14250				.width(width)
14251				.height(height)
14252				.css({top: container.offset().top,left: container.offset().left});
14253		} else {
14254
14255			if (target == null) {
14256				return;
14257			}
14258
14259			var
14260				targetOffset = target.offset(),
14261				targetOuterHeight = target.outerHeight(),
14262				top = targetOffset.top + targetOuterHeight + 10,
14263				left = targetOffset.left,
14264				win = $(window),
14265				changerWidth = changer.outerWidth(),
14266				winHeight = win.height() - 40,
14267				winWidth = win.width(),
14268				maxHeight = winHeight - top;
14269
14270			if (winWidth < left + changerWidth) {
14271				left = winWidth - changerWidth;
14272				if (left < 0) {
14273					left = 0;
14274				}
14275			}
14276
14277			changer
14278				.outerHeight(maxHeight)
14279				.css({top: top, left: left});
14280
14281			var upArrowLeft = targetOffset.left - left + 20;
14282
14283			changer.find('.up-arrow, .up-arrow-border')
14284				.css({left: upArrowLeft});
14285
14286			changer.find('.text-navigator-divisions, .text-navigator-sections')
14287				.outerHeight(maxHeight - header.outerHeight());
14288
14289		}
14290	}
14291
14292	function setTextInfo(value) {
14293		textInfo = value;
14294
14295		if (textInfo.title) {
14296			changer.find('.text-navigator-header').html( textInfo.title );
14297		}
14298
14299		// set names
14300		if (textInfo.divisionNames) {
14301			bible.addNames(textInfo.lang, textInfo.divisions, textInfo.divisionNames);
14302		}
14303	}
14304
14305	function isVisible() {
14306		return changer.is(':visible');
14307	}
14308
14309	function node() {
14310		return changer;
14311	}
14312
14313	function close() {
14314		//ext.clearListeners();
14315
14316		//changer.remove();
14317	}
14318
14319	function setTarget(_container, _target) {
14320
14321		container = _container;
14322		target = _target;
14323
14324		ext.setClickTargets([_target, changer]);
14325	}
14326
14327	function getTarget() {
14328		return target;
14329	}
14330
14331	// this is the return object!
14332	var ext = {
14333		setTarget: setTarget,
14334		getTarget: getTarget,
14335		show: show,
14336		toggle: toggle,
14337		hide: hide,
14338		isVisible: isVisible,
14339		node: node,
14340		setTextInfo: setTextInfo,
14341		size: size,
14342		close:close
14343	}
14344
14345	ext = $.extend(true, ext, EventEmitter);
14346	ext = $.extend(true, ext, ClickOff);
14347	ext.clickoffid = 'book/chapter picker';
14348	ext.on('offclick', function() {
14349		hide();
14350	});
14351
14352	return ext;
14353
14354};
14355
14356sofia.initMethods.push(function() {
14357	sofia.globalTextNavigator = new TextNavigator();
14358});
14359
14360
14361
14362/*********
14363* ../app/js/ui/infowindow.js
14364**********/
14365var InfoWindow = function(id) {
14366	var container = $(	'<div class="info-window"' + (id ? ' id="' + id + '"' : '') + '>'+
14367							'<span class="close-button"></span>' +
14368							'<div class="info-body"></div>' +
14369						'</div>')
14370							.appendTo( $(document.body) )
14371							.hide(),
14372
14373		body = container.find('.info-body'),
14374		close = container.find('.close-button'),
14375		win = $(window);
14376
14377	close
14378		.on('click', hide);
14379
14380
14381	function show() {
14382		container.show();
14383
14384		ext.onshow();
14385		ext.trigger('show');
14386
14387		return ext;
14388	}
14389	function hide() {
14390		container.hide();
14391
14392		ext.onhide();
14393
14394		ext.trigger('hide');
14395
14396		return ext;
14397	}
14398
14399	function center() {
14400		var
14401			infoWidth = container.outerWidth(),
14402			infoHeight = container.outerHeight();
14403
14404		container.css({
14405			top: win.height()/2 - infoHeight/2,
14406			left: win.width()/2 - infoWidth/2
14407		});
14408
14409		return ext;
14410	}
14411
14412	function position(target) {
14413		var tOffset = target.offset(),
14414			tHeight = target.outerHeight(),
14415			left = tOffset.left - 20,
14416			top = tOffset.top + tHeight,
14417			infoWidth = container.outerWidth(),
14418			infoHeight = container.outerHeight();
14419
14420
14421		// far left edge
14422		if (left < 0) {
14423			left = 0;
14424		// far right
14425		} else if (left + infoWidth > win.width()) {
14426			left = win.width() - infoWidth;
14427		}
14428
14429		if (top + infoHeight > win.height()) {
14430			top = tOffset.top - infoHeight;
14431		}
14432
14433		container.css({
14434			top: top,
14435			left: left
14436		});
14437		return ext;
14438	}
14439
14440	var ext =  {
14441		show: show,
14442		hide: hide,
14443		container: container,
14444		body: body,
14445		position: position,
14446		center: center
14447	};
14448
14449	ext = $.extend(true, ext, EventEmitter);
14450	ext = $.extend(true, ext, ClickOff);
14451	ext.setClickTargets([container]);
14452	ext.clickoffid = id;
14453	ext.on('offclick', function() {
14454		hide();
14455	});
14456
14457
14458
14459
14460	return ext;
14461
14462};
14463
14464
14465
14466/*********
14467* ../app/js/ui/movablewindow.js
14468**********/
14469var MovableWindow = function(width,height,titleText,id) {
14470
14471	width = width || 300;
14472	height = height || 200;
14473	titleText = titleText || '';
14474
14475	var container = $(	'<div class="movable-window" ' + (id ? ' id="' + id + '"' : '') + '>' +
14476							'<div class="movable-header">' +
14477								'<span class="movable-header-title">' + titleText + '</span>' +
14478								'<span class="close-button"></span>' +
14479							'</div>' +
14480							'<div class="movable-body"></div>' +
14481						'</div>')
14482							.appendTo( $(document.body) )
14483							.hide(),
14484		header = container.find('.movable-header'),
14485		title = container.find('.movable-header-title'),
14486		body = container.find('.movable-body'),
14487		close = container.find('.close-button'),
14488		win = $(window),
14489		doc = $(document),
14490
14491		startWindowPosition = null,
14492		startMousePosition = null;
14493
14494	header.on('mousedown', function(e) {
14495		doc
14496			.on('mousemove', move)
14497			.on('mouseup', mouseup);
14498
14499		startWindowPosition = container.offset();
14500		startMousePosition = {x:e.clientX, y:e.clientY};
14501	});
14502
14503	function mouseup() {
14504		doc
14505			.off('mousemove', move)
14506			.off('mouseup', mouseup);
14507	}
14508
14509	function move(e) {
14510		// handle move
14511		//console.log(e);
14512
14513		container.css({
14514			top: startWindowPosition.top - (startMousePosition.y - e.clientY),
14515			left: startWindowPosition.left - (startMousePosition.x - e.clientX)
14516		});
14517	}
14518
14519	close.on('click', hide);
14520
14521
14522	function size(width, height) {
14523
14524		body.width(width);
14525		body.height(height);
14526
14527		return ext;
14528	}
14529	size(width, height);
14530	center();
14531
14532	function show() {
14533		container.show();
14534		return ext;
14535	}
14536	function hide() {
14537		container.hide();
14538		return ext;
14539	}
14540	function center() {
14541		var
14542			infoWidth = container.outerWidth(),
14543			infoHeight = container.outerHeight(),
14544			top = win.height()/2 - infoHeight/2,
14545			left = win.width()/2 - infoWidth/2;
14546
14547		if (top < 0) {
14548			top = 0;
14549		}
14550		if (left < 0) {
14551			left = 0;
14552		}
14553
14554		container.css({
14555			top: top,
14556			left: left
14557		});
14558
14559		return ext;
14560	}
14561
14562	var ext = {
14563		show: show,
14564		hide: hide,
14565		size: size,
14566		container: container,
14567		body: body,
14568		title: title,
14569		center: center,
14570		closeButton: close
14571	};
14572
14573	return ext;
14574
14575};
14576
14577
14578
14579/*********
14580* ../app/js/plugins/lemmainfo.js
14581**********/
14582sofia.config = $.extend(sofia.config, {
14583
14584	enableLemmaInfoPlugin: true
14585
14586});
14587
14588var LemmaInfoPlugin = function(app) {
14589
14590	if (!sofia.config.enableLemmaInfoPlugin) {
14591		return;
14592	}
14593
14594	var lemmaInfo = $('<div class="lemma-info"></div>
14594').appendTo( $(document.body) ).hide();
14595
14596	if (!Detection.hasTouch) {
14597
14598		$('.windows-main').on('mouseover','.BibleWindow l', function(e) {
14599
14600			var l = $(this),
14601				morph = l.attr('m'),
14602				strongs = l.attr('s'),
14603				main = l.closest('.scroller-main'),
14604				mainOffset = main.offset(),
14605				section = l.closest('.section'),
14606				lang = section.attr('lang'),
14607				morphologyType = lang == 'heb' || lang == 'he' ? 'Hebrew' : lang == 'el' || lang == 'grc' || lang == 'gre' ? 'Greek' : ''
14608				morphInfo = (typeof morph == 'undefined' || morphologyType == '') ? '' : bible.morphology[morphologyType].format(morph);
14609
14610			if (morphInfo != null && morphInfo != '') {
14611				lemmaInfo.html( morphInfo );
14612
14613				lemmaInfo.show();
14614
14615				lemmaInfo.css({
14616							left: mainOffset.left + 15 + 'px',
14617							top: (mainOffset.top + main.outerHeight() - lemmaInfo.outerHeight() - 10) + 'px'
14618							});
14619			}
14620
14621
14622		}).on('mouseout','l', function(e) {
14623			lemmaInfo.hide();
14624		});
14625	}
14626};
14627
14628sofia.plugins.push('LemmaInfoPlugin');
14629
14630
14631
14632/*********
14633* ../app/js/plugins/lemmamatch.js
14634**********/
14635sofia.config = $.extend(sofia.config, {
14636
14637	enableLemmaMatchPlugin: true
14638
14639});
14640
14641var LemmaMatchPlugin = function(app) {
14642
14643	if (!sofia.config.enableLemmaMatchPlugin) {
14644		return;
14645	}
14646
14647	if (!Detection.hasTouch) {
14648
14649		$('.windows-main').on('mouseover','l', function(e) {
14650
14651			//console.log('over');
14652
14653			var l = $(this),
14654				s = l.attr('s');
14655
14656			if (!s) {
14657				return;
14658			}
14659
14660			var
14661				strongs = l.attr('s').replace('G','').replace('H',''),
14662				verse = l.closest('.verse, .v'),
14663				verseid = verse.attr('data-id'),
14664				bookid = (verseid) ? verseid.substring(0,2) : '';
14665				langPrefix = (bookid != '' && bible.OT_BOOKS.indexOf(bookid) > -1) ? 'H' : 'G';
14666
14667
14668			var strongParts = strongs.split(' ');
14669
14670			for (var i=0, il=strongParts.length; i<il; i++) {
14671				var strong = strongParts[i];
14672
14673
14674				// ignore Greek article with second word
14675				if (strong == '3588' && il > 1) {
14676					continue;
14677				}
14678
14679
14680				if (verseid) {
14681					$('.' + verseid ).find('l[s*="' + strong + '"],l[s*="' + langPrefix + strong + '"]').addClass('lemma-highlight');
14682				} else {
14683					$('l[s*="' + strong + '"],l[s*="' + langPrefix + strong + '"]').addClass('lemma-highlight');
14684
14685				}
14686
14687			}
14688
14689
14690
14691		}).on('mouseout','l', function(e) {
14692			$('.lemma-highlight').removeClass('lemma-highlight');
14693		});
14694	}
14695};
14696
14697sofia.plugins.push('LemmaMatchPlugin');
14698
14699
14700
14701/*********
14702* ../app/js/plugins/lemmapopup.js
14703**********/
14704sofia.config = $.extend(sofia.config, {
14705
14706	enableLemmaPopupPlugin: true
14707
14708});
14709
14710
14711var LemmaPopupPlugin = function(app) {
14712
14713	if (!sofia.config.enableLemmaPopupPlugin) {
14714		return;
14715	}
14716
14717	var lemmaPopup = new InfoWindow('lemma-popup'),
14718		timer = new Timer(hidePopup, 500);
14719
14720
14721	function hidePopup() {
14722		lemmaPopup.hide();
14723	}
14724
14725	lemmaPopup.on('hide', function() {
14726		$('.selected-lemma').removeClass('selected-lemma');
14727	});
14728
14729	lemmaPopup.container
14730		.on('click', '.lemma-findall', function(e) {
14731			var link = $(this),
14732				lemma = link.attr('data-lemma'),
14733				textid = link.attr('data-textid');
14734
14735			console.log(lemma, textid);
14736
14737			sofia.app.windowManager.add('SearchWindow', {searchtext: lemma, textid: textid});
14738
14739			lemmaPopup.hide();
14740		});
14741
14742
14743	$('.windows-main').on('click','.BibleWindow l', function(e) {
14744
14745		var l = $(this);
14746
14747		if (lemmaPopup.container.is(':visible')) {
14748			lemmaPopup.hide();
14749			if (lemmaPopup.currentWord == this) {
14750				lemmaPopup.currentWord == null;
14751				l.removeClass('selected-lemma');
14752				return;
14753			}
14754		}
14755
14756		lemmaPopup.currentWord = this;
14757
14758		$('.selected-lemma').removeClass('selected-lemma');
14759		l.addClass('selected-lemma');
14760
14761		var
14762			// get attribute
14763			morph = l.attr('m'),
14764			// split into array
14765			morphs = (morph && morph != null) ? morph.split(' ') : [],
14766
14767			// get attribute
14768			strong = l.attr('s'),
14769			// split into array
14770			strongs = (strong && strong != null) ? strong.replace(/H/gi,'').replace(/G/gi,'').split(' ') : [],
14771
14772			// normal elements stuff
14773			main = l.closest('.scroller-main'),
14774			verse = l.closest('.verse, .v')
14775			verse_code = verse.attr('data-id'),
14776			book_id = verse_code.substring(0,2),
14777			chapter = l.closest('.chapter'),
14778			sectionid = chapter.attr('data-id'),
14779			textid = chapter.attr('data-textid'),
14780			langPrefix = 'G',
14781			langCode = 'el',
14782			morphType = 'Greek',
14783			dir = 'ltr';
14784
14785		// make strong's into ints
14786		for (var i=0; i<strongs.length; i++) {
14787			strongs[i] = parseInt(strongs[i], 10);
14788		}
14789
14790
14791		// check for language (greek or hebrew, noting LXX is greek)
14792		var sectionLang = l.closest('.section').attr('lang');
14793
14794		if (sectionLang == 'el' ||
14795			sectionLang == 'gr' ||
14796			sectionLang == 'grc' ||
14797			sectionLang == 'grk' ||
14798			sectionLang == 'el' ||
14799			bible.NT_BOOKS.indexOf(book_id) > -1 ||
14800			bible.AP_BOOKS.indexOf(book_id) > -1
14801			) {
14802				langPrefix = 'G';
14803				langCode = 'el';
14804				dir = 'ltr';
14805				morphType = 'Greek';
14806
14807		} else if (
14808			sectionLang == 'he' ||
14809			sectionLang == 'heb' ||
14810			bible.OT_BOOKS.indexOf(book_id) > -1){
14811				langPrefix = 'H';
14812				langCode = 'he';
14813				dir = 'rtl';
14814				morphType = 'Hebrew';
14815
14816		}
14817
14818		// remove G3588 and H853 (the) when there is more than two
14819		if (strongs.length > 0) {
14820			var articleIndex = -1;
14821
14822			for (var i=0; i<strongs.length; i++) {
14823				if ((strongs[i] == 3588 && langPrefix == 'G') || (strongs[i] == 853 && langPrefix == 'H')) {
14824					articleIndex = i;
14825					break;
14826				}
14827			}
14828
14829			if (articleIndex > -1) {
14830				strongs.splice(articleIndex, 1);
14831
14832				if (morphs.length > articleIndex) {
14833					morphs.splice(articleIndex, 1);
14834				}
14835			}
14836		}
14837
14838		//console.warn(strongs);
14839
14840		// record
14841		if (sofia.analytics) {
14842			sofia.analytics.record('lemma', langPrefix + strongs, l.html());
14843		}
14844
14845
14846		// show popup
14847		lemmaPopup.setClickTargets([lemmaPopup.container, l]);
14848		lemmaPopup.show();
14849		lemmaPopup.position(l);
14850
14851		lemmaPopup.body.html('Loading...');
14852
14853		if (strongs.length > 0) {
14854
14855			lemmaPopup.body.html('');
14856			lemmaPopup.body.addClass('loading-indicator');
14857
14858			for (var i=0, il=strongs.length; i<il; i++) {
14859
14860				loadStrongsData(textid, strongs[i], i < morphs.length ? morphs[i] : '', morphType, langPrefix, langCode, l);
14861
14862			}
14863		}
14864	});
14865
14866
14867	function loadStrongsData(textid, strongsNumber, morphKey, morphType, langPrefix, langCode, l) {
14868		sofia.ajax({
14869			dataType: 'json',
14870			url: 'content/lexicons/strongs/entries/' + langPrefix + strongsNumber + '.json',
14871			success: function(data) {
14872
14873				var html = '<div class="lemma-word">' +
14874								'<span lang="' + iso2iana.convert(langCode) + '" dir="' + dir + '">' + data.lemma + '</span>' +
14875								' ' +
14876								'<span class="lemma-strongs" dir="ltr"> (' + strongsNumber + ')</span>' +
14877							'</div>';
14878
14879
14880
14881				if (morphKey != '') {
14882					html += '<span class="lemma-morphology">' + bible.morphology[morphType].format( morphKey ) + '</span>';
14883				}
14884
14885				html += '<span class="lemma-findall" data-lemma="' + langPrefix + strongsNumber + '" data-textid="' + textid + '">' +
14886					i18n.t('plugins.lemmapopup.findalloccurrences', {count: data.frequency}) +
14887				'</span>';
14888
14889				html += '<div class="lemma-outline">
14889' + data.outline + '</div>';
14890
14891				lemmaPopup.body.removeClass('loading-indicator');
14892
14893				lemmaPopup.body.append( html );
14894
14895				lemmaPopup.position(l);
14896
14897			},
14898			error: function() {
14899				lemmaPopup.body.html('Error loading ... ' + langPrefix + strongs[0] );
14900			}
14901
14902
14903		});
14904	}
14905
14906
14907};
14908
14909sofia.plugins.push('LemmaPopupPlugin');
14910
14911
14912
14913/*********
14914* ../app/js/plugins/versematch.js
14915**********/
14916sofia.config = $.extend(sofia.config, {
14917
14918	enableVerseMatchPlugin: true
14919
14920});
14921
14922var VerseMatchPlugin = function(app) {
14923
14924	if (!sofia.config.enableVerseMatchPlugin) {
14925		return;
14926	}
14927
14928
14929	if (!Detection.hasTouch) {
14930
14931		$('.windows-main').on('mouseover','.BibleWindow .verse, .BibleWindow .v', function(e) {
14932
14933			var verse = $(this),
14934				verseid = verse.attr('data-id');
14935
14936			$('.BibleWindow .' + verseid).addClass('selected-verse');
14937
14938		}).on('mouseout','.BibleWindow .verse, .BibleWindow .v', function(e) {
14939			var verse = $(this),
14940				verseid = verse.attr('data-id');
14941
14942			$('.BibleWindow  .' + verseid).removeClass('selected-verse');
14943		});
14944	}
14945};
14946
14947sofia.plugins.push('VerseMatchPlugin');
14948
14949
14950
14951/*********
14952* ../app/js/plugins/eng2p.js
14953**********/
14954sofia.config = $.extend(sofia.config, {
14955
14956	enableEng2pPlugin: true,
14957
14958	// enables all possible English options (false = just color 'you')
14959	eng2pEnableAll: true,
14960
14961	eng2pDefaultSetting: 'none',
14962
14963	eng2pShowWindowAtStartup: false
14964});
14965
14966
14967/**
14968 * Highlights words based on morphological data
14969 *
14970 * @author John Dyer (http://j.hn/)
14971 */
14972var Eng2pPlugin = function(node) {
14973
14974	var languages = ['eng','en','en-us','en-gb'];
14975
14976
14977	if (!sofia.config.enableEng2pPlugin) {
14978		return;
14979	}
14980
14981	var
14982		engWindow = new MovableWindow(550,290),
14983
14984		configBlock =
14985		$('<div class="config-options" id="config-eng2p">' +
14986			//'<h3>English Second Person Plural</h3>' +
14987'<p class="i18n" data-i18n="[html]plugins.eng2p.description"></p>' +
14988'<table>' +
14989	'<tbody>' +
14990		'<tr>' +
14991			'<th>' +
14992				'<input type="radio" name="eng2p-option" id="eng2p-option-none" value="none" />' +
14993				'<label for="eng2p-option-none">None</label>' +
14994			'</th>' +
14995			'<td>You</td>' +
14996			'<td>Your</td>' +
14997			'<td>Yours</td>' +
14998			'<td>Yourselves</td>' +
14999		'</tr>' +
15000		'<tr>' +
15001			'<th>' +
15002				'<input type="radio" name="eng2p-option" id="eng2p-option-highlight" value="highlight" />' +
15003				'<label for="eng2p-option-highlight">Highlight</label>' +
15004			'</th>' +
15005			'<td><span class="eng2p-highlight-demo">You</span></td>' +
15006			'<td><span class="eng2p-highlight-demo">Your</span></td>' +
15007			'<td><span class="eng2p-highlight-demo">Yours</span></td>' +
15008			'<td><span class="eng2p-highlight-demo">Yourselves</span></td>' +
15009		'</tr>' +
15010
15011		(typeof sofia.config["eng2pEnableAll"] != 'undefined' && sofia.config["eng2pEnableAll"] == true ?
15012		'<tr>' +
15013			'<th>' +
15014				'<input type="radio" name="eng2p-option" id="eng2p-option-youall" value="youall"  />' +
15015				'<label for="eng2p-option-youall">General US</label>' +
15016			'</th>' +
15017			'<td>You all</td>' +
15018			'<td>You all\'s</td>' +
15019			'<td>You all\'s</td>' +
15020			'<td>You allselves</td>' +
15021		'</tr>' +
15022		'<tr>' +
15023			'<th>' +
15024				'<input type="radio" name="eng2p-option" id="eng2p-option-yall" value="yall" />' +
15025				'<label for="eng2p-option-yall">Southern US</label>' +
15026			'</th>' +
15027			'<td>Y\'all</td>' +
15028			'<td>Y\'all\'s</td>' +
15029			'<td>Y\'all\'s</td>' +
15030			'<td>Y\'allselves</td>' +
15031		'</tr>' +
15032		'<tr>' +
15033			'<th>' +
15034				'<input type="radio" name="eng2p-option" id="eng2p-option-youguys"  value="youguys" />' +
15035				'<label for="eng2p-option-youguys">Western US</label>' +
15036			'</th>' +
15037			'<td>You guys</td>' +
15038			'<td>Your guys\'s</td>' +
15039			'<td>Your guys\'s</td>' +
15040			'<td>Your guys selves</td>' +
15041		'</tr>' +
15042		'<tr>' +
15043			'<th>' +
15044				'<input type="radio" name="eng2p-option" id="eng2p-option-youseguys" value="youseguys" />' +
15045				'<label for="eng2p-option-youseguys">NYC/Chicago</label>' +
15046			'</th>' +
15047			'<td>Youse guys</td>' +
15048			'<td>Youse guys\'s</td>' +
15049			'<td>Youse guys\'s</td>' +
15050			'<td>Youse guys selves</td>' +
15051		'</tr>' +
15052		'<tr>' +
15053			'<th>' +
15054				'<input type="radio" name="eng2p-option" id="eng2p-option-yinz" value="yinz" />' +
15055				'<label for="eng2p-option-yinz">Pittsburgh</label>' +
15056			'</th>' +
15057			'<td>Yinz</td>' +
15058			'<td>Yinz\'s</td>' +
15059			'<td>Yinz\'s</td>' +
15060			'<td>Yinzselves</td>' +
15061		'</tr>' +
15062		'<tr>' +
15063			'<th>' +
15064				'<input type="radio" name="eng2p-option" id="eng2p-option-youlot" value="youlot" />' +
15065				'<label for="eng2p-option-youlot">United Kingdom</label>' +
15066			'</th>' +
15067			'<td>You lot</td>' +
15068			'<td>You lot\'s</td>' +
15069			'<td>You lot\'s</td>' +
15070			'<td>Yourlot\'s</td>' +
15071		'</tr>' +
15072		'<tr>' +
15073			'<th>' +
15074				'<input type="radio" name="eng2p-option" id="eng2p-option-ye" value="ye" />' +
15075				'<label for="eng2p-option-ye">Old English</label>' +
15076			'</th>' +
15077			'<td>Ye</td>' +
15078			'<td>Ye\'s</td>' +
15079			'<td>Ye\'s</td>' +
15080			'<td>Yeselves</td>' +
15081		'</tr>' +
15082		'<tr>' +
15083			'<th>' +
15084				'<input type="radio" name="eng2p-option" id="eng2p-option-youseguys" value="youse" />' +
15085				'<label for="eng2p-option-youseguys">Australia</label>' +
15086			'</th>' +
15087			'<td>Youse</td>' +
15088			'<td>Youse\'s</td>' +
15089			'<td>Youse\'s</td>' +
15090			'<td>Youseselves</td>' +
15091		'</tr>'
15092		:'') +
15093	'</tbody>' +
15094'</table>' +
15095			'</div>')
15096			.appendTo( engWindow.body ),
15097			button = $('<span class="config-button i18n" data-i18n="[html]plugins.eng2p.title" id="config-eng2p-button"></span>')
15098						.appendTo( $('#config-tools .config-body') );
15099
15100
15101	//engWindow.title.html('English Second Person Plural');
15102	engWindow.title
15103				.addClass('i18n')
15104				.attr('data-i18n','[html]plugins.eng2p.title')
15105
15106	button.on('click', function() {
15107
15108		engWindow.show().center();
15109
15110		button.closest('.window-overlay').hide();
15111
15112	});
15113
15114	// SET DEFAULT
15115
15116	// setting from localStorage
15117	var eng2pSetting = AppSettings.getValue('docs-config-eng2p-setting', {'eng2p': sofia.config.eng2pDefaultSetting });
15118
15119	// override with querysring
15120	var queryString = window.location.search && window.location.search.length > 0 ? window.location.search.substring(1) : '',
15121		params = stringUtility.parseQuerystring( queryString );
15122
15123
15124	// if it's in the querystring, try to use it
15125	if (typeof params['eng2p'] != 'undefined') {
15126		var tempEng2pSetting = params['eng2p'];
15127
15128		// see if there is a matching value
15129		if ($('#eng2p-option-' + tempEng2pSetting).length > 0) {
15130			eng2pSetting.eng2p = tempEng2pSetting;
15131
15132		}
15133	}
15134
15135	if (typeof params['eng2pshow'] != 'undefined' || sofia.config.eng2pShowWindowAtStartup === true) {
15136		engWindow.show();
15137		engWindow.container.css({left: $(window).width() - engWindow.container.outerWidth(true) - 10});
15138	}
15139
15140	// now set the value from either localStorage or querysring
15141	$('#eng2p-option-' + eng2pSetting.eng2p).prop('checked',true);
15142	getPluralValues();
15143
15144	// create updates
15145	$('input[name="eng2p-option"]').on('click',function() {
15146		// update the setting value
15147		eng2pSetting = {eng2p: $(this).val() };
15148
15149		// store value
15150		AppSettings.setValue('docs-config-eng2p-setting', eng2pSetting);
15151
15152		// values
15153		getPluralValues();
15154
15155		// re-run
15156		$('div.chapter').each(function() {
15157			var chapter = $(this);
15158
15159			removePluralTransforms(chapter);
15160			checkChapter(chapter);
15161		});
15162	});
15163
15164	function checkChapter(chapter) {
15165
15166		var lang = chapter.attr('lang') != null ? chapter.attr('lang').toLowerCase() : '';
15167
15168		if (languages.indexOf(lang) > -1 && eng2pSetting.eng2p != 'none') {
15169			//console.log('Eng2P', e.data.content.attr('data-id'));
15170
15171			//console.log('--transforming');
15172
15173			runPluralTransforms(chapter);
15174		}
15175
15176	}
15177
15178
15179	function getPluralValues() {
15180
15181		var selectedRow = $('#eng2p-option-' + eng2pSetting.eng2p).closest('tr');
15182
15183		bible.eng2p.youPluralSubject = selectedRow.find('td:eq(0)').html();
15184		bible.eng2p.youPluralPossessiveDeterminer = selectedRow.find('td:eq(1)').html();
15185		bible.eng2p.youPluralPossessivePronoun = selectedRow.find('td:eq(2)').html();
15186		bible.eng2p.youPluralReflexive = selectedRow.find('td:eq(3)').html();
15187	}
15188
15189	function removePluralTransforms(node) {
15190		bible.eng2p.removePluralTransforms(node);
15191	}
15192
15193	function runPluralTransforms(node) {
15194		node.find('.verse, .v').each(function(index, el) {
15195			var verse = $(this),
15196				verseid = verse.attr('data-id');
15197
15198
15199			//if (bible.eng2p.doubles.indexOf(verseid) > -1) {
15200				//verse.addClass('eng2p-doubles');
15201			if (bible.eng2p.pronouns.indexOf(verseid) > -1) {
15202				verse.addClass('eng2p-pronouns');
15203			}
15204			if (bible.eng2p.verbs.indexOf(verseid) > -1) {
15205				verse.addClass('eng2p-verbs');
15206			}
15207
15208
15209			// pronouns without also having a singular verb or pronoun
15210			if ((bible.eng2p.verbs.indexOf(verseid) > -1 || bible.eng2p.pronouns.indexOf(verseid) > -1) && bible.eng2p.doubles.indexOf(verseid) == -1) {
15211				var html = verse.html();
15212
15213				if (eng2pSetting.eng2p == 'highlight') {
15214					html = bible.eng2p.highlightPlurals( html );
15215				} else if (eng2pSetting.eng2p != 'none') {
15216					html = bible.eng2p.replacePlurals( html );
15217				}
15218
15219				verse.html( html );
15220
15221			};
15222		});
15223	}
15224
15225	// run transforms
15226	var ext = {
15227		sendMessage: function() {}
15228	};
15229	ext = $.extend(true, ext, EventEmitter);
15230
15231
15232	ext.on('message', function(e) {
15233
15234		//console.log(e.data.messagetype, e.data.type, e.data.content.attr('lang').toLowerCase(), e.data.textid);
15235
15236		if (e.data.messagetype == 'textload' && e.data.type == 'bible') {
15237
15238			checkChapter(e.data.content);
15239
15240		}
15241
15242	});
15243
15244
15245	return ext;
15246}
15247
15248sofia.plugins.push('Eng2pPlugin');
15249
15250
15251bible.eng2p = {
15252
15253	youPluralRegExp:  /\b([yY])(ou(r|rs|rselves)?|e)\b/g,
15254
15255	youPluralSubject: "Y'all",
15256	youPluralPossessiveDeterminer: "Y'all's",
15257	youPluralPossessivePronoun: "Y'all's",
15258	youPluralReflexive: "Y'allselves",
15259
15260	removePluralTransforms: function(node) {
15261		// remove the changed words
15262		node.find('.eng2p-corrected').remove();
15263
15264		// remove the surrounding spans
15265		node.find('.eng2p-highlight').each(function() {
15266			var span = this;
15267
15268			this.parentNode.replaceChild(document.createTextNode(span.textContent), this);
15269		});
15270
15271		// remove the surrounding spans
15272		node.find('.eng2p-original').each(function() {
15273			var span = this;
15274
15275			this.parentNode.replaceChild(document.createTextNode(span.textContent), this);
15276		});
15277	},
15278
15279	highlightPlurals: function(input) {
15280		var output = input.replace(bible.eng2p.youPluralRegExp, function(match, $1, $2, offset, originalString) {
15281
15282			// return the greatness
15283			return '<span class="eng2p-highlight">' + match + '</span>';
15284		});
15285
15286		return output;
15287	},
15288
15289	replacePlurals: function(input) {
15290
15291		var output = input.replace(bible.eng2p.youPluralRegExp, function(match, $1, $2, offset, originalString) {
15292			var replacement = '';
15293
15294			// you, your, yours checker
15295			switch (match.toLowerCase()) {
15296				case 'ye':
15297				case 'you':
15298					replacement = bible.eng2p.youPluralSubject;
15299					break;
15300				case 'your':
15301					replacement = bible.eng2p.youPluralPossessiveDeterminer;
15302					break;
15303				case 'yours':
15304					replacement = bible.eng2p.youPluralPossessivePronoun;
15305					break;
15306				case 'yourselves':
15307					replacement = bible.eng2p.youPluralReflexive;
15308					break;
15309				default:
15310					replacement = match; // 'UNKNOWN [' + match + ',' + $1 + ']';
15311					break;
15312
15313			}
15314
15315			// You vs. you
15316			if ($1 === $1.toUpperCase()) {
15317				replacement = replacement.substring(0,1).toUpperCase() + replacement.substring(1);
15318			} else {
15319				replacement = replacement.substring(0,1).toLowerCase() + replacement.substring(1);
15320			}
15321
15322			// replace standard ' with ’
15323			replacement = replacement.replace(/'/gi,'&rsquo;');
15324
15325
15326			// return the greatness
15327			return '<span class="eng2p-original">' + match + '</span><span class="eng2p-corrected">' + replacement + '</span>';
15328		});
15329
15330		return output;
15331	},
15332
15333"pronouns":["GN1_29","GN3_5","GN9_2","GN9_3","GN9_5","GN9_7","GN9_9","GN9_10","GN9_11","GN9_12","GN9_15","GN17_10","GN17_11","GN17_12","GN17_13","GN18_4","GN18_5","GN19_2","GN19_8","GN22_5","GN23_4","GN23_8","GN23_9","GN24_49","GN26_27","GN27_45","GN29_4","GN31_5","GN31_6","GN31_7","GN31_9","GN31_29","GN32_19","GN34_8","GN34_9","GN34_10","GN34_11","GN34_15","GN34_16","GN35_2","GN37_7","GN40_7","GN41_55","GN42_9","GN42_14","GN42_15","GN42_16","GN42_19","GN42_20","GN42_22","GN42_33","GN42_34","GN42_38","GN43_3","GN43_5","GN43_6","GN43_7","GN43_11","GN43_12","GN43_13","GN43_14","GN43_23","GN43_27","GN43_29","GN44_10","GN44_17","GN44_19","GN44_23","GN44_27","GN45_4","GN45_5","GN45_7","GN45_8","GN45_12","GN45_17","GN45_18","GN45_19","GN45_20","GN46_33","GN47_3","GN47_16","GN47_23","GN47_24","GN48_21","GN49_1","GN49_2","GN50_4","GN50_20","GN50_21","GN50_24","GN50_25","EX1_16","EX3_13","EX3_14","EX3_15","EX3_16","EX3_17","EX3_19","EX3_20","EX3_22","EX4_15","EX5_4","EX5_10","EX5_11","EX5_13","EX5_14","EX5_17","EX5_18","EX5_19","EX5_21","EX6_6","EX6_7","EX6_8","EX7_4","EX7_9","EX8_25","EX8_28","EX9_8","EX9_28","EX10_5","EX10_8","EX10_10","EX10_11","EX10_16","EX10_17","EX10_24","EX11_1","EX11_9","EX12_2","EX12_5","EX12_6","EX12_11","EX12_13","EX12_14","EX12_15","EX12_16","EX12_17","EX12_19","EX12_20","EX12_21","EX12_22","EX12_23","EX12_25","EX12_26","EX12_31","EX12_32","EX12_49","EX13_3","EX13_4","EX13_19","EX14_13","EX14_14","EX16_4","EX16_6","EX16_7","EX16_8","EX16_9","EX16_12","EX16_15","EX16_16","EX16_23","EX16_29","EX16_32","EX16_33","EX18_10","EX19_4","EX19_6","EX19_12","EX20_20","EX20_22","EX20_23","EX22_24","EX23_9","EX23_13","EX23_21","EX23_25","EX23_31","EX24_8","EX24_14","EX26_33","EX29_42","EX30_8","EX30_10","EX30_15","EX30_16","EX30_31","EX30_32","EX30_36","EX30_37","EX31_13","EX31_14","EX32_2","EX32_13","EX32_29","EX32_30","EX33_5","EX35_2","EX35_3","EX35_5","EX35_10","LV1_2","LV3_17","LV6_18","LV7_26","LV7_32","LV8_33","LV8_34","LV9_4","LV9_6","LV10_4","LV10_6","LV10_7","LV10_9","LV10_17","LV11_4","LV11_5","LV11_6","LV11_7","LV11_8","LV11_10","LV11_11","LV11_12","LV11_20","LV11_23","LV11_26","LV11_27","LV11_28","LV11_29","LV11_31","LV11_35","LV11_38","LV11_39","LV11_43","LV11_44","LV11_45","LV14_34","LV16_29","LV16_30","LV16_31","LV16_34","LV17_11","LV17_12","LV18_2","LV18_3","LV18_4","LV18_24","LV18_26","LV18_27","LV18_28","LV18_30","LV19_2","LV19_3","LV19_4","LV19_5","LV19_6","LV19_9","LV19_10","LV19_23","LV19_25","LV19_27","LV19_28","LV19_31","LV19_33","LV19_34","LV19_36","LV20_7","LV20_8","LV20_14","LV20_22","LV20_23","LV20_24","LV20_25","LV20_26","LV21_8","LV22_3","LV22_19","LV22_20","LV22_24","LV22_25","LV22_29","LV22_32","LV22_33","LV23_3","LV23_7","LV23_10","LV23_11","LV23_12","LV23_14","LV23_15","LV23_17","LV23_21","LV23_22","LV23_24","LV23_27","LV23_28","LV23_31","LV23_32","LV23_36","LV23_38","LV23_39","LV23_40","LV23_41","LV23_43","LV24_3","LV24_22","LV25_2","LV25_6","LV25_9","LV25_10","LV25_11","LV25_12","LV25_17","LV25_21","LV25_23","LV25_24","LV25_38","LV25_44","LV25_45","LV25_46","LV25_55","LV26_1","LV26_4","LV26_5","LV26_6","LV26_7","LV26_8","LV26_9","LV26_11","LV26_12","LV26_13","LV26_15","LV26_16","LV26_17","LV26_18","LV26_19","LV26_20","LV26_21","LV26_22","LV26_24","LV26_25","LV26_26","LV26_28","LV26_29","LV26_30","LV26_31","LV26_32","LV26_33","LV26_34","LV26_35","LV26_36","LV26_37","LV26_38","LV26_39","NU1_4","NU1_5","NU9_8","NU9_10","NU9_14","NU10_8","NU10_9","NU10_10","NU10_29","NU11_18","NU11_20","NU12_4","NU12_6","NU14_9","NU14_25","NU14_28","NU14_29","NU14_30","NU14_31","NU14_32","NU14_33","NU14_34","NU14_41","NU14_42","NU14_43","NU15_2","NU15_3","NU15_14","NU15_15","NU15_16","NU15_18","NU15_19","NU15_20","NU15_21","NU15_23","NU15_29","NU15_39","NU15_40","NU15_41","NU16_3","NU16_6","NU16_7","NU16_9","NU16_41","NU17_4","NU17_5","NU18_1","NU18_3","NU18_4","NU18_6","NU18_7","NU18_23","NU18_26","NU18_27","NU18_28","NU18_29","NU18_30","NU18_31","NU18_32","NU20_10","NU22_8","NU22_13","NU22_19","NU25_18","NU28_11","NU28_19","NU28_22","NU28_25","NU28_26","NU28_30","NU28_31","NU29_1","NU29_5","NU29_7","NU29_8","NU29_12","NU29_35","NU29_39","NU31_3","NU31_18","NU31_19","NU31_24","NU32_
153336","NU32_8","NU32_14","NU32_21","NU32_22","NU32_23","NU32_24","NU32_29","NU32_30","NU33_51","NU33_52","NU33_53","NU33_54","NU33_55","NU33_56","NU34_2","NU34_3","NU34_4","NU34_6","NU34_7","NU34_9","NU34_10","NU34_12","NU34_17","NU35_10","NU35_11","NU35_12","NU35_13","NU35_29","NU35_33","NU35_34","DT1_6","DT1_7","DT1_8","DT1_9","DT1_10","DT1_11","DT1_12","DT1_13","DT1_15","DT1_16","DT1_17","DT1_18","DT1_20","DT1_22","DT1_23","DT1_26","DT1_27","DT1_29","DT1_30","DT1_31","DT1_32","DT1_33","DT1_34","DT1_35","DT1_37","DT1_39","DT1_40","DT1_42","DT1_43","DT1_44","DT1_45","DT2_3","DT2_4","DT2_5","DT2_13","DT3_18","DT3_19","DT3_20","DT3_21","DT3_22","DT3_26","DT4_1","DT4_2","DT4_3","DT4_4","DT4_5","DT4_6","DT4_8","DT4_12","DT4_13","DT4_14","DT4_15","DT4_16","DT4_20","DT4_21","DT4_22","DT4_23","DT4_26","DT4_27","DT4_34","DT5_1","DT5_4","DT5_5","DT5_22","DT5_23","DT5_28","DT5_30","DT5_32","DT5_33","DT6_1","DT6_14","DT6_16","DT6_17","DT6_20","DT7_4","DT7_7","DT7_8","DT8_1","DT8_19","DT8_20","DT9_7","DT9_8","DT9_9","DT9_10","DT9_16","DT9_17","DT9_18","DT9_19","DT9_21","DT9_23","DT9_24","DT9_25","DT10_4","DT10_15","DT10_16","DT10_17","DT11_2","DT11_4","DT11_5","DT11_7","DT11_8","DT11_9","DT11_11","DT11_13","DT11_14","DT11_16","DT11_17","DT11_18","DT11_19","DT11_21","DT11_22","DT11_23","DT11_24","DT11_25","DT11_26","DT11_27","DT11_28","DT11_31","DT11_32","DT12_1","DT12_2","DT12_4","DT12_5","DT12_6","DT12_7","DT12_10","DT12_11","DT12_12","DT12_32","DT13_3","DT13_4","DT13_5","DT13_7","DT14_1","DT14_7","DT14_8","DT14_10","DT14_19","DT17_16","DT20_2","DT20_3","DT20_4","DT20_18","DT23_4","DT24_8","DT24_9","DT25_17","DT27_1","DT27_4","DT27_12","DT28_14","DT28_63","DT29_2","DT29_4","DT29_5","DT29_6","DT29_10","DT29_11","DT29_14","DT29_16","DT29_18","DT29_22","DT30_18","DT30_19","DT31_5","DT31_12","DT31_13","DT31_19","DT31_26","DT31_27","DT31_28","DT31_29","DT32_17","DT32_38","DT32_46","DT32_47","JS1_3","JS1_4","JS1_11","JS1_13","JS1_14","JS1_15","JS2_9","JS2_10","JS2_11","JS2_12","JS2_14","JS2_16","JS2_21","JS3_3","JS3_4","JS3_5","JS3_8","JS3_9","JS3_10","JS3_11","JS3_12","JS4_2","JS4_3","JS4_5","JS4_6","JS4_21","JS4_22","JS4_23","JS4_24","JS5_9","JS6_5","JS6_10","JS6_16","JS6_18","JS7_12","JS7_13","JS7_14","JS8_2","JS8_4","JS8_7","JS8_8","JS9_8","JS9_11","JS9_12","JS9_22","JS9_23","JS9_24","JS10_19","JS10_24","JS10_25","JS15_4","JS18_3","JS18_4","JS18_6","JS18_7","JS18_8","JS20_2","JS20_3","JS22_2","JS22_3","JS22_4","JS22_5","JS22_8","JS22_16","JS22_18","JS22_19","JS22_24","JS22_25","JS22_27","JS22_28","JS23_3","JS23_4","JS23_5","JS23_7","JS23_8","JS23_9","JS23_10","JS23_11","JS23_12","JS23_13","JS23_14","JS23_15","JS23_16","JS24_2","JS24_3","JS24_5","JS24_6","JS24_7","JS24_8","JS24_9","JS24_10","JS24_11","JS24_12","JS24_13","JS24_14","JS24_15","JS24_19","JS24_20","JS24_22","JS24_23","JS24_27","JG2_1","JG2_2","JG2_3","JG3_28","JG6_8","JG6_9","JG6_10","JG6_31","JG7_7","JG7_15","JG7_18","JG8_2","JG8_3","JG8_7","JG8_19","JG8_23","JG8_24","JG9_2","JG9_7","JG9_15","JG9_17","JG9_18","JG9_19","JG10_12","JG10_13","JG10_14","JG11_7","JG11_9","JG12_2","JG12_4","JG14_12","JG14_13","JG15_7","JG15_12","JG18_6","JG18_8","JG18_9","JG18_10","JG18_18","JG18_25","JG19_9","JG19_24","JG19_30","JG20_7","JG20_12","JG21_21","JG21_22","RT1_8","RT1_9","RT1_11","RT1_13","RT2_4","RT4_9","RT4_10","S12_3","S12_23","S12_29","S14_9","S16_3","S16_4","S16_5","S16_6","S16_21","S17_3","S17_5","S18_11","S18_13","S18_14","S18_15","S18_16","S18_17","S18_18","S19_13","S110_2","S110_15","S110_18","S110_19","S111_2","S111_9","S111_10","S112_1","S112_2","S112_3","S112_5","S112_6","S112_7","S112_8","S112_11","S112_12","S112_13","S112_14","S112_15","S112_16","S112_17","S112_20","S112_22","S112_23","S112_24","S112_25","S114_9","S114_12","S114_40","S117_8","S117_9","S117_47","S118_23","S122_3","S122_7","S122_8","S123_21","S123_23","S125_19","S126_16","S129_10","S130_24","S130_26","S21_21","S21_24","S22_5","S22_6","S22_7","S23_17","S23_31","S24_11","S27_23","S210_5","S213_28","S215_10","S215_27","S215_28","S216_10","S216_20","S217_21","S218_2","S218_4","S219_10","S219_12","S219_22","S221_3","S221_4","K11_33","K18_61","K19_6","K111_2","K112_6","K112_9","K112_11","K112_14","K112_24","K112_28","K118_18","K118_21","K118_24","K118_25","K21_3","K21_6","K21_7","K22_18","K23_17","K23_18","K26_19","K27_12","K29_11","K29_15","K210_2","K210_3","K210_6","K210_9","K210_13","K210_23","K210_24","K211_5","K211_7","K212_7","K217_13","K217_36","K217_37","K2
1533317_38","K217_39","K218_27","K218_29","K218_30","K218_32","K219_6","K222_15","K222_18","K223_21","K225_24","R112_17","R113_2","R115_12","R115_13","R116_18","R116_19","R119_5","R122_18","R122_19","R128_8","R129_20","R27_19","R210_6","R210_9","R210_11","R210_14","R211_4","R212_5","R213_5","R213_8","R213_9","R213_11","R213_12","R215_2","R215_7","R218_14","R219_6","R219_7","R219_10","R219_11","R220_15","R220_17","R220_20","R223_4","R224_5","R224_20","R228_9","R228_10","R228_11","R228_13","R229_5","R229_8","R229_11","R229_31","R230_6","R230_7","R230_8","R230_9","R232_10","R232_11","R232_14","R232_15","R233_8","R234_23","R234_26","R235_3","R235_4","R235_5","R235_6","R236_23","ER1_3","ER4_2","ER4_3","ER8_28","ER9_11","ER9_12","ER10_10","ER10_11","NH1_8","NH1_9","NH2_17","NH2_19","NH2_20","NH4_14","NH5_7","NH5_8","NH5_9","NH5_11","NH6_3","NH8_9","NH8_10","NH9_5","NH13_17","NH13_18","NH13_21","NH13_25","NH13_27","ET8_8","JB6_22","JB6_25","JB6_27","JB6_28","JB12_2","JB12_3","JB13_2","JB13_4","JB13_5","JB13_9","JB13_10","JB13_11","JB13_12","JB13_17","JB16_2","JB16_4","JB16_5","JB17_10","JB18_3","JB19_21","JB19_29","JB21_2","JB21_27","JB21_34","JB27_5","JB27_11","JB27_12","JB32_6","JB32_11","JB32_12","JB32_14","JB42_8","PS4_4","PS11_1","PS22_26","PS24_7","PS24_9","PS31_24","PS34_11","PS48_13","PS58_2","PS58_9","PS62_3","PS62_8","PS69_32","PS75_5","PS76_11","PS78_1","PS82_6","PS95_8","PS95_9","PS105_11","PS115_14","PS115_15","PS118_26","PS127_2","PS129_8","PS134_2","PR1_23","PR1_26","PR1_27","PR4_2","PR8_4","SS2_7","SS3_5","SS5_8","SS8_4","IS1_7","IS1_11","IS1_12","IS1_14","IS1_15","IS1_16","IS1_18","IS2_22","IS3_14","IS3_15","IS5_5","IS5_8","IS7_13","IS7_14","IS8_13","IS8_19","IS10_3","IS21_10","IS22_14","IS23_7","IS23_14","IS27_12","IS28_18","IS28_19","IS28_22","IS29_10","IS29_11","IS29_16","IS30_3","IS30_12","IS30_13","IS30_15","IS30_16","IS30_18","IS30_20","IS30_29","IS31_7","IS32_20","IS33_4","IS33_11","IS35_4","IS36_12","IS36_14","IS36_15","IS36_17","IS36_18","IS37_6","IS40_1","IS40_9","IS40_21","IS40_26","IS41_21","IS41_23","IS41_24","IS41_26","IS42_9","IS42_17","IS42_23","IS43_10","IS43_12","IS43_14","IS43_15","IS44_8","IS46_1","IS48_6","IS48_14","IS50_1","IS50_10","IS50_11","IS51_2","IS51_6","IS51_12","IS52_12","IS55_2","IS55_3","IS55_8","IS55_9","IS55_12","IS57_3","IS57_4","IS58_3","IS58_4","IS59_2","IS59_3","IS61_5","IS61_6","IS61_7","IS62_6","IS65_7","IS65_11","IS65_12","IS65_13","IS65_14","IS65_15","IS66_5","IS66_13","IS66_14","IS66_20","IS66_22","JR2_5","JR2_7","JR2_9","JR2_29","JR2_30","JR2_31","JR3_12","JR3_14","JR3_15","JR3_18","JR3_22","JR4_3","JR4_4","JR4_10","JR5_14","JR5_15","JR5_18","JR5_19","JR5_25","JR6_16","JR6_17","JR6_20","JR7_3","JR7_4","JR7_5","JR7_6","JR7_7","JR7_8","JR7_11","JR7_13","JR7_14","JR7_15","JR7_21","JR7_22","JR7_23","JR7_25","JR8_17","JR9_20","JR10_1","JR11_4","JR11_5","JR11_7","JR12_13","JR13_16","JR13_18","JR13_20","JR13_23","JR14_13","JR14_14","JR15_14","JR16_9","JR16_11","JR16_12","JR16_13","JR17_1","JR17_21","JR17_22","JR18_6","JR18_11","JR21_4","JR21_5","JR21_8","JR21_9","JR21_14","JR23_2","JR23_16","JR23_17","JR23_33","JR23_38","JR23_39","JR23_40","JR25_3","JR25_4","JR25_5","JR25_6","JR25_7","JR25_27","JR25_29","JR25_34","JR26_4","JR26_5","JR26_11","JR26_13","JR26_14","JR26_15","JR27_4","JR27_9","JR27_10","JR27_12","JR27_14","JR27_15","JR27_16","JR29_6","JR29_7","JR29_8","JR29_9","JR29_10","JR29_11","JR29_12","JR29_13","JR29_14","JR29_16","JR29_20","JR29_21","JR29_27","JR29_31","JR30_22","JR32_36","JR32_43","JR33_10","JR34_13","JR34_14","JR34_15","JR34_16","JR34_17","JR34_21","JR35_6","JR35_7","JR35_14","JR35_15","JR35_18","JR36_19","JR37_7","JR37_9","JR37_10","JR37_19","JR38_5","JR40_3","JR40_9","JR40_10","JR42_4","JR42_9","JR42_10","JR42_11","JR42_12","JR42_13","JR42_15","JR42_16","JR42_18","JR42_19","JR42_20","JR42_21","JR44_2","JR44_3","JR44_4","JR44_7","JR44_8","JR44_9","JR44_10","JR44_11","JR44_21","JR44_22","JR44_23","JR44_25","JR44_29","JR48_6","JR49_30","JR50_12","JR51_24","JR51_46","JR51_50","LM1_12","EK5_7","EK5_16","EK5_17","EK6_3","EK6_4","EK6_5","EK6_6","EK6_7","EK6_8","EK6_9","EK9_5","EK11_5","EK11_6","EK11_7","EK11_8","EK11_9","EK11_10","EK11_11","EK11_12","EK11_17","EK11_19","EK12_11","EK12_22","EK12_25","EK13_8","EK13_11","EK13_12","EK13_15","EK13_18","EK13_19","EK13_20","EK13_21","EK13_23","EK14_6","EK14_22","EK14_23","EK16_45","EK16_55","EK18_2","EK18_3","EK18_25","EK18_29","EK18_30","EK18_31","EK20_3","EK20_5","EK20_7","EK20_18","
15333EK20_19","EK20_20","EK20_27","EK20_29","EK20_30","EK20_31","EK20_32","EK20_33","EK20_34","EK20_35","EK20_36","EK20_37","EK20_38","EK20_39","EK20_40","EK20_41","EK20_42","EK20_43","EK20_44","EK21_24","EK22_19","EK22_20","EK22_21","EK22_22","EK23_48","EK23_49","EK24_21","EK24_23","EK24_24","EK33_11","EK33_20","EK33_25","EK33_26","EK34_17","EK34_18","EK34_19","EK34_21","EK34_31","EK35_13","EK36_2","EK36_3","EK36_7","EK36_8","EK36_9","EK36_10","EK36_11","EK36_12","EK36_13","EK36_22","EK36_23","EK36_24","EK36_25","EK36_26","EK36_27","EK36_28","EK36_29","EK36_31","EK36_32","EK36_33","EK36_36","EK37_5","EK37_6","EK37_12","EK37_13","EK37_14","EK37_25","EK39_17","EK39_19","EK43_27","EK44_6","EK44_7","EK44_8","EK44_30","EK45_1","EK45_9","EK45_10","EK45_12","EK45_21","EK47_14","EK47_21","EK47_22","DN1_10","DN10_21","HS1_9","HS1_10","HS2_1","HS2_2","HS4_13","HS4_14","HS5_1","HS5_13","HS6_4","HS9_10","HS10_12","HS10_15","HS14_2","JL1_2","JL1_3","JL1_5","JL1_13","JL1_14","JL2_12","JL2_13","JL2_14","JL2_19","JL2_20","JL2_23","JL2_25","JL2_26","JL2_27","JL2_28","JL3_4","JL3_5","JL3_7","JL3_8","JL3_10","JL3_17","AM2_10","AM2_11","AM2_13","AM3_1","AM3_2","AM4_2","AM4_4","AM4_6","AM4_7","AM4_9","AM4_10","AM4_11","AM5_1","AM5_11","AM5_12","AM5_14","AM5_18","AM5_21","AM5_22","AM5_26","AM5_27","AM6_2","AM6_14","AM8_10","AM9_7","JH1_12","MC1_2","MC1_11","MC2_3","MC2_4","MC3_1","MC3_6","MC3_12","HK1_5","ZP2_2","ZP2_5","ZP2_12","ZP3_20","HG1_4","HG1_5","HG1_7","HG1_9","HG1_10","HG1_13","HG2_3","HG2_4","HG2_5","HG2_15","HG2_17","HG2_18","ZC1_2","ZC1_3","ZC1_4","ZC1_5","ZC1_6","ZC2_6","ZC2_8","ZC4_9","ZC6_15","ZC7_6","ZC7_10","ZC8_9","ZC8_13","ZC8_14","ZC8_16","ZC8_17","ZC8_23","ZC11_9","ZC11_12","ML1_2","ML1_5","ML1_6","ML1_7","ML1_9","ML1_10","ML1_12","ML1_13","ML2_1","ML2_2","ML2_3","ML2_4","ML2_8","ML2_9","ML2_13","ML2_15","ML2_16","ML2_17","ML3_1","ML3_5","ML3_6","ML3_7","ML3_8","ML3_9","ML3_10","ML3_11","ML3_12","ML3_13","ML4_2","ML4_3","ML4_5","MT3_7","MT3_9","MT3_11","MT4_19","MT5_11","MT5_12","MT5_13","MT5_14","MT5_16","MT5_18","MT5_20","MT5_22","MT5_28","MT5_32","MT5_34","MT5_37","MT5_39","MT5_44","MT5_45","MT5_46","MT5_47","MT5_48","MT6_1","MT6_2","MT6_5","MT6_8","MT6_9","MT6_14","MT6_15","MT6_16","MT6_19","MT6_20","MT6_25","MT6_26","MT6_27","MT6_29","MT6_30","MT6_32","MT6_33","MT7_2","MT7_6","MT7_7","MT7_9","MT7_11","MT7_12","MT7_15","MT7_23","MT8_10","MT8_11","MT9_4","MT9_11","MT9_29","MT10_9","MT10_13","MT10_14","MT10_15","MT10_16","MT10_17","MT10_19","MT10_20","MT10_23","MT10_27","MT10_29","MT10_30","MT10_31","MT10_40","MT10_42","MT11_9","MT11_11","MT11_17","MT11_21","MT11_22","MT11_24","MT11_28","MT11_29","MT12_6","MT12_11","MT12_27","MT12_28","MT12_31","MT12_36","MT13_11","MT13_16","MT13_17","MT13_18","MT14_16","MT15_3","MT15_5","MT15_6","MT15_7","MT15_16","MT16_11","MT16_15","MT16_28","MT17_12","MT17_17","MT17_20","MT17_24","MT18_3","MT18_10","MT18_12","MT18_13","MT18_14","MT18_18","MT18_19","MT18_35","MT19_8","MT19_9","MT19_23","MT19_24","MT19_28","MT20_4","MT20_7","MT20_26","MT20_27","MT20_32","MT21_2","MT21_3","MT21_13","MT21_21","MT21_24","MT21_27","MT21_28","MT21_31","MT21_32","MT21_43","MT22_31","MT22_42","MT23_3","MT23_8","MT23_9","MT23_10","MT23_11","MT23_13","MT23_15","MT23_16","MT23_23","MT23_25","MT23_27","MT23_28","MT23_29","MT23_31","MT23_32","MT23_34","MT23_35","MT23_36","MT23_38","MT23_39","MT24_2","MT24_4","MT24_9","MT24_20","MT24_23","MT24_25","MT24_26","MT24_33","MT24_34","MT24_42","MT24_44","MT24_47","MT25_8","MT25_9","MT25_12","MT25_34","MT25_40","MT25_45","MT26_13","MT26_15","MT26_21","MT26_29","MT26_31","MT26_32","MT26_64","MT26_66","MT27_17","MT27_21","MT27_24","MT28_5","MT28_7","MT28_14","MT28_20","MK1_8","MK1_17","MK2_8","MK2_18","MK3_28","MK4_11","MK4_24","MK5_19","MK6_11","MK6_31","MK6_37","MK7_6","MK7_9","MK7_11","MK7_13","MK7_18","MK8_12","MK8_17","MK8_29","MK9_1","MK9_13","MK9_19","MK9_41","MK9_50","MK10_3","MK10_5","MK10_15","MK10_29","MK10_36","MK10_43","MK10_44","MK11_2","MK11_3","MK11_17","MK11_23","MK11_24","MK11_25","MK11_29","MK11_33","MK12_43","MK13_5","MK13_9","MK13_11","MK13_21","MK13_23","MK13_29","MK13_30","MK13_36","MK13_37","MK14_7","MK14_9","MK14_13","MK14_15","MK14_18","MK14_25","MK14_28","MK14_49","MK14_64","MK15_9","MK16_7","LK2_10","LK2_11","LK2_12","LK3_7","LK3_8","LK3_13","LK3_14","LK3_16","LK4_21","LK4_24","LK4_25","LK5_4","LK5_22","LK5_33","LK6_9","LK6_20","LK6_22","LK6_23","LK6_24","LK6_25","LK6_26","LK6_27","LK6_28","LK6_30","LK6_31","LK6_32","LK6_33","LK6_34","LK6_35","LK6_36","LK6_38","LK6_47","LK7_9","LK7_26","LK7_28","LK7_32","LK8_10","LK8_25","LK9_5","LK9_13","LK9_20","LK9_27","LK9_41","LK9_44","LK9_48","LK9_50","LK10_3","LK10_6","LK10_8","LK10_9","LK10_10","LK10_11","LK10_12","LK10_13","LK10_14","LK10_16","LK10_19","LK10_20","LK10_24","LK11_5","LK11_8","LK11_9","LK11_11","LK11_13","LK11_19","LK11_20","LK11_39","LK11_41","LK11_42","LK11_43","LK11_44","LK11_46","LK11_47","LK11_48","LK11_51","LK11_52","LK12_1","LK12_4","LK12_5","LK12_7","LK12_8","LK12_11","LK12_12","LK12_14","LK12_22","LK12_24","LK12_25","
15333LK12_27","LK12_28","LK12_29","LK12_30","LK12_31","LK12_32","LK12_33","LK12_34","LK12_35","LK12_36","LK12_37","LK12_40","LK12_44","LK12_51","LK12_57","LK13_3","LK13_5","LK13_15","LK13_24","LK13_25","LK13_27","LK13_28","LK13_35","LK14_5","LK14_24","LK14_28","LK14_33","LK15_4","LK15_7","LK15_10","LK15_31","LK16_9","LK16_11","LK16_12","LK16_15","LK16_26","LK17_3","LK17_6","LK17_7","LK17_10","LK17_14","LK17_21","LK17_23","LK17_34","LK18_8","LK18_14","LK18_17","LK18_29","LK19_26","LK19_31","LK19_40","LK19_46","LK20_3","LK20_8","LK21_3","LK21_12","LK21_13","LK21_14","LK21_15","LK21_16","LK21_18","LK21_19","LK21_28","LK21_30","LK21_31","LK21_32","LK21_34","LK22_10","LK22_12","LK22_15","LK22_16","LK22_17","LK22_18","LK22_19","LK22_20","LK22_26","LK22_27","LK22_28","LK22_29","LK22_31","LK22_35","LK22_37","LK22_53","LK22_67","LK22_70","LK23_14","LK23_28","LK24_6","LK24_38","LK24_44","LK24_48","LK24_49","JN1_26","JN1_51","JN2_5","JN3_7","JN3_12","JN3_28","JN4_20","JN4_22","JN4_32","JN4_35","JN4_38","JN5_19","JN5_20","JN5_24","JN5_25","JN5_33","JN5_34","JN5_35","JN5_38","JN5_39","JN5_42","JN5_44","JN5_45","JN6_26","JN6_27","JN6_32","JN6_36","JN6_47","JN6_49","JN6_53","JN6_61","JN6_63","JN6_64","JN6_65","JN6_67","JN6_70","JN7_6","JN7_7","JN7_8","JN7_19","JN7_22","JN7_28","JN7_33","JN7_34","JN7_36","JN7_47","JN8_7","JN8_14","JN8_15","JN8_17","JN8_21","JN8_22","JN8_23","JN8_24","JN8_25","JN8_26","JN8_31","JN8_32","JN8_34","JN8_36","JN8_37","JN8_38","JN8_40","JN8_41","JN8_42","JN8_44","JN8_46","JN8_47","JN8_49","JN8_51","JN8_54","JN8_55","JN8_56","JN8_58","JN9_19","JN9_27","JN9_30","JN9_41","JN10_1","JN10_7","JN10_25","JN10_26","JN10_32","JN10_34","JN10_36","JN11_15","JN11_49","JN11_50","JN11_56","JN12_8","JN12_24","JN12_30","JN12_35","JN13_10","JN13_12","JN13_13","JN13_14","JN13_15","JN13_16","JN13_18","JN13_19","JN13_20","JN13_21","JN13_33","JN13_34","JN14_1","JN14_2","JN14_3","JN14_9","JN14_10","JN14_12","JN14_16","JN14_17","JN14_18","JN14_19","JN14_20","JN14_25","JN14_26","JN14_27","JN14_28","JN14_29","JN14_30","JN15_3","JN15_4","JN15_5","JN15_7","JN15_9","JN15_11","JN15_12","JN15_14","JN15_15","JN15_16","JN15_17","JN15_18","JN15_19","JN15_20","JN15_21","JN15_26","JN15_27","JN16_1","JN16_2","JN16_4","JN16_5","JN16_6","JN16_7","JN16_12","JN16_13","JN16_14","JN16_15","JN16_20","JN16_22","JN16_23","JN16_24","JN16_25","JN16_26","JN16_27","JN16_33","JN17_10","JN18_8","JN18_31","JN18_39","JN19_4","JN19_6","JN19_14","JN19_15","JN19_35","JN20_17","JN20_19","JN20_21","JN20_26","AC1_5","AC1_7","AC1_8","AC1_11","AC2_14","AC2_15","AC2_17","AC2_22","AC2_29","AC2_33","AC2_36","AC2_38","AC2_39","AC3_13","AC3_14","AC3_16","AC3_17","AC3_19","AC3_20","AC3_22","AC3_25","AC3_26","AC4_7","AC4_10","AC4_11","AC4_19","AC5_9","AC5_28","AC5_30","AC5_38","AC6_3","AC7_4","AC7_37","AC7_43","AC7_51","AC7_52","AC8_24","AC10_28","AC10_37","AC11_16","AC13_15","AC13_26","AC13_32","AC13_34","AC13_38","AC13_41","AC13_46","AC14_15","AC14_17","AC15_7","AC15_24","AC15_25","AC15_28","AC15_29","AC16_17","AC17_3","AC17_22","AC17_23","AC17_28","AC18_6","AC18_14","AC18_15","AC18_21","AC19_13","AC19_15","AC19_36","AC20_18","AC20_20","AC20_25","AC20_26","AC20_27","AC20_28","AC20_29","AC20_30","AC20_32","AC20_35","AC22_1","AC22_3","AC22_25","AC23_15","AC24_21","AC24_22","AC25_5","AC25_26","AC26_8","AC27_22","AC27_31","AC27_34","AC28_20","AC28_25","AC28_28","RM1_6","RM1_7","RM1_8","RM1_9","RM1_10","RM1_11","RM1_12","RM1_13","RM1_15","RM2_24","RM6_11","RM6_12","RM6_13","RM6_14","RM6_16","RM6_19","RM6_22","RM7_4","RM8_9","RM8_10","RM8_11","RM9_26","RM10_19","RM11_13","RM11_25","RM11_28","RM11_30","RM11_31","RM12_1","RM12_2","RM12_3","RM12_16","RM12_18","RM12_19","RM13_11","RM14_16","RM15_5","RM15_7","RM15_13","RM15_14","RM15_15","RM15_22","RM15_23","RM15_24","RM15_28","RM15_29","RM15_30","RM15_32","RM15_33","RM16_1","RM16_2","RM16_6","RM16_16","RM16_17","RM16_19","RM16_20","RM16_21","RM16_22","RM16_23","RM16_25","C11_3","C11_4","C11_6","C11_7","C11_8","C11_10","C11_11","C11_12","C11_13","C11_14","C11_26","C11_30","C12_1","C12_2","C12_3","C12_5","C13_1","C13_2","C13_3","C13_16","C13_17","C13_18","C13_21","C13_22","C13_23","C14_3","C14_6","C14_8","C14_10","C14_14","C14_15","C14_16","C14_17","C14_18","C14_19","C14_21","C15_1","C15_2","C15_4","C15_6","C15_9","C15_11","C15_12","C15_13","C16_1","C16_2","C16_5","C16_7","C16_8","C16_15","C16_19","C16_20","C17_5","C17_14","C17_28","C17_32","C17_35","C18_9","C19_1","C19_2","C19_11","C19_12","C110_1","C110_13","C110_15","C110_20","C110_27","C110_28","C111_2","C111_3","C111_13","C111_14","C111_18","C111_19","C111_20","C111_22","C111_23","C111_24","C111_30","C112_1","C112_3","C112_21","C112_27","C112_31","C114_5","C114_6","C114_9","C114_12","C114_18","C114_25","C114_36","C114_37","C115_1","C115_2","C115_3","C115_12","C115_14","C115_17","C115_31","C115_34","C115_51","C115_58","C116_1","C116_2","C116_3","C116_5","C116_6","C116_7","C116_10","C116_12","C116_14","C116_15","C116_16","C116_17","C116_18","C116_19","C116_20","C116_23","C116_24","C21_2","C21_6","C21_7","C21_8","C21_11","C21_12","C21_13","C21_14","C21_15","C21_16","C21_18","C21_19","C21_21","C21_23","C21_24","C22_1","C22_2","C22_3","C22_4","C22_5","C22_7","C22_8","C22_9","C22_10","C23_1","C23_2","C24_5","C24_12","C24_14","C24_15","C25_11","C25_12","C25_13","C26_1","C26_11","C26_12","C26_13","C26_17","C26_18","C27_4","C27_7","C27_8","C27_11","C27_12","C27_13","C27_14","C27_15","C27_16","C28_1","C28_6","C28_7","C28_8","C28_9","C28_10","C28_13","C28_14","C28_16","C28_17","C28_22","C28_23","C28_24","C29_1","C29_2","C29_3","C29_4","C29_5","C29_8","C29_10","C29_13","C29_14","C210_1","C210_6","C210_8","C210_9","C210_13","C210_14","C210_15","C210_16","C211_2","C211_3","C211_6","C211_7","C211_8","C211_9","C211_11","C211_20","C212_11","C212_12","C212_13","C212_14","C212_15","C212_16","C212_17","C212_18","C212_19","C212_20","C212_21","C213_
153331","C213_3","C213_4","C213_5","C213_7","C213_9","C213_11","C213_13","C213_14","GL1_3","GL1_6","GL1_7","GL1_8","GL1_9","GL1_11","GL1_20","GL2_5","GL3_1","GL3_2","GL3_5","GL3_28","GL3_29","GL4_11","GL4_12","GL4_13","GL4_14","GL4_15","GL4_16","GL4_17","GL4_18","GL4_19","GL4_20","GL4_28","GL5_2","GL5_7","GL5_8","GL5_10","GL5_12","GL5_13","GL5_21","GL6_1","GL6_11","GL6_12","GL6_13","GL6_18","EP1_2","EP1_13","EP1_15","EP1_16","EP1_17","EP1_18","EP2_1","EP2_8","EP2_11","EP2_13","EP2_17","EP2_22","EP3_1","EP3_2","EP3_13","EP3_16","EP3_17","EP4_1","EP4_4","EP4_17","EP4_20","EP4_22","EP4_23","EP4_26","EP4_29","EP4_31","EP4_32","EP5_3","EP5_6","EP5_19","EP5_33","EP6_1","EP6_4","EP6_5","EP6_9","EP6_11","EP6_14","EP6_21","EP6_22","PP1_2","PP1_3","PP1_4","PP1_5","PP1_6","PP1_7","PP1_8","PP1_9","PP1_10","PP1_12","PP1_19","PP1_24","PP1_25","PP1_26","PP1_27","PP1_28","PP1_29","PP2_3","PP2_4","PP2_5","PP2_12","PP2_13","PP2_17","PP2_18","PP2_19","PP2_20","PP2_25","PP2_26","PP2_30","PP3_1","PP3_15","PP3_18","PP4_5","PP4_6","PP4_7","PP4_9","PP4_15","PP4_17","PP4_18","PP4_19","PP4_21","PP4_22","PP4_23","CL1_2","CL1_3","CL1_4","CL1_5","CL1_6","CL1_8","CL1_9","CL1_12","CL1_21","CL1_22","CL1_24","CL1_25","CL1_27","CL2_1","CL2_4","CL2_5","CL2_8","CL2_13","CL2_16","CL2_18","CL3_3","CL3_4","CL3_7","CL3_8","CL3_13","CL3_15","CL3_16","CL3_21","CL4_1","CL4_6","CL4_7","CL4_8","CL4_9","CL4_10","CL4_12","CL4_13","CL4_14","CL4_16","CL4_18","H11_1","H11_2","H11_3","H11_4","H11_5","H11_6","H11_7","H11_8","H11_9","H12_1","H12_2","H12_6","H12_7","H12_8","H12_9","H12_10","H12_12","H12_13","H12_14","H12_17","H12_18","H12_19","H12_20","H13_2","H13_4","H13_5","H13_6","H13_7","H13_8","H13_9","H13_10","H13_11","H13_12","H13_13","H14_1","H14_2","H14_3","H14_4","H14_6","H14_8","H14_9","H14_10","H14_11","H14_13","H14_15","H15_1","H15_4","H15_5","H15_12","H15_13","H15_14","H15_18","H15_23","H15_24","H15_27","H15_28","H21_2","H21_3","H21_4","H21_5","H21_6","H21_7","H21_10","H21_11","H21_12","H22_1","H22_2","H22_3","H22_5","H22_13","H22_14","H22_17","H23_1","H23_3","H23_4","H23_5","H23_6","H23_7","H23_8","H23_9","H23_10","H23_11","H23_13","H23_16","H23_18","T16_21","T24_22","TT3_15","PM1","PM1","PM1","HB3_8","HB3_9","HB3_12","HB3_13","HB3_15","HB4_1","HB4_7","HB5_12","HB6_9","HB6_10","HB6_11","HB9_20","HB10_34","HB10_35","HB12_3","HB12_5","HB12_7","HB12_13","HB13_7","HB13_17","HB13_19","HB13_21","HB13_22","HB13_23","HB13_24","HB13_25","JM1_3","JM1_5","JM1_21","JM1_22","JM2_2","JM2_4","JM2_6","JM2_7","JM2_16","JM3_13","JM3_14","JM4_1","JM4_2","JM4_3","JM4_7","JM4_8","JM4_9","JM4_10","JM4_14","JM4_15","JM4_16","JM5_1","JM5_2","JM5_3","JM5_4","JM5_5","JM5_6","JM5_8","JM5_12","JM5_13","JM5_14","JM5_19","P11_2","P11_4","P11_7","P11_9","P11_10","P11_12","P11_13","P11_14","P11_15","P11_17","P11_18","P11_20","P11_21","P11_22","P11_25","P12_7","P12_9","P12_12","P12_21","P12_25","P13_2","P13_7","P13_13","P13_15","P13_16","P13_18","P13_21","P14_1","P14_4","P14_8","P14_12","P14_14","P14_15","P15_1","P15_2","P15_6","P15_7","P15_8","P15_9","P15_10","P15_12","P15_13","P15_14","P21_2","P21_5","P21_8","P21_10","P21_11","P21_12","P21_13","P21_15","P21_16","P21_19","P22_1","P22_3","P22_13","P23_1","P23_2","P23_8","P23_9","P23_11","P23_15","P23_17","J11_2","J11_3","J11_5","J12_1","J12_7","J12_8","J12_12","J12_13","J12_14","J12_20","J12_21","J12_24","J12_26","J12_27","J13_7","J13_13","J14_4","J15_13","J15_21","J21","J21","JD1","JD1","JD1","JD1","JD1","JD1","JD1","JD1","JD1","RV1_4","RV1_9","RV2_10","RV2_13","RV2_23","RV2_24","RV12_12","RV18_20","RV22_16"],
15334"verbs":["GN1_22","GN1_28","GN3_1","GN3_3","GN3_4","GN3_5","GN4_23","GN9_1","GN9_4","GN9_7","GN17_10","GN17_11","GN18_4","GN18_5","GN19_2","GN19_7","GN19_8","GN19_14","GN22_5","GN23_4","GN23_8","GN24_49","GN24_54","GN24_56","GN26_27","GN29_5","GN29_7","GN31_6","GN31_46","GN32_4","GN32_16","GN32_19","GN32_20","GN34_8","GN34_9","GN34_10","GN34_11","GN34_12","GN34_15","GN34_17","GN34_30","GN35_2","GN37_6","GN37_20","GN37_22","GN37_27","GN38_24","GN39_14","GN40_8","GN41_55","GN42_1","GN42_2","GN42_7","GN42_9","GN42_12","GN42_15","GN42_16","GN42_18","GN42_19","GN42_20","GN42_22","GN42_33","GN42_34","GN42_36","GN42_38","GN43_2","GN43_3","GN43_5","GN43_6","GN43_7","GN43_11","GN43_12","GN43_13","GN43_23","GN43_27","GN43_29","GN43_31","GN44_4","GN44_5","GN44_10","GN44_15","GN44_17","GN44_21","GN44_23","GN44_25","GN44_27","GN44_29","GN45_1","GN45_4","GN45_5","GN45_8","GN45_9","GN45_13","GN45_17","GN45_18","GN45_19","GN45_24","GN46_34","GN47_16","GN47_23","GN47_24","GN49_1","GN49_2","GN49_29","GN50_4","GN50_17","GN50_19","GN50_20","GN50_21","GN50_25","EX1_16","EX1_18","EX1_22","EX2_18","EX2_20","EX3_12","EX3_18","EX3_21","EX3_22","EX4_15","EX5_4","EX5_5","EX5_7","EX5_8","EX5_11","EX5_13","EX5_14","EX5_16","EX5_18","
15334EX5_19","EX5_21","EX6_7","EX6_26","EX7_9","EX8_8","EX8_25","EX8_28","EX9_8","EX9_28","EX9_30","EX10_2","EX10_8","EX10_10","EX10_11","EX10_17","EX10_24","EX11_7","EX12_3","EX12_4","EX12_5","EX12_9","EX12_10","EX12_11","EX12_14","EX12_15","EX12_17","EX12_18","EX12_20","EX12_21","EX12_22","EX12_24","EX12_25","EX12_27","EX12_31","EX12_32","EX12_46","EX13_3","EX13_19","EX14_2","EX14_13","EX14_14","EX15_21","EX16_3","EX16_6","EX16_7","EX16_9","EX16_12","EX16_16","EX16_23","EX16_25","EX16_26","EX16_28","EX16_29","EX17_2","EX19_4","EX19_5","EX19_6","EX19_12","EX19_15","EX20_20","EX20_22","EX20_23","EX22_21","EX22_22","EX22_25","EX22_31","EX23_9","EX23_13","EX23_25","EX24_1","EX24_14","EX25_2","EX25_3","EX25_9","EX25_19","EX30_9","EX30_32","EX30_37","EX31_13","EX31_14","EX32_2","EX32_24","EX32_27","EX32_29","EX32_30","EX34_13","EX35_3","EX35_5","EX35_30","LV1_2","LV2_11","LV2_12","LV3_17","LV7_23","LV7_24","LV7_26","LV7_32","LV8_31","LV8_32","LV8_33","LV8_35","LV9_3","LV9_6","LV10_4","LV10_6","LV10_7","LV10_9","LV10_12","LV10_13","LV10_14","LV10_17","LV10_18","LV11_2","LV11_3","LV11_4","LV11_8","LV11_9","LV11_11","LV11_13","LV11_21","LV11_22","LV11_24","LV11_33","LV11_42","LV11_43","LV11_44","LV11_45","LV14_34","LV15_2","LV15_31","LV16_29","LV16_30","LV16_31","LV17_14","LV18_3","LV18_4","LV18_5","LV18_6","LV18_24","LV18_26","LV18_30","LV19_2","LV19_3","LV19_4","LV19_5","LV19_11","LV19_12","LV19_15","LV19_19","LV19_23","LV19_25","LV19_26","LV19_27","LV19_28","LV19_30","LV19_31","LV19_33","LV19_34","LV19_35","LV19_37","LV20_7","LV20_8","LV20_15","LV20_22","LV20_23","LV20_24","LV20_25","LV20_26","LV22_20","LV22_22","LV22_24","LV22_25","LV22_28","LV22_29","LV22_30","LV22_31","LV22_32","LV23_2","LV23_3","LV23_4","LV23_6","LV23_7","LV23_8","LV23_10","LV23_12","LV23_14","LV23_15","LV23_16","LV23_17","LV23_18","LV23_19","LV23_21","LV23_25","LV23_27","LV23_28","LV23_31","LV23_32","LV23_35","LV23_36","LV23_37","LV23_38","LV23_39","LV23_40","LV23_41","LV23_42","LV25_2","LV25_9","LV25_10","LV25_11","LV25_12","LV25_13","LV25_14","LV25_17","LV25_18","LV25_19","LV25_20","LV25_22","LV25_24","LV25_44","LV25_45","LV25_46","LV26_1","LV26_2","LV26_3","LV26_5","LV26_6","LV26_7","LV26_10","LV26_12","LV26_14","LV26_15","LV26_16","LV26_17","LV26_18","LV26_21","LV26_23","LV26_25","LV26_26","LV26_27","LV26_29","LV26_38","NU1_2","NU1_3","NU4_18","NU4_19","NU4_27","NU4_32","NU5_3","NU6_23","NU9_3","NU9_8","NU10_5","NU10_6","NU10_7","NU10_9","NU10_10","NU11_18","NU11_19","NU11_20","NU12_4","NU12_6","NU12_8","NU13_2","NU13_17","NU13_18","NU13_20","NU14_9","NU14_25","NU14_28","NU14_29","NU14_30","NU14_31","NU14_34","NU14_42","NU14_43","NU15_2","NU15_3","NU15_12","NU15_14","NU15_19","NU15_20","NU15_21","NU15_22","NU15_39","NU15_40","NU16_3","NU16_6","NU16_7","NU16_8","NU16_10","NU16_11","NU16_16","NU16_17","NU16_21","NU16_24","NU16_26","NU16_28","NU16_30","NU16_38","NU16_41","NU16_45","NU18_1","NU18_5","NU18_7","NU18_26","NU18_28","NU18_29","NU18_31","NU18_32","NU19_3","NU20_4","NU20_5","NU20_8","NU20_10","NU20_12","NU20_24","NU21_5","NU21_17","NU21_27","NU22_8","NU22_13","NU22_19","NU25_5","NU25_17","NU26_2","NU27_8","NU27_9","NU27_10","NU27_11","NU27_14","NU28_2","NU28_3","NU28_11","NU28_18","NU28_19","NU28_20","NU28_23","NU28_24","NU28_25","NU28_26","NU28_27","NU28_31","NU29_1","NU29_2","NU29_7","NU29_8","NU29_12","NU29_13","NU29_35","NU29_36","NU29_39","NU31_3","NU31_4","NU31_15","NU31_17","NU31_18","NU31_19","NU31_20","NU31_23","NU31_24","NU31_29","NU32_6","NU32_7","NU32_14","NU32_15","NU32_20","NU32_22","NU32_23","NU32_24","NU32_29","NU33_52","NU33_53","NU33_54","NU33_55","NU34_7","NU34_8","NU34_10","NU34_13","NU34_18","NU35_2","NU35_4","NU35_5","NU35_6","NU35_7","NU35_8","NU35_11","NU35_13","NU35_14","NU35_31","NU35_32","NU35_33","DT1_7","DT1_8","DT1_13","DT1_14","DT1_16","DT1_17","DT1_18","DT1_19","DT1_20","DT1_22","DT1_26","DT1_27","DT1_29","DT1_31","DT1_33","DT1_39","DT1_40","DT1_41","DT1_42","DT1_43","DT1_45","DT1_46","DT2_3","DT2_4","DT2_5","DT2_6","DT2_13","DT2_24","DT3_18","DT3_20","DT3_22","DT4_1","DT4_2","DT4_6","DT4_11","DT4_15","DT4_16","DT4_22","DT4_23","DT4_25","DT4_26","DT4_27","DT4_28","DT4_29","DT5_1","DT5_5","DT5_23","DT5_24","DT5_30","DT5_32","DT5_33","DT6_3","DT6_14","DT6_16","DT6_17","DT7_5","DT7_12","DT7_25","DT8_1","DT8_19","DT8_20","DT9_7","DT9_8","DT9_16","DT9_18","DT9_21","DT9_22","DT9_23","DT9_24","DT10_16","DT10_19","DT11_2","DT11_8","DT11_9","DT11_10","DT11_13","DT11_16","DT11_17","DT11_18","DT11_19","DT11_22","DT11_23","DT11_25","DT11_27","DT11_28","DT11_31","DT11_32","DT12_1","DT12_2","DT12_3","DT12_4","DT12_5","DT12_6","DT12_7","DT12_8","DT12_9","DT12_10","DT12_11","DT12_12","DT12_16","DT12_32","DT13_4","DT13_13","DT14_1","DT14_4","DT14_6","DT14_7","DT14_8","DT14_9","DT14_10","DT14_11","DT14_12","DT14_20","DT14_21","DT17_16","DT18_15","DT19_19","DT20_3","DT20_18","DT22_24","DT24_8","DT27_2","DT27_4","DT28_62","DT28_63","DT28_68","DT29_2","DT29_6","DT29_7","DT29_9","DT29_16","DT29_17","DT30_18","DT31_5","DT31_6","DT31_14","DT31_19","DT31_26","DT31_27","DT31_28","DT31_29","DT32_1","DT32_3","DT32_6","DT32_7","DT32_39","DT32_43","DT32_46","DT32_47","DT32_51","JS1_11","JS1_14","JS1_15","JS2_1","JS2_5","JS2_10","JS2_12","JS2_13","JS2_14","JS2_16","JS3_3","JS3_4","JS3_5","JS3_6","JS3_8","JS3_9","JS3_10","JS3_12","JS4_2","JS4_3","JS4_5","JS4_7","JS4_17","JS4_22","JS4_24","JS6_3","JS6_4","JS6_6","JS6_7","JS6_10","JS6_16","JS6_18","JS6_22","JS7_2","JS7_12","JS7_13","JS7_14","JS8_2","JS8_4","JS8_7","JS8_8","JS9_6","JS9_8","JS9_11","JS9_22","JS10_4","JS10_18","JS10_19","JS10_22","JS10_24","JS10_25","JS18_4","JS18_6","JS18_8","JS20_2","JS22_2","JS22_3","JS22_4","JS22_5","JS22_8","JS22_16","JS22_18","JS22_19","JS22_28","JS22_31","JS23_3","JS23_4","JS23_5","JS23_6","JS23_7","JS23_8","JS23_11","JS23_12","JS23_13","JS23_14","JS23_16","JS24_6","JS24_7","JS24_8","JS24_11","JS24_13","JS24_14","JS24_15","JS24_19","JS24_20","JS24_22","JS24_23","JS24_27","JG2_2","JG3_28","JG5_2","JG5_3","JG5_9","JG5_10","JG5_23","JG6_10","JG6_31","JG7_15","JG7_17","JG7_18","JG7_24","JG8_5","JG8_15","JG8_18","JG8_19","JG8_24","JG9_2","JG9_7","JG9_15","JG9_16","JG9_18","JG9_19","JG9_28","JG9_48","JG10_12","JG10_13","JG10_14","JG11_7","JG11_26","JG12_2","JG12_3","JG14_2","JG14_12","JG14_13","JG14_15","JG14_18","JG15_7","JG15_10","JG15_12","JG16_18","JG16_25","JG18_2","JG18_6","JG18_9","JG18_10","JG18_14","JG18_24","JG19_5","JG19_9","JG19_23","JG19_24","JG19_30","JG20_3","JG20_7","JG20_13","JG20_23","JG20_28","JG21_10","JG21_11","JG21_20","JG21_21","JG21_22","RT1_8","RT1_9","RT1_11","RT1_12","RT1_13","RT1_20","RT1_21","RT2_15","RT2_16","RT4_2","S12_3","S12_23","S12_29","S14_9","S15_11","S16_2","S16_3","S16_5","S16_6","S16_7","S16_8","S16_9","S16_21","S17_3","S17_5","S18_17","S18_18","S18_22","S19_9","S19_13","S19_19","S110_14","S110_19","S110_24","S111_9","S111_10","S111_12","S111_14","S112_1","S112_3","S112_5","S112_7","S112_11","S112_12","S112_13","S112_14","S112_15","S112_16","S112_17","S112_20","S112_21","S112_24","S112_25","S113_9","S114_9","S114_10","S114_12","S114_17","S114_27","S114_29","S114_33","S114_34","S114_38","S114_40","S114_42","S115_3","S115_6","S115_32","S116_5","S116_17","S117_8","S117_9","S117_10","S117_25","S118_22","S118_25","S119_15","S121_14","S121_15","S122_7","S122_8","S122_13","S122_17","S123_21","S123_22","S123_23","S125_5","S125_6","S125_13","S125_19","S126_16","S127_10","S128_7","S129_10","S130_23","S21_20","S21_24","S22_5","S22_6","S22_7","S23_17","S23_18","S23_31","S23_38","S27_7","S210_5","S211_15","S211_20","S211_21","S213_9","S213_17","S213_28","S214_30","S215_10","S215_14","S215_28","S215_36","S216_11","S216_20","S217_16","S217_21","S218_12","S219_11","S219_12","S219_13","S2
1533419_22","S219_29","S220_16","S220_21","S221_3","S224_2","K11_28","K11_32","K11_33","K11_34","K11_35","K11_45","K13_24","K13_25","K13_26","K13_27","K19_6","K111_2","K112_5","K112_12","K112_24","K113_4","K113_13","K113_27","K113_31","K118_21","K118_24","K118_25","K118_27","K118_30","K118_33","K118_34","K118_40","K120_7","K120_9","K120_11","K120_12","K120_18","K120_28","K120_33","K121_9","K121_10","K122_3","K122_27","K122_28","K122_31","K21_2","K21_5","K21_6","K22_3","K22_5","K22_16","K22_17","K22_18","K22_20","K23_15","K23_17","K23_19","K24_41","K25_7","K26_2","K26_11","K26_13","K26_19","K26_32","K27_1","K27_4","K27_9","K27_14","K29_11","K29_27","K29_33","K29_34","K210_3","K210_6","K210_8","K210_10","K210_14","K210_19","K210_20","K210_23","K210_25","K211_5","K211_6","K211_8","K211_15","K212_7","K217_12","K217_13","K217_27","K217_35","K217_36","K217_37","K217_38","K217_39","K218_19","K218_22","K218_28","K218_31","K218_32","K218_36","K219_6","K219_10","K219_29","K220_7","K222_13","K222_15","K222_18","K223_18","K223_21","K225_24","R112_17","R115_12","R116_8","R116_9","R116_10","R116_11","R116_12","R116_15","R116_22","R116_23","R116_24","R116_28","R116_29","R116_30","R116_34","R116_35","R117_6","R119_5","R121_2","R122_19","R128_2","R128_8","R129_20","R27_19","R210_5","R210_12","R211_4","R212_5","R213_4","R213_9","R213_11","R213_12","R215_2","R215_7","R218_14","R218_18","R218_25","R218_26","R218_27","R218_30","R219_6","R219_7","R219_9","R219_10","R219_11","R220_15","R220_16","R220_17","R220_20","R220_21","R223_4","R223_7","R223_14","R224_5","R224_20","R228_9","R228_11","R228_13","R229_5","R229_11","R229_31","R230_6","R230_7","R230_8","R230_9","R232_7","R232_12","R232_13","R232_15","R234_21","R234_23","R234_26","R235_3","R235_4","R235_5","R235_6","R235_23","ER8_29","ER9_12","ER10_10","ER10_11","NH1_8","NH1_9","NH2_17","NH4_12","NH4_14","NH4_20","NH5_8","NH5_9","NH5_11","NH7_3","NH8_9","NH8_10","NH8_11","NH8_15","NH9_5","NH13_21","NH13_25","ET4_14","ET4_16","ET5_5","ET7_9","ET8_8","JB6_21","JB6_22","JB6_23","JB6_24","JB6_26","JB6_27","JB6_28","JB6_29","JB13_5","JB13_6","JB13_7","JB13_8","JB13_9","JB13_10","JB13_13","JB13_17","JB17_10","JB18_2","JB19_2","JB19_3","JB19_5","JB19_6","JB19_15","JB19_21","JB19_22","JB19_28","JB19_29","JB21_2","JB21_3","JB21_5","JB21_27","JB21_28","JB21_29","JB21_34","JB27_12","JB32_11","JB32_13","JB34_2","JB34_10","JB37_2","JB42_7","JB42_8","PS2_10","PS2_11","PS2_12","PS4_2","PS4_3","PS4_4","PS4_5","PS6_8","PS9_11","PS11_1","PS14_6","PS22_23","PS24_7","PS24_9","PS27_8","PS29_1","PS29_2","PS30_4","PS31_23","PS31_24","PS32_9","PS32_11","PS33_1","PS33_2","PS33_3","PS34_3","PS34_8","PS34_9","PS34_11","PS46_8","PS46_10","PS47_1","PS47_6","PS47_7","PS48_12","PS48_13","PS49_1","PS50_5","PS50_22","PS58_1","PS58_2","PS62_3","PS62_8","PS62_10","PS66_1","PS66_2","PS66_3","PS66_5","PS66_8","PS66_16","PS68_4","PS68_13","PS68_16","PS68_26","PS68_32","PS68_34","PS71_11","PS75_4","PS75_5","PS76_11","PS78_1","PS81_1","PS81_2","PS81_3","PS82_2","PS82_3","PS82_4","PS82_7","PS83_4","PS90_3","PS94_8","PS95_1","PS95_6","PS95_7","PS95_8","PS96_1","PS96_2","PS96_3","PS96_7","PS96_8","PS96_9","PS96_10","PS97_7","PS97_10","PS97_12","PS98_1","PS98_4","PS98_5","PS98_6","PS99_5","PS99_9","PS100_1","PS100_2","PS100_3","PS100_4","PS103_20","PS103_21","PS103_22","PS105_1","PS105_2","PS105_3","PS105_4","PS105_5","PS105_15","PS106_1","PS107_1","PS113_1","PS114_6","PS115_10","PS115_11","PS117_1","PS118_1","PS118_19","PS118_27","PS118_29","PS119_115","PS122_6","PS134_1","PS134_2","PS135_1","PS135_3","PS135_19","PS135_20","PS136_1","PS136_2","PS136_3","PS136_26","PS137_3","PS137_7","PS139_19","PS146_3","PS147_7","PS148_1","PS148_2","PS148_3","PS148_4","PS148_7","PS149_1","PS150_1","PS150_2","PS150_3","PS150_4","PS150_5","PR1_22","PR1_23","PR1_24","PR1_25","PR4_1","PR4_2","PR5_7","PR7_24","PR8_5","PR8_6","PR8_10","PR8_32","PR8_33","PR9_5","PR9_6","PR31_6","PR31_31","SS1_6","SS2_5","SS2_7","SS2_15","SS3_3","SS3_5","SS3_11","SS5_1","SS5_8","SS6_13","SS8_4","IS1_2","IS1_5","IS1_10","IS1_12","IS1_13","IS1_15","IS1_16","IS1_17","IS1_18","IS1_19","IS1_20","IS1_29","IS1_30","IS2_3","IS2_5","IS2_22","IS3_7","IS3_10","IS3_14","IS3_15","IS5_3","IS5_8","IS6_9","IS7_9","IS7_13","IS8_9","IS8_10","IS8_12","IS8_13","IS8_19","IS10_3","IS12_3","IS12_4","IS12_5","IS13_2","IS13_6","IS14_21","IS16_1","IS16_3","IS16_7","IS18_2","IS18_3","IS19_11","IS21_5","IS21_12","IS21_13","IS21_14","IS22_4","IS22_9","IS22_
1533410","IS22_11","IS22_14","IS23_1","IS23_2","IS23_6","IS23_13","IS23_14","IS24_15","IS26_2","IS26_4","IS26_19","IS27_2","IS27_12","IS28_12","IS28_14","IS28_15","IS28_18","IS28_22","IS28_23","IS29_1","IS29_9","IS30_10","IS30_11","IS30_12","IS30_15","IS30_16","IS30_17","IS30_21","IS30_22","IS31_6","IS32_9","IS32_10","IS32_11","IS33_11","IS33_13","IS34_1","IS34_16","IS35_3","IS35_4","IS36_4","IS36_7","IS36_13","IS36_16","IS36_21","IS37_6","IS37_10","IS37_30","IS40_1","IS40_2","IS40_3","IS40_18","IS40_21","IS40_25","IS40_26","IS41_1","IS41_21","IS41_22","IS41_23","IS42_10","IS42_18","IS43_10","IS43_18","IS43_19","IS44_8","IS44_23","IS45_8","IS45_11","IS45_17","IS45_19","IS45_20","IS45_21","IS45_22","IS46_3","IS46_5","IS46_8","IS46_9","IS46_12","IS48_1","IS48_6","IS48_14","IS48_16","IS48_20","IS49_1","IS49_9","IS49_13","IS50_1","IS50_11","IS51_1","IS51_2","IS51_4","IS51_6","IS51_7","IS52_2","IS52_3","IS52_9","IS52_11","IS52_12","IS55_1","IS55_2","IS55_3","IS55_6","IS55_12","IS56_1","IS56_9","IS56_12","IS57_3","IS57_4","IS57_14","IS58_3","IS58_4","IS58_6","IS61_6","IS62_7","IS62_10","IS62_11","IS65_12","IS65_13","IS65_14","IS65_15","IS65_18","IS66_1","IS66_5","IS66_10","IS66_11","IS66_12","IS66_13","IS66_14","JR2_4","JR2_7","JR2_10","JR2_12","JR2_19","JR2_29","JR2_31","JR3_13","JR3_14","JR3_16","JR3_19","JR3_20","JR3_22","JR4_3","JR4_4","JR4_5","JR4_6","JR4_8","JR4_16","JR5_1","JR5_10","JR5_19","JR5_20","JR5_21","JR5_22","JR5_31","JR6_1","JR6_4","JR6_5","JR6_6","JR6_16","JR6_17","JR6_18","JR7_2","JR7_3","JR7_4","JR7_5","JR7_6","JR7_9","JR7_10","JR7_12","JR7_13","JR7_21","JR7_23","JR8_8","JR8_14","JR9_4","JR9_17","JR9_20","JR10_1","JR10_2","JR10_5","JR11_2","JR11_4","JR11_6","JR11_7","JR11_13","JR12_9","JR12_13","JR13_15","JR13_16","JR13_17","JR13_18","JR13_23","JR14_13","JR16_12","JR16_13","JR17_4","JR17_20","JR17_21","JR17_22","JR17_24","JR17_27","JR18_11","JR18_13","JR18_18","JR19_3","JR20_6","JR20_10","JR20_13","JR21_3","JR21_11","JR21_12","JR22_3","JR22_4","JR22_5","JR22_10","JR22_26","JR22_30","JR23_2","JR23_16","JR23_20","JR23_35","JR23_36","JR23_38","JR25_3","JR25_4","JR25_5","JR25_6","JR25_7","JR25_8","JR25_27","JR25_28","JR25_29","JR25_34","JR26_4","JR26_5","JR26_11","JR26_12","JR26_13","JR26_14","JR26_15","JR27_4","JR27_9","JR27_10","JR27_12","JR27_13","JR27_14","JR27_15","JR27_16","JR27_17","JR29_5","JR29_6","JR29_7","JR29_8","JR29_12","JR29_13","JR29_15","JR29_19","JR29_20","JR29_28","JR30_6","JR30_22","JR30_24","JR31_6","JR31_7","JR31_10","JR31_34","JR32_5","JR33_11","JR33_20","JR34_14","JR34_15","JR34_16","JR34_17","JR35_5","JR35_6","JR35_7","JR35_11","JR35_13","JR35_14","JR35_15","JR35_18","JR37_7","JR37_9","JR37_10","JR37_18","JR40_3","JR40_9","JR40_10","JR41_6","JR42_9","JR42_10","JR42_11","JR42_15","JR42_16","JR42_18","JR42_19","JR42_20","JR42_21","JR42_22","JR43_2","JR44_2","JR44_4","JR44_9","JR44_21","JR44_22","JR44_23","JR44_24","JR44_25","JR44_26","JR44_29","JR46_3","JR46_4","JR46_9","JR46_14","JR48_2","JR48_6","JR48_9","JR48_14","JR48_17","JR48_20","JR48_26","JR48_28","JR49_3","JR49_5","JR49_8","JR49_11","JR49_14","JR49_20","JR49_28","JR49_30","JR49_31","JR50_2","JR50_5","JR50_8","JR50_14","JR50_15","JR50_16","JR50_26","JR50_27","JR50_29","JR50_45","JR51_3","JR51_6","JR51_8","JR51_9","JR51_10","JR51_11","JR51_12","JR51_27","JR51_28","JR51_45","JR51_46","JR51_50","LM1_12","LM1_18","LM4_15","EK5_7","EK6_3","EK6_6","EK6_7","EK6_13","EK7_4","EK7_9","EK9_1","EK9_5","EK9_6","EK9_7","EK11_5","EK11_6","EK11_7","EK11_8","EK11_10","EK11_11","EK11_12","EK11_15","EK11_17","EK12_20","EK13_2","EK13_5","EK13_7","EK13_8","EK13_9","EK13_11","EK13_12","EK13_14","EK13_18","EK13_19","EK13_21","EK13_23","EK14_6","EK14_8","EK14_22","EK14_23","EK15_7","EK17_12","EK17_21","EK18_19","EK18_25","EK18_30","EK18_31","EK18_32","EK20_7","EK20_18","EK20_19","EK20_20","EK20_34","EK20_38","EK20_39","EK20_41","EK20_42","EK20_43","EK20_44","EK21_24","EK22_21","EK22_22","EK23_49","EK24_21","EK24_22","EK24_23","EK24_24","EK25_3","EK25_5","EK30_2","EK32_20","EK33_10","EK33_11","EK33_20","EK33_25","EK33_26","EK33_30","EK34_3","EK34_4","EK34_7","EK34_9","EK34_18","EK34_21","EK35_9","EK35_13","EK36_1","EK36_3","EK36_4","
15334EK36_6","EK36_8","EK36_9","EK36_11","EK36_22","EK36_23","EK36_25","EK36_27","EK36_28","EK36_30","EK36_31","EK36_32","EK37_4","EK37_5","EK37_6","EK37_7","EK37_13","EK37_14","EK39_17","EK39_18","EK39_19","EK39_20","EK44_8","EK44_28","EK44_30","EK45_1","EK45_6","EK45_9","EK45_13","EK45_20","EK47_13","EK47_14","EK47_18","EK47_21","EK47_22","EK47_23","EK48_8","EK48_9","EK48_20","EK48_29","DN1_10","HS2_1","HS2_2","HS4_1","HS4_15","HS4_18","HS5_1","HS5_8","HS6_1","HS9_5","HS10_8","HS10_12","HS10_13","HS14_2","JL1_2","JL1_3","JL1_5","JL1_13","JL1_14","JL2_1","JL2_12","JL2_13","JL2_15","JL2_16","JL2_19","JL2_22","JL2_23","JL2_26","JL2_27","JL3_5","JL3_6","JL3_7","JL3_9","JL3_10","JL3_11","JL3_13","JL3_17","AM2_12","AM3_1","AM3_9","AM3_13","AM4_1","AM4_3","AM4_4","AM4_5","AM4_6","AM4_8","AM4_9","AM4_10","AM4_11","AM5_1","AM5_4","AM5_5","AM5_6","AM5_11","AM5_14","AM5_15","AM5_22","AM5_25","AM5_26","AM6_2","AM6_3","AM6_12","AM8_4","OB1","OB1","OB1","JH1_7","JH1_12","MC1_2","MC1_10","MC2_3","MC2_6","MC2_8","MC2_9","MC2_10","MC3_1","MC3_9","MC4_2","MC6_1","MC6_2","MC6_9","MC6_16","MC7_5","NM1_9","NM2_8","NM2_9","HK1_5","ZP1_11","ZP2_1","ZP2_3","ZP3_8","ZP3_14","HG1_5","HG1_6","HG1_7","HG1_8","HG1_9","HG2_4","HG2_5","HG2_15","HG2_18","ZC1_3","ZC1_4","ZC2_6","ZC2_9","ZC3_4","ZC3_10","ZC6_7","ZC6_15","ZC7_5","ZC7_6","ZC7_9","ZC7_10","ZC8_13","ZC8_15","ZC8_16","ZC8_17","ZC8_19","ZC9_12","ZC10_1","ZC11_2","ZC11_12","ZC14_5","ML1_2","ML1_5","ML1_6","ML1_7","ML1_8","ML1_9","ML1_10","ML1_13","ML2_2","ML2_4","ML2_8","ML2_13","ML2_14","ML2_15","ML2_16","ML2_17","ML3_6","ML3_7","ML3_8","ML3_10","ML3_12","ML3_13","ML3_14","ML3_18","ML4_2","ML4_3","ML4_4","MT2_8","MT3_2","MT3_3","MT3_8","MT3_9","MT4_17","MT5_11","MT5_12","MT5_13","MT5_14","MT5_17","MT5_20","MT5_21","MT5_27","MT5_33","MT5_38","MT5_43","MT5_44","MT5_45","MT5_46","MT5_47","MT5_48","MT6_1","MT6_5","MT6_7","MT6_8","MT6_9","MT6_14","MT6_15","MT6_16","MT6_19","MT6_20","MT6_24","MT6_25","MT6_26","MT6_28","MT6_31","MT6_32","MT6_33","MT6_34","MT7_1","MT7_2","MT7_6","MT7_7","MT7_11","MT7_12","MT7_13","MT7_15","MT7_16","MT7_20","MT7_23","MT8_26","MT8_32","MT9_4","MT9_6","MT9_13","MT9_24","MT9_28","MT9_30","MT9_38","MT10_5","MT10_6","MT10_7","MT10_8","MT10_9","MT10_11","MT10_12","MT10_14","MT10_16","MT10_17","MT10_18","MT10_19","MT10_20","MT10_22","MT10_23","MT10_26","MT10_27","MT10_28","MT10_31","MT10_34","MT11_4","MT11_7","MT11_8","MT11_9","MT11_14","MT11_17","MT11_29","MT12_3","MT12_5","MT12_7","MT12_33","MT12_34","MT13_14","MT13_17","MT13_18","MT13_29","MT13_30","MT13_51","MT14_16","MT14_18","MT14_27","MT15_3","MT15_5","MT15_6","MT15_10","MT15_14","MT15_16","MT15_17","MT15_34","MT16_2","MT16_3","MT16_6","MT16_8","MT16_9","MT16_10","MT16_11","MT16_15","MT17_5","MT17_7","MT17_9","MT17_17","MT17_20","MT18_3","MT18_10","MT18_18","MT18_35","MT19_4","MT19_14","MT19_28","MT20_4","MT20_6","MT20_7","MT20_22","MT20_23","MT20_25","MT20_32","MT21_2","MT21_3","MT21_5","MT21_13","MT21_16","MT21_21","MT21_22","MT21_24","MT21_25","MT21_32","MT21_33","MT21_42","MT22_4","MT22_9","MT22_13","MT22_18","MT22_19","MT22_21","MT22_29","MT22_31","MT23_3","MT23_8","MT23_9","MT23_10","MT23_13","MT23_15","MT23_23","MT23_25","MT23_27","MT23_28","MT23_29","MT23_30","MT23_31","MT23_32","MT23_33","MT23_34","MT23_35","MT23_37","MT23_39","MT24_2","MT24_4","MT24_6","MT24_9","MT24_15","MT24_20","MT24_23","MT24_26","MT24_32","MT24_33","MT24_42","MT24_43","MT24_44","MT25_6","MT25_8","MT25_9","MT25_13","MT25_28","MT25_30","MT25_34","MT25_35","MT25_36","MT25_40","MT25_41","MT25_42","MT25_43","MT25_45","MT26_2","MT26_10","MT26_11","MT26_15","MT26_18","MT26_26","MT26_27","MT26_31","MT26_36","MT26_38","MT26_40","MT26_41","MT26_45","MT26_46","MT26_48","MT26_55","MT26_64","MT26_65","MT27_17","MT27_21","MT27_24","MT27_65","MT28_5","MT28_6","MT28_7","MT28_9","MT28_10","MT28_13","MT28_19","MK1_3","MK1_15","MK2_8","MK2_10","MK2_25","MK4_3","MK4_13","MK4_24","MK4_40","MK5_39","MK6_9","MK6_10","MK6_11","MK6_31","MK6_37","MK6_38","MK6_50","MK7_8","MK7_9","MK7_11","MK7_12","MK7_13","MK7_14","MK7_18","MK8_5","MK8_15","MK8_17","MK8_18","MK8_19","MK8_20","MK8_21","MK8_29","MK9_7","MK9_16","MK9_19","MK9_33","MK9_39","MK9_41","MK9_50","MK10_14","MK10_36","MK10_38","MK10_39","MK10_42","MK10_49","MK11_2","MK11_3","MK11_5","MK11_17","MK11_22","MK11_24","MK11_25","MK11_29","MK11_30","MK11_31","MK12_10","MK12_15","MK12_17","MK12_24","MK12_26","MK12_27","MK12_38","MK13_5","MK13_7","MK13_9","MK13_11","MK13_13","MK13_14","MK13_18","MK13_21","MK13_23","MK13_28","MK13_29","MK13_33","MK13_35","MK13_37","MK14_6","MK14_7","MK14_13","MK14_14","MK14_15","MK14_22","MK14_27","MK14_32","MK14_34","MK14_38","MK14_41","MK14_42","MK14_44","MK14_48","MK14_49","MK14_62","MK14_64","MK14_71","MK15_9","MK15_12","MK15_36","MK16_6","MK16_7","MK16_15","LK2_10","LK2_12","LK2_49","LK3_4","LK3_8","LK3_13","LK3_14","LK4_23","LK5_4","LK5_22","LK5_24","LK5_30","LK5_34","LK6_2","LK6_3","LK6_21","LK6_22","LK6_23","LK6_24","LK6_25","LK6_27","LK6_28","LK6_31","LK6_32","LK6_33","LK6_34","LK6_35","LK6_36","LK6_37","LK6_38","LK6_46","LK7_22","LK7_24","LK7_25","LK7_26","LK7_32","LK7_33","LK7_34","LK8_18","LK8_52","LK9_3","LK9_4","LK9_5","LK9_13","LK9_14","LK9_20","LK9_35","LK9_44","LK9_50","LK10_2","LK10_3","LK10_4","LK10_5","LK10_7","LK10_8","LK10_9","LK10_10","LK10_11","LK10_20","LK10_23","LK10_24","LK11_2","LK11_9","LK11_13","LK11_18","LK11_39","LK11_41","LK11_42","LK11_43","LK11_44","LK11_46","LK11_47","LK11_48","LK11_52","LK12_1","LK12_3","LK12_4","LK12_5","LK12_7","LK12_11","LK12_15","LK12_22","LK12_24","LK12_26","LK12_27","LK12_29","LK12_30","LK12_31","LK12_33","LK12_39","LK12_40","LK12_51","LK12_54","LK12_55","LK12_56","LK12_57","LK13_2","LK13_3","LK13_4","LK13_5","LK13_14","LK13_24","LK13_25","LK13_26","LK13_27","LK13_28","LK13_32","LK13_34","LK13_35","LK14_17","LK15_6","LK15_9","LK15_22","LK15_23","LK16_9","LK16_11","LK16_12","LK16_13","LK16_15","LK17_3","LK17_6","LK17_10","LK17_14","LK17_22","LK17_23","LK17_32","LK18_6","LK18_16","LK19_13","LK19_24","LK19_27","LK19_30","LK19_31","LK19_33","LK19_46","LK20_3","LK20_5","
15334LK20_24","LK20_25","LK20_46","LK21_6","LK21_8","LK21_9","LK21_14","LK21_16","LK21_17","LK21_19","LK21_20","LK21_28","LK21_29","LK21_30","LK21_31","LK21_34","LK21_36","LK22_8","LK22_10","LK22_11","LK22_12","LK22_17","LK22_19","LK22_28","LK22_30","LK22_35","LK22_40","LK22_46","LK22_51","LK22_52","LK22_53","LK22_67","LK22_68","LK22_70","LK23_14","LK23_28","LK23_30","LK24_5","LK24_6","LK24_17","LK24_38","LK24_39","LK24_41","LK24_48","LK24_49","JN1_23","JN1_26","JN1_38","JN1_39","JN1_51","JN2_5","JN2_7","JN2_8","JN2_16","JN2_19","JN3_11","JN3_12","JN3_28","JN4_20","JN4_21","JN4_22","JN4_29","JN4_32","JN4_35","JN4_38","JN4_48","JN5_20","JN5_28","JN5_33","JN5_34","JN5_35","JN5_37","JN5_38","JN5_39","JN5_40","JN5_42","JN5_43","JN5_44","JN5_45","JN5_46","JN5_47","JN6_10","JN6_12","JN6_20","JN6_26","JN6_27","JN6_29","JN6_36","JN6_43","JN6_53","JN6_62","JN6_67","JN7_8","JN7_19","JN7_21","JN7_22","JN7_23","JN7_24","JN7_28","JN7_34","JN7_36","JN7_45","JN7_47","JN8_14","JN8_15","JN8_19","JN8_21","JN8_22","JN8_23","JN8_24","JN8_28","JN8_31","JN8_32","JN8_33","JN8_36","JN8_37","JN8_38","JN8_39","JN8_40","JN8_41","JN8_42","JN8_43","JN8_44","JN8_45","JN8_46","JN8_47","JN8_49","JN8_54","JN8_55","JN9_19","JN9_21","JN9_23","JN9_27","JN9_30","JN9_41","JN10_20","JN10_25","JN10_26","JN10_32","JN10_34","JN10_36","JN10_37","JN10_38","JN11_15","JN11_34","JN11_39","JN11_44","JN11_49","JN11_50","JN12_8","JN12_19","JN12_35","JN12_36","JN13_10","JN13_11","JN13_12","JN13_13","JN13_14","JN13_15","JN13_17","JN13_19","JN13_33","JN13_34","JN13_35","JN14_1","JN14_3","JN14_4","JN14_7","JN14_11","JN14_13","JN14_14","JN14_15","JN14_17","JN14_19","JN14_20","JN14_24","JN14_28","JN14_29","JN14_31","JN15_3","JN15_4","JN15_5","JN15_7","JN15_8","JN15_9","JN15_10","JN15_12","JN15_14","JN15_16","JN15_17","JN15_18","JN15_19","JN15_20","JN15_27","JN16_1","JN16_4","JN16_10","JN16_12","JN16_16","JN16_17","JN16_19","JN16_20","JN16_22","JN16_23","JN16_24","JN16_26","JN16_27","JN16_31","JN16_32","JN16_33","JN18_4","JN18_7","JN18_8","JN18_29","JN18_31","JN18_39","JN19_4","JN19_6","JN19_35","JN20_22","JN20_23","JN20_31","JN21_5","JN21_6","JN21_10","JN21_12","AC1_4","AC1_5","AC1_8","AC1_11","AC2_14","AC2_15","AC2_22","AC2_23","AC2_33","AC2_36","AC2_38","AC2_40","AC3_12","AC3_13","AC3_14","AC3_15","AC3_16","AC3_17","AC3_19","AC3_22","AC3_25","AC4_7","AC4_10","AC4_19","AC5_8","AC5_20","AC5_25","AC5_28","AC5_30","AC5_35","AC5_38","AC5_39","AC6_3","AC7_2","AC7_4","AC7_26","AC7_42","AC7_43","AC7_49","AC7_51","AC7_52","AC7_53","AC8_19","AC8_24","AC10_21","AC10_28","AC10_29","AC10_37","AC11_16","AC12_17","AC13_2","AC13_15","AC13_16","AC13_25","AC13_39","AC13_40","AC13_41","AC13_46","AC14_15","AC15_1","AC15_7","AC15_10","AC15_13","AC15_29","AC16_15","AC16_36","AC17_23","AC18_15","AC19_2","AC19_3","AC19_15","AC19_25","AC19_26","AC19_37","AC19_39","AC20_10","AC20_18","AC20_25","AC20_28","AC20_31","AC20_34","AC21_13","AC21_28","AC22_1","AC22_3","AC23_15","AC23_23","AC25_24","AC27_25","AC27_31","AC27_33","AC28_26","RM1_6","RM6_3","RM6_11","RM6_13","RM6_14","RM6_16","RM6_17","RM6_18","RM6_19","RM6_20","RM6_21","RM6_22","RM7_1","RM7_4","RM8_9","RM8_13","RM8_15","RM11_2","RM11_25","RM11_30","RM12_2","RM12_14","RM12_16","RM12_19","RM13_6","RM13_7","RM13_8","RM13_14","RM14_1","RM14_13","RM15_6","RM15_7","RM15_10","RM15_11","RM15_14","RM16_2","RM16_3","RM16_5","RM16_6","RM16_7","RM16_8","RM16_9","RM16_10","RM16_11","RM16_12","RM16_13","RM16_14","RM16_15","RM16_16","RM16_17","C11_5","C11_9","C11_10","C11_13","C11_15","C11_26","C11_30","C13_2","C13_3","C13_4","C13_5","C13_9","C13_16","C13_17","C14_5","C14_6","C14_8","C14_15","C14_16","C14_21","C15_2","C15_6","C15_7","C15_10","C15_12","C15_13","C16_2","C16_3","C16_4","C16_7","C16_8","C16_9","C16_11","C16_15","C16_16","C16_18","C16_19","C16_20","C17_1","C17_5","C17_23","C18_9","C18_12","C19_1","C19_2","C19_13","C19_24","C110_7","C110_10","C110_13","C110_14","C110_15","C110_18","C110_21","C110_25","C110_27","C110_28","C110_31","C110_32","C111_1","C111_2","C111_13","C111_17","C111_22","C111_24","C111_25","C111_26","C111_33","C111_34","C112_2","C112_27","C112_31","C114_1","C114_5","C114_9","C114_12","C114_20","C114_23","C114_26","C114_31","C114_39","C115_1","C115_2","C115_11","C115_17","C115_33","C115_34","C115_58","C116_1","C116_3","C116_6","C116_10","C116_11","C116_13","C116_15","C116_16","C116_18","C116_20","C21_7","C21_13","C21_14","C21_15","C21_24","C22_4","C22_9","C22_10","C23_2","C23_3","C25_12","C25_20","C26_12","C26_13","C26_14","C26_17","C26_18","C27_2","C27_3","C27_9","C27_11","C27_15","C28_7","C28_9","C28_10","C28_11","C29_3","C29_8","C210_7","C211_1","C211_4","C211_7","C211_16","C211_19","C211_20","C212_11","C212_13","C212_19","C212_20","C213_3","C213_5","C213_6","C213_7","C213_9","C213_11","C213_
1533412","GL1_6","GL1_9","GL1_13","GL3_2","GL3_3","GL3_4","GL3_7","GL3_26","GL3_27","GL3_28","GL3_29","GL4_6","GL4_8","GL4_9","GL4_10","GL4_12","GL4_13","GL4_14","GL4_15","GL4_17","GL4_21","GL4_28","GL5_1","GL5_2","GL5_4","GL5_7","GL5_10","GL5_13","GL5_15","GL5_16","GL5_17","GL5_18","GL6_1","GL6_2","GL6_7","GL6_11","EP1_13","EP2_2","EP2_5","EP2_8","EP2_11","EP2_12","EP2_13","EP2_19","EP2_22","EP3_2","EP3_4","EP3_18","EP3_19","EP4_1","EP4_4","EP4_20","EP4_21","EP4_25","EP4_26","EP4_27","EP4_30","EP4_32","EP5_1","EP5_2","EP5_5","EP5_7","EP5_8","EP5_11","EP5_15","EP5_17","EP5_18","EP5_25","EP6_1","EP6_4","EP6_5","EP6_9","EP6_10","EP6_11","EP6_13","EP6_14","EP6_16","EP6_17","EP6_21","EP6_22","PP1_10","PP1_27","PP1_30","PP2_2","PP2_5","PP2_12","PP2_14","PP2_15","PP2_18","PP2_22","PP2_26","PP2_28","PP2_29","PP3_1","PP3_2","PP3_15","PP3_17","PP4_1","PP4_4","PP4_6","PP4_8","PP4_9","PP4_10","PP4_14","PP4_15","PP4_16","PP4_21","CL1_4","CL1_5","CL1_6","CL1_7","CL1_9","CL1_22","CL1_23","CL2_6","CL2_7","CL2_8","CL2_10","CL2_11","CL2_12","CL2_20","CL3_1","CL3_2","CL3_3","CL3_4","CL3_5","CL3_7","CL3_8","CL3_9","CL3_12","CL3_15","CL3_17","CL3_18","CL3_19","CL3_20","CL3_21","CL3_22","CL3_23","CL3_24","CL4_1","CL4_2","CL4_5","CL4_8","CL4_10","CL4_12","CL4_15","CL4_16","CL4_17","CL4_18","H11_5","H11_6","H11_9","H12_1","H12_2","H12_5","H12_8","H12_9","H12_11","H12_13","H12_14","H12_20","H13_3","H13_4","H13_6","H13_8","H14_1","H14_2","H14_9","H14_10","H14_12","H14_13","H14_18","H15_1","H15_2","H15_4","H15_5","H15_11","H15_13","H15_14","H15_15","H15_16","H15_17","H15_18","H15_19","H15_20","H15_21","H15_22","H15_25","H15_26","H21_4","H21_5","H22_5","H22_6","H22_15","H23_1","H23_4","H23_7","H23_13","H23_14","H23_15","HB3_1","HB3_7","HB3_8","HB3_12","HB3_13","HB3_15","HB4_7","HB5_11","HB5_12","HB6_10","HB6_12","HB7_4","HB10_25","HB10_29","HB10_32","HB10_34","HB10_35","HB10_36","HB11_33","HB12_3","HB12_4","HB12_5","HB12_7","HB12_8","HB12_12","HB12_13","HB12_14","HB12_17","HB12_18","HB12_22","HB12_25","HB13_2","HB13_3","HB13_7","HB13_9","HB13_16","HB13_17","HB13_18","HB13_22","HB13_23","HB13_24","JM1_2","JM1_4","JM1_16","JM1_19","JM1_21","JM1_22","JM2_1","JM2_3","JM2_4","JM2_5","JM2_6","JM2_8","JM2_9","JM2_12","JM2_16","JM2_24","JM3_1","JM3_14","JM4_2","JM4_3","JM4_4","JM4_5","JM4_7","JM4_8","JM4_9","JM4_10","JM4_11","JM4_14","JM4_16","JM5_1","JM5_3","JM5_5","JM5_6","JM5_7","JM5_8","JM5_9","JM5_10","JM5_11","JM5_12","JM5_16","P11_6","P11_8","P11_13","P11_15","P11_16","P11_17","P11_18","P11_22","P12_2","P12_3","P12_5","P12_9","P12_13","P12_17","P12_20","P12_21","P12_24","P12_25","P13_6","P13_9","P13_13","P13_14","P13_15","P13_16","P14_1","P14_7","P14_12","P14_13","P14_14","P15_2","P15_4","P15_5","P15_6","P15_8","P15_9","P15_12","P15_14","P21_4","P21_5","P21_10","P21_19","P23_14","P23_15","P23_17","P23_18","J11_3","J12_1","J12_7","J12_13","J12_14","J12_15","J12_18","J12_20","J12_21","J12_24","J12_27","J12_28","J12_29","J13_1","J13_5","J13_11","J13_13","J13_15","J14_1","J14_2","J14_3","J14_4","J15_13","J15_21","J21","J21","J21","JD1","JD1","JD1","JD1","RV2_10","RV2_25","RV6_16","RV7_3","RV11_12","RV12_12","RV14_7","RV16_1","RV18_4","RV18_6","RV18_7","RV19_5","RV19_17","RV19_18"],
15335"doubles":["GN17_10","GN17_12","GN17_13","GN18_5","GN27_45","GN31_29","GN32_4","GN32_20","GN38_24","GN43_5","GN43_29","GN44_4","GN44_21","GN44_23","GN44_27","GN45_9","GN45_17","GN45_19","GN46_34","GN47_3","GN50_17","EX3_12","EX3_14","EX3_15","EX3_16","EX3_18","EX4_15","EX5_16","EX6_6","EX7_9","EX9_30","EX10_2","EX10_17","EX12_24","EX12_46","EX14_2","EX16_9","EX16_12","EX16_33","EX19_6","EX19_12","EX20_22","EX22_21","EX22_25","EX23_9","EX23_13","EX23_21","EX23_25","EX23_31","EX24_1","EX25_2","EX25_9","EX25_19","EX26_33","EX29_42","EX30_16","EX30_31","EX30_36","EX30_37","EX31_13","EX32_13","EX33_5","LV1_2","LV7_23","LV9_3","LV10_9","LV10_13","LV10_14","LV18_2","LV19_2","LV19_9","LV19_10","LV19_12","LV19_15","LV19_19","LV19_27","LV19_33","LV19_34","LV21_8","LV22_3","LV23_2","LV23_10","LV23_22","LV23_24","LV25_2","LV25_6","LV25_9","LV25_14","LV25_17","LV25_44","LV25_46","NU1_3","NU6_23","NU9_10","NU10_29","NU11_18","NU13_2","NU14_28","NU15_2","NU15_18","NU16_10","NU16_11","NU16_16","NU16_17","NU16_24","NU17_4","NU18_1","NU18_3","NU18_4","NU18_7","NU18_26","NU18_30","NU20_8","NU21_17","NU27_8","NU28_2","NU28_3","NU31_29","NU33_51","NU33_54","NU34_2","NU35_2","NU35_10","NU35_34","DT1_8","DT1_14","DT1_31","DT1_37","DT1_42","DT2_4","DT2_24","DT3_21","DT3_26","DT4_1","DT4_3","DT4_5","DT4_21","DT4_23","DT4_25","DT4_29","DT4_34","DT5_1","DT5_28","DT5_30","DT6_3","DT6_17","DT6_20","DT7_4","DT7_8","DT7_12","DT7_25","DT8_1","DT8_19","DT9_7","DT10_15","DT11_8","DT11_10","DT11_14","DT11_19","DT11_26","DT12_1","DT12_5","DT12_7","DT12_9","DT12_16","DT12_32","DT13_3","DT13_5","DT13_7","DT13_13","DT14_21","DT18_15","DT19_19","DT20_3","DT22_24","DT23_4","DT24_8","DT24_9","DT25_17","DT27_2","DT27_4","DT28_14","DT28_62","DT28_63","DT28_68","DT29_5","DT29_11","DT30_18","DT30_19","DT31_6","DT31_12","DT31_14","DT31_19","DT31_26","DT31_27","DT32_6","DT32_7","JS2_14","JS3_8","JS6_3","JS7_13","JS8_2","JS9_8","JS9_24","JS20_2","JS24_12","JS24_13","JG7_7","JG8_15","JG8_18","JG12_3","JG14_13","JG14_15","JG15_12","JG18_9","JG18_24","JG18_25","JG19_5","JG19_9","JG20_28","RT2_4","S12_29","S18_22","S19_19","S110_2","S110_15","S110_19","S114_12","S114_40","S115_3","S115_6","S118_22","S122_13","S125_6","S126_16","S129_10","S27_23","S211_20","S211_21","S213_17","S215_27","S216_10","S217_16","S218_12","S219_13","S2
1533519_29","S220_16","S220_21","S224_2","K13_26","K112_9","K112_28","K118_18","K120_9","K120_28","K120_33","K121_10","K122_27","K122_28","K21_3","K21_6","K22_3","K22_5","K22_16","K24_41","K29_11","K218_19","K218_27","K219_6","K219_10","K219_29","K222_18","R116_18","R116_35","R210_9","R218_27","R234_26","ER9_11","NH1_8","NH9_5","ET4_14","ET4_16","JB6_29","JB21_3","JB42_7","PS27_8","PS32_9","PS66_3","PS78_1","PS90_3","PS103_22","PS105_11","PS122_6","PS137_7","PS139_19","SS6_13","IS1_2","IS6_9","IS16_3","IS23_2","IS26_19","IS29_11","IS30_20","IS30_21","IS30_22","IS36_4","IS36_7","IS36_12","IS37_6","IS37_10","IS37_30","IS40_9","IS44_8","IS48_6","IS49_13","IS51_12","IS52_2","IS58_3","IS62_6","IS62_11","IS65_15","JR2_19","JR2_31","JR3_12","JR3_13","JR3_19","JR3_22","JR4_10","JR5_14","JR5_15","JR5_19","JR6_18","JR7_2","JR11_2","JR11_6","JR11_13","JR13_18","JR13_20","JR15_14","JR16_11","JR17_4","JR17_20","JR18_11","JR19_3","JR20_6","JR21_8","JR22_26","JR23_33","JR25_27","JR25_28","JR26_4","JR27_4","JR27_13","JR29_27","JR29_31","JR31_7","JR34_14","JR35_13","JR36_19","JR37_18","JR42_20","JR43_2","JR46_14","JR48_2","JR48_20","JR49_3","JR49_5","JR49_11","EK5_17","EK6_3","EK7_4","EK7_9","EK11_5","EK11_15","EK11_17","EK12_11","EK13_2","EK13_11","EK13_18","EK14_6","EK16_45","EK16_55","EK17_12","EK20_3","EK20_5","EK20_27","EK20_30","EK24_21","EK25_3","EK30_2","EK33_10","EK33_11","EK33_25","EK33_30","EK35_9","EK36_1","EK36_3","EK36_6","EK36_12","EK36_13","EK36_22","EK37_4","EK37_12","EK39_17","EK44_6","EK44_30","EK45_20","DN10_21","HS1_9","HS4_15","HS5_8","HS6_4","HS10_13","HS14_2","JL3_11","AM4_1","AM4_5","AM4_7","OB1","MC1_2","MC1_11","MC6_1","MC6_9","MC6_16","MC7_5","ZP2_5","ZP3_14","HG2_4","ZC1_3","ZC3_4","ZC4_9","ZC7_5","ZC9_12","ZC11_2","ZC14_5","ML1_2","ML1_6","ML1_7","ML1_8","ML2_14","ML2_15","ML3_8","ML3_13","MT5_21","MT5_27","MT5_33","MT5_39","MT5_43","MT6_2","MT6_9","MT8_32","MT9_6","MT10_16","MT11_8","MT11_21","MT11_24","MT15_5","MT17_5","MT17_20","MT21_5","MT21_16","MT21_21","MT21_28","MT22_4","MT23_34","MT23_37","MT23_38","MT24_23","MT24_25","MT24_26","MT25_6","MT26_18","MT26_45","MT26_46","MT26_64","MT26_65","MT28_7","MT28_9","MT28_20","MK4_3","MK5_19","MK7_11","MK8_29","MK10_49","MK11_23","MK13_21","MK14_41","MK14_42","MK16_6","LK2_10","LK4_23","LK5_4","LK5_24","LK6_23","LK6_30","LK7_25","LK7_34","LK9_41","LK10_3","LK10_13","LK10_19","LK11_2","LK11_5","LK11_41","LK12_32","LK13_25","LK13_26","LK13_32","LK13_34","LK13_35","LK15_31","LK16_12","LK17_3","LK17_6","LK17_7","LK17_21","LK17_23","LK22_10","LK22_11","LK22_31","LK22_67","LK22_70","LK23_14","LK24_49","JN1_38","JN3_7","JN3_11","JN4_21","JN4_35","JN7_6","JN8_17","JN8_19","JN8_25","JN8_33","JN10_36","JN11_34","JN12_19","JN14_9","JN14_10","JN15_20","JN16_5","JN16_32","JN19_4","JN19_6","JN19_14","JN19_15","JN20_17","JN21_12","AC3_25","AC5_8","AC5_9","AC5_25","AC5_28","AC10_21","AC13_25","AC13_46","AC20_25","AC25_26","AC28_26","C17_28","C112_21","C115_51","C27_11","C210_9","C212_14","GL1_20","GL5_2","GL6_1","CL4_17","T24_22","TT3_15","PM1","HB12_5","HB13_19","JM2_3","JM2_8","JM4_11","JM5_1","JM5_4","JM5_7","JM5_9","JM5_11","RV2_10","RV2_13","RV18_20"]
15336
15337};
15338
15339
15340
15341/*********
15342* ../app/js/plugins/visualfilters.js
15343**********/
15344/*
15345x click off dropdowns
15346x hebrew morphology
15347- multi-select morphology
15348- Strong's selector
15349- TYPE: [Any Text, Greek, Hebrew]
15350*/
15351
15352sofia.config = $.extend(sofia.config, {
15353
15354	enableVisualFilters: true
15355
15356});
15357
15358
15359
15360/**
15361 * Highlights words based on morphological data
15362 *
15363 * @author John Dyer (http://j.hn/)
15364 */
15365
15366var VisualFilters = function(node) {
15367
15368	if (!sofia.config.enableVisualFilters) {
15369		return;
15370	}
15371
15372
15373	var
15374		settingsKey = 'docs-config-visualfilters',
15375
15376		filtersWindow = new MovableWindow(550,290).hide(),
15377
15378		defaultSettings = {
15379			transforms: [
15380
15381				// underline Jesus in red
15382				{
15383					active: false,
15384					strongs: 'G2424',
15385					morphType: '',
15386					morph: '',
15387					styleLabel: 'Red Underline',
15388					style: 'border-bottom: solid 1px #ff3333'
15389				},
15390
15391				// aorist verbs in blue
15392				{
15393					active: false,
15394					strongs: '',
15395					morphType: 'robinson',
15396					morph: 'V-A',
15397					styleLabel: 'Blue Text',
15398					style: 'color: #3333cc'
15399				},
15400
15401				// proper names in gray
15402				{
15403					active: false,
15404					strongs: '',
15405					morphType: 'morphbh',
15406					morph: 'Np',
15407					styleLabel: 'Gray Text',
15408					style: 'color: #999999'
15409				}
15410
15411			]
15412		},
15413		visualSettings = AppSettings.getValue(settingsKey, defaultSettings),
15414
15415		visualNode = $('<div id="visualfilters-config">' +
15416
15417							'<input type="button" value="New Filter" />' +
15418
15419							'<table>' +
15420								'<thead>' +
15421									'<tr>' +
15422										'<th class="visualfilters-active"></th>' +
15423										'<th class="visualfilters-strongs i18n" data-i18n="[html]plugins.visualfilters.strongsnumber"></th>' +
15424										'<th class="visualfilters-morph i18n" data-i18n="[html]plugins.visualfilters.morphology">Morphology</th>' +
15425										'<th class="visualfilters-style i18n" data-i18n="[html]plugins.visualfilters.style">Style</th>' +
15426										'<th class="visualfilters-example"></th>' +
15427										'<th class="visualfilters-remove"></th>' +
15428									'</tr>' +
15429								'</thead>' +
15430								'<tbody>' +
15431								'</tbody>' +
15432							'</table>' +
15433
15434						'</div>')
15435						.appendTo( filtersWindow.body ),
15436
15437		stylesSelector = new StylesSelector(),
15438
15439		morphSelector = new MorphologySelector(),
15440
15441		tbody = visualNode.find('tbody'),
15442
15443		addRowButton = visualNode.find('input')
15444									.on('click', function() {
15445										var row = createRow();
15446										tbody.append(row);
15447									}),
15448
15449		openVisualizationsButton = $('<span class="config-button i18n" data-i18n="[html]plugins.visualfilters.button" id="config-visualfilters-button"></span>')
15450						.appendTo( $('#config-tools .config-body') );
15451
15452	//console.log('LOADED VIZ',visualSettings);
15453
15454	// Attach to Config pane
15455
15456	//filtersWindow.title.html('Visual Filters');
15457	filtersWindow.title
15458				.addClass('i18n')
15459				.attr('data-i18n','[html]plugins.visualfilters.title')
15460
15461	openVisualizationsButton.on('click', function() {
15462
15463		filtersWindow.show().center();
15464
15465		openVisualizationsButton.closest('.window-overlay').hide();
15466
15467	});
15468
15469	// ROWS
15470	tbody.on('click', '.visualfilters-remove', function() {
15471
15472		// remove row
15473
15474		$(this).closest("tr").remove();
15475
15476		saveTransforms();
15477
15478		VisualTransformer.resetTransforms(visualSettings);
15479	});
15480
15481	tbody.on('change keyup', '.visualfilters-active input, .visualfilters-morph select, .visualfilters-strongs input, .visualfilters-morph input', function() {
15482		//updateExamples();
15483
15484		saveTransforms();
15485
15486		VisualTransformer.resetTransforms(visualSettings);
15487	});
15488
15489	// STYLE
15490	var activeSpan = null;
15491	filtersWindow.body.on('click', '.visualfilters-style span', function(e) {
15492		e.preventDefault();
15493		morphSelector.hide();
15494
15495		if (stylesSelector.is(':visible') && activeSpan != null && activeSpan[0] == $(this)[0]) {
15496			stylesSelector.hide();
15497			return;
15498		}
15499
15500		activeSpan = $(this);
15501
15502		stylesSelector
15503			.css({scrollTop: 0, top: activeSpan.offset().top + activeSpan.outerHeight(true), left: activeSpan.offset().left  })
15504			.show();
15505
15506		return false;
15507	});
15508
15509	stylesSelector.on('click', 'div', function() {
15510
15511
15512		var div = $(this);
15513
15514		stylesSelector.hide();
15515
15516		if (activeSpan != null) {
15517
15518			var newStyle = div.find('span'),
15519				newStyleParams = newStyle.attr('data-style'),
15520				newStyleLabel = newStyle.html();
15521
15522			console.log('new style', newStyleParams);
15523
15524			// add to data
15525			activeSpan
15526				.attr('data-style', newStyleParams)
15527				.html(newStyleLabel);
15528
15529			// actually style it
15530			activeSpan.attr('style','');
15531			VisualTransformer.applyStyle(activeSpan, newStyleParams);
15532
15533
15534			saveTransforms();
15535
15536			VisualTransformer.resetTransforms(visualSettings);
15537		}
15538
15539		return false;
15540
15541	});
15542
15543	filtersWindow.container.find('.close-button').on('click', function() {
15544		stylesSelector.hide();
15545		morphSelector.hide();
15546	});
15547
15548
15549	// morph
15550	filtersWindow.body.on('click', '.visualfilters-morph input', function(e) {
15551		e.preventDefault();
15552		stylesSelector.hide();
15553
15554		//morphSelector.attach( $(this) );
15555		var input = $(this);
15556
15557
15558		if (morphSelector.is(':visible') && morphSelector.currentInput != null && morphSelector.currentInput[0] == input[0]) {
15559			morphSelector.hide();
15560			return false;
15561		}
15562
15563		morphSelector.css({
15564			top: input.offset().top + input.outerHeight(),
15565			left: input.offset().left
15566		});
15567
15568		morphSelector.show();
15569
15570		morphSelector.currentInput = input;
15571		morphSelector.setMorphology( input.siblings('select').val() );
15572		morphSelector.updateMorphSelector( input.val() );
15573
15574
15575
15576		return false;
15577	});
15578
15579	morphSelector.on('update', function(value) {
15580		saveTransforms();
15581
15582		VisualTransformer.resetTransforms(visualSettings);
15583	});
15584
15585	// this will be cancelled if the user clicks in an input
15586	filtersWindow.body.on('click', function(e) {
15587
15588		if (morphSelector.is(':visible')) {
15589			morphSelector.hide();
15590		}
15591
15592		if (stylesSelector.is(':visible')) {
15593			stylesSelector.hide();
15594		}
15595	});
15596
15597
15598
15599	// DRAW!
15600
15601	function drawTransforms() {
15602		tbody.empty();
15603
15604		for (var i=0, il=visualSettings.transforms.length; i<il; i++) {
15605			var row = createRow(),
15606				transform = visualSettings.transforms[i];
15607
15608			row.find('.visualfilters-active input').prop('checked', transform.active);
15609			row.find('.visualfilters-strongs input').val(transform.strongs);
15610			row.find('.visualfilters-morph input').val(transform.morph);
15611			row.find('.visualfilters-morph select').val(transform.morphType);
15612			row.find('.visualfilters-style span')
15613						.html(transform.styleLabel)
15614						.attr('data-style', transform.style);
15615
15616			VisualTransformer.applyStyle( row.find('.visualfilters-style span'), transform.style);
15617
15618			tbody.append(row);
15619		}
15620	}
15621
15622
15623	function createRow() {
15624
15625		return $(
15626			'<tr>' +
15627				'<td class="visualfilters-active">' +
15628					'<input type="checkbox" checked />' +
15629				'</td>' +
15630				'<td class="visualfilters-strongs">' +
15631					'<input type="text" placeholder="G2424, H234" />' +
15632				'</td>' +
15633				'<td class="visualfilters-morph">' +
15634					'<select>' +
15635						'<option value="morphhb">Hebrew</option>' +
15636						'<option value="robinson">Greek</option>' +
15637					'</select>' +
15638					'<input type="text" placeholder="V-A?" />' +
15639				'</td>' +
15640				'<td class="visualfilters-style">' +
15641					'<span>
15641Choose...</span>' +
15642				'</td>' +
15643				'<td class="visualfilters-remove">' +
15644					'<span class="close-button"></span>' +
15645				'</td>' +
15646			 '</tr>');
15647	}
15648
15649	function saveTransforms() {
15650
15651		// clear
15652		visualSettings.transforms = [];
15653
15654		tbody.find('tr').each(function() {
15655			var row = $(this),
15656				transform = {};
15657
15658			transform.active = row.find('.visualfilters-active input').is(':checked');
15659			transform.strongs = row.find('.visualfilters-strongs input').val();
15660			transform.morph = row.find('.visualfilters-morph input').val();
15661			transform.morphType = row.find('.visualfilters-morph select').val();
15662			transform.style = row.find('.visualfilters-style span').attr('data-style');
15663			transform.styleLabel = row.find('.visualfilters-style span').html();
15664
15665			//console.log('saving', transform.style, row.find('.visualfilters-style span').prop('data-style'));
15666
15667
15668			if (transform.morph != '') {
15669
15670				if (transform.morphType == 'robinson') {
15671					transform.morphRegExp = new RegExp('^' + transform.morph.replace(/\?/gi,'.{1}'), 'gi');
15672				} else if (transform.morphType == 'morphhb') {
15673					transform.morphRegExp = new RegExp(
15674								'(^H' + transform.morph.replace(/\?/gi,'.{1}') + ')|'+
15675								'(/' + transform.morph.replace(/\?/gi,'.{1}') + ')', 'gi');
15676				}
15677			} else {
15678				transform.morphRegExp = null;
15679			}
15680
15681			visualSettings.transforms.push(transform);
15682		});
15683
15684		//console.log('saving', visualSettings);
15685
15686		// store for next load
15687		AppSettings.setValue(settingsKey, visualSettings);
15688	}
15689
15690
15691
15692
15693	// global object
15694	var ext = {
15695		sendMessage: function() {}
15696	};
15697	ext = $.extend(true, ext, EventEmitter);
15698
15699
15700	ext.on('message', function(e) {
15701		if (e.data.messagetype == 'textload') {
15702
15703			var lang = e.data.content.attr('lang'),
15704				validLangs = ['heb','gre','grc', 'eng'];
15705
15706			if (validLangs.indexOf(lang) > -1) {
15707
15708				VisualTransformer.runTransforms(e.data.content, visualSettings);
15709			}
15710		}
15711	});
15712
15713	// START UP
15714	drawTransforms();
15715	saveTransforms();
15716
15717
15718	return ext;
15719}
15720
15721sofia.plugins.push('VisualFilters');
15722
15723
15724var VisualTransformer = (function() {
15725
15726
15727	function resetTransforms(visualSettings) {
15728		// remove all inline styles
15729		$('l').attr('style','');
15730
15731		// re-run on existing chapters
15732		$('.section').each(function() {
15733			runTransforms( $(this), visualSettings);
15734		});
15735	}
15736
15737	function runTransforms(sectionNode, visualSettings) {
15738		if (visualSettings.transforms.length === 0)
15739			return;
15740
15741		var sectionLang = sectionNode.attr('lang');
15742
15743		sectionNode.find('l').each(function(index, node) {
15744
15745			var word = $(this);
15746
15747			for (var i=0, il=visualSettings.transforms.length; i<il; i++) {
15748				var transform = visualSettings.transforms[i],
15749					isMatch = false;
15750
15751				if (!transform.active) {
15752					continue;
15753				}
15754
15755
15756
15757				// strongs number
15758				if (transform.strongs != '') {
15759
15760					if (word.attr('s') == transform.strongs) {
15761						//applyStyle(word, transform.style);
15762
15763						isMatch = true;
15764
15765					} else {
15766
15767						continue;
15768					}
15769				}
15770
15771				// morphology
15772				if (transform.morph != '') {
15773
15774					// sectionLang == transform.morphType &&
15775					if (transform.morphRegExp && transform.morphRegExp != null) {
15776						var wordMorphData = word.attr('m');
15777
15778						if (wordMorphData != null && transform.morphRegExp.test(wordMorphData)) {
15779							//applyStyle(word, transform.style);
15780
15781							isMatch = true;
15782						} else {
15783
15784							continue;
15785						}
15786					}
15787				}
15788
15789				if (isMatch) {
15790					applyStyle(word, transform.style);
15791				}
15792
15793
15794				/*
15795						break;
15796					case 'frequency':
15797
15798						if (transform.data  > 0) {
15799							var strongs = w.attr('data-lemma');
15800
15801							if (strongs != null ) {
15802
15803								// run all possible strongs on this word or phrase
15804								strongs = strongs.split(' ');
15805								for (var j=0, jl=strongs.length; j<jl; j++) {
15806									var freq = (typeof strongsGreekFrequencies != 'undefined') ? strongsGreekFrequencies[ strongs[j] ] : 0;
15807
15808									if (freq > 0 && freq <= transform.data) {
15809										applyStyle(w, transform.style);
15810									}
15811								}
15812							}
15813						}
15814
15815
15816						break;
15817					*/
15818
15819			}
15820
15821		});
15822	}
15823
15824	function applyStyle(node, css) {
15825
15826		if (typeof css == 'undefined' || css == null || css == '') {
15827			return;
15828		}
15829
15830
15831		var props = css.split(';');
15832
15833		for (var i=0, il=props.length; i<il; i++) {
15834
15835			var parts = props[i].split(':');
15836
15837			if (parts.length === 2) {
15838				node.css(parts[0], parts[1]);
15839			}
15840		}
15841	}
15842
15843
15844	return {
15845		resetTransforms: resetTransforms,
15846		runTransforms: runTransforms,
15847		applyStyle: applyStyle
15848	};
15849
15850})();
15851
15852
15853
15854
15855var MorphologySelector = function(parent) {
15856	// morph selector
15857	var currentMorphologyKey = 'robinson',
15858
15859		robinsonElements = {
15860
15861		"nounCase": {
15862					breakBefore: true,
15863					declension : 'Case',
15864					parts :  [
15865						{
15866							letter : 'N',
15867							type : 'Nominative'
15868						},
15869						{
15870							letter : 'A',
15871							type : 'Accusative'
15872						},
15873						{
15874							letter : 'D',
15875							type : 'Dative'
15876						},
15877						{
15878							letter : 'G',
15879							type : 'Genative'
15880						},
15881						{
15882							letter : 'V',
15883							type : 'Vocative'
15884						}
15885					]
15886				},
15887
15888		"number": {
15889					declension : 'Number',
15890					parts :  [
15891						{
15892							letter : 'P',
15893							type : 'Plural'
15894						},
15895						{
15896							letter : 'S',
15897							type : 'Singular'
15898						}
15899					]
15900				},
15901		"gender":{
15902					declension : 'Gender',
15903					parts :  [
15904						{
15905							letter : 'F',
15906							type : 'Feminine'
15907						},
15908						{
15909							letter : 'M',
15910							type : 'Masculine'
15911						},
15912						{
15913							letter : 'N',
15914							type : 'Nueter'
15915						}
15916					]
15917				},
15918
15919
15920		"verbTense": {
15921					breakBefore: true,
15922					declension : 'Tense',
15923					parts :  [
15924						{
15925							letter : 'A',
15926							type : 'Aorist'
15927						},
15928						{
15929							letter : 'F',
15930							type : 'Future'
15931						},
15932						{
15933							letter : 'I',
15934							type : 'Imperfect'
15935						},
15936						{
15937							letter : 'R',
15938							type : 'Perfect'
15939						},
15940						{
15941							letter : 'L',
15942							type : 'Pluperfect'
15943						},
15944						{
15945							letter : 'P',
15946							type : 'Present'
15947						}
15948					]
15949				},
15950		"verbVoice":		{
15951					declension : 'Voice',
15952					parts :  [
15953						{
15954							letter : 'A',
15955							type : 'Active'
15956						},
15957						{
15958							letter : 'M',
15959							type : 'Middle'
15960						},
15961						{
15962							letter : 'P',
15963							type : 'Passive'
15964						}
15965					]
15966				},
15967		"verbMood":		{
15968					declension : 'Mood',
15969					parts :  [
15970						{
15971							letter : 'I',
15972							type : 'Indicative'
15973						},
15974						{
15975							letter : 'S',
15976							type : 'Subjunctive'
15977						},
15978						{
15979							letter : 'O',
15980							type : 'Optative'
15981						},
15982						{
15983							letter : 'M',
15984							type : 'Imperative'
15985						},
15986						{
15987							letter : 'N',
15988							type : 'Infinitive'
15989						},
15990						{
15991							letter : 'P',
15992							type : 'Participle'
15993						}
15994					]
15995				}
15996				,
15997		"person": {
15998					declension : 'Person',
15999					breakBefore: true,
16000					parts :  [
16001						{
16002							letter : '1',
16003							type : '1st Person'
16004						},
16005						{
16006							letter : '2',
16007							type : '2nd Person'
16008						},
16009						{
16010							letter : '3',
16011							type : '3rd Person'
16012						}
16013					]
16014				}
16015
16016		},
16017
16018		morphhbElements = {
16019			"nounTypes":{
16020					declension : 'Type',
16021					parts :  [
16022						{
16023							letter : 'c',
16024							type : 'Common'
16025						},
16026						{
16027							letter : 'g',
16028							type : 'Gentilic'
16029						},
16030						{
16031							letter : 'p',
16032							type : 'Proper name'
16033						}
16034					]
16035				},
16036
16037			"person": {
16038					declension : 'Person',
16039					breakBefore: true,
16040					parts :  [
16041						{
16042							letter : '1',
16043							type : '1st Person'
16044						},
16045						{
16046							letter : '2',
16047							type : '2nd Person'
16048						},
16049						{
16050							letter : '3',
16051							type : '3rd Person'
16052						}
16053					]
16054				},
16055			"number": {
16056					declension : 'Number',
16057					parts :  [
16058						{
16059							letter : 'p',
16060							type : 'Plural'
16061						},
16062						{
16063							letter : 's',
16064							type : 'Singular'
16065						}
16066						,
16067						{
16068							letter : 'd',
16069							type : 'Dual'
16070						}
16071					]
16072				},
16073			"state":{
16074					declension : 'State',
16075					parts :  [
16076						{
16077							letter : 'a',
16078							type : 'Absolute'
16079						},
16080						{
16081							letter : 'c',
16082							type : 'Construct'
16083						},
16084						{
16085							letter : 'd',
16086							type : 'Determined'
16087						}
16088					]
16089				},
16090			"nounGender":{
16091					declension : 'Gender',
16092					parts :  [
16093						{
16094							letter : 'f',
16095							type : 'Feminine'
16096						},
16097						{
16098							letter : 'm',
16099							type : 'Masculine'
16100						},
16101						{
16102							letter : 'b',
16103							type : 'Both'
16104						}
16105					]
16106				},
16107			"verbGender":{
16108					declension : 'Gender',
16109					parts :  [
16110						{
16111							letter : 'f',
16112							type : 'Feminine'
16113						},
16114						{
16115							letter : 'm',
16116							type : 'Masculine'
16117						},
16118						{
16119							letter : 'c',
16120							type : 'Common'
16121						}
16122					]
16123				},
16124
16125			verbStem: {
16126				declension : 'Stem',
16127				parts :  [
16128					{
16129						letter: 'q',
16130						type: 'qal',
16131					},
16132					{
16133						letter: 'N',
16134						type: 'niphal',
16135					},
16136					{
16137						letter: 'p',
16138						type: 'piel',
16139					},
16140					{
16141						letter: 'P',
16142						type: 'pual',
16143					},
16144					{
16145						letter: 'h',
16146						type: 'hiphil',
16147					},
16148					{
16149						letter: 'H',
16150						type: 'hophal',
16151					},
16152					{
16153						letter: 't',
16154						type: 'hithpael',
16155					},
16156					{
16157						letter: 'o',
16158						type: 'polel',
16159					},
16160					{
16161						letter: 'O',
16162						type: 'polal',
16163					},
16164					{
16165						letter: 'r',
16166						type: 'hithpolel',
16167					},
16168					{
16169						letter: 'm',
16170						type: 'poel',
16171					},
16172					{
16173						letter: 'M',
16174						type: 'poal',
16175					},
16176					{
16177						letter: 'k',
16178						type: 'palel',
16179					},
16180					{
16181						letter: 'K',
16182						type: 'pulal',
16183
16184					},
16185					{
16186						letter: 'Q',
16187						type: 'qal passive',
16188					},
16189					{
16190						letter: 'l',
16191						type: 'pilpel',
16192					},
16193					{
16194						letter: 'L',
16195						type: 'polpal',
16196					},
16197					{
16198						letter: 'f',
16199						type: 'hithpalpel',
16200					},
16201					{
16202						letter: 'D',
16203						type: 'nithpael',
16204					},
16205					{
16206						letter: 'j',
16207						type: 'pealal',
16208					},
16209					{
16210						letter: 'i',
16211						type: 'pilel',
16212					},
16213					{
16214						letter: 'u',
16215						type: 'hothpaal',
16216					},
16217					{
16218						letter: 'c',
16219						type: 'tiphil',
16220					},
16221					{
16222						letter: 'v',
16223						type: 'hishtaphel',
16224					},
16225					{
16226						letter: 'w',
16227						type: 'nithpalel',
16228					},
16229					{
16230						letter: 'y',
16231						type: 'nithpoel',
16232					},
16233					{
16234						letter: 'z',
16235						type: 'hithpoel'
16236					}
16237				]
16238			},
16239
16240			verbType: {
16241				declension : 'Type',
16242				parts :  [
16243					{
16244						letter: 'p',
16245						type: 'perfect (qatal)',
16246					},
16247					{
16248						letter: 'q',
16249						type: 'sequential perfect (weqatal)',
16250					},
16251					{
16252						letter: 'i',
16253						type: 'imperfect (yiqtol)',
16254					},
16255					{
16256						letter: 'w',
16257						type: 'sequential imperfect (wayyiqtol)',
16258					},
16259					{
16260						letter: 'h',
16261						type: 'cohortative',
16262					},
16263					{
16264						letter: 'j',
16265						type: 'jussive',
16266					},
16267					{
16268						letter: 'v',
16269						type: 'imperative',
16270					},
16271					{
16272						letter: 'r',
16273						type: 'participle active',
16274					},
16275					{
16276						letter: 's',
16277						type: 'participle passive',
16278					},
16279					{
16280						letter: 'a',
16281						type: 'infinitive absolute',
16282					},
16283					{
16284						letter: 'c',
16285						type: 'infinitive construct'
16286					}
16287					]
16288			},
16289
16290			verbVoices: {
16291					declension : 'Voice',
16292					parts :  [
16293						{
16294							letter: 'A',
16295							type: 'active'
16296						},
16297						{
16298							letter: 'M',
16299							type: 'middle'
16300						},
16301						{
16302							letter: 'P',
16303							type: 'passive'
16304						},
16305						{
16306							letter: 'E',
16307							type: 'middle or passive'
16308						},
16309						{
16310							letter: 'D',
16311							type: 'middle deponent'
16312						},
16313						{
16314							letter: 'O',
16315							type: 'passive deponent'
16316						},
16317						{
16318							letter: 'N',
16319							type: 'middle or passive deponent'
16320						},
16321						{
16322							letter: 'Q',
16323							type: 'impersonal active'
16324						},
16325						{
16326							letter: 'X',
16327							type: 'no voice'
16328						}
16329					]
16330			},
16331
16332			verbMoods: {
16333				declension : 'Mood',
16334				parts :  [
16335					{
16336						letter: 'I',
16337						type: 'indicative'
16338					},
16339					{
16340						letter: 'S',
16341						type: 'subjunctive'
16342					},
16343					{
16344						letter: 'O',
16345						type: 'optative'
16346					},
16347					{
16348						letter: 'M',
16349						type: 'imperative'
16350					},
16351					{
16352						letter: 'N',
16353						type: 'infinitive'
16354					},
16355					{
16356						letter: 'P',
16357						type: 'participle'
16358					},
16359					{
16360						letter: 'R',
16361						type: 'imperative participle'
16362					}
16363				]
16364			}
16365		},
16366
16367
16368		morphologies = {
16369			"robinson":
16370			[
16371				{
16372					letter : 'N',
16373					type : 'Noun',
16374					declensions : [
16375						robinsonElements.nounCase,
16376						robinsonElements.number,
16377						robinsonElements.gender
16378					]
16379				},
16380				{
16381					letter : 'V',
16382					type : 'Verb',
16383					declensions : [
16384						robinsonElements.verbTense,
16385						robinsonElements.verbVoice,
16386						robinsonElements.verbMood,
16387						robinsonElements.person,
16388						robinsonElements.number
16389					]
16390				}
16391			],
16392			"morphhb": [
16393				{
16394					letter : 'N',
16395					type : 'Noun',
16396					declensions : [
16397						morphhbElements.nounTypes,
16398						morphhbElements.nounGender,
16399						morphhbElements.number,
16400						morphhbElements.state
16401					]
16402				},
16403				{
16404					letter : 'V',
16405					type : 'Verb',
16406					declensions : [
16407						morphhbElements.verbStem,
16408						morphhbElements.verbType,
16409						morphhbElements.person,
16410						morphhbElements.number,
16411						morphhbElements.verbGender,
16412						morphhbElements.state
16413					]
16414				}
16415			]
16416		},
16417		currentMorphology = morphologies[currentMorphologyKey];
16418
16419
16420	var
16421		morphSelector = $('<div class="morph-selector"><table>' +
16422			'<thead>' +
16423				'<tr><th>Part of Speech</th></tr>' +
16424			'</thead>' +
16425			'<tbody>' +
16426				'<tr></tr>' +
16427			'</tbody>' +
16428			'</table></div>')
16429				.appendTo( $(document.body) )
16430				.hide(),
16431
16432		morphSelectorTimer = null,
16433
16434		startMorphSelectorTimer = function() {
16435			stopMorphSelectorTimer();
16436
16437			morphSelectorTimer = setTimeout(function() {
16438				morphSelector.hide();
16439			}, 1000);
16440		},
16441		stopMorphSelectorTimer = function() {
16442			if (morphSelectorTimer != null) {
16443				clearTimeout(morphSelectorTimer);
16444				morphSelectorTimer = null;
16445			}
16446		};
16447	/*
16448	morphSelector
16449		.on('mouseleave', function() {
16450			startMorphSelectorTimer();
16451		})
16452		.on('mouseover', function() {
16453			stopMorphSelectorTimer();
16454		});
16455	*/
16456
16457
16458	// find table parts
16459	var morphSelectorHeaderRow = morphSelector.find('thead tr'),
16460		morphSelectorMainRow = morphSelector.find('tbody tr'),
16461		morphSelectorPOS = $('<td class="morph-pos"></td>').appendTo(morphSelectorMainRow);
16462
16463
16464
16465	function setMorphology(value) {
16466		currentMorphologyKey = value;
16467		currentMorphology = morphologies[currentMorphologyKey];
16468
16469		drawPartsOfSpeech();
16470	}
16471
16472	function drawPartsOfSpeech() {
16473
16474		//console.log('drawing', currentMorphology);
16475
16476		morphSelectorPOS.empty();
16477
16478		// add parts of speech
16479		for (var i=0, il=currentMorphology.length; i<il; i++) {
16480			morphSelectorPOS.append(
16481				$('<span data-value="' + currentMorphology[i].letter + '">' + currentMorphology[i].type + '</span>')
16482			);
16483		}
16484
16485		drawSelectedPartOfSpeech();
16486
16487	}
16488	// do first one
16489	drawPartsOfSpeech();
16490
16491	function updateMorphSelector(value) {
16492
16493		if (value.length == 0) {
16494			morphSelector.find('span').removeClass('selected');
16495			drawSelectedPartOfSpeech();
16496			return;
16497		}
16498
16499		var firstChar = value.substring(0,1);
16500
16501		// select it
16502		var partOfSpeechSpan = morphSelectorPOS.find('span[data-value="' + firstChar + '"]');
16503
16504		if (partOfSpeechSpan.length > 0) {
16505			partOfSpeechSpan.addClass('selected')
16506				.siblings()
16507					.removeClass('selected');
16508
16509			drawSelectedPartOfSpeech();
16510
16511			// do the rest of the characters one by one
16512			if (value.length > 1) {
16513				var remainder = value.substr(1);
16514				if (remainder.substr(0,1) == '-') {
16515					remainder = remainder.substr(1);
16516				}
16517
16518				for (var i=0, il=remainder.length; i<il; i++) {
16519					var letter = remainder[i];
16520
16521					morphSelectorMainRow
16522						.find('td:nth-child(' + (i+2) + ')')
16523						.find('span[data-value="' + letter + '"]')
16524							.addClass('selected');
16525				}
16526			}
16527		}
16528	}
16529
16530	function drawSelectedPartOfSpeech() {
16531
16532		// clear out headers
16533		morphSelectorHeaderRow.find('th').first()
16534			.siblings()
16535			.remove();
16536
16537		// clear out the main declensions
16538		morphSelectorMainRow.find('td').first()
16539			.siblings()
16540			.remove();
16541
16542		// find whatever is selected
16543		var selectedSpan = morphSelectorPOS.find('.selected'),
16544			selectedValue = selectedSpan.attr('data-value');
16545
16546		if (selectedSpan.length == 0) {
16547			return;
16548		}
16549
16550		// find part of speech
16551		var partOfSpeech = null;
16552		for (var i=0, il=currentMorphology.length; i<il; i++) {
16553			if (currentMorphology[i].letter === selectedValue) {
16554				partOfSpeech = currentMorphology[i];
16555				break;
16556			}
16557		}
16558
16559		// now make the new siblings
16560		for (var i=0, il=partOfSpeech.declensions.length; i<il; i++) {
16561			// create td
16562			//var td = $('<td />').appendTo( selectedSpan.closest('tr') );
16563			var declension = partOfSpeech.declensions[i],
16564				td = $('<th>' + declension.declension + '</th>').appendTo( morphSelectorHeaderRow );
16565				td = $('<td />').appendTo( selectedSpan.closest('tr') );
16566
16567			for (var j=0, jl=declension.parts.length; j<jl; j++) {
16568				$('<span data-value="' + declension.parts[j].letter + '"' + (declension.breakBefore ? ' data-breakbefore="true"' : '') + '>' + declension.parts[j].type + '</span>')
16569					.appendTo(td);
16570			}
16571		}
16572
16573		morphSelector.height( morphSelector.find('table').height() );
16574	}
16575
16576	// selecting a span
16577	morphSelector.on('click', 'span', function() {
16578		var selectedSpan = $(this),
16579			selectedValue = selectedSpan.attr('data-value');
16580
16581
16582		// select or delect a class
16583		if (selectedSpan.hasClass('selected')) {
16584
16585			selectedSpan
16586				.removeClass('selected')
16587
16588		} else {
16589
16590			selectedSpan
16591				.addClass('selected')
16592				.siblings()
16593					.removeClass('selected');
16594
16595		}
16596
16597		// redraw the parts if needed
16598		if (selectedSpan.closest('td').hasClass('morph-pos')) {
16599			drawSelectedPartOfSpeech();
16600		}
16601
16602
16603		// push new value to input
16604		var selector = '',
16605			lastPartOfSpeechWithSelection = -1;
16606
16607		// first see what the last position with a selected value is
16608		// [Verb] [ ] [ ] [Subjunctive] == 3
16609		selectedSpan.closest('tr').find('td').each(function(index, node) {
16610			var td = $(this),
16611				selectedDeclension = td.find('span.selected');
16612
16613			// if something is selected then mark it
16614			if (selectedDeclension.length > 0) {
16615				lastPartOfSpeechWithSelection = index;
16616			}
16617		});
16618
16619		// construct the text input from the selected positions
16620		selectedSpan.closest('tr').find('td').each(function(index, node) {
16621
16622			var td = $(this),
16623				selectedDeclension = td.find('span.selected'),
16624				includeBreak = td.find('span').first().attr('data-breakbefore') == "true";
16625
16626			// if nothing selected add ? but only if there is something after it
16627			if (selectedDeclension.length == 0) {
16628				if (index <= lastPartOfSpeechWithSelection) {
16629					selector += (includeBreak ? '-' : '') + '?';
16630				}
16631			} else {
16632				selector += (includeBreak ? '-' : '') + selectedDeclension.attr('data-value');
16633			}
16634		});
16635
16636
16637		if (morphSelector.currentInput != null) {
16638			morphSelector.currentInput.val(selector);
16639		}
16640
16641		//saveTransforms();
16642		//resetTransforms();
16643
16644		morphSelector.trigger('update', selector);
16645	});
16646
16647	morphSelector.updateMorphSelector = updateMorphSelector;
16648	morphSelector.setMorphology = setMorphology;
16649	morphSelector.currentInput = null;
16650
16651	return morphSelector;
16652};
16653
16654
16655var StylesSelector = function() {
16656	var selector = $('<div id="visualfilters-styles"></div>')
16657					.appendTo( $(document.body) )
16658					.hide();
16659
16660
16661	// STYLES GUIDE
16662	// create styles
16663	var
16664		textColors = 			['#999999', '#ff3333',	'#33cc33',	'#3333cc"',	'#ffcc33',	'#ff33ff',	'#33ffff'],
16665		textColorNames = 		['Gray', 	'Red',		'Green',	'Blue',		'Orange', 	'Magenta',	'Cyan'],
16666		backgroundColors = 		['#ccccccc','#ff9999',	'#99ff99',	'#9999ff"',	'#ffcc33',	'#ffff33',	'#ff99ff',	'#99ffff'],
16667		backgroundColorNames = 	['Gray', 	'Red',		'Green',	'Blue',		'Orange', 	'Yellow',	'Magenta',	'Cyan'],
16668
16669		styleCss = [],
16670		styleNames = []
16671		;
16672
16673
16674	// text
16675	for (var i=0, il=textColors.length; i<il; i++) {
16676		styleCss.push('color: ' + textColors[i] + ';');
16677		styleNames.push(textColorNames[i] + ' Text');
16678	}
16679
16680	// highlight
16681	for (var i=0, il=backgroundColors.length; i<il; i++) {
16682		styleCss.push('background-color: ' + backgroundColors[i] + ';');
16683		styleNames.push(backgroundColorNames[i] + ' Highlight');
16684	}
16685
16686	// underline
16687	for (var i=0, il=textColors.length; i<il; i++) {
16688		styleCss.push('border-bottom: solid 1px ' + textColors[i] + ';');
16689		styleNames.push(textColorNames[i] + ' Underline');
16690	}
16691
16692	// custom
16693	styleCss.push('color:red;text-shadow:yellow 0 0 2px;');
16694	styleNames.push('Fire');
16695
16696	styleCss.push('text-shadow:0 0 1px gray;');
16697	styleNames.push('Shadow');
16698
16699	styleCss.push('font-weight: bold;');
16700	styleNames.push('Bold');
16701
16702	styleCss.push('border: 2px dashed #333;');
16703	styleNames.push('Dashed Box');
16704
16705	styleCss.push('text-transform: uppercase;');
16706	styleNames.push('Capitalize');
16707
16708	styleCss.push('font-variant: small-caps;');
16709	styleNames.push('Small Caps');
16710
16711
16712	// draw styles
16713	for (var i=0, il=styleCss.length; i<il; i++) {
16714		selector.append(
16715			$('<div><span data-style="' + styleCss[i] + '" style="' + styleCss[i] + '">' + styleNames[i] + '</span></div>')
16716		);
16717	}
16718
16719	return selector;
16720}
16721
16722
16723
16724/*********
16725* ../app/js/plugins/crossreferences.js
16726**********/
16727
16728
16729var CrossReferencePopupPlugin = function(app) {
16730
16731	if (!sofia.config.enableNotesPopupPlugin) {
16732		return;
16733	}
16734
16735	var referencePopup = new InfoWindow('CrossReferencePopup');
16736
16737	referencePopup.container.css({zIndex: 1000});
16738
16739	function getFragmentidFromNode(node) {
16740		var possibleTexts = [node.attr('data-id'), node.attr('title'), node.html()],
16741			fragmentid = null;
16742
16743		for (var i=0, il=possibleTexts.length; i<il; i++) {
16744			var text = possibleTexts[i];
16745
16746			if (typeof text != 'undefined' && text != null) {
16747				var bref = new bible.Reference(text.split(';')[0].trim());
16748				if (typeof bref.toSection != 'undefined') {
16749					fragmentid = bref.toSection();
16750
16751					break;
16752				}
16753			}
16754
16755		}
16756
16757		return fragmentid;
16758	}
16759
16760	sofia.globals.handleBibleRefClick = function(e) {
16761		var link = $(this),
16762			newfragmentid = getFragmentidFromNode(link);
16763
16764		// where are we?
16765		var currentLocationData = PlaceKeeper.getFirstLocation();
16766
16767		// store the current one
16768		TextNavigation.locationChange(currentLocationData.fragmentid);
16769
16770
16771		if (newfragmentid != null && newfragmentid != '') {
16772
16773			TextNavigation.locationChange(newfragmentid);
16774
16775			ext.trigger('globalmessage', {
16776								type: 'globalmessage',
16777								target: this,
16778								data: {
16779									messagetype:'nav',
16780									type: 'bible',
16781									locationInfo: {
16782										fragmentid: newfragmentid,
16783										sectionid: newfragmentid.split('_')[0],
16784										offset: 0
16785									}
16786								}
16787							});
16788		}
16789	};
16790
16791
16792	sofia.globals.handleBibleRefMouseover = function(e, textid) {
16793		var link = $(this),
16794			fragmentid = getFragmentidFromNode(link);
16795
16796		if (fragmentid != null) {
16797
16798			var sectionid = fragmentid.split('_')[0];
16799
16800			if (typeof textid == 'undefined') {
16801
16802				if (link.closest('.section').hasClass('commentary')) {
16803
16804					textid = $('.BibleWindow:first .section:first').attr('data-textid');
16805
16806				} else {
16807					textid = link.closest('.section').attr('data-textid');
16808				}
16809			}
16810
16811
16812			console.log('hover', textid, sectionid, fragmentid);
16813
16814			TextLoader.getText(textid, function(textInfo) {
16815
16816				TextLoader.loadSection(textInfo, sectionid, function(contentNode) {
16817
16818					var verse = contentNode.find('.' + fragmentid),
16819						html = '';
16820
16821					verse.find('.note').remove();
16822
16823					verse.each(function() {
16824						html += $(this).html();
16825					});
16826
16827
16828					referencePopup.body.html( html );
16829					referencePopup.show();
16830					referencePopup.position(link);
16831
16832				});
16833			});
16834
16835		}
16836	}
16837
16838	sofia.globals.handleBibleRefMouseout = function(e) {
16839
16840		referencePopup.hide();
16841	}
16842
16843
16844	$('.windows-main').on('click','.bibleref, .xt', sofia.globals.handleBibleRefClick);
16845
16846	if (!Detection.hasTouch) {
16847		$('.windows-main').on('mouseover','.bibleref, .xt', sofia.globals.handleBibleRefMouseover);
16848		$('.windows-main').on('mouseout','.bibleref, .xt', sofia.globals.handleBibleRefMouseout);
16849	}
16850
16851
16852	var ext = {
16853		getData: function() {
16854			return null;
16855		}
16856	};
16857	ext = $.extend(true, ext, EventEmitter);
16858
16859	return ext;
16860};
16861
16862sofia.plugins.push('CrossReferencePopupPlugin');
16863
16864
16865
16866/*********
16867* ../app/js/plugins/notes.js
16868**********/
16869sofia.config = $.extend(sofia.config, {
16870
16871	enableNotesPopupPlugin: true
16872
16873});
16874
16875var NotesPopupPlugin = function(app) {
16876
16877	if (!sofia.config.enableNotesPopupPlugin) {
16878		return;
16879	}
16880
16881	var notesPopup = new InfoWindow('NotesPopup');
16882
16883	notesPopup.body.on('click', '.bibleref, .xt', function(e) {
16884
16885		sofia.globals.handleBibleRefClick.call(this, e);
16886
16887		notesPopup.hide();
16888
16889	});
16890
16891
16892	if (!Detection.hasTouch) {
16893		notesPopup.body.on('mouseover', '.bibleref, .xt', function(e) {
16894			sofia.globals.handleBibleRefMouseover.call(this, e, $(notesPopup.currentWord).closest('.section').attr('data-textid') );
16895		});
16896
16897		notesPopup.body.on('mouseout', '.bibleref, .xt', function(e) {
16898			sofia.globals.handleBibleRefMouseout.call(this, e);
16899		});
16900	}
16901
16902	$('.windows-main').on('click','.note .key, .cf .key', function(e) {
16903
16904		e.preventDefault();
16905
16906		console.log('notes');
16907
16908		var key = $(this);
16909
16910
16911		// hide if second click
16912		if (notesPopup.container.is(':visible') && notesPopup.currentWord == this) {
16913			notesPopup.hide();
16914			notesPopup.currentWord == null;
16915			return;
16916		}
16917		notesPopup.currentWord = this;
16918
16919		// clone and attach content
16920		var content = key.parent().find('.text').clone();
16921
16922		notesPopup.body
16923					.html('')
16924					.append(content);
16925
16926		// show popup
16927		notesPopup.show();
16928		notesPopup.position(key);
16929
16930		return false;
16931	});
16932
16933
16934
16935	var ext = {
16936		getData: function() {
16937			return null;
16938		}
16939	};
16940	ext = $.extend(true, ext, EventEmitter);
16941
16942	return ext;
16943};
16944
16945sofia.plugins.push('NotesPopupPlugin');
16946
16947
16948
16949/*********
16950* ../app/js/plugins/googleanalytics.js
16951**********/
16952sofia.config = $.extend(sofia.config, {
16953	// Google Analytics key
16954	gaKey: '',
16955
16956	// Google URL
16957	gaUrl: ''
16958});
16959
16960
16961var GoogleAnalyticsPlugin = function(app) {
16962
16963	/*
vendor: 392 bytes, lines 16963-16980
16963
16964	(function(i,s,o,g,r,a,m){
16965
16966		i['GoogleAnalyticsObject']=r;
16967		i[r]= i[r]|| function(){
16968			(i[r].q = i[r].q || [] ).push(arguments)
16969		},
16970		i[r].l=1*new Date();
16971
16972		a=s.createElement(o),
16973		m=s.getElementsByTagName(o)[0];
16974
16975		a.async=1;
16976		a.src=g;
16977		m.parentNode.insertBefore(a,m)
16978	})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
16979
16980	ga('create', '
16980UA-3734687-19
vendor: 48 bytes, lines 16980-16982
16980', 'inscript.org');
16981	ga('send', 'pageview');
16982	
16982*/
16983
16984	if (sofia.config.enableOnlineSources && sofia.config.gaKey && sofia.config.gaKey != '')	{
16985		loadGoogleAnalytics();
16986	}
16987
16988	function loadGoogleAnalytics() {
16989
16990		window['GoogleAnalyticsObject'] = 'ga';
16991		window['ga'] = window['ga'] || function() {
16992			(window['ga'].q = window['ga'].q || [] ).push(arguments)
16993		}
16994		window['ga'].l = 1*new Date();
16995
16996		var script = document.createElement('script');
16997		var lastScript = document.getElementsByTagName('script')[0];
16998
16999		script.async = true;
17000		script.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'www.google-analytics.com/analytics.js';
17001		lastScript.parentNode.insertBefore(script, lastScript);
17002
17003		ga('create', sofia.config.gaKey, (sofia.config.gaUrl) ? sofia.config.gaUrl : '');
17004		ga('send', 'pageview');
17005	}
17006
17007
17008	// EVENT MODEL
17009
17010	/*
17011	var ext = {
17012		sendMessage: function() {}
17013	};
17014	ext = $.extend(true, ext, EventEmitter);
17015
17016	// listen for times when the user changes location
17017	ext.on('globalmessage', function(e) {
17018
17019		console.log('GOOGLE', e.data);
17020
17021		if (e.data.messagetype == 'usernav') {
17022
17023
17024
17025			if (ga) {
17026				//ga('send', 'event', 'usernav', 'click', 'nav buttons', 4);
17027			}
17028
17029		}
17030
17031	});
17032
17033	return ext;
17034	*/
17035
17036	// DIRECT MODEL
17037
17038	var analytics = {
17039
17040		record: function(category, action, label, value) {
17041
17042			//console.log('GOOGLE', arguments);
17043
17044			if (sofia.config.enableOnlineSources && typeof ga != 'undefined') {
17045				ga('send', 'event', category, action, label, value);
17046			}
17047
17048		}
17049
17050	}
17051
17052	sofia.analytics = analytics;
17053
17054};
17055
17056sofia.plugins.push('GoogleAnalyticsPlugin');
17057
17058
17059
17060/*********
17061* ../app/js/plugins/mediaplugin.js
17062**********/
17063sofia.config = $.extend(sofia.config, {
17064
17065	enableMediaLibraryPlugin: true
17066
17067});
17068
17069var MediaLibraryPlugin = function(app) {
17070
17071	if (!sofia.config.enableMediaLibraryPlugin) {
17072		return;
17073	}
17074
17075	var mediaLibraries = null,
17076		mediaPopup = new InfoWindow('mediapopup'),
17077		contentToProcess = [];
17078
17079	//console.log('MediaLibraryPlugin startup', MediaLibrary.getMediaLibraries);
17080
17081	MediaLibrary.getMediaLibraries(function(data) {
17082		mediaLibraries = data;
17083
17084		setupMediaEvents();
17085
17086		addMedia();
17087	});
17088
17089	function setupMediaEvents() {
17090
17091		// handle clicks
17092		$('.windows-main').on('click', '.mediathumb', function(e) {
17093
17094			// determine what kind of media this is
17095			var
17096				icon = $(this),
17097				mediaFolder = icon.attr('data-mediafolder'),
17098				verse = icon.closest('.verse, .v'),
17099				verseid = verse.attr('data-id'),
17100				reference = new bible.Reference(verseid).toString(),
17101				mediaLibrary = null,
17102				mediaForVerse = null;
17103
17104			// find library
17105			for (var i=0, il=mediaLibraries.length; i<il; i++ ) {
17106				var ml = mediaLibraries[i]
17107
17108				if (ml.folder == mediaFolder) {
17109					mediaLibrary = ml;
17110					break;
17111				}
17112
17113			}
17114
17115			console.log('media click', mediaLibrary);
17116
17117
17118			mediaForVerse = mediaLibrary.data[verseid];
17119
17120			switch (mediaLibrary.type) {
17121
17122				case 'image':
17123					// clear it out!
17124					mediaPopup.body.html('');
17125
17126					var html = '';
17127					for (var i=0, il=mediaForVerse.length; i<il; i++ ) {
17128						var mediaInfo = mediaForVerse[i],
17129							fullUrl = sofia.config.baseContentUrl + 'content/' + 'media/' + mediaLibrary.folder  + '/' + mediaInfo.filename + '.' + mediaInfo.exts[0],
17130							thumbUrl = fullUrl.replace('.jpg', '-thumb.jpg');
17131
17132						html += '<li>' +
17133									'<a href="' + fullUrl + '" target="_blank">' +
17134										'<img src="' + thumbUrl + '" />' +
17135									'</a>' +
17136								'</li>';
17137					}
17138
17139					mediaPopup.body.append('<strong>' + reference.toString() + '</strong>');
17140					mediaPopup.body.append($('<ul class="inline-image-library-thumbs">' + html + '</ul>'));
17141
17142
17143					//mediaPopup.center().show();
17144					mediaPopup.setClickTargets( [icon] );
17145					mediaPopup.position( icon ).show();
17146
17147					break;
17148
17149				case 'video':
17150					var mediaInfo = mediaForVerse[0],
17151						videoUrl = sofia.config.baseContentUrl + 'content/' + 'media/' + mediaLibrary.folder + '/' + mediaInfo.filename + '.' + mediaInfo.exts[0];
17152
17153					sofia.globals.showVideo(videoUrl, mediaInfo.name);
17154
17155					break;
17156
17157				case 'jfm':
17158
17159					var lang = icon.closest('.section').attr('data-lang3'),
17160						mediaInfo = mediaForVerse[0],
17161						videoUrl = JesusFilmMediaApi.getPlayer(lang, mediaInfo.filename, function(iframeUrl, iframeLang) {
17162
17163							if (iframeUrl != null) {
17164								sofia.globals.showIframe(iframeUrl, mediaInfo.name + ' (' + iframeLang + (lang != iframeLang ? '/' + lang : '') + ')');
17165							}
17166
17167						});
17168
17169					break;
17170
17171			}
17172		});
17173
17174	}
17175
17176	// process chapters, add image icon to verses
17177	function addMedia() {
17178
17179		//console.log('addMedia',mediaLibraries);
17180
17181		if (mediaLibraries == null) {
17182			return;
17183		}
17184
17185		while (contentToProcess.length > 0) {
17186			var content = contentToProcess.pop();
17187
17188			if (content.data('has-media') != undefined) {
17189				continue;
17190			}
17191
17192			// add images to verses
17193			content.find('.verse, .v').each(function() {
17194				var verse = $(this),
17195					verseid = verse.attr('data-id');
17196
17197				// make sure we're just doing the first verse
17198				verse = verse.closest('.section').find('.' + verseid).first();
17199
17200				if (verseid == 'LK1_1') {
17201					// //console.log('check');
17202				}
17203
17204				if (!verse.hasClass('has-media')) {
17205					// check all libraries
17206					for (var i=0, il=mediaLibraries.length; i<il; i++) {
17207						var mediaLibrary = mediaLibraries[i],
17208							iconClassName = mediaLibrary.iconClassName,
17209							mediaForVerse = mediaLibrary.data ? mediaLibrary.data[verseid] : undefined;
17210
17211						// add media
17212						if (typeof mediaForVerse != 'undefined') {
17213
17214							// check if it's already been added
17215							//if (verse.closest('.chapter').find('.' + verseid).find('.' + iconClassName).length == 0) {
17216
17217								var icon = $('<span class="inline-icon ' + iconClassName + ' mediathumb" data-mediafolder="' + mediaLibrary.folder + '"></span>'),
17218									verseNumber = verse.find('.verse-num, v-num');
17219
17220								if (verseNumber.length > 0) {
17221									verseNumber.after(icon);
17222								} else {
17223									icon.prependTo(verse);
17224								}
17225							//}
17226						}
17227					} // libraries loop
17228
17229
17230					verse.closest('.section').find('.' + verseid).addClass('has-media');
17231
17232				}
17233
17234			}); // verse loop
17235
17236
17237
17238			content.data('has-media', true);
17239
17240		} // while
17241
17242	}
17243
17244	mediaPopup.body.on('click', '.inline-image-library-thumbs a', sofia.globals.mediaImageClick);
17245	mediaPopup.body.on('click', '.inline-video-library-thumbs a', sofia.globals.mediaVideoClick);
17246
17247
17248	var ext = {};
17249	ext = $.extend(true, ext, EventEmitter);
17250	ext.on('message', function(e) {
17251		if (e.data.messagetype == 'textload' && e.data.type == 'bible') {
17252			//store
17253			contentToProcess.push(e.data.content);
17254			// run
17255			addMedia();
17256		}
17257	});
17258
17259	return ext;
17260}
17261
17262sofia.plugins.push('MediaLibraryPlugin');
17263
17264
17265
17266/*********
17267* ../app/js/windows/textwindow.js
17268**********/
17269
17270var TextWindow = function(id, parent, init_data, text_type) {
17271
17272
17273	// detect backflip
17274	var
17275		ua = window.navigator.userAgent.toLowerCase(),
17276		supports3d = !(ua.indexOf('unix') > -1
17277						|| ua.indexOf('linux') > -1
17278						|| ua.indexOf('opera') > -1
17279						//|| ua.indexOf('msie') > -1
17280						//|| ua.indexOf('trident') > -1
17281						//|| ua.indexOf('explorer')  > -1
17282						);
17283
17284
17285	var
17286		container =
17287			$('<div class="scroller-container' + (supports3d ? ' supports-3d' : '') + '">'+
17288				'<div class="window-header scroller-header">'+
17289					'<div class="scroller-header-inner">'+
17290						//(Detection.hasTouch ? '<span class="header-input text-nav" ></span>' : '<input type="text" class="header-input text-nav" />') +
17291						'<input type="text" class="app-input text-nav" />' +
17292						'<div class="app-list text-list"></div>'+
17293						'<span class="header-icon info-button"></span>'+
17294						'<span class="header-icon audio-button"></span>'+
17295					'</div>'+
17296				'</div>'+
17297				'<div class="scroller-flipper">' +
17298					'<div class="scroller-main">' +
17299						'<div class="scroller-text-wrapper reading-text"><div class="loading-indicator" style="height:' + parent.node.height() + 'px;"></div></div>' +
17300					'</div>'+
17301					'<div class="scroller-info">Text info</div>' +
17302				'</div>' +
17303			'</div>').appendTo(parent.node),
17304
17305		// dom nodes
17306		flipper = container.find('.scroller-flipper'),
17307		header = container.find('.scroller-header'),
17308		main = container.find('.scroller-main'),
17309		info = container.find('.scroller-info'),
17310		infoBtn = container.find('.info-button'),
17311		wrapper = container.find('.scroller-text-wrapper'),
17312		navui = header.find('.text-nav'),
17313		textlistui = header.find('.text-list'),
17314
17315		// objects
17316		textChooser = sofia.globalTextChooser,
17317		textNavigator = sofia.globalTextNavigator,
17318		scroller = new Scroller(main),
17319
17320		audioui = container.find('.audio-button'),
17321		audioController = new AudioController(id, container, audioui, scroller),
17322
17323		// settings
17324		currentTextInfo = null,
17325		currentLocationInfo = null,
17326		hasFocus = false,
17327		isInitialized = false;
17328
17329
17330	/*
17331	if (Detection.hasTouch) {
17332		navui.prop('disabled',true);
17333	}
17334	*/
17335
17336
17337
17338	infoBtn.on('click', function() {
17339
17340		textChooser.hide();
17341		textNavigator.hide();
17342
17343		flipper.toggleClass('showinfo');
17344
17345		if (flipper.hasClass('showinfo')) {
17346
17347			if (typeof currentTextInfo.aboutHtml != 'undefined') {
17348
17349				var aboutDoc = $(currentTextInfo.aboutHtml);
17350
17351				info.html('');
17352				info.append( aboutDoc );
17353
17354			} else {
17355
17356				sofia.ajax({
17357					dataType: 'text',
17358					url: 'content/texts/' + currentTextInfo.id + '/about.html',
17359					success: function(htmlString) {
17360
17361						var breakTag = '<body',
17362							fixedHtml = htmlString.indexOf(breakTag) > -1 ?
17363												breakTag + htmlString.split(breakTag)[1] :
17364												'',
17365							aboutDoc = $(fixedHtml);
17366
17367						info.html('');
17368						info.append( aboutDoc );
17369
17370						// store for next time
17371						currentTextInfo.aboutHtml = fixedHtml;
17372					}
17373				});
17374			}
17375		}
17376	});
17377
17378
17379	textlistui
17380		.on('click', function(e) {
17381
17382			//console.log('clicked');
17383
17384			if (flipper.hasClass('showinfo')) {
17385				flipper.removeClass('showinfo')
17386			}
17387
17388			// if this is selected, then toggle
17389			if (textChooser.getTarget() == textlistui) {
17390				textChooser.toggle();
17391			} else {
17392				textChooser.setTarget(container, textlistui, text_type);
17393				textChooser.setTextInfo(currentTextInfo);
17394				textChooser.show();
17395			}
17396		});
17397
17398	navui
17399		.on('click', function(e) {
17400
17401			if (Detection.hasTouch) {
17402				this.blur();
17403			}
17404
17405			if (flipper.hasClass('showinfo')) {
17406				flipper.removeClass('showinfo')
17407			}
17408
17409			if (textNavigator.getTarget() == navui) {
17410				textNavigator.toggle();
17411			} else {
17412				textNavigator.setTarget(container, navui);
17413				textNavigator.setTextInfo(currentTextInfo);
17414				textNavigator.show();
17415			}
17416		})
17417		.on('keypress', function(e) {
17418			if (e.keyCode == 13) {
17419				var
17420					userinput = navui.val(),
17421					bibleref = new bible.Reference(userinput),
17422					fragmentid = (bibleref.toSection) ? bibleref.toSection() : '',
17423					sectionid = fragmentid.split('_')[0];
17424
17425				if (sectionid != '') {
17426
17427					if (sofia.analytics) {
17428						sofia.analytics.record('usernav', 'input', sectionid + ':' + currentTextInfo.id);
17429					}
17430
17431					TextNavigation.locationChange(fragmentid);
17432
17433					scroller.load('text', sectionid, fragmentid);
17434					textNavigator.hide();
17435
17436					navui.val(bibleref.toString())
17437					navui[0].blur();
17438				}
17439			}
17440		});
17441
17442	textNavigator.on('change', function (e) {
17443		//console.log('scrollerapp:navigator:change', e);
17444
17445		if (e.data.target != navui) {
17446			return;
17447		}
17448
17449		if (sofia.analytics) {
17450			sofia.analytics.record('usernav', 'menu', e.data + ':' + currentTextInfo.id);
17451		}
17452
17453		TextNavigation.locationChange(e.data.sectionid);
17454
17455		// load new content
17456		scroller.load('text', e.data.sectionid);
17457	});
17458
17459	textChooser.on('change', function (e) {
17460
17461		if (e.data.target != textlistui) {
17462			return;
17463		}
17464
17465		var newTextInfo = e.data.textInfo;
17466
17467		// ALWAYS UPDATE: for first load
17468		// update version name
17469		//textlistui.html( newTextInfo.abbr );
17470
17471		setTextInfoUI(newTextInfo);
17472
17473		parent.tab.find('span').html( newTextInfo.abbr );
17474
17475		// update the navigator with the latest header
17476		textNavigator.setTextInfo(newTextInfo);
17477
17478		audioController.setTextInfo(newTextInfo);
17479
17480		// if it has *changed* then we need to reload the text in the scroller
17481		if (currentTextInfo == null || newTextInfo.id != currentTextInfo.id) {
17482
17483			// store
17484			currentTextInfo = newTextInfo;
17485
17486			var oldLocationInfo = scroller.getLocationInfo(),
17487				nearestSectionId = oldLocationInfo != null ? oldLocationInfo.sectionid : currentTextInfo.sections[0];
17488
17489			// load new text
17490			wrapper.html('');
17491			scroller.setTextInfo(currentTextInfo);
17492			scroller.load( 'text', nearestSectionId);
17493		}
17494
17495	});
17496
17497	scroller.on('scroll', update_textnav);
17498	scroller.on('locationchange', update_textnav);
17499	scroller.on('load', update_textnav);
17500	scroller.on('globalmessage', function(e) {
17501		if ((e.data.messagetype == 'nav' && hasFocus) || e.data.messagetype != 'nav') {
17502
17503			//console.log('sending global');
17504			if (ext) {
17505				ext.trigger('globalmessage', {type: e.type, target: ext, data: e.data});
17506			}
17507		}
17508	});
17509
17510	// show the current position to the user
17511	function update_textnav(e) {
17512
17513		var newLocationInfo = scroller.getLocationInfo();
17514
17515		// found a fragment
17516		if (newLocationInfo != null) {
17517
17518			currentLocationInfo = newLocationInfo;
17519			navui
17520				.html(  currentLocationInfo.label )
17521				.val(  currentLocationInfo.label );
17522
17523			ext.trigger('settingschange', {type: 'settingschange', target: this, data: getData() });
17524		}
17525
17526
17527	}
17528
17529	// START UP
17530
17531	function init() {
17532
17533		// TEMP
17534		navui.html('Reference').val('Reference');
17535		textlistui.html('Version');
17536
17537		console.log('textsindow init', init_data, isInitialized, text_type);
17538
17539		if (init_data == null) {
17540			return;
17541		}
17542
17543		if (typeof init_data.textid == 'undefined' || init_data.textid == '') {
17544			init_data.textid = sofia.config.newBibleWindowVersion;
17545		}
17546
17547		// load the text specified by the init data
17548		TextLoader.getText(init_data.textid,
17549
17550			// success
17551			function(loadedTextInfo) {
17552
17553				// store this setting
17554				currentTextInfo = loadedTextInfo;
17555
17556				isInitialized = true;
17557
17558				startup();
17559			},
17560
17561			// error handler
17562			function() {
17563
17564				console.log('ERROR', init_data.textid, 'doesnt exist');
17565
17566				// load all possible versions
17567				TextLoader.loadTexts(function(textInfoData) {
17568
17569					// find a text with the same language
17570					// REMOVED b/c the ids no longer use the lang_version syntax
17571					/*
17572					var newTextInfo = null,
17573						lang = init_data.textid.toString().split('-')[0].split('_')[0];		
17574
17575					for (var i=0, il=textInfoData.length; i<il; i++) {
17576						var textInfo = textInfoData[i];
17577
17578						if (textInfo.type == text_type && (textInfo.lang == lang || textInfo.id.substring(0, lang.length) == lang)) {
17579							newTextInfo = textInfo;
17580							break;
17581						}
17582					}
17583					*/
17584					var textsWithType = textInfoData.filter(function(ti) { return ti.type == text_type; });
17585					
17586					if (textsWithType.length > 0) {
17587						newTextInfo = textsWithType[0];
17588					}
17589
17590					// still nothing
17591					if (newTextInfo == null) {
17592						newTextInfo = textInfoData[0];
17593					}
17594
17595					// let's try again with first one
17596					TextLoader.getText(newTextInfo.id, function(loadedTextInfo) {
17597						// store this setting
17598						currentTextInfo = loadedTextInfo;
17599
17600						isInitialized = true;
17601
17602						startup();
17603
17604					});
17605
17606				});
17607
17608		});
17609	}
17610
17611	function setTextInfoUI(textinfo) {
17612
17613
17614		switch (textinfo.type ) {
17615			default:
17616				textlistui.removeClass('app-list-image');
17617				textlistui.html( textinfo.abbr );
17618				break;
17619			case 'deafbible':
17620				textlistui.addClass('app-list-image');
17621				textlistui.html( '<img src="content/texts/' + textinfo.id + '/' + textinfo.id + '.png" />' );
17622				break;
17623
17624		}
17625
17626	}
17627
17628	function startup() {
17629
17630		// send to objects
17631		textChooser.setTextInfo(currentTextInfo);
17632		setTextInfoUI(currentTextInfo);
17633		parent.tab.find('span').html( currentTextInfo.abbr );
17634		textNavigator.setTextInfo(currentTextInfo);
17635		audioController.setTextInfo(currentTextInfo);
17636
17637		scroller.setTextInfo(currentTextInfo);
17638
17639		if (!init_data.sectionid && init_data.fragmentid) {
17640			init_data.sectionid = init_data.fragmentid.split('_')[0];
17641		}
17642
17643		scroller.load('text', init_data.sectionid, init_data.fragmentid);
17644
17645	}
17646
17647	init();
17648
17649	function size(width, height) {
17650
17651		//console.log('winsize',id,width,height);
17652
17653		container
17654			.outerWidth(width)
17655			.outerHeight(height);
17656
17657		flipper
17658			.outerWidth(width)
17659			.outerHeight( container.height() - header.outerHeight(true));
17660
17661		main
17662			.outerWidth(width)
17663			.outerHeight( container.height() - header.outerHeight(true));
17664
17665		info
17666			.outerWidth(width)
17667			.outerHeight( container.height() - header.outerHeight(true));
17668
17669		textChooser.size(width, height);
17670		textNavigator.size(width, height);
17671	}
17672
17673	function getData() {
17674		// get data
17675		if (currentTextInfo == null) {
17676			currentTextInfo = textChooser.getTextInfo();
17677		}
17678		if (currentLocationInfo == null) {
17679			currentLocationInfo = scroller.getLocationInfo();
17680		}
17681
17682		// if not ready, then kill it
17683		if (currentTextInfo == null || currentLocationInfo == null) {
17684			return null;
17685		}
17686
17687		var data = {
17688			// textinfo
17689			textid: currentTextInfo.providerid,
17690			abbr: currentTextInfo.abbr,
17691
17692			// location info
17693			sectionid: currentLocationInfo.sectionid,
17694			fragmentid: currentLocationInfo.fragmentid,
17695			label: currentLocationInfo.label,
17696			labelTab: currentTextInfo.abbr,
17697			labelLong: currentLocationInfo.labelLong,
17698			hasFocus: hasFocus,
17699			params: {
17700				'win': text_type,
17701				'textid': currentTextInfo.providerid,
17702				'fragmentid': currentLocationInfo.fragmentid
17703			}
17704		};
17705
17706		return data;
17707	}
17708
17709
17710	function close() {
17711
17712		textChooser.close();
17713		textNavigator.close();
17714		audioController.close();
17715
17716		ext.clearListeners();
17717	}
17718
17719	var ext = {
17720		size: size,
17721		getData: getData,
17722		close: close
17723	}
17724	ext = $.extend(true, ext, EventEmitter);
17725
17726	ext.on('focus', function() {
17727		hasFocus = true;
17728	});
17729	ext.on('blur', function() {
17730		hasFocus = false;
17731	});
17732	ext.on('message', function(e) {
17733		var data = e.data;
17734
17735		if (data.messagetype == 'nav' && (data.type == 'bible' || data.type == 'commentary'|| data.type == 'videobible' || data.type == 'deafbible') && data.locationInfo != null) {
17736			//console.log(id, data.locationInfo.fragmentid, data.locationInfo.offset)
17737			scroller.scrollTo( data.locationInfo.fragmentid, data.locationInfo.offset);
17738		}
17739	});
17740
17741	return ext;
17742};
17743
17744var BibleWindow = function(id, node, init_data) {
17745	return new TextWindow(id, node, init_data, 'bible');
17746};
17747
17748sofia.initMethods.push(function() {
17749
17750	sofia.windowTypes.push( {
17751		className:'BibleWindow',
17752		param: 'bible',
17753		paramKeys: {
17754			'textid': 't',
17755			'fragmentid':'v'
17756		},
17757		init: {
17758			'textid':sofia.config.newBibleWindowVersion,
17759			'fragmentid':sofia.config.newBibleWindowVerse
17760		}
17761	});
17762
17763});
17764
17765var CommentaryWindow = function(id, node, init_data) {
17766	return new TextWindow(id, node, init_data, 'commentary');
17767};
17768
17769sofia.initMethods.push(function() {
17770
17771	if (typeof sofia.config.newCommentaryWindowTextId != 'undefined') {
17772
17773		sofia.windowTypes.push({
17774			className:'CommentaryWindow',
17775			param: 'commentary',
17776			paramKeys: {
17777				'textid': 't',
17778				'fragmentid':'v'
17779			},
17780			init: {
17781				'textid':sofia.config.newCommentaryWindowTextId,
17782				'fragmentid':''
17783			}
17784		});
17785	}
17786});
17787
17788
17789
17790/*********
17791* ../app/js/windows/searchwindow.js
17792**********/
17793
17794
17795// test/sample types
17796var SearchWindow = function(id, parent, init_data) {
17797
17798	var header = $('<div class="window-header search-header" >' +
17799
17800						'<input type="text" class="search-text app-input i18n" data-i18n="[placeholder]windows.search.placeholder" />' +
17801						'<div class="text-list app-list" style="">&nbsp;</div>' +
17802
17803						'<div class="search-options-button header-icon" style=""></div>' +
17804
17805						'<input type="button" value="Search" data-i18n="[value]windows.search.button" class="search-button header-button i18n" />' +
17806
17807						//'<select class="search-list header-list" style="max-width: 100px; top: 22px; right: 5px; position: absolute;" ></select>' +
17808					'</div>').appendTo(parent.node),
17809		main = $('<div class="search-main"><div class="search-wrapper">' +
17810					'<div class="search-top">' +
17811						//'<h2></h2>' +
17812						'<div class="search-progress-bar">' +
17813							'<div class="search-progress-bar-inner"></div>' +
17814							'<span class="search-progress-bar-label"></span>' +
17815						'</div>' +
17816						'<div class="search-visual"></div>' +
17817						'<span class="search-visual-label"></span>' +
17818						'<div class="search-lemma-info"></div>' +
17819						'<div class="search-usage"></div>' +
17820					'</div>' +
17821					'<div class="search-results reading-text"></div>' +
17822				'</div></div>').appendTo(parent.node),
17823		footer = $('<div class="search-footer window-footer"></div>').appendTo(parent.node),
17824
17825		topLemmaInfo = main.find('.search-lemma-info').hide(),
17826		topVisual = main.find('.search-visual').hide(),
17827		topVisualLabel = main.find('.search-visual-label'),
17828		topUsage = main.find('.search-usage'),
17829		topBlock = main.find('.search-top'),
17830		topBlockTitle = topBlock.find('h2'),
17831		searchProgressBar = topBlock.find('.search-progress-bar').hide(),
17832		searchProgressBarInner = topBlock.find('.search-progress-bar-inner'),
17833		searchProgressBarLabel = topBlock.find('.search-progress-bar-label'),
17834
17835		resultsBlock = main.find('.search-results'),
17836		input = header.find('.search-text'),
17837		button = header.find('.search-button'),
17838
17839		textlistui = header.find('.text-list'),
17840		//textChooser = new TextChooser(parent.node, textlistui, 'bible'),
17841		textChooser = sofia.globalTextChooser,
17842		textChooserFocused = false,
17843
17844		searchOptionsButton = header.find('.search-options-button'),
17845
17846		divisionChooser = $('<div class="search-division-chooser">' +
17847								'<div class="search-division-header">' + i18n.t('windows.search.options') + '</div>' +
17848								'<div class="search-division-main"></div>' +
17849							'</div>').appendTo($('body')),
17850
17851		selectedTextInfo = null,
17852
17853		// used for redrawing divisions
17854		previousTextInfo = null,
17855
17856		currentResults = null,
17857		searchIndexesData = null,
17858		searchTermsRegExp = null,
17859		isLemmaSearch = false
17860		;
17861
17862	header.find('.i18n').i18n();
17863
17864	// EVENTS
17865	input.on('keypress', function(e) {
17866		if (e.which == 13) {
17867			doSearch();
17868
17869			// record
17870			if (sofia.analytics) {
17871				sofia.analytics.record('search', input.val(), textChooser.getTextInfo().id );
17872			}
17873		}
17874	});
17875
17876	button.on('click', function() {
17877
17878		// record
17879		if (sofia.analytics) {
17880			sofia.analytics.record('search', input.val(), textChooser.getTextInfo().id );
17881		}
17882
17883		doSearch() ;
17884
17885	});
17886
17887	textChooser.on('change', function(e) {
17888
17889		if (e.data.target != textlistui) {
17890			return;
17891		}
17892
17893		setTextInfo(e.data.textInfo, false);
17894
17895		// reset UI
17896		clearResults();
17897	});
17898
17899	textlistui.on('click', function(e) {
17900
17901		if (textChooser.getTarget() == textlistui) {
17902			textChooser.toggle();
17903		} else {
17904			textChooser.setTarget(parent.node, textlistui, 'bible');
17905			textChooser.setTextInfo(selectedTextInfo);
17906			textChooser.show();
17907			textChooserFocused = true;
17908		}
17909	});
17910
17911	searchOptionsButton.on('click', function(e) {
17912
17913		if (divisionChooser.is(':visible')) {
17914			divisionChooser.hide();
17915
17916		} else {
17917			divisionChooser.show();
17918
17919			var uiPos = searchOptionsButton.offset(),
17920				top = uiPos.top + searchOptionsButton.outerHeight(true) + 10,
17921				left = uiPos.left,
17922				divWidth = divisionChooser.outerWidth(true),
17923				winWidth = $(window).width();
17924
17925			if (left + divWidth > winWidth) {
17926				left = winWidth - divWidth - 10;
17927			}
17928
17929			divisionChooser.css({
17930				top: top,
17931				left: left
17932			});
17933
17934		}
17935
17936		// $(document).on('click', docClick);
17937	});
17938
17939	function drawDivisions() {
17940
17941		// TODO: store the selected ones from this book to reselect on this one (unless it's a shorter one)
17942
17943		var otListHtml = '',
17944			ntListHtml = '';
17945
17946		for (var i=0, il=selectedTextInfo.divisions.length; i<il; i++) {
17947
17948			var dbsBookCode = selectedTextInfo.divisions[i],
17949				bookName = selectedTextInfo.divisionNames[i],
17950				checkedStatus = ' checked',
17951				html = '<label class="division-name"><input type="checkbox" value="' + dbsBookCode + '"' + checkedStatus + ' />' + bookName + '</label>';
17952
17953			if (bible.EXTRA_MATTER.indexOf(dbsBookCode) > -1 ) {
17954				continue;
17955			}
17956
17957			if (bible.NT_BOOKS.indexOf(dbsBookCode) > -1 ) {
17958				ntListHtml += html;
17959			} else {
17960				otListHtml += html;
17961			}
17962		}
17963
17964		var completeHtml =
17965					'<div class="division-list division-list-ot">' +
17966						'<label class="division-header">' +
17967							'<input type="checkbox" value="list-ot" checked />' + i18n.t('windows.bible.ot') + '</label>' +
17968						'</label>' +
17969						'<div class="division-list-items">' +
17970							otListHtml +
17971						'</div>' +
17972					'</div>' +
17973					'<div class="division-list division-list-nt">' +
17974						'<label class="division-header">' +
17975							'<input type="checkbox" value="list-nt" checked />' + i18n.t('windows.bible.nt') + '</label>' +
17976						'</label>' +
17977						'<div class="division-list-items">' +
17978							ntListHtml +
17979						'</div>' +
17980					'</div>';
17981
17982		divisionChooser.attr('dir', selectedTextInfo.dir);
17983		divisionChooser.find('.search-division-main').html(completeHtml);
17984
17985		// TODO: check for items then hide
17986		var hasOtBooks = divisionChooser.find('.division-list-ot .division-list-items input').length > 0,
17987			hasNtBooks = divisionChooser.find('.division-list-nt .division-list-items input').length > 0;
17988
17989		if (!hasOtBooks) {
17990			divisionChooser.find('.division-list-ot').hide();
17991		}
17992		if (!hasNtBooks) {
17993			divisionChooser.find('.division-list-nt').hide();
17994		}
17995	}
17996
17997	function setDivisions(divisions) {
17998
17999		console.log('init divisions', divisions);
18000		if (typeof divisions == 'string') {
18001			divisions = divisions.split(',');
18002		}
18003
18004
18005		if (divisions && divisions.length > 0) {
18006			divisionChooser.find('.division-list input').prop('checked', false);
18007
18008			for (var i=0, il = divisions.length; i<il; i++) {
18009				divisionChooser
18010					.find('.division-list input[value="' + divisions[i] + '"]')
18011					.prop('checked',true);
18012			}
18013		}
18014
18015		// check headers
18016		checkDivisionHeader( divisionChooser.find('.division-list-ot') );
18017		checkDivisionHeader( divisionChooser.find('.division-list-nt') );
18018
18019	}
18020
18021	function checkDivisionHeader(divisionList) {
18022		var items = divisionList.find('.division-list-items input'),
18023			allChecked = true;
18024
18025		items.each(function(i,el) {
18026
18027			if (!$(el).is(':checked')) {
18028				allChecked = false;
18029				return false;
18030			}
18031		});
18032
18033		divisionList.find('.division-header input').prop('checked', allChecked);
18034	}
18035
18036	divisionChooser.on('click', '.division-header input', function() {
18037		var checkbox = $(this),
18038			setChildrenTo = checkbox.is(':checked');
18039
18040		checkbox.closest('.division-list').find('.division-list-items input').prop('checked', setChildrenTo);
18041	});
18042
18043	divisionChooser.on('click', '.division-list-items input', function() {
18044		var checkbox = $(this);
18045
18046		checkDivisionHeader( checkbox.closest('.division-list') );
18047	});
18048
18049	resultsBlock.on('click', 'tr', function(e) {
18050
18051		var tr = $(this),
18052			fragmentid = tr.attr('data-fragmentid');
18053
18054
18055
18056		// is there a bible window?
18057		var
18058			bibleWindows = sofia.app.windowManager.getWindows().filter(function(w) { return w.className == 'BibleWindow'});
18059
18060console.log('search click', fragmentid, bibleWindows);
18061
18062		if (bibleWindows.length == 0) {
18063
18064			// open new window
18065			sofia.app.windowManager.add('BibleWindow', {
18066				textid: sofia.config.newBibleWindowVersion,
18067				fragmentid: fragmentid,
18068				sectionid: fragmentid.split('_')[0],
18069			});
18070
18071		} else {
18072
18073
18074			ext.trigger('globalmessage', {
18075								type: 'globalmessage',
18076								target: this,
18077								data: {
18078									messagetype:'nav',
18079									type: 'bible',
18080									locationInfo: {
18081										fragmentid: fragmentid,
18082										sectionid: fragmentid.split('_')[0],
18083										offset: 0
18084									}
18085								}
18086							});
18087		}
18088
18089	});
18090
18091	function searchLoadHandler(e) {
18092		searchProgressBar.show();
18093
18094		// give feedback!
18095		var reference = new bible.Reference(e.data.sectionid),
18096			label = e.data.sectionid,
18097			progress = (e.data.index+1) + ' / ' + e.data.total;
18098
18099		if (bible.BOOK_DATA['GN'].names[textInfo.lang]) {
18100			reference.lang = textInfo.lang;
18101		}
18102		label = reference.toString();
18103
18104
18105		// show results in footer and bar
18106		footer.html('Loading: ' + progress + ' :: ' + label );
18107		searchProgressBarInner.css({'width': ((e.data.index+1) / e.data.total * 100) + '%'  });
18108		searchProgressBarLabel.html(label);
18109
18110		// move label into place
18111		var progressWidth = searchProgressBarInner.outerWidth(true),
18112			labelWidth = searchProgressBarLabel.outerWidth(true);
18113
18114		if (labelWidth > progressWidth) {
18115
18116			searchProgressBarLabel
18117				.css({left: progressWidth + 'px', margin: '' })
18118				.addClass('search-progress-bar-label-outside');
18119
18120
18121		} else {
18122			searchProgressBarLabel
18123				.css({left: (progressWidth-labelWidth) + 'px', margin: '' })
18124				.removeClass('search-progress-bar-label-outside');
18125		}
18126	}
18127
18128	function searchIndexCompleteHandler(e) {
18129		var results = e.data.results;
18130		//console.log('searcher:indexcomplete', e.data);
18131
18132
18133		footer.html(i18n.t('windows.search.results') + e.data.searchIndexesData.length );
18134	}
18135
18136	function searchCompleteHandler(e) {
18137
18138		// store results
18139		currentResults = e.data.results;
18140		searchIndexesData = e.data.searchIndexesData;
18141		searchTermsRegExp = e.data.searchTermsRegExp;
18142		isLemmaSearch = e.data.isLemmaSearch;
18143
18144		//console.log('searcher:complete'); // , e.data.results);
18145
18146		var results = e.data.results,
18147			html = //'<h2>Results: ' + results.length + '</h2>' +
18148					'<table>';
18149
18150		searchProgressBarInner.css({'width': '100%'  });
18151		setFinalResultsCount(e.data.results ? e.data.results.length : 0);
18152		resultsBlock.removeClass('loading-indicator');
18153
18154		if (results && results.length > 0) {
18155
18156			// create visual array
18157			var divisionCount = {},
18158				bookList = null;
18159
18160
18161			if (isLemmaSearch) {
18162				var text = input.val();
18163
18164				if (text.substr(0,1) == 'G') {
18165					bookList = bible.NT_BOOKS;
18166				} else if (text.substr(0,1) == 'H') {
18167					bookList = bible.OT_BOOKS;
18168				}
18169			} else {
18170				bookList = textInfo.divisions;
18171			}
18172
18173			for (var i=0, il=bookList.length; i<il; i++) {
18174				divisionCount[ bookList[i] ] = 0;
18175			}
18176
18177
18178
18179			for (var i=0, il=results.length; i<il; i++) {
18180				var result = results[i],
18181					label = '',
18182					fragmentid = result.fragmentid,
18183					dbsBookCode = fragmentid.substr(0,2);
18184
18185				divisionCount[dbsBookCode]++;
18186
18187				if (textInfo.type.toLowerCase() == 'bible') {
18188					var br = new bible.Reference(result.fragmentid);
18189
18190					////console.log(br, br.toString(), bible.BOOK_DATA['GN'].names[textInfo.lang]);
18191
18192					if (bible.BOOK_DATA['GN'].names[textInfo.lang]) {
18193						br.lang = textInfo.lang;
18194					}
18195					label = br.toString();
18196				} else {
18197					label = result.fragmentid;
18198				}
18199
18200				html += '<tr data-fragmentid="' + result.fragmentid + '" class="divisionid-' + result.fragmentid.substr(0,2) + '"><th>
18200' + label + '</th><td lang="' + iso2iana.convert(textInfo.lang) + '">' + result.html + '</td></tr>';
18201			}
18202			html += '</table>';
18203
18204
18205			resultsBlock
18206				.html( html )
18207				.find('.v-num').remove();
18208
18209			// render book list
18210			renderResultsVisual(divisionCount, bookList);
18211
18212			if (isLemmaSearch) {
18213				renderLemmaInfo();
18214				renderUsage();
18215			}
18216			
18217			// TEMP 
18218			var strongsUsage = {}; 
18219			resultsBlock.find('.highlight').each(function() { 
18220				var h = $(this), l = h.closest('l'); 
18221				if (l.length > 0) { 
18222					var strongs = l.attr('s').split(' ')[0]; 
18223					if (typeof strongsUsage[strongs] != 'undefined') {
18224						strongsUsage[strongs].count++;
18225					} else {
18226						strongsUsage[strongs] = { count: 1 };
18227					}
18228				} 
18229			});
18230			var strongsHtml = '';
18231			var strongsKeys = Object.keys(strongsUsage);
18232			for (var i=0, il=strongsKeys.length; i<il; i++) {
18233				strongsHtml += '<tr><td>' + strongsKeys[i] + '</td><td>' + strongsUsage[strongsKeys[i]].count + '</td></tr>';
18234			}
18235			topUsage
18236				.append( $('<table>' + strongsHtml + '</table>') )
18237				.show();
18238			
18239			
18240
18241			createHighlights();
18242			
18243			//createLemmaUsage();
18244		} else {
18245
18246			resultsBlock.html( "No results" );
18247
18248		}
18249
18250		ext.trigger('settingschange', {type: 'settingschange', target: this, data: null});
18251
18252	}
18253
18254	function renderLemmaInfo() {
18255		var text = input.val(),
18256			strongs = text.split(' ')[0],
18257			strongsNumber = strongs.substr(1),
18258			strongLang = strongs.substr(0,1),
18259			langCode = (strongLang == 'H' ? 'he' : 'el'),
18260			dir = langCode == 'he' ? 'ltr' : 'rtl';
18261
18262		sofia.ajax({
18263			dataType: 'json',
18264			url: 'content/lexicons/strongs/entries/' + strongs + '.json',
18265			success: function(data) {
18266
18267				var html = '<div class="lemma-word">' +
18268								'<span lang="' + iso2iana.convert(langCode) + '" dir="' + dir + '">' + data.lemma + '</span>' +
18269								'  <span class="lemma-strongs" dir="ltr"> [strongs:' + strongsNumber + ']</span>' +
18270							'</div>';
18271
18272				//html += '<div class="lemma-outline">' + data.outline + '</div>';
18273
18274				topLemmaInfo
18275					.html(html)
18276					.show();
18277			}
18278		});
18279
18280	}
18281
18282	function renderUsage() {
18283		var usages = {},
18284			usageArray = [];
18285
18286		resultsBlock.find('tr').each(function() {
18287			var tr = $(this),
18288				fragmentid = tr.attr('data-fragmentid'),
18289				highlightedPhrase = tr.find('.highlight')[0].textContent;
18290
18291			highlightedPhrase = highlightedPhrase.replace(/\b(with|or|and|if|a|the|in|a|by|of|for)\b/gi,'').trim();
18292
18293			if (typeof usages[highlightedPhrase] == 'undefined') {
18294				usages[highlightedPhrase] = 0;
18295			}
18296
18297			usages[highlightedPhrase]++;
18298		});
18299
18300		// turn into array
18301		for (var usage in usages) {
18302			usageArray.push({usage: usage, count: usages[usage]});
18303		}
18304
18305		// sort
18306		usageArray.sort(function(a, b) {
18307			if (a.count < b.count) {
18308				return 1;
18309			} else if (a.count > b.count) {
18310				return -1;
18311			} else {
18312				return 0;
18313			}
18314
18315		});
18316
18317		var html = '';
18318		for (var i=0, il=usageArray.length; i<il; i++) {
18319			html += (i > 0 ? ', ' : '') + usageArray[i].usage + ' (' + usageArray[i].count + ')';
18320		}
18321
18322		topUsage
18323			.html(html)
18324			.show();
18325	}
18326
18327	function renderResultsVisual(divisionCount, bookList) {
18328		var totalWidth = topVisual.outerWidth(),
18329			totalBooks = bookList.length,
18330			width = 1/totalBooks*100,
18331			html = '',
18332			maxCount = 0,
18333			baseHeight = 2;
18334			maxHeight = 38;
18335
18336		for (var i=0, il=bookList.length; i<il; i++) {
18337			var count = divisionCount[ bookList[i] ];
18338			if (count > maxCount) {
18339				maxCount = count;
18340			}
18341		}
18342
18343
18344		for (var i=0, il=bookList.length; i<il; i++) {
18345			var dbsBookCode = bookList[i],
18346				count = divisionCount[dbsBookCode],
18347				height = maxHeight * count / maxCount + baseHeight,
18348				top = maxHeight + baseHeight - height;
18349
18350			html += '<span class="search-result-book-bar ' + dbsBookCode + '" data-count="' + count + '" data-id="' + dbsBookCode + '" style="width:' + width + '%;"><span class="divisionid-' + dbsBookCode + '" style="height:' + height + 'px; margin-top: ' + top + 'px;"></span></span>';
18351		}
18352
18353		topVisual.html(html).show();
18354	}
18355
18356	topVisual.on('mouseover', '.search-result-book-bar', function() {
18357
18358		var bookBar = $(this),
18359			count = bookBar.attr('data-count'),
18360			dbsBookCode = bookBar.attr('data-id'),
18361			bookInfo = bible.BOOK_DATA[dbsBookCode],
18362			win = bookBar.closest('.window'),
18363			winPos = win.offset(),
18364			winWidth = win.outerWidth(true),
18365			bookBarPos = bookBar.offset(),
18366			top = bookBarPos.top + bookBar.height() + 10 - winPos.top,
18367			left = bookBarPos.left - winPos.left;
18368
18369
18370		topVisualLabel
18371			.html(bookInfo.names[textInfo.lang][0] + ': ' + count)
18372			.css({top: top, left: left})
18373			.show();
18374
18375		if (left + topVisualLabel.outerWidth() > winWidth) {
18376			left = winWidth - topVisualLabel.outerWidth() - 20;
18377
18378			topVisualLabel
18379				.css({left: left});
18380		}
18381
18382
18383	}).on('mouseout', '.search-result-book-bar', function() {
18384
18385		topVisualLabel
18386			.hide();
18387	}).on('click', '.search-result-book-bar', function() {
18388
18389		var bookBar = $(this),
18390			dbsBookCode = bookBar.attr('data-id'),
18391			bookInfo = bible.BOOK_DATA[dbsBookCode];
18392
18393		// find first result?
18394		resultsBlock.find('tr').each(function() {
18395			var tr = $(this),
18396				fragmentid = tr.attr('data-fragmentid');
18397
18398			if (fragmentid.indexOf(dbsBookCode) == 0) {
18399
18400				// scrollTo
18401				console.log('found', tr.offset());
18402
18403				main.scrollTop( tr.offset().top - tr.outerHeight(true) - 50);
18404
18405				return false;
18406			}
18407
18408		});
18409
18410
18411	});
18412
18413	function setFinalResultsCount(count) {
18414
18415		footer.html(i18n.t('windows.search.results') + ': ' + count );
18416
18417		// move to center
18418		searchProgressBarLabel.html(count + ' ' + i18n.t('windows.search.verses'));
18419
18420		var progressWidth = searchProgressBarInner.outerWidth(true),
18421			labelWidth = searchProgressBarLabel.outerWidth(true),
18422			labelLeft = progressWidth/2 - labelWidth;
18423
18424		searchProgressBarLabel
18425			//.css({left: labelLeft + 'px'});
18426			.css({left: '50%', marginLeft: '-' + (labelWidth/2) + 'px'});
18427
18428	}
18429
18430	function clearResults() {
18431		footer.html('');
18432		topBlockTitle.html('');
18433		resultsBlock.html('');
18434		topVisual.html('').hide();
18435		topLemmaInfo.html('').hide();
18436		topUsage.html('').hide();
18437		searchProgressBar.hide();
18438		searchProgressBarLabel.html('');
18439		searchProgressBarInner.width(0);
18440	}
18441
18442	// ACTIONS
18443	function doSearch()	{
18444
18445		disable();
18446
18447		textInfo = textChooser.getTextInfo();
18448
18449		var text = input.val().trim(),
18450			//textid = list.val(),
18451			textid = textInfo.id,
18452
18453			divisions = getSelectedDivisions(),
18454
18455			allDivisions = divisionChooser.find('.division-list-items input');
18456
18457		parent.tab.find('span').html(text);
18458
18459		// don't send the list if it's all books
18460		if (allDivisions.length == divisions.length) {
18461			divisions = [];
18462		}
18463
18464		clearResults();
18465
18466		topBlockTitle.html('[' + text + '] in [' + textInfo.name + ']');
18467
18468		removeHighlights();
18469
18470		resultsBlock.addClass('loading-indicator');
18471
18472		enable();
18473
18474		TextLoader.startSearch(textid, divisions, text, searchLoadHandler, searchIndexCompleteHandler, searchCompleteHandler);
18475	}
18476
18477	function getSelectedDivisions() {
18478
18479		var divisions = [],
18480			selectedBooks = divisionChooser.find('.division-list-items input:checked');
18481
18482		selectedBooks.each(function() {
18483			divisions.push( $(this).val() );
18484		});
18485
18486		return divisions;
18487	}
18488
18489	function disable() {
18490		input.prop('disabled', true);
18491		button.prop('disabled', true);
18492	}
18493	function enable() {
18494		input.prop('disabled', false);
18495		button.prop('disabled', false);
18496	}
18497
18498
18499	function size(width, height) {
18500
18501		header.outerWidth(width);
18502		footer.outerWidth(width);
18503
18504		main.outerWidth(width)
18505			.outerHeight(height - header.outerHeight() - footer.outerHeight());
18506	}
18507
18508	function setTextInfo(textInfo, sendToChooser) {
18509
18510		// keep old one
18511		previousTextInfo = selectedTextInfo;
18512
18513		// store new one
18514		selectedTextInfo = textInfo;
18515
18516		textlistui.html(textInfo.abbr);
18517
18518		// draw books
18519		drawDivisions();
18520
18521		if (sendToChooser) {
18522			textChooser.setTextInfo(textInfo);
18523		}
18524	}
18525
18526	// init
18527	function init() {
18528
18529		if (init_data.textid) {
18530			TextLoader.getText(init_data.textid, function(data) {
18531
18532				setTextInfo(data, true);
18533
18534				if (init_data.divisions) {
18535					setDivisions(init_data.divisions);
18536				}
18537
18538				if (init_data.searchtext && init_data.searchtext != '') {
18539					input.val(init_data.searchtext);
18540
18541					doSearch();
18542				}
18543
18544			});
18545		} else {
18546			console.log('SEARCH: no init textid');
18547
18548			for (var index in TextLoader.textData) {
18549
18550				setTextInfo(TextLoader.textData[index], true);
18551				break;
18552			}
18553		}
18554	}
18555	init();
18556
18557	function removeHighlights() {
18558		$('.BibleWindow .highlight').each(function(i, el) {
18559
18560			if (el.tagName.toLowerCase() == 'l') {
18561				// for Lemma tags, jsut remove hte hlight
18562				el.className = el.className.replace(/highlight/gi, '');
18563			} else {
18564				// if it's just <span class="highlight">, replace it with text
18565				var textFragment = document.createTextNode(el.textContent);
18566				if (el && el.parentNode) {
18567					el.parentNode.insertBefore(textFragment, el);
18568					el.parentNode.removeChild(el);
18569				}
18570			}
18571
18572		});
18573	}
18574
18575	function createHighlights() {
18576
18577		if (currentResults == null) {
18578			return;
18579		}
18580
18581		removeHighlights();
18582
18583		// try to highlight!
18584		for (var i=0, il=currentResults.length; i<il; i++) {
18585			var result = currentResults[i];
18586
18587
18588			$('.' + result.fragmentid).each(function(i,el) {
18589
18590
18591				for (var j=0, jl=searchTermsRegExp.length; j<jl; j++) {
18592
18593					searchTermsRegExp[j].lastIndex = 0;
18594
18595
18596
18597					if (isLemmaSearch) {
18598
18599						// add the 'highlight' class to the <l> node
18600						el.innerHTML = el.innerHTML.replace(searchTermsRegExp[j], function(match) {
18601							return match + ' class="highlight" ';
18602						});
18603
18604					} else {
18605
18606						// surround the word with a highlight
18607						el.innerHTML = el.innerHTML.replace(searchTermsRegExp[j], function(match) {
18608							return '<span class="highlight">' + match + '</span>';
18609						});
18610
18611					}
18612				}
18613
18614			});
18615
18616		}
18617
18618	}
18619	
18620	function createLemmaUsage() {
18621		var strongsUsage = {}; 
18622		
18623		$('.highlight').each(function() { 
18624		   var h = $(this), l = h.closest('l'); 
18625		   if (l.length > 0) { 
18626		      var strongs = l.attr('s').split(' ')[0]; 
18627		      if (typeof words[strongs] != 'undefined') {
18628		         strongsUsage[strongs]++;
18629		      } else {
18630		         strongsUsage[strongs] = 1;
18631		      }
18632		   } 
18633		});
18634		console.log(words);
18635		
18636	}
18637
18638	function close() {
18639		removeHighlights();
18640
18641		divisionChooser.remove();
18642		textChooser.close();
18643
18644		ext.clearListeners();
18645		ext = null;
18646	}
18647
18648	var ext = {
18649		size: size,
18650		getData: function() {
18651
18652			var divisions = divisionChooser.find('.division-list-ot .division-header input').is(':checked') &&
18653							divisionChooser.find('.division-list-nt .division-header input').is(':checked') ?
18654								[] : getSelectedDivisions();
18655
18656			return {
18657				searchtext: input.val().trim(),
18658				textid: (selectedTextInfo != null) ? selectedTextInfo.providerid : null,
18659				divisions: divisions,
18660				params: {
18661					'win': 'search',
18662					'textid': (selectedTextInfo != null) ? selectedTextInfo.providerid : null,
18663					'searchtext': input.val(),
18664					'divisions': divisions
18665				}
18666			}
18667		},
18668		close: close
18669	};
18670	ext = $.extend(true, ext, EventEmitter);
18671
18672	ext.on('message', function(e) {
18673
18674		if (e.data.messagetype == 'textload') {
18675			////console.log('search:textload', e.data.sectionid);
18676
18677			createHighlights(e.data.content);
18678
18679		}
18680	});
18681
18682	return ext;
18683
18684};
18685
18686sofia.initMethods.push(function() {
18687
18688	if (sofia.config.enableOnlineSources) {
18689
18690		sofia.windowTypes.push( {
18691			className:'SearchWindow',
18692			param: 'search',
18693			paramKeys: {
18694				'textid': 't',
18695				'searchtext': 's',
18696				'divisions': 'd'
18697			},
18698			init: {
18699			}
18700		});
18701	}
18702});
18703
18704
18705
18706/*********
18707* ../app/js/windows/mapswindow.js
18708**********/
18709
18710var MapWindow = function(id, parent, data) {
18711
18712	//parentNode.css({position: 'relative'});
18713
18714	var now = new Date(),
18715		//id = 'maps_' + now.getYear() + '_' + (now.getMonth()+1) + '_' + now.getDate() + '_' + now.getHours() + '_' + now.getMinutes() + '_' + now.getSeconds(),
18716		loadMapFunctionName = 'loadMap_' + id,
18717		//header = $('<div id="map-header" style="height: 40px; background: #ddd; padding: 9px;"><input type="text" style="width:100%;" /</div>').appendTo(parentNode),
18718
18719
18720		inputMarginLeft = 10,
18721		inputMarginRight = 45,
18722		inputMarginTop = 10,
18723		mapSearchInput = $('<input placeholder="" type="text" class="i18n" data-i18n="[placeholder]windows.map.placeholder" style="height: 32px; background: #fff; border: solid 0px #333; padding: 7px; position: absolute; top: ' + inputMarginTop + 'px; left: ' + inputMarginLeft + 'px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); z-index: 2; font-size: 14px;" />').appendTo(parent.node),
18724
18725		//mapSearchInput = header.find('input'),
18726
18727		mapContainer = $('<div class="window-maps" id="' + id + '"></div>').appendTo(parent.node),
18728		map = null,
18729		geocoder = null,
18730		infowindow = null,
18731		locationData = null,
18732		locationDataByVerse = null;
18733
18734
18735	// bind init method to window object
18736	window[loadMapFunctionName] = function() {
18737		var mapOptions = {
18738			zoom: 8,
18739			disableDefaultUI: true,
18740			center: new google.maps.LatLng(data.latitude, data.longitude),
18741			//mapTypeId: google.maps.MapTypeId.ROADMAP,
18742
18743			mapTypeId: google.maps.MapTypeId.HYBRID,
18744			mapTypeControl: false,
18745			mapTypeControlOptions: {
18746				style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
18747				position: google.maps.ControlPosition.BOTTOM_CENTER
18748			},
18749			panControl: false,
18750				panControlOptions: {
18751				position: google.maps.ControlPosition.TOP_RIGHT
18752			},
18753			zoomControl: true,
18754			zoomControlOptions: {
18755				style: google.maps.ZoomControlStyle.LARGE,
18756				position: google.maps.ControlPosition.LEFT_CENTER
18757			},
18758			scaleControl: false,
18759				scaleControlOptions: {
18760				position: google.maps.ControlPosition.TOP_LEFT
18761			},
18762			streetViewControl: false,
18763				streetViewControlOptions: {
18764				position: google.maps.ControlPosition.LEFT_TOP
18765			}
18766		};
18767
18768		map = new google.maps.Map(document.getElementById(id), mapOptions);
18769
18770		google.maps.event.addListener(map, 'center_changed', function() {
18771
18772			var center = map.getCenter();
18773
18774			ext.trigger('settingschange', {
18775						type:'settingschange',
18776						target: this,
18777						data: {
18778							latitude: center.lat(),
18779							longitude: center.lng(),
18780							label: 'Map: ' + center.lat().toFixed(3) +', ' + center.lng().toFixed(3)
18781						}
18782					});
18783
18784		});
18785
18786
18787		geocoder = new google.maps.Geocoder();
18788
18789		infowindow = new google.maps.InfoWindow({
18790			content: 'Empty for now'
18791		});
18792
18793
18794		loadPins();
18795	}
18796
18797	mapSearchInput.on('keypress', function(e) {
18798		if (map != null && e.which == 13) {
18799
18800			var search_value = mapSearchInput.val();
18801
18802
18803			// find a marker!
18804			findMarkerByText(search_value);
18805
18806		}
18807	});
18808
18809	function findMarkerByText(value) {
18810		for (var i=0, il=locationData.length; i<il; i++) {
18811			var location = locationData[i];
18812
18813			//if (location.name.toLowerCase().indexOf(value.toLowerCase()) > -1) {
18814			if (location.name.toLowerCase() == value.toLowerCase()) {
18815
18816				openLocation(location);
18817
18818				break;
18819			}
18820		}
18821	}
18822
18823	mapContainer.on('click', '.verse, .v', function() {
18824		var link = $(this),
18825			sectionid = link.attr('data-sectionid'),
18826			fragmentid = link.attr('data-fragmentid');
18827
18828		ext.trigger('globalmessage', {
18829									type: 'globalmessage',
18830									target: this,
18831									data: {
18832										messagetype: 'nav',
18833										type: 'bible',
18834										locationInfo: {
18835											sectionid: sectionid,
18836											fragmentid: fragmentid
18837										}
18838									}
18839								});
18840
18841		console.log('clicked', this);
18842
18843	});
18844
18845	// dynamically load map
18846	if (typeof window.google == 'undefined' ||
18847		typeof window.google.maps == 'undefined' ||
18848		typeof window.google.maps.Map == 'undefined') {
18849
18850		var script = document.createElement('script');
18851		script.type = 'text/javascript';
18852		script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=' + loadMapFunctionName;
18853
18854		document.body.appendChild(script);
18855	} else {
18856		console.log('gogole is loaded');
18857
18858		// load now!
18859		window[loadMapFunctionName]();
18860	}
18861
18862
18863	function loadPins() {
18864
18865		console.log('MAP: loading pins');
18866
18867		sofia.ajax({
18868			dataType: 'json',
18869			url: 'content/maps/maps.json',
18870			success: function(data) {
18871
18872				// store data
18873				locationData = data.names;
18874
18875				createPins();
18876				//setTimeout(createPins, 50);
18877			}
18878		});
18879	}
18880
18881
18882	function createPins() {
18883
18884		locationDataByVerse = {};
18885
18886		var before = new Date();
18887		for (var i=0, il=locationData.length; i<il; i++) {
18888			var location = locationData[i];
18889
18890			// create pin
18891			(function(location) {
18892
18893				var marker = new google.maps.Marker({
18894					position: new google.maps.LatLng(location.coordinates[1], location.coordinates[0]),
18895					map: map,
18896					title: location.name
18897				});
18898
18899				location.marker = marker;
18900
18901				google.maps.event.addListener(marker, 'click', function() {
18902
18903					openLocation(location);
18904
18905				});
18906
18907
18908			})(location);
18909
18910			// create back link
18911			for (var j=0, jl=location.verses.length; j<jl; j++) {
18912
18913				var verseid = location.verses[j],
18914					//bible_ref = new bible.Reference(verse),
18915					//verseid = bible_ref.bookid + bible_ref.chapter + '_' + bible_ref.verse,
18916					verseInfo = locationDataByVerse[verseid];
18917
18918				if (typeof verseInfo == 'undefined')	{
18919					verseInfo = [];
18920					locationDataByVerse[verseid] = verseInfo;
18921				}
18922
18923				verseInfo.push(location);
18924			}
18925		}
18926		var after = new Date();
18927
18928		console.log('process time', after - before);
18929
18930
18931		// SO SLOW!
18932
18933		// create locationDataByVerse
18934
18935		/*var before = new Date();
18936
18937		for (var i=0, il=locationData.length; i<il; i++) {
18938			var location = locationData[i];
18939
18940			for (var j=0, jl=location.verses.length; j<jl; j++) {
18941
18942				var verseid = location.verses[j],
18943					//bible_ref = new bible.Reference(verse),
18944					//verseid = bible_ref.bookid + bible_ref.chapter + '_' + bible_ref.verse,
18945					verseInfo = locationDataByVerse[verseid];
18946
18947				if (typeof verseInfo == 'undefined')	{
18948					verseInfo = [];
18949					locationDataByVerse[verseid] = verseInfo;
18950				}
18951
18952				verseInfo.push(location);
18953			}
18954		}
18955		var after = new Date();
18956
18957		console.log('process time', after - before);
18958		*/
18959
18960		//parentNode.html( JSON.stringify(locationDataByVerse) );
18961		//return;
18962
18963		highlightStoredLocations();
18964	}
18965
18966	function openLocation(location) {
18967		var verses_html = $.map(location.verses, function(a) {
18968			var bible_ref = new bible.Reference(a),
18969				sectionid = bible_ref.bookid + bible_ref.chapter,
18970				fragmentid = sectionid + '_' + bible_ref.verse1;
18971
18972			return '<span class="verse" style="text-decoration:underline; cursor: pointer" data-sectionid="' + sectionid + '" data-fragmentid="' + fragmentid + '">' + bible_ref.toString() + '</span>';
18973		});
18974
18975		infowindow.setContent(
18976							'<div style="width: 250px; height: 150px; overflow: auto;">' +
18977								'<h2>' + location.name + '</h2>' +
18978								'<p>' +
18979								verses_html.join('; ') +
18980								'</p>' +
18981							'</div>');
18982		infowindow.open(map, location.marker);
18983
18984		map.setCenter(location.marker.getPosition());
18985	}
18986
18987	function size(width, height) {
18988
18989		mapSearchInput.outerWidth(width - (inputMarginLeft+inputMarginRight));
18990
18991		mapContainer
18992				.width(width)
18993				.height(height); // - header.outerHeight());
18994	}
18995
18996	function getData() {
18997
18998		if (map == null) {
18999			return {
19000				params: {
19001					win: 'map'
19002				}
19003			};
19004		}
19005
19006		var center = map.getCenter(),
19007			data = {
19008				latitude: center.lat(),
19009				longitude: center.lng(),
19010				params: {
19011					'win': 'map',
19012					'latitude': center.lat(),
19013					'longitude': center.lng()
19014				}
19015			};
19016
19017		return data
19018	}
19019
19020	var contentToHighlight = [];
19021	function highlightStoredLocations() {
19022		//removeHighlights();
19023
19024
19025		if (contentToHighlight.length > 0) {
19026			for (var i=0, il=contentToHighlight.length; i<il; i++) {
19027				highlightLocations(contentToHighlight[i]);
19028			}
19029			contentToHighlight = [];
19030		}
19031	}
19032
19033	function highlightLocations(content) {
19034
19035		content.find('.verse, .v').each(function() {
19036			var verse = $(this),
19037				verseid = verse.attr('data-id');
19038				verseLocations = locationDataByVerse[verseid],
19039				html = verse.html();
19040
19041			//console.log(verseid, verseLocations);
19042
19043			if (typeof verseLocations != 'undefined') {
19044
19045				for (var i=0, il=verseLocations.length; i<il; i++) {
19046					var location = verseLocations[i],
19047						regexp = new RegExp('\\b' + location.name + '\\b', 'gi'),
19048					html = html.replace(regexp , '<span class="linked-location">' + location.name + '</span>');
19049
19050					//location.marker.setIcon('http://maps.google.com/intl/en_us/mapfiles/ms/micons/purple-dot.png');
19051					location.marker.setIcon('http://mt.google.com/vt/icon?color=ff135C13&name=icons/spotlight/spotlight-waypoint-a.png');
19052				}
19053			}
19054
19055			verse.html( html );
19056
19057		});
19058
19059
19060		content.on('click', '.location', text_location_clicked);
19061
19062
19063		// highlight pins?
19064
19065
19066	}
19067
19068	function removeHighlights() {
19069		$('.BibleWindow .linked-location').each(function(i, el) {
19070
19071			if (el.tagName.toLowerCase() == 'l') {
19072				// for Lemma tags, just remove the class
19073				el.className = el.className.replace(/linked-location/gi, '');
19074			} else {
19075				// if it's just <span class="linked-location">, replace it with text
19076				var textFragment = document.createTextNode(el.textContent);
19077				el.parentNode.insertBefore(textFragment, el);
19078				el.parentNode.removeChild(el);
19079			}
19080
19081		});
19082	}
19083
19084
19085	function close() {
19086		console.log('closing maps');
19087
19088		geocoder = null;
19089		map = null;
19090		infowindow = null;
19091
19092		ext.clearListeners();
19093
19094		removeHighlights();
19095	}
19096
19097	function text_location_clicked() {
19098		var element = $(this);
19099
19100		findMarkerByText( element.html() );
19101	}
19102
19103	var ext = {
19104		size: size,
19105		getData: getData,
19106		sendMessage: function() {},
19107		close: close
19108	};
19109	ext = $.extend(true, ext, EventEmitter);
19110
19111
19112	ext.on('message', function(e) {
19113		if (e.data.messagetype == 'textload') {
19114			//console.log('maps got text load', e.data.sectionid);
19115
19116			// store until data is loaded
19117			if (locationDataByVerse == null) {
19118				contentToHighlight.push(e.data.content);
19119				return;
19120			} else {
19121				// are there ones left to do
19122				highlightStoredLocations();
19123
19124				// do this one
19125				highlightLocations(e.data.content);
19126			}
19127		}
19128	});
19129
19130	return ext;
19131};
19132
19133//sofia.windowTypes.push('MapsWindow');
19134
19135
19136
19137sofia.initMethods.push(function() {
19138
19139	if (sofia.config.enableOnlineSources) {
19140
19141		sofia.windowTypes.push( {
19142					className:'MapWindow',
19143					param: 'map',
19144					paramKeys: {
19145						'latitude': 'la',
19146						'longitude': 'ln',
19147					},
19148					init: {
19149						'latitude': 31.7833,
19150						'longitude': 35.2167
19151					}
19152		});
19153	}
19154});
19155
19156
19157
19158/*********
19159* ../app/js/windows/mediawindow.js
19160**********/
19161
19162var MediaWindow = function(id, parent, data) {
19163
19164	var mediaLibraries = null,
19165		contentToProcess = null,
19166		currentSectionId = '';
19167		header = $('<div class="window-header"><span class="window-title i18n" data-i18n="[html]windows.media.label"></span></div>').appendTo(parent.node),
19168		main = $('<div class="window-main">' +
19169					'<div class="media-video"></div>' +
19170					'<div class="media-content"></div>' +
19171				'</div>').appendTo(parent.node),
19172		videoArea = main.find('media-video'),
19173		contentArea = main.find('media-content');
19174
19175	header.find('.i18n').i18n();
19176
19177	MediaLibrary.getMediaLibraries(function(data) {
19178		mediaLibraries = data;
19179
19180		processContent();
19181	});
19182
19183	function processContent() {
19184		if (mediaLibraries == null || contentToProcess == null) {
19185			return;
19186		}
19187
19188		var sectionid = contentToProcess.attr('data-id');
19189
19190		if (currentSectionId == sectionid) {
19191			return;
19192		}
19193
19194		currentSectionId = sectionid;
19195
19196
19197		var bibleReference = new bible.Reference(sectionid);
19198		bibleReference.language = contentToProcess.attr('lang');
19199
19200
19201		// remove all previous checks
19202		$('.checked-media').removeClass('checked-media');
19203
19204		main.html('');
19205		main.scrollTop(0);
19206
19207
19208		// vers images
19209		var node = $('<div class="media-library-verses">' +
19210						'<h2>' + bibleReference.toString() + '</h2>' +
19211						'<div class="media-library-thumbs"></div>' +
19212					'</div>').appendTo(main),
19213			gallery = node.find('.media-library-thumbs'),
19214			html = '';
19215
19216
19217		// create HTML for <img>s
19218		contentToProcess.find('.verse, .v').each(function(i,el) {
19219			var verse = $(this),
19220				verseid = verse.attr('data-id'),
19221				reference = new bible.Reference(verseid);
19222
19223			verse = verse.closest('.chapter').find('.' + verseid + '').first();
19224
19225			if (verse.hasClass('checked-media')) {
19226				return;
19227			}
19228
19229			html += renderVerse(verseid, reference, mediaLibraries);
19230
19231			verse.addClass('checked-media');
19232		});
19233
19234		//console.log(html);
19235
19236		gallery.html(html);
19237
19238
19239		var images = gallery.find('img');
19240
19241		// show error message
19242		if (images.length == 0) {
19243			gallery.html('No media for this chapter');
19244			return;
19245		}
19246
19247		// determine if all have loaded
19248		images.on('load', function() {
19249			var img = $(this);
19250			img.addClass('loaded');
19251
19252			if (images.filter('.loaded').length == images.length) {
19253				resizeImages(gallery);
19254			}
19255		});
19256
19257	}
19258
19259
19260	// handle resizing
19261	var timeoutVar = null;
19262	$(window).on('resize', function() {
19263
19264		if (timeoutVar != null) {
19265			clearTimeout(timeoutVar);
19266		}
19267
19268		timeoutVar = setTimeout(function() {
19269			startResize();
19270		}, 100);
19271	});
19272
19273
19274	main.on('click', '.media-library-thumbs a.mediatype-image', sofia.globals.mediaImageClick);
19275	main.on('click', '.media-library-thumbs a.mediatype-video', sofia.globals.mediaVideoClick);
19276	main.on('click', '.media-library-thumbs a.mediatype-jfm', sofia.globals.mediaVideoJfmClick);
19277
19278
19279	function renderVerse(verseid, reference, mediaLibraries) {
19280
19281		var html = '';
19282
19283		for (var i=0, il=mediaLibraries.length; i<il; i++) {
19284
19285			var mediaLibrary = mediaLibraries[i],
19286				mediaForVerse = mediaLibrary.data ? mediaLibrary.data[verseid] : undefined;
19287
19288			// add media
19289			if (typeof mediaForVerse != 'undefined') {
19290
19291				for (var j=0, jl = mediaForVerse.length; j<jl; j++) {
19292					var mediaInfo = mediaForVerse[j],
19293						baseUrl = sofia.config.baseContentUrl + 'content/' + 'media/' + mediaLibrary.folder + '/',
19294						fullUrl = baseUrl + mediaInfo.filename + '.' + mediaInfo.exts[0],
19295						thumbUrl = baseUrl + mediaInfo.filename + '-thumb.jpg';
19296
19297					html += '<a href="' + fullUrl + '" class="mediatype-' + mediaLibrary.type + '" target="_blank" ' + (mediaInfo.name ? 'title="' + mediaInfo.name + '"' : '') + ' data-filename="' + mediaInfo.filename + '">' +
19298								'<img src="' + thumbUrl + '" />' +
19299								//((mediaLibrary.type != 'image') ? '<span>' + mediaInfo.name + '</span>'  : '') +
19300								((mediaLibrary.type != 'image') ? '<b><i></i></b>'  : '') +
19301								'<span>' + reference.toString() + '</span>' +
19302							'</a>';
19303				}
19304
19305			}
19306		}
19307
19308		return html;
19309
19310	}
19311
19312	function startResize() {
19313		resizeImages( main.find('.media-library-thumbs'));
19314	}
19315
19316
19317	function resizeImages(gallery) {
19318
19319		// once loaded!
19320		var TARGET_ROW_HEIGHT = 80,
19321			TARGET_GUTTER_WIDTH = 4,
19322			container_width = gallery.width(),
19323			current_width = 0,
19324			currentRow = [];
19325
19326		gallery.find('img').each(function() {
19327			var img = $(this),
19328				a = img.closest('a'),
19329				width = img.data('original-width');
19330				height = img.data('original-height');
19331
19332			// store for resize
19333			if (width == null) {
19334				width = img.width();
19335				img.data('original-width', width);
19336			}
19337			if (height == null) {
19338				height = img.height();
19339				img.data('original-height', height);
19340			}
19341
19342			var
19343				height_ratio = TARGET_ROW_HEIGHT / height, // (height > TARGET_ROW_HEIGHT) ? TARGET_ROW_HEIGHT / height :
19344				new_width = Math.floor(height_ratio*width);
19345
19346			// will this push the last row
19347			//if (false && container_width < current_width + new_width ) {
19348			if (container_width < current_width + new_width ) {
19349
19350				// resize the previous ones
19351				var row_ratio = container_width / current_width,
19352					remainder = container_width - current_width,
19353					width_per_item = Math.ceil(remainder / currentRow.length);
19354
19355
19356				/*
19357				console.log('row',
19358								row_ratio,
19359								container_width, current_width,
19360								container_width - current_width);
19361				*/
19362
19363				for (var j=0, jl=currentRow.length; j<jl; j++) {
19364					var row_a = currentRow[j],
19365						row_img = row_a.find('img'),
19366						row_width = parseInt(row_a.width(), 10),
19367						row_height = parseInt(row_a.height(), 10),
19368						//new_row_width = Math.floor(row_width * row_ratio),
19369						new_row_width = row_width + width_per_item,
19370						new_row_height = Math.floor(row_height * row_ratio);
19371
19372					row_a.width(new_row_width);
19373					row_a.height(new_row_height);
19374
19375					row_img.width(new_row_width);
19376					row_img.height(new_row_height);
19377
19378
19379					if (j+1 == jl) {
19380						row_a.css('marginRight',0);
19381					}
19382
19383					remainder = remainder - width_per_item;
19384
19385					if (width_per_item > remainder) {
19386						width_per_item = remainder;
19387					}
19388
19389				}
19390
19391
19392				// start over
19393				currentRow = [];
19394				current_width = 0;
19395			}
19396
19397
19398			// restart
19399			//console.log(width, height, new_width);
19400
19401			a
19402				.width(new_width)
19403				.height(TARGET_ROW_HEIGHT)
19404				.css({
19405					"marginRight": TARGET_GUTTER_WIDTH + 'px',
19406					"marginBottom": TARGET_GUTTER_WIDTH + 'px'});
19407
19408			img
19409				.width(new_width)
19410				.height(TARGET_ROW_HEIGHT);
19411
19412			currentRow.push(a);
19413
19414			current_width += new_width + TARGET_GUTTER_WIDTH;
19415
19416
19417
19418		});
19419
19420	}
19421
19422
19423
19424	function close() {
19425
19426		ext.clearListeners();
19427	}
19428
19429	function size(width, height) {
19430		// do notheirng?
19431		main.outerHeight(height - header.outerHeight())
19432			.outerWidth(width);
19433
19434
19435		startResize();
19436	}
19437
19438	var ext = {
19439		size: size,
19440		getData: function() {
19441			return {
19442
19443				params: {
19444					'win': 'media'
19445				}
19446
19447			}
19448		},
19449		close: close
19450	};
19451	ext = $.extend(true, ext, EventEmitter);
19452
19453	ext.on('message', function(e) {
19454		if (e.data.messagetype == 'textload') {
19455			//processContent(e.data.content);
19456		}
19457
19458		if (e.data.messagetype == 'nav' && e.data.type == 'bible') {
19459
19460			var content = $('.section[data-id="' + e.data.locationInfo.sectionid + '"]').first();
19461
19462			contentToProcess = content;
19463
19464			processContent();
19465		}
19466	});
19467
19468	setTimeout(function() {
19469		var firstWindowSettings = sofia.app.windowManager.getSettings()[0],
19470			firstWin = $('.window:first'),
19471			selectedChapter = (firstWindowSettings && firstWindowSettings.data) ? firstWin.find('.section[data-id="' + firstWindowSettings.data.sectionid + '"]').first() : null;
19472
19473		if (selectedChapter != null) {
19474			contentToProcess = selectedChapter;
19475			processContent();
19476		}
19477
19478	}, 500);
19479
19480	// try to find the first one
19481	//var firstWindow = sofia.windowManager.windows[0];
19482
19483
19484
19485	return ext;
19486
19487};
19488//sofia.windowTypes.push('MediaWindow');
19489
19490
19491
19492
19493
19494sofia.initMethods.push(function() {
19495
19496
19497	sofia.windowTypes.push( {
19498				className:'MediaWindow',
19499				param: 'media',
19500				paramKeys: {},
19501				init: {
19502					'latitude': 31.7833,
19503					'longitude': 35.2167
19504				}
19505	});
19506
19507});
19508
19509
19510
19511/*********
19512* ../app/js/windows/parallelswindow.js
19513**********/
19514
19515var ParallelsWindow = function(id, parent, init_data) {
19516
19517	var
19518		parallelsDataFolder = 'content/parallels/',
19519
19520		container =
19521			$('<div class="parallels-container">'+
19522				'<div class="window-header parallels-header">'+
19523					'<div class="scroller-header-inner">'+
19524						'<div class="parallel-list">' +
19525							'<select class="header-list app-list"></select>' +
19526						'</div>' +
19527						'<div class="header-list app-list text-list"></div>'+
19528						//'<div class="header-list parallels-list"></div>'+
19529					'</div>'+
19530				'</div>'+
19531				'<div class="parallels-main">' +
19532				'</div>' +
19533			'</div>').appendTo(parent.node),
19534
19535		// dom nodes
19536		header = container.find('.parallels-header'),
19537		main = container.find('.parallels-main'),
19538		textlistui = header.find('.text-list'),
19539
19540		// objects
19541		textChooser = sofia.globalTextChooser,
19542
19543		parallelsList = container.find('.parallel-list select'),
19544
19545		// settings
19546		currentTextInfo = null,
19547		textsInitialized = false,
19548		parallelsData = null;
19549
19550	parallelsList.on('change', function() {
19551
19552		loadParallelData();
19553	});
19554
19555	textlistui.on('click', function(e) {
19556
19557		// if this is selected, then toggle
19558		if (textChooser.getTarget() == textlistui) {
19559			textChooser.toggle();
19560		} else {
19561			textChooser.setTarget(container, textlistui, 'bible');
19562			textChooser.setTextInfo(currentTextInfo);
19563			textChooser.show();
19564		}
19565
19566	});
19567
19568
19569
19570	textChooser.on('change', function (e) {
19571
19572		if (e.data.target != textlistui) {
19573			return;
19574		}
19575
19576		var newTextInfo = e.data.textInfo;
19577
19578		// ALWAYS UPDATE: for first load
19579		// update version name
19580		textlistui.html( newTextInfo.abbr );
19581
19582		// if it has *changed* then we need to reload the text in the scroller
19583		if (currentTextInfo == null || newTextInfo.id != currentTextInfo.id) {
19584
19585			// store
19586			currentTextInfo = newTextInfo;
19587
19588			// load new text
19589			main.html('');
19590
19591			loadParallelData();
19592		}
19593
19594	});
19595
19596
19597	// START UP
19598
19599	function init() {
19600
19601		// TEMP
19602		textlistui.html('Version');
19603
19604		console.log('textsindow init',init_data, textsInitialized);
19605
19606		if (init_data == null) {
19607			return;
19608		}
19609
19610		sofia.ajax({
19611
19612			dataType: 'json',
19613			url: 'content/parallels/parallels.json',
19614			success: function(data) {
19615
19616				parallelsData = data.parallels;
19617
19618				for (var i=0, il=parallelsData.length; i<il; i++) {
19619					parallelsList.append('<option data-id="' + parallelsData[i].id + '" value="' + parallelsData[i].filename + '">' + parallelsData[i].title + '</option>');
19620				}
19621
19622				//
19623				console.log('parallel init', init_data.parallelid);
19624
19625				if (init_data.parallelid) {
19626
19627					parallelsList.find('option[data-id="' + init_data.parallelid + '"]').prop('selected', true);
19628
19629				} else {
19630
19631					parallelsList.find('option[data-id*="gospel"]:first').prop('selected', true);
19632
19633					console.log('first gospel', parallelsList.find('option[data-id~="gospel"]:first'));
19634				}
19635
19636				startup();
19637			}
19638		});
19639
19640
19641		// load the text specified by the init data
19642		TextLoader.getText(init_data.textid,
19643
19644			// success
19645			function(loadedTextInfo) {
19646
19647				// store this setting
19648				currentTextInfo = loadedTextInfo;
19649
19650				textsInitialized = true;
19651
19652				// send to objects
19653				textChooser.setTextInfo(currentTextInfo);
19654				textlistui.html(currentTextInfo.abbr);
19655
19656				startup();
19657			},
19658
19659			// error handler
19660			function() {
19661
19662				console.log('ERROR', init_data.textid, 'doesnt exist');
19663
19664				// load all possible versions
19665				TextLoader.loadTexts(function(textInfoData) {
19666
19667					// find a text with the same language
19668					var newTextInfo = null,
19669						lang = init_data.textid.toString().split('-')[0].split('_')[0];
19670
19671					for (var i=0, il=textInfoData.length; i<il; i++) {
19672						var textInfo = textInfoData[i];
19673
19674						if (textInfo.type == text_type && (textInfo.lang == lang || textInfo.id.substring(0, lang.length) == lang)) {
19675							newTextInfo = textInfo;
19676							break;
19677						}
19678					}
19679
19680					// still nothing
19681					if (newTextInfo == null) {
19682						newTextInfo = textInfoData[0];
19683					}
19684
19685					// let's try again with first one
19686					TextLoader.getText(newTextInfo.id, function(loadedTextInfo) {
19687						// store this setting
19688						currentTextInfo = loadedTextInfo;
19689
19690						textsInitialized = true;
19691
19692						// send to objects
19693						textChooser.setTextInfo(currentTextInfo);
19694						textlistui.html(currentTextInfo.abbr);
19695
19696						startup();
19697					});
19698
19699				});
19700
19701		});
19702	}
19703
19704	function startup() {
19705
19706
19707		if (textsInitialized && parallelsData != null) {
19708			// load !
19709			loadParallelData();
19710		}
19711	}
19712
19713	init();
19714
19715
19716	var currentParallelData = null,
19717		currentCells = null,
19718		currentCellIndex = -1;
19719
19720	function loadParallelData() {
19721
19722		// reset
19723		main.html( '' );
19724
19725		currentParallelData = null;
19726		currentCells = null;
19727		currentCellIndex = -1;
19728
19729		console.log('parallels', parallelsList.val());
19730
19731		sofia.ajax({
19732			dataType: 'json',
19733			url: 'content/parallels/' + parallelsList.val(),
19734			success: function(data) {
19735				console.log('loaded parallel data', data);
19736				currentParallelData = data;
19737				createParallel();
19738			},
19739			error: function(e) {
19740				console.log('error laoding parallel data', e);
19741			}
19742		});
19743	}
19744
19745	//var style = 'separatetitle';
19746	var columnFormat = 'inlinetitle';
19747
19748	function createParallel() {
19749
19750		var html = [];
19751
19752		// title
19753		html.push('<h1>' + currentParallelData.title + '</h1>');
19754
19755		html.push('<p class="parallel-description">' + currentParallelData.description + '</p>');
19756
19757		html.push('<div class="parallels-buttons">');
19758			html.push('<span class="parallel-show-all">' + i18n.t('windows.parallel.showall') + '</span>');
19759			html.push('<span class="parallel-hide-all">' + i18n.t('windows.parallel.hideall') + '</span>');
19760		html.push('</div>');
19761
19762		// opening
19763		html.push('<table dir="' + currentTextInfo.dir + '">');
19764
19765
19766
19767		if (columnFormat == 'inlinetitle') {
19768
19769			var style = ' style="width: ' + (100/(currentParallelData.books.length+1)) + '%"';
19770
19771			// main
19772			html.push('<tbody>');
19773			for (var i=0, il=currentParallelData.parallels.length; i<il; i++) {
19774				var row = currentParallelData.parallels[i];
19775
19776				if (typeof row.sectionTitle != 'undefined') {
19777					html.push('<tr><th class="section-title" colspan="' + (currentParallelData.books.length+1) + '">' + row.sectionTitle + '</th></tr>');
19778				} else {
19779
19780
19781					// title row
19782					html.push('<tr class="parallel-entry-header">' +
19783									'<th class="parallel-title" ' + style + '>' +
19784									row.title +
19785									'</th>');
19786
19787
19788
19789					// Passage titles
19790					for (var j=0, jl=row.passages.length; j<jl; j++) {
19791						var passage = row.passages[j];
19792
19793						if (passage == null) {
19794							html.push('<td class="parallel-passage" ' + style + '>-</td>');
19795						} else {
19796							var books = row.books ? row.books : currentParallelData.books;
19797
19798							//console.log(currentTextInfo.lang);
19799							html.push('<td class="parallel-passage" ' + style + ' lang="' + iso2iana.convert(currentTextInfo.lang) + '">' +
19800											bible.BOOK_DATA[books[j]].names[currentTextInfo.lang][0] + ' ' + passage +
19801										'</td>');
19802						}
19803					}
19804
19805					html.push('</tr>');
19806
19807
19808					// text
19809					html.push('<tr class="parallel-entry-text parallel-entry-text-collapsed">');
19810					html.push('<th></th>');
19811
19812					for (var j=0, jl=row.passages.length; j<jl; j++) {
19813						var passage = row.passages[j];
19814
19815						if (passage == null) {
19816							html.push('<td></td>');
19817						} else {
19818
19819							var books = row.books ? row.books : currentParallelData.books;
19820
19821							html.push('<td class="reading-text" data-bookid="' + books[j] + '" ' +
19822											'data-passage="' + passage + '" lang="' + iso2iana.convert(currentTextInfo.lang) + '">' +
19823											//i18n.t('windows.parallel.loading') +
19824										'</td>');
19825						}
19826					}
19827
19828					html.push('</tr>');
19829
19830				}
19831			}
19832			html.push('</tbody>');
19833		}
19834
19835		if (columnFormat == 'separatetitle') {
19836
19837			// main
19838			html.push('<tbody>');
19839			for (var i=0, il=currentParallelData.parallels.length; i<il; i++) {
19840				var row = currentParallelData.parallels[i];
19841
19842				if (typeof row.sectionTitle != 'undefined') {
19843					html.push('<tr><th class="section-title" colspan="' + (currentParallelData.books.length).toString() + '">' + row.sectionTitle + '</th></tr>');
19844				} else {
19845
19846					html.push('<tr><th class="parallel-title" colspan="' + (currentParallelData.books.length).toString() + '">' + row.title + '</th></tr>');
19847
19848
19849
19850					// Passage titles
19851					html.push('<tr class="passage-row passage-notloaded">');
19852
19853					for (var j=0, jl=row.passages.length; j<jl; j++) {
19854						var passage = row.passages[j];
19855
19856						if (passage == null) {
19857							html.push('<td class="parallel-passage">-</td>');
19858						} else {
19859							//console.log(currentTextInfo.lang);
19860							html.push('<td class="parallel-passage" data-bookid="' + currentParallelData.books[j] + '" ' +
19861											'data-passage="' + passage + '">' +
19862											bible.BOOK_DATA[currentParallelData.books[j]].names[currentTextInfo.lang][0] + ' ' + passage +
19863										'</td>');
19864						}
19865					}
19866
19867					html.push('</tr>');
19868
19869				}
19870			}
19871			html.push('</tbody>');
19872		}
19873
19874
19875
19876
19877		// close
19878		html.push('</table>');
19879
19880		main.html( html.join('') );
19881
19882
19883		// find all TDs
19884		//currentCells = main.find('td');
19885		//currentCellIndex = 0;
19886		//loadNextPassage();
19887	}
19888
19889	if (columnFormat == 'separatetitle') {
19890		main.on('click', '.passage-notloaded', function() {
19891			var row = $(this);
19892
19893			row.removeClass('passage-notloaded');
19894
19895			var textRow = row.clone(); // .after(row);
19896
19897			textRow.find('td').each(function() {
19898				var cell = $(this).removeClass('parallel-passage');
19899
19900				if (cell.attr('data-passage')) {
19901					cell.html(i18n.t('windows.parallel.loading'));
19902				} else {
19903					cell.html('');
19904				}
19905			});
19906
19907			row.after(textRow);
19908
19909			currentCells = textRow.find('td');
19910			currentCellIndex = 0;
19911
19912
19913			loadNextPassage();
19914
19915		});
19916	}
19917
19918	if (columnFormat == 'inlinetitle') {
19919		main.on('click', '.parallel-entry-header', function() {
19920			var headerRow = $(this),
19921				textRow = headerRow.next('tr');
19922
19923			if (textRow.hasClass('parallel-entry-text-collapsed')) {
19924				// load
19925				textRow.removeClass('parallel-entry-text-collapsed')
19926
19927				currentCells = textRow.find('td');
19928				currentCellIndex = 0;
19929
19930				loadNextPassage();
19931
19932			} else {
19933
19934				textRow.addClass('parallel-entry-text-collapsed')
19935
19936			}
19937		});
19938	}
19939
19940	main.on('click', '.parallel-show-all', function() {
19941
19942		currentCells = $();
19943
19944		// find any closed ones
19945		/*
19946		main.find('tr.parallel-entry-text-collapsed').each(function() {
19947			var tr = $(this).removeClass('parallel-entry-text-collapsed');
19948
19949
19950			currentCells.add(tr.find('td'));
19951
19952
19953
19954			/*
19955			td.find('td').each(function() {
19956				processCell( $(this), null);
19957			});
19958
19959		});
19960		*/
19961
19962
19963		currentCells = main.find('tr.parallel-entry-text-collapsed td');
19964
19965		console.log('total cells', currentCells.length);
19966
19967		currentCellIndex = 0;
19968		loadNextPassage();
19969
19970	});
19971
19972	main.on('click', '.parallel-hide-all', function() {
19973
19974		// find any closed ones
19975		main.find('tr.parallel-entry-text').each(function() {
19976			var tr = $(this).addClass('parallel-entry-text-collapsed');
19977		});
19978
19979
19980	});
19981
19982
19983
19984	function loadNextPassage() {
19985		if (currentCellIndex < currentCells.length) {
19986
19987			// find passage and load
19988			var cell = currentCells.eq(currentCellIndex);
19989
19990			processCell(cell, function() {
19991				currentCellIndex++;
19992				loadNextPassage();
19993			});
19994		}
19995	}
19996
19997	function processCell(cell, callback) {
19998
19999		cell.closest('tr').removeClass('parallel-entry-text-collapsed');
20000
20001		if (cell.hasClass('parallel-text-loaded')) {
20002
20003			if (callback) {
20004				callback();
20005			}
20006
20007			return;
20008		}
20009
20010		var
20011			bookid = cell.attr('data-bookid'),
20012			passage = cell.attr('data-passage');
20013
20014		if (bookid && passage) {
20015
20016			// load
20017			//console.log(bookid, passage);
20018
20019			var sectionid = bookid + passage.split(':')[0],
20020				verseParts = passage.split(':')[1],
20021				verseRanges = verseParts.split(','),
20022				fragmentids = [];
20023
20024			// go through each ##-##,##-##
20025			for (var i=0, il=verseRanges.length; i<il; i++) {
20026				var verseRange = verseRanges[i].split('-');
20027
20028				if (verseRange.length == 1) {
20029					fragmentids.push(sectionid + '_' + verseRange[0].trim());
20030				} else if (verseRange.length == 2) {
20031					var start = parseInt(verseRange[0], 10),
20032						end= parseInt(verseRange[1], 10);
20033
20034
20035					for (verse = start; verse<= end; verse++) {
20036						fragmentids.push(sectionid + '_' + verse);
20037					}
20038				}
20039			}
20040
20041			if (verseRanges.length > 1){
20042				console.log(verseParts, fragmentids);
20043			}
20044
20045			TextLoader.loadSection( currentTextInfo, sectionid, function(content) {
20046
20047				// remove
20048				content.find('.cf,.note').remove();
20049
20050				cell.html('');
20051				//cell.append($('<span class="passage">' + bookid + ' ' + passage + '</span>'));
20052
20053				for (var i=0, il=fragmentids.length; i<il; i++) {
20054					var fragmentid = fragmentids[i],
20055						verseNode = content.find('.v[data-id="' + fragmentid + '"]');
20056
20057					//console.log(verseNode.prev());
20058
20059					if (verseNode.prev().hasClass('v-num')) {
20060						cell.append(verseNode.prev());
20061					}
20062
20063					cell.append(verseNode);
20064				}
20065
20066				cell.addClass('parallel-text-loaded');
20067
20068
20069
20070				if (callback)
20071					callback();
20072
20073			}, function() {
20074
20075				if (callback)
20076					callback();
20077			});
20078
20079
20080		} else {
20081
20082			if (callback)
20083				callback();
20084		}
20085
20086
20087	}
20088
20089
20090	function size(width, height) {
20091
20092		//console.log('newsize',width,height);
20093
20094		container
20095			.outerWidth(width)
20096			.outerHeight(height);
20097
20098
20099		main
20100			.outerWidth(width)
20101			.outerHeight( container.height() - header.outerHeight(true));
20102
20103
20104		textChooser.size(width, height);
20105	}
20106
20107
20108	function getData() {
20109
20110		var data = {
20111			// textinfo
20112			textid: currentTextInfo.providerid,
20113			parallelid: parallelsList.find('option:selected').attr('data-id'),
20114			label: 'Parallel',
20115			labelLong: 'Parallel',
20116			params: {
20117				'win': 'parallel',
20118				'textid': currentTextInfo.providerid,
20119				'parallelid': parallelsList.find('option:selected').attr('data-id')
20120			}
20121		};
20122
20123		return data;
20124	}
20125
20126
20127	function close() {
20128
20129		textChooser.close();
20130
20131		ext.clearListeners();
20132	}
20133
20134	var ext = {
20135		size: size,
20136		getData: getData,
20137		close: close
20138	}
20139	ext = $.extend(true, ext, EventEmitter);
20140
20141	return ext;
20142};
20143
20144
20145sofia.initMethods.push(function() {
20146
20147	sofia.windowTypes.push( {
20148		className:'ParallelsWindow',
20149		param: 'parallel',
20150		paramKeys: {
20151			'textid': 't',
20152			'parallelid': 'p'
20153		},
20154		init: {
20155			'textid': sofia.config.newBibleWindowVersion
20156		}
20157	});
20158
20159});
20160
20161
20162
20163/*********
20164* ../app/js/windows/scroller.js
20165**********/
20166
20167var Scroller = function(node) {
20168
20169	var
20170		wrapper = node.find('.scroller-text-wrapper'),
20171		currentTextInfo = null,
20172		locationInfo = {},
20173		ignoreScrollEvent = false,
20174		speedLastPos = null,
20175		speedDelta = 0;
20176		
20177		speedIndicator = $('<div class="scroller-speed" style="z-index: 50; position: absolute; top: 0; left: 0; width: 50; background: black; padding: 5px;color:#fff"></div>')
20178							.appendTo(node.parent())
20179							.hide();
20180	
20181
20182	var globalTimeout = null;
20183
20184	function startGlobalTimeout() {
20185		if (globalTimeout == null) {
20186			setTimeout(triggerGlobalEvent, 10);
20187		}
20188	}
20189
20190	function triggerGlobalEvent() {
20191
20192		ext.trigger('globalmessage', {type: 'globalmessage', target: this, data: {messagetype: 'nav', type: currentTextInfo.type.toLowerCase(), locationInfo: locationInfo}});
20193
20194		clearTimeout(globalTimeout);
20195		globalTimeout = null;
20196	}
20197
20198	node.on('scroll', function() {
20199
20200		if (ignoreScrollEvent) {
20201			return;
20202		}
20203		//console.log('sendingscroll');
20204
20205		update_location_info();
20206
20207		ext.trigger('scroll', {type: 'scroll', target: this, data: {locationInfo: locationInfo}});
20208		startGlobalTimeout();
20209		
20210		start_speed_test();		
20211	});
20212	
20213	
20214	var speedInterval = null;
20215	
20216	function start_speed_test() {
20217		if (speedInterval == null) {
20218
20219			speedInterval = setInterval(checkSpeed, 100);
20220		}
20221	}
20222	
20223	function stop_speed_test() {
20224		if (speedInterval != null) {
20225			clearInterval(speedInterval);
20226			speedInterval = null;
20227		}		
20228		
20229	}
20230	
20231	function checkSpeed() {
20232			
20233		var speedNewPos = node.scrollTop();
20234		if ( speedLastPos != null ){ // && newPos < maxScroll 
20235			speedDelta = speedNewPos -  speedLastPos;
20236		}
20237		speedLastPos = speedNewPos;
20238		
20239		if (speedDelta === 0) {
20240			load_more();
20241			stop_speed_test();			
20242		}
20243				
20244/*
20245		if (speedDelta != null) {
20246			speedIndicator.html(speedDelta);
20247		}		
20248*/
20249	}
20250	
20251
20252
20253	// find the top most visible node (verse, page, etc.)
20254	// pass it up as an event
20255	function update_location_info() {
20256
20257		// reset info
20258		var
20259			topOfContentArea = node.offset().top,
20260			sectionid = '',
20261			fragmentid = '',
20262			label = '',
20263			labelLong = '',
20264			fragmentSelector = currentTextInfo.fragmentSelector,
20265			newLocationInfo = null;
20266
20267		// magic for bibles or books
20268		if (typeof fragmentSelector == 'undefined' || fragmentSelector == '') {
20269			switch (currentTextInfo.type.toLowerCase()) {
20270				case 'videobible':
20271				case 'deafbible':
20272				case 'bible':
20273				case 'commentary':
20274					// find top
20275					fragmentSelector = '.verse, .v';
20276
20277					break;
20278				case 'book':
20279					// find top
20280					fragmentSelector = '.page';
20281
20282					break;
20283			}
20284		}
20285
20286
20287		var fragments = node.find( fragmentSelector );
20288
20289		if (fragments.length == 1) {
20290			fragments = node.find('.section');
20291		}
20292
20293		// look through all the markers and find the first one that is fully visible
20294		fragments.each(function(e) {
20295			var fragment = $(this),
20296				isFirstVisibleFragment = false;
20297
20298			// is the top of the fragment at the top of the scroll pane
20299			if (fragment.offset().top - topOfContentArea > -2) {
20300				isFirstVisibleFragment = true;
20301
20302				fragmentid = fragment.attr('data-id');
20303				var totalFragments = fragment.parent().find('.' + fragmentid);
20304
20305				// multi-line verses
20306				if (totalFragments.length > 1) {
20307
20308					fragment = totalFragments.first();
20309
20310					if (fragment.offset().top - topOfContentArea > -2) {
20311						isFirstVisibleFragment = true;
20312					} else {
20313						isFirstVisibleFragment = false;
20314					}
20315				}
20316
20317			}
20318
20319			if (isFirstVisibleFragment) {
20320
20321				// TODO: are there other fragments with this id
20322
20323				fragmentid = fragment.attr('data-id');
20324
20325
20326				switch (currentTextInfo.type.toLowerCase()) {
20327					case 'videobible':
20328					case 'deafbible':
20329					case 'bible':
20330					case 'commentary':
20331						// find top
20332						var bibleref = new bible.Reference( fragmentid );
20333						bibleref.language = currentTextInfo.lang;
20334
20335						label = bibleref.toString();
20336						labelLong = label +  ' (' + currentTextInfo.abbr + ')';
20337
20338						break;
20339					case 'book':
20340						labelLong = label = currentTextInfo.name + ' ' + sectionid;
20341
20342						break;
20343				}
20344
20345
20346				// pass the marker data
20347				newLocationInfo = {
20348					// verse ID
20349					fragmentid: fragment.attr('data-id'),
20350
20351					sectionid: fragment.hasClass('section') ? fragment.attr('data-id') : fragment.closest('.section').attr('data-id'),
20352
20353					// extra positioning info
20354					offset: topOfContentArea - fragment.offset().top,
20355
20356					label: label,
20357
20358					labelLong: labelLong,
20359
20360					textid: currentTextInfo.id
20361
20362				};
20363				return false;
20364			}
20365
20366			// means "keep looking" :)
20367			return true;
20368		});
20369
20370		// found a fragment
20371		if (newLocationInfo != null && (locationInfo == null || newLocationInfo.fragmentid != locationInfo.fragmentid)) {
20372			ext.trigger('locationchange', {type:'locationchange', target: this, data: newLocationInfo});
20373		}
20374
20375		//console.log('new location', newLocationInfo);
20376
20377		locationInfo = newLocationInfo;
20378	};
20379
20380
20381	/*
20382	var load_more_timeout = null;
20383	function start_load_more_timeout() {
20384
20385		if (load_more_timeout === null) {
20386
20387			load_more_timeout = setTimeout(function() {
20388				load_more();
20389				clearTimeout( load_more_timeout );
20390				load_more_timeout = null;
20391			}, 100);
20392
20393		}
20394	}
20395	*/
20396
20397	function load_more() {
20398			
20399		// measure top and bottom height
20400		var
20401			fragmentid = null;
20402			wrapper_height = wrapper.height(),
20403			node_height = node.height(),
20404			above_top = node_scrolltop = node.scrollTop(),
20405			sections = wrapper.find( '.section' ),
20406			sections_count = sections.length,
20407			total_height = 0,
20408			below_bottom = wrapper_height /*total_height*/ - node_height - node_scrolltop;
20409
20410		//console.log('load_more', speedDelta, below_bottom < node_height*2, above_top < node_height*2, above_top > node_height*15, sections_count > 4 && below_bottom > node_height*15)
20411
20412
20413		// only load if stopped
20414		if (speedDelta == 0) {
20415			
20416			// add below
20417			if (below_bottom < node_height*2) {
20418	
20419				fragmentid = sections
20420								.last() // the last chapter (bottom)
20421								.attr( 'data-nextid' );
20422	
20423				if (fragmentid != null && fragmentid != 'null' && sections.length < 50) {
20424					load('next', fragmentid);
20425				}
20426	
20427	
20428			}
20429	
20430			// add above
20431			else if (above_top < node_height*2) {
20432	
20433				fragmentid = sections
20434								.first() // the first chapter (top)
20435								.attr( 'data-previd' );
20436	
20437				//console.warn('load prev', fragmentid);
20438	
20439				if (fragmentid != null && fragmentid != 'null' && sections.length < 50) {
20440					load('prev',fragmentid);
20441				}
20442	
20443			}
20444			
20445			
20446				
20447			// remove above
20448			else if (above_top > node_height*15) {
20449				//console.warn('remove above');
20450	
20451				if (wrapper.children().length >= 2) {
20452	
20453					// we're removing the first section, so we need to find the first one and
20454					// measure where its first child should appear
20455					var first_node_of_second_section = wrapper.find('.section:eq(1)').children().first(),
20456						first_node_offset_before = first_node_of_second_section.offset().top;
20457	
20458					wrapper.find('.section:first').remove();
20459	
20460					// now, remeasure where the first node appears and adjust the scrolltop
20461					var
20462						first_node_offset_after = first_node_of_second_section.offset().top,
20463						offset_difference = first_node_offset_after - first_node_offset_before;
20464						new_scrolltop = node.scrollTop();
20465						updated_scrolltop = new_scrolltop - Math.abs(offset_difference);
20466	
20467					node.scrollTop(updated_scrolltop);
20468				}
20469			}
20470			
20471	
20472			// remove below
20473			else if (sections_count > 4 && below_bottom > node_height*15) {
20474				//console.warn('remove below', below_bottom, node_height);
20475	
20476				wrapper.find('.section:last').remove();
20477			}
20478		} else {
20479			
20480			
20481		}
20482				
20483	}
20484
20485	function load(loadType, sectionid, fragmentid) {
20486
20487		if (sectionid === 'null' || sectionid === null || sectionid == '') {
20488			return;
20489		}
20490
20491		// check if this exists
20492		//if ( wrapper.find('[data-id="' + sectionid + '"]').length > 0 ) {
20493		if ( wrapper.find('.' + sectionid).length > 0 ) {
20494
20495			if (fragmentid && fragmentid.trim() != '' && wrapper.find('.' + fragmentid).length > 0) {
20496				scrollTo(fragmentid);
20497			}
20498			return;
20499		}
20500
20501		if (loadType == 'text') {
20502			wrapper.html('<div class="loading-indicator" style="height:' + node.height() + 'px;"></div>');
20503			node.scrollTop(0);
20504		}
20505
20506		//console.log(loadType, sectionid, fragmentid);
20507
20508		TextLoader.loadSection( currentTextInfo, sectionid, function(content) {
20509
20510			// check if this exists
20511			//if ( wrapper.find('[data-id="' + sectionid + '"]').length > 0 ) {
20512			if ( wrapper.find('.' + sectionid).length > 0 ) {
20513
20514				if (fragmentid && fragmentid.trim() != '' && wrapper.find('.' + fragmentid).length > 0) {
20515					scrollTo(fragmentid);
20516				}
20517				return;
20518			}
20519
20520			ignoreScrollEvent = true;
20521
20522			switch (loadType) {
20523				default:
20524				case 'text':
20525
20526					// clear out and reset
20527					wrapper.html('');
20528					node.scrollTop(0);
20529					wrapper.append(content);
20530
20531
20532
20533					// TODO: scrollto fragmentid
20534					if (fragmentid) {
20535						scrollTo(fragmentid);
20536					}
20537
20538					locationInfo = null;
20539
20540					update_location_info();
20541					//ext.trigger('scroll', {type: 'scroll', target: this, data: {locationInfo: locationInfo}});
20542
20543					break;
20544
20545				case 'next':
20546
20547					wrapper.append(content);
20548
20549
20550					break;
20551
20552				case 'prev':
20553
20554					// add to top, measure, then reset scroll position
20555					/*
20556					var	node_scrolltop_before = node.scrollTop(),
20557						first_item = node.find('.section').children().first();
20558					
20559					if (first_item.length > 0) {
20560						var first_item_offset_top_before = first_item.offset().top;
20561
20562						// add to top and measure
20563						wrapper.prepend(content);
20564
20565						var first_item_offset_top_after = first_item.offset().top,
20566							offest_difference = first_item_offset_top_after - first_item_offset_top_before,
20567							new_scrolltop = node_scrolltop_before + offest_difference;
20568
20569						node.scrollTop( Math.abs(new_scrolltop));
20570					}
20571					*/
20572
20573					// add to bottom, then move up
20574
20575					var	node_scrolltop_before = node.scrollTop();
20576					var wrapper_height_before = wrapper.height();
20577					wrapper.append(content);
20578					var wrapper_height_after = wrapper.height();
20579					wrapper.prepend(content);
20580
20581					var height_difference = wrapper_height_after - wrapper_height_before,
20582						new_scrolltop = node_scrolltop_before + height_difference;
20583
20584					node.scrollTop( Math.abs(new_scrolltop ));
20585
20586					break;
20587
20588			}
20589
20590			ignoreScrollEvent = false;
20591
20592			// send load event up to Window/App
20593			//console.log('loaded', sectionid);
20594			ext.trigger('globalmessage', {type: 'globalmessage',
20595											target: this,
20596											data: {
20597												messagetype: 'textload',
20598												texttype: currentTextInfo.type.toLowerCase(),
20599												type: currentTextInfo.type.toLowerCase(),
20600												textid: currentTextInfo.id,
20601												abbr: currentTextInfo.abbr,
20602												sectionid: sectionid,
20603												fragmentid: fragmentid,
20604												content:content
20605											}
20606										});
20607
20608			load_more();
20609			//start_load_more_timeout();
20610		});
20611
20612	}
20613
20614	function scrollTo(fragmentid, offset) {
20615
20616		if (typeof fragmentid == 'undefined') {
20617			return;
20618		}
20619
20620
20621		// find the fragment
20622		var fragment = wrapper.find('.' + fragmentid);
20623
20624		//console.log('scrollTo', fragmentid, fragment.length);
20625
20626		// if it exists, we'll move to it
20627		if (fragment.length > 0) {
20628
20629			var
20630				// calculate node position
20631				paneTop = node.offset().top,
20632				scrollTop = node.scrollTop(),
20633				nodeTop = fragment.offset().top,
20634				nodeTopAdjusted = nodeTop - paneTop + scrollTop;
20635
20636			// go to it
20637			ignoreScrollEvent = true;
20638			node.scrollTop(nodeTopAdjusted + (offset || 0));
20639			ignoreScrollEvent = false;
20640		}
20641		// if it's not there, we'll see if we can load it for this book (or bible version)
20642		else {
20643			// need to load it!
20644			//console.log('need to load', fragmentid);
20645
20646			var sectionid = fragmentid.split('_')[0],
20647				hasSection = currentTextInfo != null ? currentTextInfo.sections.indexOf(sectionid) > -1 : false;
20648
20649			if (hasSection) {
20650				load('text', sectionid, fragmentid);
20651			}
20652
20653		}
20654	}
20655
20656	function size(width, height) {
20657		node
20658			.width(width)
20659			.height(height);
20660	}
20661
20662
20663	function getTextInfo() {
20664		return currentTextInfo;
20665	}
20666
20667	function setTextInfo(textinfo) {
20668
20669		if (typeof textinfo.stylesheet != 'undefined' ) {
20670
20671			var styleId = 'style-' + textinfo.id,
20672				styleLink = $('#' + styleId);
20673
20674			if (styleLink.length == 0) {
20675				styleLink = $('<link id="' + styleId + '" rel="stylesheet" href="' + sofia.config.baseContentUrl + 'content/texts/' + textinfo.id + '/' + textinfo.stylesheet + '" />')
20676								.appendTo($('head'));
20677			}
20678		}
20679
20680
20681		currentTextInfo = textinfo;
20682	}
20683
20684	function getLocationInfo() {
20685		return locationInfo;
20686	}
20687
20688	function setFocus(newHasFocus) {
20689		hasFocus = newHasFocus;
20690	}
20691
20692
20693	var ext = {
20694		load_more: load_more,
20695		load: load,
20696		size: size,
20697		getTextInfo: getTextInfo,
20698		setTextInfo: setTextInfo,
20699		getLocationInfo: getLocationInfo,
20700		scrollTo: scrollTo,
20701		setFocus: setFocus
20702	};
20703
20704	ext = $.extend(true, ext, EventEmitter);
20705
20706	return ext;
20707
20708};
20709
20710
20711
20712/*********
20713* ../app/js/windows/scrolleraudio.js
20714**********/
20715
20716var AudioController = function(id, container, toggleButton, scroller) {
20717
20718	var block = $(
20719				'<div class="audio-controller">' +
20720					'<audio></audio>' +
20721					'<div class="audio-slider">' +
20722						'<div class="audio-slider-current"></div>' +
20723						'<div class="audio-slider-loaded"></div>' +
20724						'<span class="audio-slider-handle"></span>' +
20725					'</div>' +
20726					'<input type="button" class="audio-prev" value="Prev" />' +
20727					'<input type="button" class="audio-play" value="Play" />' +
20728					'<input type="button" class="audio-next" value="Next" />' +
20729					'<span class="audio-currenttime">00:00</span>' +
20730					'<span class="audio-duration">00:00</span>' +
20731					'<span class="audio-title"></span>' +
20732					'<span class="audio-subtitle"></span>' +
20733					'<input type="button" class="audio-options-button image-config-light" />' +
20734				'</div>'
20735				).appendTo(container),
20736		optionsButton = block.find('.audio-options-button'),
20737
20738		options = $(
20739				'<div class="audio-options">' +
20740					'<span class="close-button"></span>' +
20741					'<strong class="i18n" data-i18n="[html]windows.audio.options"></strong>' +
20742					'<label><input type="checkbox" class="audio-scroll" checked /><span class="i18n" data-i18n="[html]windows.audio.synctext" /></label>' +
20743					'<label><input type="checkbox" class="audio-autoplay" checked /><span class="i18n" data-i18n="[html]windows.audio.autoplay" /></label>' +
20744
20745					'<div class="audio-dramatic-option">' +
20746						'<label><input type="radio" name="' + id + '-dramatic-option" class="audio-dramatic-audio" disabled /><span class="i18n" data-i18n="[html]windows.audio.nondrama" /></label>' +
20747						'<label><input type="radio" name="' + id + '-dramatic-option" class="audio-dramatic-drama" disabled /><span class="i18n" data-i18n="[html]windows.audio.drama" /></label>' +
20748					'</div>' +
20749				'</div>'
20750				).appendTo(container),
20751		scrollCheckbox = options.find('.audio-scroll').prop('checked', true),
20752		autoplayCheckbox = options.find('.audio-autoplay').prop('checked', true),
20753		optionsCloseButton = options.find('.close-button'),
20754
20755		optionsDramaticBox = options.find('.audio-dramatic-option'),
20756		optionsDramaticDrama = options.find('.audio-dramatic-drama'),
20757		optionsDramaticAudio = options.find('.audio-dramatic-audio'),
20758
20759		audio = block.find('audio')[0],
20760
20761		playButton = block.find('.audio-play'),
20762		nextButton = block.find('.audio-next'),
20763		prevButton = block.find('.audio-prev'),
20764		currenttime = block.find('.audio-currenttime'),
20765		duration = block.find('.audio-duration'),
20766		title = block.find('.audio-title'),
20767		subtitle = block.find('.audio-subtitle'),
20768
20769		audioSlider = block.find('.audio-slider'),
20770		audioSliderCurrent = block.find('.audio-slider-current'),
20771		audioSliderHandle = block.find('.audio-slider-handle'),
20772
20773		isDraggingSliderHandle = false,
20774
20775		textInfo = null,
20776		audioInfo = null,
20777		locationInfo = null,
20778		sectionid = '',
20779		fragmentid = '',
20780		fragmentAudioData = null,
20781		loadAudioWhenPlayIsPressed = false,
20782		sectionHeight = 0,
20783		sectionNode = null,
20784		hasAudio = false,
20785		audioDataManager = new AudioDataManager();
20786
20787	// START UP
20788	options.find('.i18n').i18n();
20789
20790	if (toggleButton != null) {
20791		toggleButton.hide();
20792		block.hide();
20793	}
20794	options.hide();
20795
20796	// OPTIONS
20797	optionsButton.on('click', function() {
20798		if (options.is(':visible')) {
20799			options.hide();
20800
20801			$(document).off('click', doc_click);
20802		} else {
20803			options.show();
20804
20805			setTimeout(function() {
20806				$(document).on('click', doc_click);
20807			});
20808		}
20809	});
20810	optionsCloseButton.on('click', function() {
20811		options.hide();
20812		$(document).off('click', doc_click);
20813	});
20814
20815
20816	// click off functionality
20817	function doc_click(e) {
20818
20819		var target = $(e.target),
20820			clickedOnOptions = false;
20821
20822		// go through all nested clicked elements
20823		while (target != null && target.length > 0) {
20824
20825			if (target[0] == options[0]) {
20826				clickedOnOptions = true;
20827				break;
20828			}
20829
20830			target = target.parent();
20831		}
20832
20833		//return;
20834		if (!clickedOnOptions) {
20835			e.preventDefault();
20836
20837			options.hide();
20838			$(document).off('click', doc_click);
20839
20840			return false;
20841		}
20842	}
20843
20844	optionsDramaticAudio.on('change', updateDramatic);
20845	optionsDramaticDrama.on('change', updateDramatic);
20846
20847	function updateDramatic() {
20848
20849		var storedFragmentid = fragmentid;
20850
20851		// kill all existing values
20852		fragmentid = '';
20853		sectionid = '';
20854		fragmentAudioData = null;
20855		loadAudioWhenPlayIsPressed = false;
20856
20857		// stop audio
20858		if (!audio.paused && !audio.ended) {
20859			audio.pause();
20860			audio.src = '';
20861		}
20862
20863		$(audio).on('loadeddata', playWhenLoaded);
20864		loadAudio(storedFragmentid);
20865	}
20866
20867	// MAIN
20868	if (toggleButton != null) {
20869		toggleButton.on('click', function() {
20870			if (block.is(':visible')) {
20871				block.hide();
20872			} else {
20873				block.show();
20874			}
20875		});
20876	}
20877
20878	playButton.on('click', function() {
20879
20880		if (audio.src == '' || audio.src == null) {
20881
20882			if (loadAudioWhenPlayIsPressed) {
20883				audio.src = fragmentAudioData.url;
20884				audio.load();
20885				$(audio).on('loadeddata', playWhenLoaded);
20886				loadAudioWhenPlayIsPressed = false;
20887			}
20888
20889			return;
20890		}
20891
20892
20893		if (audio.paused || audio.ended) {
20894			audio.play();
20895		} else {
20896			audio.pause();
20897		}
20898	});
20899
20900	prevButton.on('click', function() {
20901
20902		audioDataManager.getPrevFragment(textInfo, audioInfo, fragmentid, function(prevFragmentid) {
20903
20904			if (prevFragmentid == null) {
20905				return;
20906			}
20907
20908			if (scrollCheckbox.is(':checked')) {
20909				if (scroller != null && scroller.load) {
20910					scroller.load('text', prevFragmentid.split('_')[0], prevFragmentid);
20911				}
20912			}
20913
20914			if (fragmentAudioData == null || prevFragmentid != fragmentAudioData.fragmentid) {
20915				loadAudio(prevFragmentid);
20916				$(audio).on('loadeddata', playWhenLoaded);
20917			}
20918		});
20919
20920	});
20921	nextButton.on('click', function() {
20922
20923		audioDataManager.getNextFragment(textInfo, audioInfo, fragmentid, function(nextFragmentid) {
20924
20925			if (nextFragmentid == null) {
20926				return;
20927			}
20928
20929			if (scrollCheckbox.is(':checked')) {
20930				if (scroller != null && scroller.load) {
20931					scroller.load('text', nextFragmentid.split('_')[0], nextFragmentid);
20932				}
20933			}
20934
20935			if (fragmentAudioData == null || nextFragmentid != fragmentAudioData.fragmentid) {
20936				loadAudio(nextFragmentid);
20937				$(audio).on('loadeddata', playWhenLoaded);
20938			}
20939		});
20940
20941		return;
20942	});
20943
20944	// only for the smaller player below a scroller
20945	if (scroller != null) {
20946
20947		function updateLocation(e) {
20948
20949			var newLocationInfo = e.data;
20950
20951			// found a fragment
20952			if (newLocationInfo != null) {
20953
20954				locationInfo = newLocationInfo;
20955
20956				loadAudio(locationInfo.fragmentid);
20957			}
20958		}
20959
20960		scroller.on('locationchange', updateLocation);
20961	}
20962
20963	function loadAudio(newFragmentid) {
20964
20965		if (!hasAudio) {
20966			return;
20967		}
20968
20969		if (typeof newFragmentid == 'undefined') {
20970			return;
20971		}
20972
20973		if (fragmentid != newFragmentid) {
20974
20975			//// //console.log('AUDIO loading',newFragmentid,textInfo, hasAudio);
20976
20977			fragmentid = newFragmentid;
20978
20979			var newSectionid = fragmentid.split('_')[0],
20980				dbsBookCode =  sectionid.substring(0,2),
20981				chaperNumber = sectionid.substring(2),
20982				loadNewData = audioInfo.pericopeBased || newSectionid != sectionid;
20983
20984			sectionid = newSectionid;
20985
20986
20987			// only do checks when we need to!
20988			if (loadNewData) {
20989
20990				var audioOption = optionsDramaticDrama.is(':checked') ? 'drama' : optionsDramaticAudio.is(':checked') ? 'audio' : '';
20991
20992				audioDataManager.getFragmentAudio(textInfo, audioInfo, fragmentid, audioOption, function(newFragmentAudioData) {
20993
20994					// only update if this is new data
20995					if (fragmentAudioData == null || newFragmentAudioData == null || fragmentAudioData.id != newFragmentAudioData.id) {
20996
20997
20998						// if no data was received, then we need to hide everything
20999						if (!newFragmentAudioData || newFragmentAudioData.url == null) {
21000							audio.src = null;
21001
21002							title.html('[No audio]');
21003
21004							if (toggleButton) {
21005								toggleButton.hide();
21006								block.hide();
21007							}
21008
21009							fragmentAudioData = newFragmentAudioData;
21010							return;
21011						} else {
21012
21013							// if we get a URL, then show the ear icon again
21014							if (toggleButton) toggleButton.show();
21015
21016							// only when the previous data was null, do we reshow the control bar
21017							if (fragmentAudioData == null) {
21018								// let's not do it ever
21019								//block.show();
21020							}
21021
21022							fragmentAudioData = newFragmentAudioData;
21023						}
21024
21025						// only load audio if player is visible
21026						if (block.is(':visible')) {
21027							//audio.currentTime = 0;
21028							audio.src = fragmentAudioData.url;
21029							audio.load();
21030						} else {
21031							loadAudioWhenPlayIsPressed = true;
21032						}
21033
21034
21035						// store height info
21036
21037						// TODO: start and finish verse instead!
21038						sectionNode = container.find('.section[data-id="' + sectionid + '"]');
21039						sectionHeight = sectionNode.height();
21040
21041						// give feedback
21042						//title.html( new bible.Reference(sectionid).toString() + (audioInfo.title ? ' [' + audioInfo.title + ']' : ''));
21043						title.html( new bible.Reference(sectionid).toString() );
21044						subtitle.html( audioInfo.title );
21045
21046					}
21047				});
21048			}
21049		}
21050	}
21051
21052
21053	function playWhenLoaded(){
21054		//// //console.log('playWhenLoaded');
21055		audio.play();
21056		$(audio).off('loadeddata', playWhenLoaded);
21057	}
21058
21059
21060	$(audio)
21061		.on('play playing', function() {
21062
21063			var thisAudio = this;
21064
21065			playButton
21066				.attr('value','Pause')
21067				.addClass('playing');
21068
21069
21070			// pause all other media on the page
21071			$('audio,video').each(function() {
21072				var audioOrVideoNode = this;
21073
21074				if (audioOrVideoNode != thisAudio && !audioOrVideoNode.paused && !audioOrVideoNode.ended) {
21075					audioOrVideoNode.pause();
21076				}
21077			});
21078
21079		})
21080		.on('pause ended', function() {
21081			playButton
21082				.attr('value','Play')
21083				.removeClass('playing');
21084		})
21085		.on('loadstart', function() {
21086			playButton
21087				.attr('value','Play')
21088				.removeClass('playing');
21089
21090			audioSliderHandle.css({left: '0%' });
21091			currenttime.html( secondsToTimeCode(0) );
21092			duration.html( secondsToTimeCode(0) );
21093		})
21094		.on('loadedmetadata', function() {
21095			duration.html( secondsToTimeCode(audio.duration) );
21096		})
21097		.on('ended', function() {
21098			if (autoplayCheckbox.is(':checked')) {
21099
21100				$(audio).on('loadeddata', playWhenLoaded);
21101
21102				nextButton.trigger('click');
21103				//audio.play();
21104			}
21105		})
21106		.on('timeupdate', function() {
21107			currenttime.html( secondsToTimeCode(audio.currentTime) );
21108			duration.html( secondsToTimeCode(audio.duration) );
21109
21110			// slider
21111			audioSliderCurrent.css({width: (audio.currentTime / audio.duration * 100) + '%' });
21112			if (!isDraggingSliderHandle) {
21113				audioSliderHandle.css({left: (audio.currentTime / audio.duration * 100) + '%' });
21114			}
21115
21116			// don't auto scroll if not checked or if there was no connecting UI
21117			if (!scrollCheckbox.is(':checked') || toggleButton == null) {
21118				return;
21119			}
21120
21121			if (sectionNode.length == 0) {
21122				sectionNode = container.find('.section[data-id="' + sectionid + '"]');
21123			}
21124
21125			sectionHeight = sectionNode.height();
21126
21127			var
21128				// calculate percent to scroll
21129				chapter = parseInt(sectionid.substring(2), 10),
21130				skipSeconds = (chapter == 1) ? 10 : 8,
21131				fraction = (audio.currentTime-skipSeconds) / (audio.duration-skipSeconds);
21132
21133				// calculate node position
21134				pane = container.find('.scroller-main'),
21135				paneTop = pane.offset().top,
21136				scrollTop = pane.scrollTop(),
21137				nodeTop = sectionNode.offset().top,
21138				nodeTopAdjusted = nodeTop - paneTop + scrollTop,
21139
21140				offset = sectionHeight * fraction
21141							// adjust the offset by two lines or so
21142							// - 20
21143							- sectionNode.find('.v:first').height()
21144							- (sectionNode.find('.v:last').height()*fraction);
21145
21146			//// //console.log(fraction, sectionHeight, offset);
21147
21148			if (offset <= 0) {
21149				offset = 0;
21150			}
21151
21152			// go to it
21153			scroller.setFocus(true);
21154			pane.scrollTop(nodeTopAdjusted + offset);
21155		});
21156
21157	audioSliderHandle.on('mousedown', function(e) {
21158		isDraggingSliderHandle = true;
21159		$(document).on('mousemove', documentMouseMove);
21160		$(document).on('mouseup', documentMouseUp);
21161	});
21162
21163
21164	function documentMouseUp(e) {
21165		// //console.log('mouseup');
21166
21167		isDraggingSliderHandle = false;
21168		$(document).off('mousemove', documentMouseMove);
21169		$(document).off('mouseup', documentMouseUp);
21170	}
21171
21172	function documentMouseMove(e) {
21173
21174		var
21175			width = audioSlider.width(),
21176			pos = audioSlider.offset(),
21177			clientX = e.clientX,
21178			offsetX = clientX - pos.left,
21179			percent = offsetX/width,
21180			newTime = percent * audio.duration;
21181
21182		audioSliderHandle.css({left: (percent*100) + '%'});
21183
21184		audio.currentTime = newTime;
21185	}
21186
21187
21188	audioSlider.on('click', function(e) {
21189
21190		// //console.log('slider:click');
21191
21192		var
21193			width = audioSlider.width(),
21194			offsetX = e.offsetX,
21195			percent = offsetX/width,
21196			newTime = percent * audio.duration;
21197
21198		audio.currentTime = newTime;
21199
21200	});
21201
21202	function setTextInfo(newTextInfo) {
21203		if (textInfo == null || textInfo.id != newTextInfo.id) {
21204
21205			// reset
21206			title.html('');
21207			subtitle.html('');
21208			audioSliderCurrent.css({left: '0%' });
21209			audioSliderHandle.css({left: '0%' });
21210			currenttime.html( secondsToTimeCode(0) );
21211			duration.html( secondsToTimeCode(0) );
21212
21213			textInfo = newTextInfo;
21214
21215			if (!audio.paused && !audio.ended) {
21216				try {
21217					audio.pause();
21218					audio.src = null;
21219				} catch (e) {
21220					// boom!
21221				}
21222			}
21223
21224			if (textInfo.type == 'bible') {
21225
21226				audioDataManager.getAudioInfo(textInfo, function(newAudioInfo) {
21227
21228					if (newAudioInfo != null) {
21229						audioInfo = newAudioInfo;
21230
21231						console.log('AUDIO: YES', textInfo.id, textInfo.lang, audioInfo.type);
21232
21233						hasAudio = true;
21234
21235						sectionid = '';
21236						fragmentAudioData = null;
21237
21238						if (audioInfo.type == 'local') {
21239							optionsDramaticBox.hide();
21240						} else if (audioInfo.type == 'fcbh') {
21241							optionsDramaticBox.show();
21242
21243							var hasNonDrama =
21244										(typeof audioInfo.fcbh_audio_nt != 'undefined' && audioInfo.fcbh_audio_nt != '') ||
21245										(typeof audioInfo.fcbh_audio_ot != 'undefined' && audioInfo.fcbh_audio_ot != ''),
21246								hasDrama =
21247										(typeof audioInfo.fcbh_drama_nt != 'undefined' && audioInfo.fcbh_drama_nt != '') ||
21248										(typeof audioInfo.fcbh_drama_ot != 'undefined' && audioInfo.fcbh_drama_ot != '');
21249
21250							console.log(audioInfo, 'drama', hasDrama, 'audio', hasNonDrama);
21251
21252							// show hide
21253							if (hasNonDrama && hasDrama) {
21254								optionsDramaticAudio.prop('disabled', false);
21255								optionsDramaticDrama.prop('disabled', false);
21256							} else {
21257								optionsDramaticAudio.prop('disabled', true);
21258								optionsDramaticDrama.prop('disabled', true);
21259							}
21260
21261							if (hasNonDrama) {
21262								optionsDramaticAudio.prop('checked', true);
21263								optionsDramaticDrama.prop('checked', false);
21264							} else {
21265								optionsDramaticAudio.prop('checked', false);
21266								optionsDramaticDrama.prop('checked', true);
21267							}
21268						}
21269
21270
21271						if (fragmentid != '') {
21272							var newFragmentid = fragmentid;
21273
21274							fragmentid = '';
21275
21276							// //console.log('AUDIO, new from old ', newFragmentid);
21277							loadAudio(newFragmentid);
21278						} else {
21279
21280							locationInfo = scroller.getLocationInfo();
21281
21282							// //console.log('AUDIO, new from new ', locationInfo);
21283							if (locationInfo != null) {
21284								loadAudio(locationInfo.fragmentid);
21285							}
21286						}
21287						//}
21288
21289						// start load
21290						//block.show();
21291						if (toggleButton) toggleButton.show();
21292
21293					} else {
21294						hasAudio = false;
21295
21296						console.log('AUDIO: NO', textInfo.id, textInfo.lang, newAudioInfo);
21297
21298
21299						if (toggleButton) {
21300							toggleButton.hide();
21301							block.hide();
21302						}
21303					}
21304				});
21305			} // if text is bible
21306		} // if this is a different text
21307	}
21308
21309	function secondsToTimeCode(time) {
21310
21311		var hours = Math.floor(time / 3600) % 24,
21312			minutes = Math.floor(time / 60) % 60,
21313			seconds = Math.floor(time % 60),
21314			result =
21315   					(minutes < 10 ? '0' + minutes : minutes) + ':'
21316					+ (seconds < 10 ? '0' + seconds : seconds);
21317
21318		return result;
21319	}
21320
21321	function size(width, height) {
21322
21323		block.outerWidth(width);
21324	}
21325
21326	//$(window).on('resize')
21327
21328	function close() {
21329		ext.clearListeners();
21330
21331		block.remove();
21332		options.remove();
21333
21334		block = null;
21335		options = null;
21336	}
21337
21338	var ext = {
21339		setTextInfo: setTextInfo,
21340		size: size,
21341		close: close
21342	}
21343	ext = $.extend(true, ext, EventEmitter);
21344
21345	return ext;
21346}
21347
21348
21349
21350/*********
21351* ../app/js/windows/textcomparisonwindow.js
21352**********/
21353
21354var TextComparisonWindow = function(id, parent, init_data) {
21355
21356	console.log('text comparisino', init_data );
21357
21358
21359	var header = $('<div class="window-header" >' +
21360						'<input type="text" class="app-input comparison-fragmentid" value="" />' +
21361						'<input type="text" class="app-input comparison-texts" value="NASB, NET, WEB" />' +
21362						'<input type="button" value="GO" data-i18n="[value]windows.comparison.button" class="comparison-button header-button i18n" />' +
21363					'</div>').appendTo(parent.node),
21364		main = $('<div class="comparison-main">' +
21365				'</div>').appendTo(parent.node),
21366		footer = $('<div class="comparison-footer">' +
21367				'</div>').appendTo(parent.node)
21368
21369		inputFragment = header.find('.comparison-fragmentid'),
21370		inputTexts = header.find('.comparison-texts'),
21371		goButton = header.find('.comparison-button');
21372
21373
21374	inputFragment.on('keydown', handleEnter);
21375	inputTexts.on('keydown', handleEnter);
21376
21377	goButton.on('click', function() {
21378		console.log('clicked');
21379		doComparison();
21380	});
21381
21382
21383	function init() {
21384
21385		inputFragment.val( init_data.fragmentid ? init_data.fragmentid : init_data.params.fragmentid ? init_data.params.fragmentid : '');
21386		inputTexts.val( init_data.textids ? init_data.textids : init_data.params.textids ? init_data.params.textids : '' );
21387
21388		doComparison();
21389
21390	}
21391
21392	function handleEnter(e) {
21393		if (e.which == 13) {
21394			doComparison();
21395		}
21396	}
21397
21398	var	sectionid = '',
21399		reference = '',
21400		textAbbrs = '',
21401		textids = [],
21402		textIndex = -1,
21403		textData = [],
21404		textInfoData = null,
21405		isComparing = false;
21406
21407	function doComparison() {
21408
21409		if (isComparing) {
21410			return;
21411		}
21412
21413		isComparing = true;
21414
21415		main.html();
21416		main.addClass('loading-indicator');
21417
21418
21419		reference = new bible.Reference(inputFragment.val());
21420
21421		if (typeof reference.toSection == 'undefined') {
21422			return;
21423		}
21424		inputFragment.val( reference.toString() );
21425		sectionid = reference.toSection().split('_')[0]; // 'JN1';
21426		textAbbrs = inputTexts.val().split(',');
21427		textids = [];
21428		textIndex = -1;
21429		textData = [];
21430
21431
21432		TextLoader.loadTexts(function(tid) {
21433			textInfoData = tid;
21434
21435
21436			for (var i=0, il=textAbbrs.length; i<il; i++ ) {
21437				var abbr = textAbbrs[i].trim().toLowerCase();
21438
21439				if (abbr != '') {
21440
21441					var possibleTexts = textInfoData.filter(function(t) {
21442
21443						return t.abbr.toLowerCase().indexOf(abbr) > -1;
21444					});
21445
21446					if (possibleTexts.length > 0) {
21447						textids.push(possibleTexts[0].id);
21448					}
21449				}
21450			}
21451
21452			getNextText();
21453
21454		});
21455
21456
21457
21458	}
21459
21460	//var textids = ['eng-NASB1995', 'eng_net', 'eng_web']; // , 'esv'];
21461
21462	function getNextText() {
21463
21464		textIndex++;
21465
21466		if (textIndex < textids.length) {
21467
21468			var textid = textids[textIndex];
21469
21470			TextLoader.getText(textid, function(textInfo) {
21471
21472
21473				TextLoader.loadSection( textInfo, sectionid, function(content) {
21474				//TextLoader.loadSection( textid, sectionid, function(content) {
21475
21476					textData.push({textInfo: textInfo,
21477									content: content});
21478
21479					getNextText();
21480
21481				});
21482			});
21483
21484		//} else if (textIndex == textids.length+1){
21485		} else {
21486
21487			// complete!
21488			processData();
21489		}
21490
21491	}
21492
21493
21494
21495	function processData() {
21496
21497		if (textData.length <= 1) {
21498
21499			main.removeClass('loading-indicator');
21500
21501			return;
21502
21503		}
21504
21505
21506		var html = '<table class="comparison-table section">'; // using 'section' to get fonts and such
21507
21508		// heading
21509		html += '<thead>';
21510		html += '<th></th>';
21511		for (var i=0, il = textData.length; i<il; i++) {
21512			html += '<th>' + textData[i].textInfo.abbr + '</th>';
21513		}
21514		html += '</thead>';
21515
21516		html += '<tbody>';
21517
21518		var startVerse = reference.verse1 > 0 ? reference.verse1 : 1,
21519			endVerse = reference.verse2 > 0 ? reference.verse2 : bible.BOOK_DATA[ reference.bookid ].chapters[reference.chapter];
21520
21521		for (var verse=startVerse; verse<=endVerse; verse++) {
21522
21523			var verseid = sectionid + '_' + verse.toString(),
21524				baseText = getPlainText(textData[0].content, verseid);
21525
21526			html += '<tr>';
21527
21528			html += '<th>' + verse + '</th>';
21529
21530			html += '<td class="reading-text" style="width:' + (100/textData.length) + '%">' + baseText + '</td>';
21531
21532
21533			// comparison
21534			for (var i=1, il = textData.length; i<il; i++) {
21535				var comparisonText = getPlainText(textData[i].content, verseid);
21536
21537				html += '<td class="reading-text" style="width:' + (100/textData.length) + '%">' +
21538							rejoinPunctuation(
21539								diffString(
21540									separatePunctuation(baseText)
21541									,
21542									separatePunctuation(comparisonText)
21543								)
21544							) +
21545						'</td>';
21546
21547			}
21548
21549
21550			html += '<tr>';
21551		}
21552
21553
21554
21555		html += '</tbody>';
21556
21557
21558		html += '</table>';
21559
21560		main.html(html);
21561
21562		main.removeClass('loading-indicator');
21563
21564		// trigger settings change
21565		ext.trigger('settingschange', {type: 'settingschange', target: this, data: getData() });
21566
21567		isComparing = false;
21568	}
21569
21570	function separatePunctuation(input) {
21571
21572		return input.replace(/([\.,;])/gi,' $1'); // “”‘’
21573	}
21574
21575	function rejoinPunctuation(input) {
21576
21577		// fix HTML
21578		input = input.replace(/\s+<\/([^>]+)>/gi,'</$1> ');
21579
21580		// connect
21581		input = input.replace(/<\/ins>\s+<ins>/gi,' ');
21582		input = input.replace(/<\/del>\s+<del>/gi,' ');
21583
21584		// fix punctuation
21585		input = input.replace(/\s+([\.,;])/gi,'$1'); 	// “”
21586
21587		return input;
21588	}
21589
21590
21591	function getPlainText(content, verseid) {
21592
21593		var verseNodes = content.find('.' + verseid),
21594			plainText = '';
21595
21596		for (var i=0, il=verseNodes.length; i<il; i++) {
21597
21598			var verseNode = $(verseNodes[i]);
21599
21600			verseNode.find('.note, .cf, .v-num, .verse-num').remove();
21601
21602			var text = verseNode.html();
21603
21604			text = text.replace(/<[^>]+>/gi,'');
21605
21606			plainText += text + ' ';
21607		}
21608
21609		return plainText;
21610
21611	}
21612
21613
21614
21615
21616
21617
21618	function size(width, height) {
21619		main
21620			.width(width)
21621			.height(height - footer.outerHeight() - header.outerHeight() );
21622	}
21623
21624	function getData() {
21625
21626		var
21627			data = {
21628				params: {
21629					win: 'comparison',
21630					textids: inputTexts.val(),
21631					fragmentid: inputFragment.val()
21632				}
21633			};
21634
21635		return data;
21636	}
21637
21638	function close() {
21639
21640		ext.clearListeners();
21641	}
21642
21643	var ext = {
21644		size: size,
21645		getData: getData,
21646		sendMessage: function() {
21647			// nothing for now
21648		},
21649		close: close
21650	};
21651	ext = $.extend(true, ext, EventEmitter);
21652
21653
21654	ext.on('message', function(e) {
21655		// nothing for now
21656	});
21657
21658	init();
21659
21660	return ext;
21661};
21662
21663sofia.initMethods.push(function() {
21664
21665	sofia.windowTypes.push( {
21666		className:'TextComparisonWindow',
21667		param: 'comparison',
21668		paramKeys: {
21669			'textids': 't',
21670			'fragmentid': 'f'
21671		},
21672		init: {
21673			textids: 'WEB, ASV',
21674			fragmentid: 'John 1:1-8'
21675		}
21676	});
21677});
21678
21679
21680
21681/*********
21682* ../app/js/windows/audiowindow.js
21683**********/
21684sofia.config = $.extend(sofia.config, {
21685	enableAudioWindow: true,
21686	audioWindowDefaultBibleFragmentid: 'JN1_1',
21687	audioWindowDefaultBibleVersion: 'ENGESV'
21688});
21689
21690
21691var AudioWindow = function(id, parent, init_data) {
21692
21693	console.log('audiowindow init', init_data);
21694
21695	var
21696		container =
21697			$('<div class="audio-window-container">'+
21698				'<div class="window-header audio-window-header">'+
21699					'<div class="audio-window-header-inner">'+
21700						'<input type="text" class="app-input text-nav" />' +
21701						'<div class="app-list text-list"></div>'+
21702						//'<span class="header-icon info-button"></span>'+
21703					'</div>'+
21704				'</div>'+
21705				'<div class="audio-window-main">' +
21706				'</div>' +
21707				'<div class="audio-window-version-info">' +
21708				'</div>' +
21709			'</div>').appendTo(parent.node),
21710
21711
21712		header = container.find('.audio-window-header'),
21713		main = container.find('.audio-window-main'),
21714		navui = header.find('.text-nav'),
21715		textlistui = header.find('.text-list'),
21716		versioninfo = container.find('.audio-window-version-info'),
21717
21718		text_type = 'audio',
21719		currentTextInfo = null,
21720		currentLocationInfo = null,
21721
21722		// objects
21723		textChooser = sofia.globalTextChooser,
21724		textNavigator = sofia.globalTextNavigator,
21725
21726		scrollerMimic = {},
21727		audioController = null;
21728
21729	// make something that functions like the text scroller so that the AudioController can understand it
21730	scrollerMimic = $.extend(true, scrollerMimic, EventEmitter);
21731	scrollerMimic.getLocationInfo = function() {
21732		return currentLocationInfo;
21733	};
21734
21735	audioController = new AudioController(id, main, null, scrollerMimic);
21736
21737	// TEXT NAVIGATOR
21738	navui
21739		.on('click', function(e) {
21740
21741			if (Detection.hasTouch) {
21742				this.blur();
21743			}
21744
21745			// if this is selected, then toggle
21746			if (textNavigator.getTarget() == navui) {
21747				textNavigator.toggle();
21748			} else {
21749				textNavigator.setTarget(container, navui);
21750				textNavigator.setTextInfo(currentTextInfo);
21751				textNavigator.show();
21752			}
21753		})
21754		.on('keypress', function(e) {
21755			if (e.keyCode == 13) {
21756				var
21757					userinput = navui.val(),
21758					bibleref = new bible.Reference(userinput),
21759					fragmentid = (bibleref.toSection) ? bibleref.toSection() : '',
21760					sectionid = fragmentid.split('_')[0];
21761
21762				if (sectionid != '') {
21763					changeLocation(fragmentid);
21764				}
21765			}
21766		});
21767
21768	function changeLocation(inputLocation) {
21769		var bibleref = new bible.Reference(inputLocation),
21770			fragmentid = (bibleref.toSection) ? bibleref.toSection() : '',
21771			sectionid = fragmentid.split('_')[0],
21772
21773			newLocationInfo = {
21774				fragmentid: fragmentid,
21775				sectionid: sectionid
21776			};
21777
21778		currentLocationInfo = newLocationInfo;
21779
21780		if (scrollerMimic.trigger) {
21781			scrollerMimic.trigger('locationchange', {type:'locationchange', target: this, data: newLocationInfo});
21782		}
21783		if (ext && ext.trigger) {
21784			ext.trigger('settingschange', {type: 'settingschange', target: this, data: getData() });
21785		}
21786
21787		textNavigator.hide();
21788
21789		navui.val(bibleref.toString());
21790		navui[0].blur();
21791	}
21792
21793	textNavigator.on('change', function (e) {
21794
21795		if (e.data.target != navui) {
21796			return;
21797		}
21798
21799		changeLocation(e.data.sectionid);
21800	});
21801
21802	// TEXT CHOOSER
21803	textlistui.on('click', function(e) {
21804
21805		// if this is selected, then toggle
21806		if (textChooser.getTarget() == textlistui) {
21807			textChooser.toggle();
21808		} else {
21809			textChooser.setTarget(container, textlistui, text_type);
21810			textChooser.setTextInfo(currentTextInfo);
21811			textChooser.show();
21812		}
21813	});
21814
21815	textChooser.on('change', function(e) {
21816		if (e.data.target != textlistui) {
21817			return;
21818		}
21819
21820		var newTextInfo = e.data.textInfo;
21821
21822		updateText(newTextInfo);
21823
21824	});
21825
21826	function updateText(newTextInfo) {
21827
21828		// ALWAYS UPDATE: for first load
21829		// update version name
21830		textlistui.html( newTextInfo.abbr );
21831
21832		parent.tab.find('span').html( newTextInfo.abbr );
21833
21834		// update the navigator with the latest header
21835		textNavigator.setTextInfo(newTextInfo);
21836
21837		audioController.setTextInfo(newTextInfo);
21838
21839		currentTextInfo = newTextInfo;
21840
21841		//versioninfo.html( currentTextInfo.aboutHtml );
21842	}
21843
21844
21845
21846	function init() {
21847
21848		// TEMP
21849		navui.html('Reference').val('Reference');
21850		textlistui.html('Version');
21851
21852		if (init_data == null) {
21853			return;
21854		}
21855
21856		if (typeof init_data.fragmentid != 'undefined' && init_data.fragmentid != '') {
21857			//var bibleRef = new bible.Reference(init_data.fragmentid);
21858			//navui.val(bibleRef.toString());
21859			changeLocation(init_data.fragmentid);
21860		}
21861
21862		if (typeof init_data.textid == 'undefined' || init_data.textid == '') {
21863			init_data.textid = sofia.config.newBibleWindowVersion;
21864		}
21865
21866		// load the text specified by the init data
21867		TextLoader.getText(init_data.textid,
21868
21869			// success
21870			function(loadedTextInfo) {
21871
21872				// store this setting
21873				currentTextInfo = loadedTextInfo;
21874
21875				updateText(loadedTextInfo);
21876			},
21877
21878			// error handler
21879			function() {
21880//
21881//				console.log('ERROR', init_data.textid, 'doesnt exist');
21882//
21883//				// load all possible versions
21884//				TextLoader.loadTexts(function(textInfoData) {
21885//
21886//					// find a text with the same language
21887//					var newTextInfo = null,
21888//						lang = init_data.textid.toString().split('-')[0].split('_')[0];
21889//
21890//					for (var i=0, il=textInfoData.length; i<il; i++) {
21891//						var textInfo = textInfoData[i];
21892//
21893//						if (textInfo.type == text_type && (textInfo.lang == lang || textInfo.id.substring(0, lang.length) == lang)) {
21894//							newTextInfo = textInfo;
21895//							break;
21896//						}
21897//					}
21898//
21899//					// still nothing
21900//					if (newTextInfo == null) {
21901//						newTextInfo = textInfoData[0];
21902//					}
21903//
21904//					// let's try again with first one
21905//					TextLoader.getText(newTextInfo.id, function(loadedTextInfo) {
21906//						// store this setting
21907//						currentTextInfo = loadedTextInfo;
21908//
21909//						isInitialized = true;
21910//
21911//						startup();
21912//
21913//					});
21914//
21915//				});
21916
21917		});
21918	}
21919
21920
21921	init();
21922
21923
21924
21925	function size(width, height) {
21926
21927		container.outerWidth(width);
21928
21929		container
21930				.width(width)
21931				.height(height); // - header.outerHeight());
21932	}
21933
21934
21935	function getData() {
21936		// get data
21937		if (currentTextInfo == null) {
21938			currentTextInfo = textChooser.getTextInfo();
21939		}
21940		if (currentLocationInfo == null) {
21941			currentLocationInfo = scrollerMimic.getLocationInfo();
21942		}
21943
21944		// if not ready, then kill it
21945		if (currentTextInfo == null || currentLocationInfo == null) {
21946			return null;
21947		}
21948
21949		var data = {
21950			// textinfo
21951			textid: currentTextInfo.providerid,
21952			abbr: currentTextInfo.abbr,
21953
21954			// location info
21955			sectionid: currentLocationInfo.sectionid,
21956			fragmentid: currentLocationInfo.fragmentid,
21957			label: currentLocationInfo.label,
21958			labelTab: currentTextInfo.abbr,
21959			labelLong: currentLocationInfo.labelLong,
21960			//hasFocus: hasFocus,
21961			params: {
21962				'win': 'audio',
21963				'textid': currentTextInfo.providerid,
21964				'fragmentid': currentLocationInfo.fragmentid
21965			}
21966		};
21967
21968		return data;
21969	}
21970
21971//	function getData() {
21972//
21973//		var data = {
21974//			params: {
21975//				'win': 'audio'
21976//			}
21977//		};
21978//
21979//		return data
21980//	}
21981
21982	function close() {
21983
21984		ext.clearListeners();
21985
21986	}
21987
21988
21989	var ext = {
21990		size: size,
21991		getData: getData,
21992		sendMessage: function() {},
21993		close: close
21994	};
21995	ext = $.extend(true, ext, EventEmitter);
21996
21997
21998	ext.on('message', function(e) {
21999		if (e.data.messagetype == 'textload') {
22000		}
22001	});
22002
22003	return ext;
22004};
22005
22006sofia.initMethods.push(function() {
22007
22008	if (sofia.config.enableAudioWindow) {
22009
22010		sofia.windowTypes.push( {
22011				className:'AudioWindow',
22012				param: 'audio',
22013				paramKeys: {
22014					'textid': 't',
22015					'fragmentid':'v'
22016				},
22017				init: {
22018					'textid':sofia.config.audioWindowDefaultBibleVersion,
22019					'fragmentid':sofia.config.audioWindowDefaultBibleFragmentid
22020				}
22021		});
22022	}
22023});
22024
22025
22026
22027/*********
22028* ../app/js/windows/statisticswindow.js
22029**********/
22030var StatisticsWindow = function(id, parent, data) {
22031
22032	var
22033		header = $('<div class="window-header"><span class="window-title i18n" data-i18n="[html]windows.stats.label"></span></div>').appendTo(parent.node),
22034		main = $('<div class="window-main">' +
22035					//'<div class="statistics-header"></div>' +
22036					'<div class="statistics-content loading-indicator"></div>' +
22037				'</div>').appendTo(parent.node),
22038		//statsHeaderNode = main.find('.statistics-header'),
22039		statsMainNode = main.find('.statistics-content');
22040
22041	//header.find('.i18n').i18n();
22042
22043
22044	var isReady = false,
22045		textid = '',
22046		sectionid = '',
22047		textInfo = null,
22048		isLoading = false,
22049		isLoaded = false,
22050
22051		word_stats = [],
22052		lemma_data = [],
22053		has_lemma = false;
22054
22055
22056	if (typeof WordCloud != 'undefined') {
22057		isReady = true;
22058
22059	} else {
22060
22061		$.ajax({
22062			url: 'js/lib/wordcloud2.js',
22063			dataType: "script",
22064			success:  function() {
22065				isReady = true;
22066				loadIntro();
22067			}
22068		});
22069
22070	}
22071
22072
22073	function startProcess(tid, sid) {
22074
22075		var tparts = tid.split(':');
22076
22077		if (tparts.length > 1) {
22078			tid = tparts[tparts.length-1];
22079		}
22080
22081
22082		if (tid == textid && sid == sectionid) {
22083			// do nothing, carry on!
22084		} else {
22085
22086			removeHighlights();
22087
22088			// store the current ones
22089			sectionid = sid;
22090			textid = tid;
22091			textInfo = null;
22092
22093			word_stats = [];
22094			lemma_data = [];
22095			has_lemma = false;
22096
22097			isLoading = true;
22098			isLoaded = false;
22099
22100			main.scrollTop(0);
22101			statsMainNode.html('');
22102			statsMainNode.addClass('loading-indicator');
22103
22104			loadIntro();
22105		}
22106
22107	}
22108
22109	function loadIntro() {
22110
22111		if (!isReady) {
22112			return;
22113		}
22114
22115		if (sectionid == '' || textid == '') {
22116			return;
22117		}
22118
22119		console.log('stats',textid,sectionid, 'intro start');
22120
22121		//var intro = $('<div class="statistics-intro"><div>').appendTo(statsMainNode);
22122
22123		TextLoader.getText(textid, function(data) {
22124
22125			statsMainNode.removeClass('loading-indicator');
22126
22127			textInfo = data;
22128
22129			var bibleReference = new bible.Reference(sectionid);
22130			bibleReference.lang = textInfo.lang;
22131
22132			if (bibleReference.toSection) {
22133				header.find('span').html( bibleReference.toString() + ' (' + textInfo.abbr + ')');
22134			}
22135
22136			loadChapterInfo();
22137		});
22138	}
22139
22140	function loadChapterInfo() {
22141
22142		console.log('stats',textid,sectionid, 'loadChapterInfo start');
22143
22144		var resultsNode = $('<div class="statistics-section statistics-frequent-words">' +
22145								'<h3>' + i18n.t('windows.stats.frequentwords') + '</h3>' +
22146								'<div class="statistics-wordcloud"></div>' +
22147								'<div class="statistics-results loading-indicator"></div>' +
22148							'<div>')
22149								.appendTo(statsMainNode),
22150			wordFrequenciesNode = resultsNode
22151									.find('.statistics-results'),
22152			wordCloudNode = resultsNode
22153									.find('.statistics-wordcloud');
22154
22155		TextLoader.loadSection(textInfo, sectionid, function(content) {
22156
22157			// analyze verses
22158			var html = '';
22159
22160			content.find('.verse, .v').each(function(i,el) {
22161				var verse = $(this),
22162					verseid = verse.attr('data-id')
22163					verse.find('.note').remove();
22164
22165				//console.log('processing', verseid, words);
22166
22167				if (verse.find('l').length > 0) {
22168
22169					has_lemma = true;
22170
22171					verse.find('l').each(function() {
22172						var lemma = $(this),
22173							strongs_array = lemma.attr('s').split(' ');
22174
22175						strongs_array.forEach(function(strongs) {
22176
22177							// article
22178							if (
22179								strongs == 'G2532' // καί
22180								|| strongs == 'G3588'
22181								|| strongs == 'G846' // αὐτός
22182								|| strongs == 'G1722' // ἐν (1722)
22183								|| strongs == 'G1519' // εἰς (1519)
22184								|| strongs == 'G1537' // ἐξ (1537)
22185								|| strongs == 'G1611'// δέ (1161)
22186							) {
22187								return;
22188							}
22189
22190							var
22191								word_infos = word_stats.filter(function(wi) {
22192									return wi.strongs == strongs;
22193								}),
22194
22195								word = lemma.html();
22196
22197							if (word_infos.length > 0) {
22198								word_info = word_infos[0];
22199								word_info.count++;
22200								if (word_info.words.indexOf(word) == -1) {
22201									word_info.words.push(word);
22202								}
22203							} else {
22204								word_stats.push({
22205									strongs: strongs,
22206									word: lemma.html(), // + ' [' + strongs + ']',
22207									words: [word],
22208									count: 1
22209								})
22210							}
22211						});
22212
22213
22214					});
22215
22216				} else {
22217					var
22218						verse_html = verse.html(),
22219						verse_text = verse_html.replace(/<.*?>/gi,'');
22220
22221					if (textInfo.lang.indexOf('en') == 0) {
22222						verse_text = verse_text.replace(/[^(A-Za-z\s)]/gi,'');
22223					}
22224
22225					// english?
22226					words = verse_text.split(' ');
22227
22228					for (var i=0, il=words.length; i<il; i++) {
22229						var word = words[i];//.toLowerCase();
22230
22231						if (word == '' || (exclusions[textInfo.lang] && exclusions[textInfo.lang].indexOf(word.toLowerCase()) > -1)) {
22232							continue;
22233						}
22234
22235						var
22236							word_infos = word_stats.filter(function(wi) {
22237								return wi.word == word || wi.word.toLowerCase() == word.toLowerCase();
22238							});
22239
22240						if (word_infos.length > 0) {
22241							word_infos[0].count++;
22242						} else {
22243							word_stats.push({
22244								word: word,
22245								count: 1
22246							})
22247						}
22248					}
22249				}
22250			});
22251
22252
22253			// sort by most frequent
22254
22255			word_stats.sort(function(a, b) {
22256				if (a.count > b.count) {
22257					return -1;
22258				} else if (a.count < b.count) {
22259					return 1;
22260				} else {
22261					return 0;
22262				}
22263			});
22264			var max = Math.max.apply(Math, word_stats.map(function(o) {return o.count;})),
22265				min = Math.min.apply(Math, word_stats.map(function(o) {return o.count;})),
22266				smallestSize = 9,
22267				biggestSize = 24;
22268
22269			// remove uncommon words
22270			var min_to_keep = 0,
22271				display_words = word_stats;
22272
22273			if (min_to_keep > 0) {
22274				display_words = word_stats.filter(function(w) { return w.count >= min_to_keep;} );
22275			}
22276
22277			// create HTML
22278			var wordle_data = '',
22279				wordcloud_data = [];
22280			for (var i in display_words) {
22281				var word_info = display_words[i];
22282
22283				//if (exclusions.en.indexOf(word_info.word.toLowerCase()) == -1) {
22284
22285					var size = smallestSize +
22286								((biggestSize-smallestSize) * word_info.count / (max-min) ),
22287						displayWord = word_info.words ? word_info.words.join(', ') : word_info.word,
22288						wordleWord = word_info.word;
22289
22290					if (word_info.strongs) {
22291						displayWord = '<l s="' + word_info.strongs + '">' + displayWord + '</l>';
22292					}
22293
22294
22295					html += '<span class="word" style="font-size:' + size + 'px" data-wordindex="' + i + '"><span dir="' + textInfo.dir + '">' + displayWord + '</span> <span dir="ltr">(' + word_info.count + ')</span></span>';
22296					wordle_data += word_info.count + ' ' + displayWord + '\n';
22297					wordcloud_data.push( [ wordleWord , word_info.count] );
22298				//}
22299			}
22300
22301			console.log(word_stats);
22302			console.log(wordle_data);
22303
22304			// append
22305			wordFrequenciesNode
22306				.attr('dir',textInfo.dir)
22307				.html(html)
22308				.removeClass('loading-indicator');
22309
22310			wordFrequenciesNode.find('.word')
22311				.on('mouseout', function() { removeHighlights(); })
22312				.on('mouseover', function() {
22313					var el = this,
22314						index = parseInt($(el).attr('data-wordindex'), 10),
22315						word_info = word_stats[index];
22316
22317					//console.log(el, index, word_info);
22318					createHighlights(word_info);
22319
22320				})
22321
22322			var windowWidth = statsMainNode.width();
22323
22324			wordCloudNode
22325					.width(windowWidth)
22326					.height(windowWidth*3/4);
22327
22328			WordCloud(wordCloudNode[0], {
22329				//gridSize: Math.round(16 * wordle.width() / 1024),
22330				//fontFamily: 'Times',
22331				minSize: 5,
22332				/*
22333				weightFactor: function (size) {
22334					return Math.pow(size, 2.3) * wordle.width() / 4000;
22335				},
22336				*/
22337
22338				weightFactor: function (size) {
22339
22340					//console.log(size, b, this);
22341
22342					//return size * wordCloudNode.width() / 120;
22343
22344					var sizeMax = Math.min(wordCloudNode.width() / 7, 80),
22345						sizeMin = sizeMax * 0.1,
22346						newSize = sizeMin + (Math.abs(sizeMax-sizeMin) * (size-min) / (max-min) );
22347
22348					// sizeMax = 10, sizeMin = 2
22349					// size = 5, min=1, max=10
22350					// 2 + (8 * 5 / 9)
22351
22352
22353					//console.log(min, max, size, sizeMin, sizeMax, newSize);
22354					return newSize;
22355				},
22356
22357				list: wordcloud_data,
22358				//backgroundColor: '#efefef',
22359				hover: function(hover_word_info, word_position, mouse_event) {
22360
22361					removeHighlights();
22362
22363					if (hover_word_info) {
22364						var word = hover_word_info[0];
22365
22366						var word_info = word_stats.filter(function(a) {
22367							return a.word == word;
22368						})[0];
22369
22370						createHighlights(word_info);
22371					}
22372
22373				},
22374				color: function (word, size) {
22375					/*
22376					var rMax = 42,
22377						rMin = 138,
22378						rValue = Math.round( rMax - (Math.abs(rMax-rMin) * (size-min) / (max-min) ) ),
22379						gMax = 133,
22380						gMin = 182,
22381						gValue = Math.round( gMax - (Math.abs(gMax-gMin) * (size-min) / (max-min) ) )
22382
22383						;
22384
22385					//return '#ff0000';
22386
22387					var bMax = 232,
22388						bMin = 230,
22389						bValue = Math.round( bMin + (Math.abs(bMax-bMin) * (size-min) / (max-min) ) );
22390					*/
22391
22392					// search blue = 42, 133, 232
22393					// dark blue = 22, 71, 123
22394
22395					var rValue = Math.round( getRangeValue(42,  22, min, max, size) ),
22396						gValue = Math.round( getRangeValue(133, 71, min, max, size) ),
22397						bValue = Math.round( getRangeValue(232, 123, min, max, size) );
22398
22399					console.log(word, min, max, size, rValue, gValue, bValue);
22400
22401					//console.log(word, weight, rValue, gValue, 'rgb(' + rValue + ',' + gValue + ',215)');
22402					//return 'rgb(' + rValue + ',' + gValue + ',215)';
22403					//return 'rgb(' + (weight * 10) + ',' + (weight * 10) + ',215)';
22404					return 'rgb(' + rValue + ',' + gValue + ',' + bValue + ')';
22405				},
22406				click: function() {
22407
22408				}
22409			});
22410
22411			if (has_lemma) {
22412
22413				loadLemmaInfo();
22414			}
22415
22416		});
22417	}
22418
22419	function getRangeValue(value1, value2, minValue, maxValue, value) {
22420
22421		if (value2 > value1) {
22422			return value1 + (Math.abs(value1-value2) * (value-minValue) / (maxValue-minValue) );
22423		} else if (value1 > value2) {
22424			return value2 + (Math.abs(value1-value2) * (value-minValue) / (maxValue-minValue) );
22425		} else {
22426			return value1;
22427		}
22428
22429	}
22430
22431	sofia.getRangeValue = getRangeValue;
22432
22433	var currentLemmaIndex = 0;
22434	function loadLemmaInfo() {
22435
22436		console.log('stats',textid,sectionid, 'lemma start');
22437
22438		var lemmaNode = $('<div class="statistics-section statistics-rare-words">' +
22439								'<h3>Rare Words</h3>' +
22440								'<div class="statistics-results loading-indicator"></div>' +
22441							'<div>')
22442								.appendTo(statsMainNode)
22443								.find('.statistics-results');
22444
22445		currentLemmaIndex = 0;
22446
22447		getNextLemma(function() {
22448
22449			lemma_data.sort(function(a, b) {
22450				if (a.word_info.count > b.word_info.count) {
22451					return -1;
22452				} else if (a.word_info.count < b.word_info.count) {
22453					return 1;
22454				} else {
22455					return 0;
22456				}
22457			});
22458
22459			var rare_words = lemma_data.filter(function(lemma) { return lemma.frequency <= 5;});
22460
22461			html = '';
22462			for (var i=0, il=rare_words.length; i<il; i++) {
22463				var lemma = rare_words[i],
22464					lemmaLang = lemma.word_info.strongs.substr(0,1).toUpperCase() == 'G' ? 'gr' : 'he',
22465					dir = lemmaLang == 'gr' ? 'ltr' : 'rtl';
22466
22467				html += '<tr class="rare"><td><l s="' + lemma.word_info.strongs + '" lang="' + lemmaLang + '" dir="' + dir + '">' + lemma.lemma + '</td><td>' + lemma.word_info.words.join(', ') + '</td><td>' + lemma.word_info.count + ' of ' + lemma.frequency + ' in ' + (lemmaLang == 'gr' ? 'NT' : 'OT') + '</td></tr>';
22468
22469			}
22470
22471			lemmaNode
22472				.html( '<table>' + html + '</table>')
22473				.removeClass('loading-indicator');
22474
22475		});
22476	}
22477
22478	function getNextLemma(callback) {
22479
22480		if (currentLemmaIndex >= word_stats.length) {
22481
22482			callback();
22483
22484		} else {
22485
22486			var word_info = word_stats[currentLemmaIndex];
22487
22488			$.ajax({
22489				dataType: 'json',
22490				url: 'content/lexicons/strongs/entries/' + word_info.strongs + '.json',
22491				success: function(data) {
22492					data.word_info = word_info;
22493					lemma_data.push(data);
22494				},
22495				complete: function() {
22496					getNextLemma(callback);
22497					currentLemmaIndex++;
22498				}
22499			});
22500		}
22501	}
22502
22503
22504	function removeHighlights() {
22505		$('.BibleWindow .highlight-stats').each(function(i, el) {
22506
22507			if (el.tagName.toLowerCase() == 'l') {
22508				// for Lemma tags, jsut remove hte hlight
22509				el.className = el.className.replace(/highlight/gi, '');
22510			} else {
22511				// if it's just <span class="highlight">, replace it with text
22512				var textFragment = document.createTextNode(el.textContent);
22513				if (el && el.parentNode) {
22514					el.parentNode.insertBefore(textFragment, el);
22515					el.parentNode.removeChild(el);
22516				}
22517			}
22518
22519		});
22520	}
22521
22522	function createHighlights(word_info) {
22523
22524		removeHighlights();
22525
22526
22527		// do for all sections / chapters
22528		$('.' + sectionid).each(function(i,el) {
22529
22530			if (typeof word_info.strongs != 'undefined') {
22531				//var r = new RegExp('s=("|\')(\\w\\d{1,4}[a-z]?\\s)?' + '(G|H)?' + word_info.strongs.substr(1) + '[a-z]?(\\s\\w\\d{1,4}[a-z]?)?("|\')', 'gi');
22532
22533				$(el).find('l[s*="' + word_info.strongs.substr(1) + '"],l[s*="' + word_info.strongs + '"]').addClass('highlight highlight-stats lemma-highlight');
22534
22535				// add the 'highlight' class to the <l> node
22536				//el.innerHTML = el.innerHTML.replace(r, function(match) {
22537				//	return match + ' class="highlight highlight-stats" ';
22538				//});
22539
22540			} else {
22541
22542				//var r = new RegExp('\b' + word_info.word + '\b', 'gi');
22543				var r = new XRegExp( '\\b' + word_info.word + '\\b', 'gi');
22544
22545				// surround the word with a highlight
22546				el.innerHTML = el.innerHTML.replace(r, function(match) {
22547					return '<span class="highlight highlight-stats">' + match + '</span>';
22548				});
22549
22550			}
22551
22552		});
22553
22554
22555	}
22556
22557
22558	function close() {
22559
22560		ext.clearListeners();
22561	}
22562
22563	function size(width, height) {
22564		// do notheirng?
22565		main.outerHeight(height - header.outerHeight())
22566			.outerWidth(width);
22567	}
22568
22569	var ext = {
22570		size: size,
22571		getData: function() {
22572			return {
22573
22574				params: {
22575					'win': 'stats'
22576				}
22577
22578			}
22579		},
22580		close: close
22581	};
22582	ext = $.extend(true, ext, EventEmitter);
22583
22584	ext.on('message', function(e) {
22585		if (e.data.messagetype == 'textload') {
22586			//processContent(e.data.content);
22587		}
22588
22589		if (e.data.messagetype == 'nav' && e.data.type == 'bible' && e.data.locationInfo) {
22590
22591			startProcess(e.data.locationInfo.textid, e.data.locationInfo.sectionid);
22592		}
22593	});
22594
22595	setTimeout(function() {
22596		var firstWindowSettings = sofia.app.windowManager.getSettings()[0],
22597			firstWin = $('.window:first'),
22598
22599			selectedSectionid = firstWindowSettings.data.sectionid,
22600			selectedTextid = firstWindowSettings.data.textid;
22601
22602		startProcess(selectedTextid, selectedSectionid);
22603
22604	}, 1500);
22605
22606	return ext;
22607
22608};
22609
22610
22611var exclusions = {
22612	"es": ["de"],
22613
22614	"chs": ["─",":",",","。","(",")","!",";","一","?"],
22615
22616	"eng": [
22617		// prepositions
22618"a",
22619"abaft",
22620"aboard",
22621"about",
22622"above",
22623"absent",
22624"across",
22625"afore",
22626"after",
22627"against",
22628"along",
22629"alongside",
22630"amid",
22631"amidst",
22632"among",
22633"amongst",
22634"an",
22635"anenst",
22636"apud",
22637"around",
22638"as",
22639"aside",
22640"astride",
22641"at",
22642"athwart",
22643"atop",
22644"barring",
22645"before",
22646"behind",
22647"below",
22648"beneath",
22649"beside",
22650"besides",
22651"between",
22652"beyond",
22653"but",
22654"by",
22655"circa",
22656"concerning",
22657"despite",
22658"down",
22659"during",
22660"except",
22661"excluding",
22662"failing",
22663"following",
22664"for",
22665"forenenst",
22666"from",
22667"given",
22668"in",
22669"including",
22670"inside",
22671"into",
22672"lest",
22673"like",
22674"minus",
22675"modulo",
22676"near",
22677"next",
22678"notwithstanding",
22679"of",
22680"off",
22681"on",
22682"onto",
22683"opposite",
22684"out",
22685"outside",
22686"over",
22687"pace",
22688"past",
22689"per",
22690"plus",
22691"pro",
22692"qua",
22693"regarding",
22694"round",
22695"sans",
22696"save",
22697"since",
22698"than",
22699"through",
22700"throughout",
22701"till",
22702"to",
22703"toward",
22704"towards",
22705"under",
22706"underneath",
22707"unlike",
22708"until",
22709"unto",
22710"up",
22711"upon",
22712"versus",
22713"via",
22714"with",
22715"within",
22716"without",
22717"worth",
22718
22719// articles
22720"a",
22721"the",
22722
22723// pronouns
22724"him",
22725"his",
22726"he",
22727"she",
22728"it",
22729"her",
22730"hers",
22731
22732// conjunctions
22733"and",
22734"but",
22735"yet",
22736
22737// other
22738"that",
22739
22740"was",
22741"were",
22742"be",
22743"being",
22744"been",
22745"had",
22746"its",
22747"i",
22748
22749	]
22750};
22751
22752sofia.initMethods.push(function() {
22753
22754
22755	sofia.windowTypes.push( {
22756				className:'StatisticsWindow',
22757				param: 'stats',
22758				paramKeys: {},
22759				init: {
22760				}
22761	});
22762
22763});
22764
22765
22766
22767/*********
22768* ../app/js/windows/deafbiblewindow.js
22769**********/
22770sofia.config = $.extend(sofia.config, {
22771	enableDeafBibleWindow: true,
22772	deafBibleWindowDefaultBibleFragmentid: 'JN1_1',
22773	deafBibleWindowDefaultBibleVersion: 'deaf_ASESLV'
22774});
22775
22776var DeafBibleWindow = function(id, node, init_data) {
22777
22778	var window =  new TextWindow(id, node, init_data, 'deafbible');
22779
22780	node.node.on('click', '.deaf-video-header input', function() {
22781		var button = $(this),
22782			url = button.attr('data-src'),
22783			video = button.closest('.deaf-video').find('video');
22784
22785		button
22786			.addClass('active')
22787			.siblings()
22788				.removeClass('active');
22789
22790		console.log(url, video);
22791
22792		video.attr('src', url);
22793	});
22794
22795	return window;
22796};
22797
22798sofia.initMethods.push(function() {
22799
22800	if (sofia.config.enableDeafBibleWindow)	{
22801		sofia.windowTypes.push({
22802			className:'DeafBibleWindow',
22803			param: 'deafbible',
22804			paramKeys: {
22805				'textid': 't',
22806				'fragmentid':'v'
22807			},
22808			init: {
22809				'textid': sofia.config.deafBibleWindowDefaultBibleVersion,
22810				'fragmentid': sofia.config.deafBibleWindowDefaultBibleFragmentid
22811			}
22812		});
22813	}
22814});
22815
22816
22817
22818/*********
22819* ../app/js/menu/mainmenu.js
22820**********/
22821var MainMenu = function(node) {
22822
22823	// simply create all 'menuComponents' define below
22824	for (var i=0, il=sofia.menuComponents.length; i<il; i++) {
22825		var component = new window[sofia.menuComponents[i]](node);
22826	}
22827
22828	var ext = {
22829
22830	};
22831
22832	ext = $.extend(true, ext, EventEmitter);
22833	return ext;
22834
22835};
22836
22837
22838var MainMenuButton = function(node) {
22839	var
22840		body = $(document.body),
22841		win = $(window),
22842
22843		container = $('.windows-container'),
22844		mainMenuLogo = $('<div id="app-logo"></div>')
22845					.appendTo(node),
22846        support = $('<a href="https://patreon.com/johndyer?utm_medium=website&utm_source=biblewebapp.com&utm_campaign=mainlink" target="_blank" id="support">Support</a>')
22847					.appendTo(node),                    
22848		mainMenuButton = $('<div id="main-menu-button"></div>')
22849					.appendTo(node)
22850					.on('click', mainMenuClick),
22851		mainMenuDropDown = $('<div id="main-menu-dropdown">' +
22852								'<div class="main-menu-heading i18n" data-i18n="[html]menu.labels.addwindow">Add Window</div>' +
22853								'<div id="main-menu-windows-list" class="main-menu-list"></div>' +
22854								'<div class="main-menu-heading i18n" data-i18n="[html]menu.labels.options"></div>' +
22855								'<div id="main-menu-features" class="main-menu-list"></div>' +
22856							'</div>')
22857							.appendTo( body )
22858							.hide();
22859
22860
22861	function mainMenuClick(e) {
22862
22863		if (mainMenuDropDown.is(':visible')) {
22864			hide();
22865		} else {
22866			show();
22867		}
22868	}
22869
22870	function show() {
22871		mainMenuButton.addClass('active');
22872		mainMenuDropDown.show();
22873		ext.onshow();
22874	}
22875
22876	function hide() {
22877		mainMenuButton.removeClass('active');
22878		mainMenuDropDown.hide();
22879		ext.onhide();
22880	}
22881
22882	mainMenuDropDown.on('click', '.main-menu-item', function() {
22883		hide();
22884	});
22885
22886
22887	var ext = {};
22888	ext = $.extend(true, ext, EventEmitter);
22889	ext = $.extend(true, ext, ClickOff);
22890	ext.clickoffid = 'version picker';
22891	ext.on('offclick', function() {
22892		hide();
22893	});
22894	ext.setClickTargets([mainMenuButton, mainMenuDropDown]);
22895
22896	return ext;
22897
22898}
22899
22900
22901sofia.menuComponents.push('MainMenuButton');
22902
22903
22904
22905/*********
22906* ../app/js/menu/topsearch.js
22907**********/
22908
22909
22910
22911var MainSearchBox = function(node) {
22912
22913	var searchBox = $('<div id="main-search-box">' +
22914							//'<input type="search" placeholder="Search" id="main-search-input" />' +
22915
22916							//'<input type="search" placeholder="' + i18n.t('menu.search.placeholder') + '" id="main-search-input" />' +
22917							'<input type="search" class="i18n" data-i18n="[placeholder]menu.search.placeholder" id="main-search-input" />' +
22918
22919							'<input type="button" id="main-search-button" value="" />' +
22920					'</div>')
22921						.appendTo(node),
22922
22923		suggestions = $('<div id="main-search-suggestions"></div>')
22924						.appendTo(document.body)
22925						.hide(),
22926
22927		searchInput = searchBox.find('#main-search-input')
22928								.on('keydown', checkNavigation)
22929								.on('keyup', checkInput)
22930								//.on('focus', showSuggestions)
22931								.on('blur', hideSuggestions),
22932
22933		searchButton = searchBox.find('#main-search-button')
22934								.on('click', doSelectedAction),
22935		inputText = '',
22936		firstSearchWindow = null,
22937		firstBibleWindow = null;
22938
22939
22940	suggestions.on('click', '.suggestion-item', function() {
22941		var item = $(this);
22942
22943		// select the clicked one
22944		item
22945			.addClass('selected')
22946			.siblings()
22947				.removeClass('selected');
22948
22949		// do it!
22950		doSelectedAction();
22951	});
22952
22953
22954	function getWindowInfo() {
22955		var
22956			appSettings = sofia.app.windowManager.getSettings();
22957
22958		for (var i=0,il=appSettings.length; i<il; i++) {
22959			var settings = appSettings[i];
22960			// first text
22961			if (settings.windowType == 'BibleWindow' && firstBibleWindow == null) {
22962				firstBibleWindow = settings;
22963			}
22964
22965			// first search
22966			if (settings.windowType == 'SearchWindow' && firstSearchWindow == null) {
22967				firstSearchWindow = settings;
22968			}
22969		}
22970	}
22971
22972
22973	function doSelectedAction() {
22974
22975		var actionName = suggestions.find('.selected').attr('data-name'),
22976			suggestedAction = suggestionActions.filter(function(item) {
22977				return item.name == actionName;
22978			})[0];
22979
22980		suggestedAction.doAction( searchInput.val(), firstBibleWindow, firstSearchWindow );
22981
22982		searchInput.val('');
22983		suggestions.val('');
22984
22985		hideSuggestions();
22986	}
22987
22988
22989	function checkNavigation(e) {
22990		showSuggestions();
22991
22992		var selectedSuggestion = suggestions.find('.selected');
22993
22994
22995		if (e.which == 40) { // arrow down
22996			// go down
22997
22998			var nextSuggestion = selectedSuggestion.next();
22999
23000			if (nextSuggestion.length == 0) {
23001				nextSuggestion = suggestions.children().first();
23002			}
23003
23004			nextSuggestion
23005				.addClass('selected')
23006				.siblings()
23007					.removeClass('selected');
23008
23009		} else if (e.which == 38) { // arrow up
23010			// go up
23011
23012			var prevSuggestion = selectedSuggestion.prev();
23013
23014			if (prevSuggestion.length == 0) {
23015				prevSuggestion = suggestions.children().last();
23016			}
23017
23018			prevSuggestion
23019				.addClass('selected')
23020				.siblings()
23021					.removeClass('selected');
23022
23023		} else {
23024			//checkInput(e);
23025		}
23026	}
23027
23028	function checkInput(e) {
23029
23030		if (e.which == 13) { // ENTER
23031
23032			doSelectedAction();
23033
23034			return;
23035		} else if (e.which == 38 || e.which == 40) { // UP or DOWN
23036
23037			return false;
23038		}
23039
23040		// try all actions
23041		var inputText = searchInput.val(),
23042			isFirst = true;
23043
23044		suggestions.html('');
23045
23046		if (inputText == '') {
23047			hideSuggestions();
23048			return;
23049		} else {
23050			showSuggestions();
23051		}
23052
23053		for (var i=0,il=suggestionActions.length; i<il; i++) {
23054			var suggestionAction = suggestionActions[i],
23055				suggestionText = suggestionAction.checkInput(inputText, firstBibleWindow, firstSearchWindow);
23056
23057
23058			if (suggestionText != '') {
23059				var text = $('<span class="suggestion-item' + (isFirst ? ' selected' : '') + '" data-name="' + suggestionAction.name + '">' + suggestionText + '</span>')
23060					.appendTo(suggestions);
23061
23062				isFirst = false;
23063			}
23064		}
23065	}
23066
23067	function showSuggestions() {
23068		getWindowInfo();
23069
23070		suggestions.show();
23071		positionSuggetions();
23072	}
23073
23074	function hideSuggestions() {
23075		suggestions.hide();
23076	}
23077
23078	function positionSuggetions() {
23079		var inputPos = searchBox.offset();
23080
23081		suggestions.css({
23082			top: inputPos.top + searchBox.height(),
23083			left: inputPos.left
23084		})
23085
23086
23087	}
23088
23089
23090	var suggestionActions = [
23091
23092
23093
23094		{
23095			name: 'search',
23096			checkInput: function(inputText, firstBibleWindow, firstSearchWindow) {
23097				return i18n.t('menu.search.suggestsearch').replace('{0}', inputText).replace('{1}', (firstBibleWindow != null ? firstBibleWindow.data.abbr : ''));
23098			},
23099			doAction: function(inputText, firstBibleWindow, firstSearchWindow) {
23100
23101				PlaceKeeper.storePlace();
23102
23103				var textid = firstBibleWindow != null ? firstBibleWindow.data.textid : sofia.config.newBibleWindowVersion;
23104				sofia.app.windowManager.add('SearchWindow', {searchtext: inputText, textid: textid});
23105
23106				PlaceKeeper.restorePlace();
23107
23108				searchBox.val('');
23109			}
23110		},
23111
23112		{
23113			name: 'version',
23114			checkInput: function(inputText, firstBibleWindow, firstSearchWindow) {
23115
23116				var matchingVersions = TextLoader.getTextInfoData().filter(function(textInfo) {
23117					return textInfo.abbr.toLowerCase() == inputText.toLowerCase();
23118				});
23119
23120				if (matchingVersions.length > 0) {
23121					//return 'Open <strong>' + matchingVersions[0].abbr + '</strong> in new window';
23122					return i18n.t('menu.search.suggestversion').replace('{0}', matchingVersions[0].abbr );
23123				}
23124
23125				return '';
23126				//return 'Open ' + inputText + ' in XXXXX';
23127			},
23128			doAction: function(inputText, firstBibleWindow, firstSearchWindow) {
23129				//
23130
23131				var matchingTexts = TextLoader.getTextInfoData().filter(function(textInfo) {
23132						return textInfo.abbr.toLowerCase() == inputText.toLowerCase();
23133					}),
23134					selectedTextInfo = matchingTexts[0];
23135
23136				sofia.app.windowManager.add('BibleWindow', {textid: selectedTextInfo.id, abbr: selectedTextInfo.abbr, fragmentid: firstBibleWindow ? firstBibleWindow.fragmentid : ''});
23137
23138			}
23139		},
23140
23141		{
23142			name: 'reference',
23143			checkInput: function(inputText, firstBibleWindow, firstSearchWindow) {
23144
23145				var reference = new bible.Reference(inputText);
23146
23147				if (typeof reference.isValid != 'undefined') {
23148					//return 'Go to <strong>' + reference.toString() + '</strong>' + (firstBibleWindow != null ? ' in ' + firstBibleWindow.data.abbr : '');
23149					return i18n.t('menu.search.suggestreference').replace('{0}', reference.toString()).replace('{1}', (firstBibleWindow != null ? firstBibleWindow.data.abbr : ''));
23150				}
23151
23152				return '';
23153			},
23154			doAction: function(inputText, firstBibleWindow, firstSearchWindow) {
23155
23156				var reference = new bible.Reference(inputText),
23157					fragmentid = reference.toSection();
23158
23159				if (firstBibleWindow == null) {
23160					sofia.app.windowManager.add('BibleWindow', {textid: sofia.config.newBibleWindowVersion, fragmentid: fragmentid});
23161
23162				} else {
23163
23164					// HACK!!
23165					sofia.app.handleGlobalMessage(
23166						{
23167							data: {
23168								messagetype:'nav',
23169								type: 'bible',
23170								locationInfo: {
23171									fragmentid: fragmentid,
23172									sectionid: fragmentid.split('_')[0],
23173									offset: 0
23174								}
23175							}
23176						}
23177					);
23178
23179				}
23180
23181			}
23182		}
23183	];
23184
23185	return searchBox;
23186
23187};
23188sofia.menuComponents.push('MainSearchBox');
23189
23190
23191
23192/*********
23193* ../app/js/menu/fullscreen.js
23194**********/
23195var FullScreenButton = function(node) {
23196
23197
23198	if (!fullScreenApi.supportsFullScreen) {
23199		return null;
23200	}
23201
23202	$('html').addClass('supports-fullscreen');
23203
23204	var
23205		el = document.body,
23206		//fullscreenButton = $('<div id="main-fullscreen-button" class="main-menu-item">Fullscreen</div>')
23207		//	.appendTo( $('#main-menu-features') )
23208		fullscreenButton = $('<div id="main-fullscreen-button"></div>')
23209			.appendTo( $('.windows-header') )
23210			.on('click', toggleFullscreen),
23211
23212		isFullscreen = false;
23213
23214	function toggleFullscreen() {
23215		if (fullScreenApi.isFullScreen()) {
23216			exitFullscreen();
23217		} else {
23218			enterFullscreen();
23219		}
23220	}
23221
23222	function enterFullscreen() {
23223		fullScreenApi.requestFullScreen(el);
23224	}
23225
23226	function exitFullscreen() {
23227		fullScreenApi.cancelFullScreen();
23228
23229	}
23230
23231	//console.log(fullScreenApi);
23232
23233
23234	return fullscreenButton;
23235};
23236sofia.menuComponents.push('FullScreenButton');
23237
23238(function() {
23239    var
23240        fullScreenApi = {
23241            supportsFullScreen: false,
23242            isFullScreen: function() { return false; },
23243            requestFullScreen: function() {},
23244            cancelFullScreen: function() {},
23245            fullScreenEventName: '',
23246            prefix: ''
23247        },
23248        browserPrefixes = 'webkit moz o ms khtml'.split(' ');
23249
23250    // check for native support
23251    if (typeof document.cancelFullScreen != 'undefined') {
23252        fullScreenApi.supportsFullScreen = true;
23253    } else {
23254        // check for fullscreen support by vendor prefix
23255        for (var i = 0, il = browserPrefixes.length; i < il; i++ ) {
23256            fullScreenApi.prefix = browserPrefixes[i];
23257
23258            if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] != 'undefined' ) {
23259                fullScreenApi.supportsFullScreen = true;
23260
23261                break;
23262            }
23263        }
23264    }
23265
23266    // update methods to do something useful
23267    if (fullScreenApi.supportsFullScreen) {
23268        fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
23269
23270        fullScreenApi.isFullScreen = function() {
23271            switch (this.prefix) {
23272                case '':
23273                    return document.fullScreen;
23274                case 'webkit':
23275                    return document.webkitIsFullScreen;
23276                default:
23277                    return document[this.prefix + 'FullScreen'];
23278            }
23279        }
23280        fullScreenApi.requestFullScreen = function(el) {
23281        	if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
23282	        	el.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
23283        	}
23284            else {
23285            	return (this.prefix === '') ? el.requestFullScreen() : el[this.prefix + 'RequestFullScreen']();
23286            }
23287        }
23288        fullScreenApi.cancelFullScreen = function(el) {
23289            return (this.prefix === '') ? document.cancelFullScreen() : document[this.prefix + 'CancelFullScreen']();
23290        }
23291    }
23292
23293    // jQuery plugin
23294    if (typeof jQuery != 'undefined') {
23295        jQuery.fn.requestFullScreen = function() {
23296
23297            return this.each(function() {
23298                if (fullScreenApi.supportsFullScreen) {
23299                    fullScreenApi.requestFullScreen(this);
23300                }
23301            });
23302        };
23303    }
23304
23305    // export api
23306    window.fullScreenApi = fullScreenApi;
23307})();
23308
23309
23310
23311/*********
23312* ../app/js/menu/addwindow.js
23313**********/
23314sofia.config = $.extend(sofia.config, {
23315	windowTypesOrder: []
23316});
23317
23318
23319
23320var AddWindowButton = function(node) {
23321
23322	var buttonMenu = $('#main-menu-windows-list');
23323
23324
23325	// create window buttons from window Types
23326	var windowTools = [];
23327	
23328	if (sofia.config.windowTypesOrder.length > 0 ) {
23329		
23330		
23331		for (var i=0, il=sofia.config.windowTypesOrder.length; i<il; i++) {
23332			var windowTypeName = sofia.config.windowTypesOrder[i],
23333				winType = sofia.windowTypes.filter(function(wt) { return wt.className == windowTypeName});
23334				
23335			if (winType.length > 0) {
23336				winType = winType[0];
23337				
23338				windowTools.push({
23339					type: winType.className,
23340					label: winType.param,
23341					data: winType.init
23342				});
23343			}
23344		}		
23345		
23346		
23347	} else {
23348	
23349		// build default array
23350		
23351		for (var i=0, il=sofia.windowTypes.length; i<il; i++) {
23352			var winType = sofia.windowTypes[i];
23353	
23354			windowTools.push({
23355				type: winType.className,
23356				label: winType.param,
23357				data: winType.init
23358			});
23359		}
23360	}
23361
23362	// add buttons
23363	for (var x in windowTools) {
23364		var tool = windowTools[x];
23365		// ADD Button
23366		var addButton = $('<div class="main-menu-item window-add i18n" id="add-' + tool.type + '" data-i18n="[html]windows.' + tool.label + '.label"></div>')
23367					.appendTo(buttonMenu)
23368					.data('init', tool);
23369	}
23370
23371	buttonMenu.on('click', '.window-add', function(e) {
23372		//buttonMenu.hide();
23373
23374		var label = $(this),
23375			settings = label.data('init');
23376
23377		// when starting a bible or commentary window, try to match it up with the others
23378		if (settings.type == 'BibleWindow' || settings.type == 'CommentaryWindow') {
23379
23380			// get location from first window
23381			var
23382				firstBCWindow = (sofia.app.windowManager) ?
23383									sofia.app.windowManager.getWindows().filter(function(w) { return w.className == 'BibleWindow' || w.className == 'CommentaryWindow'})[0] :
23384									null,
23385				currentData = (firstBCWindow != null) ? firstBCWindow.getData() : null;
23386
23387
23388			// if no location, then use the defaults from config
23389			if (currentData != null) {
23390				console.log('first window', currentData);
23391				settings.data.fragmentid = currentData.fragmentid;
23392				settings.data.sectionid = currentData.sectionid;
23393			} else {
23394				var fragmentid = (typeof sofia.config.newWindowFragmentid != 'undefined') ? sofia.config.newWindowFragmentid : 'JN1_1',
23395					sectionid = fragmentid.split('_')[0];
23396
23397				settings.data.fragmentid = fragmentid;
23398				settings.data.sectionid = sectionid;
23399			}
23400
23401			console.log('new window', settings);
23402		}
23403
23404		PlaceKeeper.storePlace();
23405		sofia.app.windowManager.add(settings.type, settings.data);
23406		PlaceKeeper.restorePlace();
23407
23408		if (sofia.analytics) {
23409			sofia.analytics.record('createwindow', settings.type);
23410		}
23411
23412		//windowManager.trigger('settingschange',{});
23413	});
23414
23415	return addButton;
23416
23417};
23418sofia.menuComponents.push('AddWindowButton');
23419
23420
23421
23422/*********
23423* ../app/js/menu/config.js
23424**********/
23425
23426var ConfigButton = function(node) {
23427	var
23428
23429		container = $('.windows-container'),
23430		body = $(document.body),
23431		win = $(window),
23432
23433		configButton = $('<div class="main-menu-item image-config i18n" data-i18n="[html]menu.labels.settings"></div>')
23434					.appendTo( $('#main-menu-features') )
23435					.on('click', buttonClick),
23436
23437		modalOverlay = $('<div class="modal-overlay"></div>')
23438							.appendTo( body )
23439							.hide(),
23440
23441		configWindow = new MovableWindow(300, 380, i18n.t('menu.labels.settings'), 'config-window');
23442
23443		configWindow.body.html(
23444					'<div id="main-config-box">' +
23445						'<div class="config-section" id="config-type">' +
23446							'<span class="config-header i18n" data-i18n="[html]menu.config.font"></span>' +
23447							'<div class="config-body"></div>' +
23448							'<div class="clear"></div>' +
23449						'</div>' +
23450						/*
23451						'<div class="config-section" id="config-toggles">' +
23452							'<span class="config-header i18n" data-i18n="[html]menu.config.settings"></span>' +
23453							'<div class="config-body"></div>' +
23454							'<div class="clear"></div>' +
23455						'</div>' +
23456						*/
23457						'<div class="config-section" id="config-tools">' +
23458							'<span class="config-header i18n" data-i18n="[html]menu.config.tools"></span>' +
23459							'<div class="config-body"></div>' +
23460							'<div class="clear"></div>' +
23461						'</div>' +
23462					'</div>'
23463					);
23464
23465	function showConfig() {
23466		var winWidth = win.width(),
23467			winHeight = win.height();
23468
23469		modalOverlay
23470			.width( winWidth )
23471			.height( winHeight )
23472			.show();
23473
23474		configWindow
23475			.show()
23476			.center();
23477
23478		container.addClass('blur');
23479
23480		$('#main-menu-dropdown').hide();
23481		$('#main-menu-button').removeClass('active');
23482	}
23483
23484	function hideConfig() {
23485		configWindow.hide();
23486		modalOverlay.hide();
23487		container.removeClass('blur');
23488	}
23489
23490	modalOverlay.on('click', function() {
23491		hideConfig();
23492	});
23493
23494	configWindow.closeButton.on('click', function() {
23495		hideConfig();
23496	});
23497
23498	function buttonClick(e) {
23499
23500		e.preventDefault();
23501
23502		if (configWindow.container.is(':visible')) {
23503
23504			hideConfig();
23505
23506		} else {
23507
23508			showConfig();
23509
23510		}
23511
23512		return false;
23513	}
23514};
23515sofia.menuComponents.push('ConfigButton');
23516
23517
23518
23519/*********
23520* ../app/js/menu/about.js
23521**********/
23522
23523var AboutScreen = function(node) {
23524	var
23525		container = $('.windows-container'),
23526		body = $(document.body),
23527		win = $(window),
23528
23529		aboutButton = $('<div class="main-menu-item about-logo i18n" data-i18n="[html]menu.labels.about">About</div>')
23530					.appendTo( $('#main-menu-features') )
23531					.on('click', aboutClick),
23532
23533		modalOverlay = $('<div class="modal-overlay"></div>')
23534							.appendTo( body )
23535							.hide(),
23536		isAboutLoaded = false,
23537		aboutWindow = new MovableWindow(500, 250, i18n.t('menu.labels.about'));
23538
23539	aboutWindow.body
23540					.css({padding: '20px'});
23541
23542	aboutWindow.title.addClass('i18n').attr('data-i18n','[html]menu.labels.about');
23543
23544
23545	function hideAbout() {
23546		aboutWindow.hide();
23547		modalOverlay.hide();
23548		container.removeClass('blur');
23549	}
23550
23551	modalOverlay.on('click', function() {
23552		hideAbout();
23553	});
23554
23555	aboutWindow.closeButton.on('click', function() {
23556		hideAbout();
23557	});
23558
23559	function aboutClick() {
23560		if (aboutWindow.container.is(':visible')) {
23561			hideAbout();
23562		} else {
23563
23564			$('#main-menu-dropdown').hide();
23565
23566			var winWidth = win.width(),
23567				winHeight = win.height();
23568
23569
23570			container.addClass('blur');
23571
23572			aboutWindow
23573				.size(.8*winWidth, 0.7*winHeight)
23574				.show()
23575				.center();
23576
23577			modalOverlay
23578				.width( winWidth )
23579				.height( winHeight )
23580				.show();
23581
23582
23583			if (!isAboutLoaded) {
23584
23585				aboutWindow.body.addClass('loading-indicator');
23586
23587				// assume a local file first
23588				$.ajax({
23589					url: sofia.config.aboutPagePath,
23590					dataType: 'html',
23591					success: function(data) {
23592						aboutWindow.body.removeClass('loading-indicator');
23593
23594						isAboutLoaded = true;
23595
23596						showAbout(data, sofia.config.aboutPagePath);
23597					},
23598					error: function() {
23599
23600						if (sofia.config.baseContentUrl != '') {
23601
23602							console.log("No local about.html");
23603
23604							// this one will go through the CDN
23605							sofia.ajax({
23606								url: sofia.config.aboutPagePath,
23607								dataType: 'text',
23608								success: function(data) {
23609
23610									console.log('Success: CDN about.html', data.indexOf('<html'));
23611
23612									aboutWindow.body.removeClass('loading-indicator');
23613
23614									isAboutLoaded = true;
23615
23616									showAbout(data, sofia.config.baseContentUrl + sofia.config.aboutPagePath);
23617								},
23618								error: function() {
23619
23620									// error
23621									console.log("Can't find a about.html");
23622
23623								}
23624							});
23625						} else {
23626
23627							console.log("No local about.html, no CDN to check");
23628
23629						}
23630					}
23631				});
23632			}
23633		}
23634	}
23635
23636	function showAbout(data, url) {
23637
23638		if (data.indexOf('<html') > -1) {
23639
23640			aboutWindow.body.html('<iframe style="border: 0;" src="' + url + '"></iframe>');
23641			aboutWindow.body.css({padding: 2});
23642
23643			var iframe = aboutWindow.body.find('iframe');
23644
23645			iframe.css({
23646				width: aboutWindow.body.width(),
23647				height: aboutWindow.body.height() - 5
23648			});
23649
23650		} else {
23651
23652			aboutWindow.body.html(data);
23653		}
23654	}
23655
23656	return aboutButton;
23657};
23658sofia.menuComponents.push('AboutScreen');
23659
23660
23661
23662/*********
23663* ../app/js/menu/feedback.js
23664**********/
23665sofia.config = $.extend(sofia.config, {
23666
23667	enableFeedback: false,
23668
23669	feedbackUrl: ''
23670
23671});
23672
23673
23674
23675var Feedback = function(node) {
23676
23677	if (!sofia.config.enableFeedback|| sofia.config.feedbackUrl == '') {
23678		return;
23679	}
23680
23681
23682	var
23683		container = $('.windows-container'),
23684		body = $(document.body),
23685		win = $(window),
23686
23687		feedbackButton = $('<div class="main-menu-item feedback-logo i18n" data-i18n="[html]menu.labels.feedback">Feedback</div>')
23688					.appendTo( $('#main-menu-features') )
23689					.on('click', clickFeedback),
23690
23691		modalOverlay = $('<div class="modal-overlay"></div>')
23692							.appendTo( body )
23693							.hide(),
23694
23695		feedbackWindow = new MovableWindow(Math.min( win.width(), 500), 300, i18n.t('menu.labels.feedback') ),
23696
23697		name = $('<input type="text" id="feedback-from" class="app-input i18n" data-i18n="[placeholder]menu.feedback.name"  />')
23698						.appendTo(feedbackWindow.body),
23699
23700		email = $('<input type="email" id="feedback-email" class="app-input i18n" data-i18n="[placeholder]menu.feedback.email"  />')
23701						.appendTo(feedbackWindow.body),
23702
23703		subject = $('<select id="feedback-subject" class="app-list" >' +
23704						'<option class="i18n" data-i18n="[html]menu.feedback.feature"></option>' +
23705						'<option class="i18n" data-i18n="[html]menu.feedback.bug"></option>' +
23706						'<option class="i18n" data-i18n="[html]menu.feedback.other"></option>' +
23707					'</select>'
23708					)
23709						.appendTo(feedbackWindow.body),
23710
23711		comments = $('<textarea id="feedback-comment" class="app-input i18n" data-i18n="[placeholder]menu.feedback.comments" />')
23712						.appendTo(feedbackWindow.body),
23713
23714		send = $('<input type="button" id="feedback-submit" class="app-button i18n" data-i18n="[value]menu.feedback.send" />')
23715						.appendTo(feedbackWindow.body),
23716
23717		message = $('<div class="feedback-message i18n" data-i18n="[placeholder]menu.feedback.thankyou"></div>')
23718						.appendTo(feedbackWindow.body)
23719						.hide()
23720
23721		;
23722
23723
23724	feedbackWindow.body.addClass("feedback-body");
23725	feedbackWindow.title.addClass('i18n').attr('data-i18n','[html]menu.labels.feedback');
23726
23727	modalOverlay.on('click', function() {
23728		hideFeedback();
23729	});
23730
23731	feedbackWindow.closeButton.on('click', function() {
23732		hideFeedback();
23733	});
23734
23735	function hideFeedback() {
23736		feedbackWindow.hide();
23737		modalOverlay.hide();
23738		container.removeClass('blur');
23739	}
23740
23741	function clickFeedback() {
23742		if (feedbackWindow.container.is(':visible')) {
23743			hideFeedback();
23744		} else {
23745			feedbackWindow.show();
23746			feedbackWindow.size( Math.min(500, win.width()-40), 300 );
23747			feedbackWindow.center();
23748
23749			message.hide();
23750			feedbackWindow.body.find('input,textarea,select').show();
23751
23752			$('#main-menu-dropdown').hide();
23753
23754			var winWidth = win.width(),
23755				winHeight = win.height();
23756
23757			container.addClass('blur');
23758			modalOverlay
23759				.width( winWidth )
23760				.height( winHeight )
23761				.show();
23762		}
23763	}
23764
23765	/// OPERATE FEEDBACK
23766	send.on('click', function() {
23767
23768		if (validateForm() ) {
23769
23770			$.ajax({
23771				url: sofia.config.baseContentUrl + sofia.config.feedbackUrl,
23772				dataType: (sofia.config.baseContentUrl.indexOf('http') > -1 ? 'jsonp' : 'json'),
23773				data: {
23774					name: name.val(),
23775					email: email.val(),
23776					subject: subject.val(),
23777					comments: comments.val()
23778				},
23779				success: function() {
23780
23781					message.show();
23782					feedbackWindow.body.find('input,textarea,select').hide();
23783
23784					setTimeout(function() {
23785						hideFeedback();
23786					}, 500);
23787
23788				},
23789				error: function() {
23790					console.log('error', arguments);
23791				}
23792
23793			})
23794
23795		}
23796
23797
23798	});
23799
23800	function validateForm() {
23801		var valid = true;
23802
23803		if (name.val() === '') {
23804			name.addClass('invalid');
23805			valid = false;
23806		} else {
23807			name.removeClass('invalid');
23808		}
23809
23810		if (email.val() === '' || !email.val().match(/.+@.+\..+/gi)) {
23811			email.addClass('invalid');
23812			valid = false;
23813		} else {
23814			email.removeClass('invalid');
23815		}
23816
23817
23818		if (comments.val() === '') {
23819			comments.addClass('invalid');
23820			valid = false;
23821		} else {
23822			comments.removeClass('invalid');
23823		}
23824
23825		return valid;
23826	}
23827
23828
23829
23830	return feedbackButton;
23831};
23832sofia.menuComponents.push('Feedback');
23833
23834
23835
23836/*********
23837* ../app/js/menu/restore.js
23838**********/
23839sofia.config = $.extend(sofia.config, {
23840	enableRestore: false
23841});
23842
23843
23844var RestoreButton = function(node) {
23845
23846	if (!sofia.config.enableRestore) {
23847		return;
23848	}
23849
23850	var buttonMenu = $('#main-menu-windows-list');
23851
23852	var restoreButton = $('<span class="window-reset i18n" data-i18n="[html]menu.reset">Reset</span>')
23853		.on('click', function() {
23854
23855			if (typeof sofia.config.windows != 'undefined') {
23856
23857				var querystring = [];
23858
23859				for (var i=0, il=sofia.config.windows.length; i<il; i++) {
23860					var win = sofia.config.windows[i];
23861
23862					// type
23863					querystring.push('win'+ (i+1) + '=' + win.type);
23864
23865					// data
23866					var keys = Object.keys(win.data);
23867					for (var k in keys) {
23868						querystring.push(keys[k] + (i+1) + '=' + win.data[keys[k]]);
23869					}
23870				}
23871
23872				window.location.href = window.location.pathname + '?' + querystring.join('&');
23873
23874
23875
23876			} else {
23877				window.location.reload();
23878			}
23879		})
23880		.appendTo(buttonMenu);
23881
23882
23883	return restoreButton;
23884
23885};
23886sofia.menuComponents.push('RestoreButton');
23887
23888
23889
23890/*********
23891* ../app/js/menu/navigation.js
23892**********/
23893sofia.config = $.extend(sofia.config, {
23894	enableNavigationButtons: true
23895});
23896
23897
23898
23899var NavigationButtons = function(node) {
23900
23901	if (!sofia.config.enableNavigationButtons) {
23902		return;
23903	}
23904
23905	$('html').addClass('supports-fullscreen');
23906
23907	var
23908		forwardButton = $('<div id="main-forward-button" class="inactive"></div>')
23909			.appendTo( $('.windows-header') )
23910			.on('click', forward),
23911		backButton = $('<div id="main-back-button" class="inactive"></div>')
23912			.appendTo( $('.windows-header') )
23913			.on('click', back),
23914
23915		compactBackButton = $('<div id="compact-back-button"><span id="compact-back-button-label"></span></div>')
23916			.appendTo( $('body') )
23917			.on('click', back),
23918
23919		isFullscreen = false;
23920
23921	function back() {
23922		TextNavigation.back();
23923		//window.history.go(-1);
23924	}
23925
23926	function forward() {
23927		TextNavigation.forward();
23928		//window.history.go(1);
23929	}
23930
23931	/*
23932	$(window).on('popstate', function(e) {
23933		console.log('fwrd/back pop', e);
23934		updateButtonStates();
23935	});
23936	*/
23937
23938	TextNavigation.on('locationchange', function(e) {
23939		console.log('fwrd/back locationchange', e);
23940		updateButtonStates();
23941	});
23942
23943	function updateButtonStates() {
23944
23945		var locations = TextNavigation.getLocations(),
23946			locationIndex = TextNavigation.getLocationIndex();
23947
23948		// BACK
23949		if (locationIndex > 0) {
23950			backButton.removeClass('inactive');
23951
23952
23953			// setup mobile/compact button
23954			var lastRef = new bible.Reference(locations[locations.length-2]);
23955			compactBackButton.find('#compact-back-button-label').html( lastRef.toString() );
23956
23957			compactBackButton
23958				.addClass('active')
23959				.css({'display':''});
23960
23961
23962			if ($('body').hasClass('compact-ui')) {
23963
23964				startCompactTimer();
23965
23966			}
23967
23968
23969		} else {
23970			backButton.addClass('inactive');
23971
23972			compactBackButton.removeClass('active');
23973		}
23974
23975		// FORWARD
23976		if (locationIndex < locations.length-1) {
23977			forwardButton.removeClass('inactive');
23978		} else {
23979			forwardButton.addClass('inactive');
23980		}
23981	}
23982
23983	var compactTimer = null;
23984	function startCompactTimer() {
23985		clearCompactTimer();
23986
23987		compactTimer = setTimeout(hideCompactTimer, 5000);
23988	}
23989
23990	function clearCompactTimer() {
23991		if (compactTimer != null) {
23992			clearTimeout(compactTimer);
23993		}
23994	}
23995
23996	function hideCompactTimer() {
23997		if (compactBackButton.is(':visible')) {
23998			compactBackButton.fadeOut();
23999		}
24000	}
24001
24002
24003
24004	updateButtonStates();
24005
24006
24007	return null;
24008};
24009sofia.menuComponents.push('NavigationButtons');
24010
24011
24012
24013/*********
24014* ../app/js/menu/config-fontsize.js
24015**********/
24016sofia.config = $.extend(sofia.config, {
24017
24018	enableFontSizeSelector: true,
24019
24020	fontSizeMin: 14,
24021	fontSizeMax: 28,
24022	fontSizeStep: 2,
24023	fontSizeDefault: 18
24024
24025});
24026
24027
24028var FontSizeSettings = function(node) {
24029
24030	// generate font sizes
24031	var styleCode = '';
24032	for (var size = sofia.config.fontSizeMin; size <= sofia.config.fontSizeMax; size += sofia.config.fontSizeStep) {
24033		styleCode += '.config-font-size-' + size.toString() + ' .reading-text { font-size: ' + size.toString() + 'px; }';
24034	}
24035	$('<style>' + styleCode + '</style>').appendTo( $('head') );
24036
24037
24038	if (!sofia.config.enableFontSizeSelector) {
24039
24040		setFontSize(sofia.config.fontSizeDefault);
24041
24042		return;
24043	}
24044
24045	var
24046		body = $('#config-type .config-body'),
24047		fontSizeKey = 'config-font-size',
24048		defaultFontSizeSetting = {"fontSize": sofia.config.fontSizeDefault},
24049		fontSizeSetting = AppSettings.getValue(fontSizeKey, defaultFontSizeSetting);
24050
24051	$('<table id="font-size-table"><tr><td><span style="font-size:' + sofia.config.fontSizeMin + 'px">A</span><td style="width:100%"></td><td><span style="font-size:' + sofia.config.fontSizeMax + 'px">A</span></td></tr></table>')
24052		.appendTo(body);
24053
24054	// HTML5 range control (IE10+, FF35+)
24055	$('<input type="range" min="' + sofia.config.fontSizeMin + '" max="' + sofia.config.fontSizeMax + '" step="' + sofia.config.fontSizeStep + '" value="' + fontSizeSetting.fontSize + '" style="width: 100%;" />')
24056		.appendTo(body.find('td:eq(1)') )
24057		.on('change input', function() {
24058
24059			console.log( $(this).val() );
24060			setFontSize( $(this).val() );
24061
24062		});
24063
24064	setFontSize(fontSizeSetting.fontSize);
24065
24066	function setFontSize(newFontSize) {
24067
24068
24069		var	body = $('body');
24070
24071		PlaceKeeper.storePlace();
24072
24073		// remove all others
24074		for (var size = sofia.config.fontSizeMin; size <= sofia.config.fontSizeMax; size += sofia.config.fontSizeStep) {
24075
24076			var className = 'config-font-size-' + size;
24077
24078			body.removeClass(className);
24079		}
24080
24081		body.addClass('config-font-size-' + newFontSize);
24082
24083		AppSettings.setValue(fontSizeKey, {fontSize: newFontSize});
24084
24085
24086		if (sofia.analytics) {
24087			sofia.analytics.record('setting', 'fontsize', newFontSize);
24088		}
24089
24090		PlaceKeeper.restorePlace();
24091	}
24092
24093};
24094
24095sofia.menuComponents.push('FontSizeSettings');
24096
24097
24098
24099/*********
24100* ../app/js/menu/config-fontfamily.js
24101**********/
24102sofia.config = $.extend(sofia.config, {
24103
24104	enableFontFamilySelector: true,
24105
24106	fontFamilyStacks: {
24107		'Cambria': 'Cambria, Georgia, serif',
24108		'Palatino': 'Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif',
24109		//'Georgia': 'Georgia, Times, "Times New Roman", serif',
24110		'Libertine': 'Libertine', // Serif family but does not need fallback because we're loding it as a webfont if not present on system
24111		'Helvetica': '"Helvetica Neue", Helvetica, Arial, sans-serif',
24112		'Trebuchet': '"Trebuchet MS", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Tahoma, sans-serif'
24113	}
24114
24115});
24116
24117
24118
24119var FontFamilySettings = function(node) {
24120
24121	var
24122		body = $('#config-type .config-body'),
24123		fontFamilyStackNames = Object.keys(sofia.config.fontFamilyStacks), // ['Cambria', 'Helvetica', 'Baskerville', 'Times', 'Libertine'],
24124		defaultFontSetting = {"fontName": fontFamilyStackNames[0]},
24125		fontFamilyKey = 'config-font-family',
24126		fontFamilySetting = AppSettings.getValue(fontFamilyKey, defaultFontSetting),
24127		fontSettingHtml = '',
24128		fontFamilyStyle = '';
24129
24130	//
24131	for(var i=0, il=fontFamilyStackNames.length; i<il; i++) {
24132		var fontStackName = fontFamilyStackNames[i],
24133			fontStackValue = sofia.config.fontFamilyStacks[fontStackName];
24134
24135		fontSettingHtml +=
24136			'<label id="config-font-family-' + fontStackName + '" class="config-font-family" title="' + fontStackName + '" >' +
24137				'<input type="radio" id="config-font-family-' + fontStackName + '-value" name="config-font-family" value="' + fontStackName + '" />' +
24138				'Aa' +
24139			'</label>';
24140
24141		fontFamilyStyle +=
24142				'#config-font-family-' + fontStackName + ', ' +
24143				'.config-font-family-' + fontStackName + ' .reading-text,' +
24144				'.config-font-family-' + fontStackName + ' #font-size-table {' +
24145				'  font-family: ' + fontStackValue + ';' +
24146				'}';
24147
24148	}
24149
24150	$('<style>' + fontFamilyStyle + '</style>').appendTo( $('head') );
24151
24152	if (!sofia.config.enableFontFamilySelector) {
24153		setFontFamily(defaultFontSetting.fontName);
24154		return;
24155	}
24156
24157
24158	$(fontSettingHtml).appendTo(body);
24159
24160
24161	function setFontFamily(newFontStackName) {
24162
24163		var body = $('body');
24164
24165		PlaceKeeper.storePlace();
24166
24167		// remove all others
24168		for(var i=0, il=fontFamilyStackNames.length; i<il; i++) {
24169			var fontStackName = fontFamilyStackNames[i],
24170				className = 'config-font-family-' + fontStackName;
24171
24172			body.removeClass(className);
24173		}
24174
24175		$(body).addClass('config-font-family-' + newFontStackName);
24176
24177		AppSettings.setValue(fontFamilyKey, {fontName: newFontStackName});
24178
24179		if (sofia.analytics) {
24180			sofia.analytics.record('setting', 'fontfamily', newFontStackName);
24181		}
24182
24183		PlaceKeeper.restorePlace();
24184
24185	}
24186
24187	// handle clicks
24188	body.on('change', 'input[name=config-font-family]', function() {
24189		var radio = $(this),
24190			newFontFamilyValue = radio.val();
24191
24192		setFontFamily(newFontFamilyValue);
24193	});
24194
24195	// set default
24196	body.find('#config-font-family-' + fontFamilySetting.fontName + '-value').trigger('click');
24197};
24198
24199sofia.menuComponents.push('FontFamilySettings');
24200
24201
24202
24203/*********
24204* ../app/js/menu/config-theme.js
24205**********/
24206sofia.config = $.extend(sofia.config, {
24207
24208	enableThemeSelector: true
24209
24210});
24211
24212var ThemeSetting = function(node) {
24213
24214	if (!sofia.config.enableThemeSelector) {
24215		return;
24216	}
24217
24218	var
24219		body = $('#config-type .config-body'),
24220		themesBlock = $('<div id="config-themes"></div>')
24221			.appendTo(body),
24222		themeNames = ['default','sepia','dark'],
24223		defaultThemeSetting = {"themeName": themeNames[0]},
24224		themeKey = 'config-theme',
24225		themeSetting = AppSettings.getValue(themeKey, defaultThemeSetting);
24226
24227
24228	for(var i=0, il=themeNames.length; i<il; i++) {
24229		var themeName = themeNames[i];
24230
24231		$('<span id="config-theme-' + themeName + '" class="config-theme-toggle i18n" data-i18n="[html]menu.themes.' + themeName + '" data-themename="' + themeName + '">' + themeName + '</span>')
24232			.appendTo(themesBlock);
24233	}
24234
24235	// handle clicks
24236	themesBlock.on('click', '.config-theme-toggle', function() {
24237		var span = $(this),
24238			selectedTheme = span.attr('data-themename'),
24239			selectedThemeClass = 'theme-' + selectedTheme,
24240			$body = $(document.body);
24241
24242		// remove all themes
24243		for(var i=0, il=themeNames.length; i<il; i++) {
24244			var themeClassName = 'theme-' + themeNames[i];
24245			$body.removeClass(themeClassName);
24246		}
24247
24248		$body.addClass(selectedThemeClass);
24249
24250		span
24251			.addClass('config-theme-toggle-selected')
24252			.siblings()
24253			.removeClass('config-theme-toggle-selected');
24254
24255
24256		AppSettings.setValue(themeKey, {themeName: selectedTheme} );
24257	});
24258
24259	body.find('#config-theme-' + themeSetting.themeName + '').trigger('click');
24260};
24261
24262sofia.menuComponents.push('ThemeSetting');
24263
24264
24265
24266/*********
24267* ../app/js/menu/config-language.js
24268**********/
24269sofia.config = $.extend(sofia.config, {
24270
24271	enableLanguageSelector: true,
24272
24273	languageSelectorFallbackLang: 'en'
24274
24275});
24276
24277
24278var LanguageSetting = function(node) {
24279
24280	if (!sofia.config.enableLanguageSelector) {
24281		return;
24282	}
24283
24284	var
24285		body = $('#config-tools .config-body'),
24286		list = $('<select id="config-language" class="app-list"></select>')
24287					.appendTo(body),
24288		langKeys = Object.keys(sofia.resources);
24289
24290	body.append($('<div class="clear"></div>'));
24291
24292	// make sure English isn't first!
24293	langKeys.sort();
24294
24295	for(var i=0, il=langKeys.length; i<il; i++) {
24296		var langKey = langKeys[i],
24297			langName = sofia.resources[langKey].translation.name;
24298
24299		$('<option value="' + langKey + '">' + langName + '</option>')
24300			.appendTo(list);
24301	}
24302
24303	// handle clicks
24304	list.on('change', function() {
24305		var newLang = list.val();
24306
24307		i18n.setLng(newLang);
24308		$('.i18n').i18n();
24309
24310		localizeLanguages();
24311	});
24312
24313	function localizeLanguages() {
24314		var usersLanguage = i18n.lng();
24315
24316		// go through options and add new info
24317		list.children('option').each(function() {
24318			var option = $(this),
24319				langValue = option.attr('value');
24320				resourceData = sofia.resources[langValue].translation,
24321				name = resourceData.name,
24322				fallbackName = resourceData.names[sofia.config.languageSelectorFallbackLang],
24323				localizedName = resourceData.names[usersLanguage],
24324				fullname = name;
24325
24326			// use the localized name if possible
24327			if (localizedName != null && typeof localizedName != 'undefined' && localizedName != fullname) {
24328				fullname += ' (' + localizedName + ')';
24329
24330			// fallback to english
24331			} else if (fallbackName != null && typeof fallbackName != 'undefined' && fullname != fallbackName) {
24332				fullname += ' (' + fallbackName + ')';
24333			}
24334
24335			option.html(fullname);
24336
24337		});
24338	}
24339
24340	list[0].localizeLanguages = localizeLanguages;
24341};
24342
24343sofia.menuComponents.push('LanguageSetting');
24344
24345
24346
24347/*********
24348* ../app/js/menu/config-toggles.js
24349**********/
24350sofia.config = $.extend(sofia.config, {
24351
24352	enableSettingToggles: true,
24353	settingToggleNames: ['Chapters', 'Verses', 'Titles', 'Notes', 'Words of Christ', 'Media', 'Justify'],
24354	settingToggleDefaults: [true, true, true, true, true, true, false]
24355
24356});
24357
24358
24359var ConfigToggles = function(node) {
24360
24361	var body = $('#config-type .config-body'),
24362		toggleNames = sofia.config.settingToggleNames,
24363		toggleDefaults = sofia.config.settingToggleDefaults,
24364		prefix = 'config-toggle-';
24365
24366	if (!sofia.config.enableSettingToggles) {
24367		for(var i=0, il=toggleNames.length; i<il; i++) {
24368			var toggleName = toggleNames[i]
24369			setToggle(toggleName, toggleDefaults[i]);
24370		}
24371
24372		return;
24373	}
24374
24375	sofia.globals.createToggle = function(toggleName, defaultValue) {
24376
24377		var
24378			toggleId = toggleName.replace(/\s/gi, '').toLowerCase(),
24379			toggleDefaultSetting = {checked: defaultValue},
24380			toggleSetting = AppSettings.getValue(toggleId, toggleDefaultSetting),
24381			toggle = $('<div id="config-toggle-' + toggleId + '" class="config-toggle">' +
24382						'<input id="config-toggle-' + toggleId + '-input" type="checkbox" value="' + toggleId + '" />' +
24383						'<label for="config-toggle-' + toggleId + '-input" title="' + toggleName + '">' + toggleName + '</label>' +
24384					'</div>')
24385						.appendTo(body);
24386
24387		toggle
24388			.find('input')
24389				.on('click', function() {
24390					var cb = $(this),
24391						checked = cb.is(':checked'),
24392						value = cb.val();
24393
24394					setToggle(value, checked);
24395
24396				});
24397
24398		setToggle(toggleId, toggleSetting.checked);
24399
24400	}
24401
24402	function setToggle(toggleId, checked) {
24403
24404		PlaceKeeper.storePlace();
24405
24406		var toggle = $('#config-toggle-' + toggleId),
24407			body = $('body'),
24408			onClass = 'toggle-' + toggleId + '-on',
24409			offClass = 'toggle-' + toggleId + '-off';
24410
24411		if (checked == true || checked == 'true') {
24412			toggle.addClass('toggle-on');
24413			toggle.find('input').prop('checked', true);
24414			body
24415				.addClass(onClass)
24416				.removeClass(offClass);
24417
24418		} else {
24419			toggle.removeClass('toggle-on');
24420			toggle.find('input').prop('checked', false);
24421			body
24422				.removeClass(onClass)
24423				.addClass(offClass);
24424		}
24425
24426		PlaceKeeper.restorePlace();
24427
24428
24429		if (sofia.analytics) {
24430			sofia.analytics.record('setting', toggleId, checked);
24431		}
24432
24433		//console.log('setValue', toggleId, checked);
24434		AppSettings.setValue(toggleId, {checked: checked});
24435
24436	}
24437
24438
24439	for(var i=0, il=toggleNames.length; i<il; i++) {
24440		var toggleName = toggleNames[i]
24441		sofia.globals.createToggle(toggleName, toggleDefaults[i]);
24442	}
24443
24444};
24445
24446sofia.menuComponents.push('ConfigToggles');
24447
24448
24449
24450/*********
24451* ../app/js/menu/config-url.js
24452**********/
24453sofia.config = $.extend(sofia.config, {
24454
24455	enableUrlCopier: true
24456
24457});
24458
24459
24460var ConfigUrl = function(node) {
24461
24462	if (!sofia.config.enableUrlCopier || location.protocol == 'file:') {
24463		return;
24464	}
24465
24466	var // body = $('#config-tools .config-body'),
24467		body = $('#main-menu-features'),
24468		urlBox =
24469				$('<div id="config-global-url">' +
24470						//'<span class="config-header">URL</span>' +
24471						'<span data-clipboard-action="copy" data-clipboard-target="#config-global-url-input"></span>' +
24472						'<input type="text" id="config-global-url-input" />' +
24473						//'<div ></div>' +
24474					'</div>'),
24475		linkButton = urlBox.find('span'),
24476		urlInput = urlBox.find('input'),
24477		urlDiv = urlBox.find('div'),
24478		clickables = [linkButton, urlInput, urlDiv];
24479
24480	body.after(urlBox);
24481
24482	var urlTimer = new Timer(updateUrl, 500);
24483
24484	setTimeout(function() {
24485		sofia.app.windowManager.on('settingschange', function(e) {
24486
24487			// title to show active window's position
24488			urlTimer.start();
24489
24490		});
24491
24492		updateUrl();
24493	}, 1000);
24494
24495
24496	var clipboard1 = new Clipboard(linkButton[0]);
24497	var clipboard2 = new Clipboard('#config-global-url-input');	
24498
24499	/*
24500	if (Detection.hasFlash) {
24501		ZeroClipboard.config( { moviePath: sofia.config.baseContentUrl + 'build/ZeroClipboard.swf' } );
24502		for (var c in clickables) {
24503			var el = clickables[c];
24504
24505			var client = new ZeroClipboard(el);
24506			client.on( 'dataRequested', function (client, args) {
24507
24508				updateUrl();
24509
24510				client.setText( urlInput.val() );
24511
24512				urlInput.focus();
24513				urlInput.select();
24514
24515				console.log('flash clipboard clicked');
24516			});
24517		}
24518	}
24519	*/
24520
24521	urlInput.on('click', function() {
24522		updateUrl();
24523
24524		urlInput.focus();
24525		urlInput.select();
24526
24527	});
24528
24529	linkButton.on('click', function() {
24530		updateUrl();
24531
24532		urlInput.focus();
24533		urlInput.select();
24534	});
24535
24536
24537	function updateUrl() {
24538			// get settings from all windows
24539		var windowSettings = sofia.app.windowManager.getSettings(),
24540			existingParams = stringUtility.parseQuerystring();
24541			newParams = {},
24542			mergedParams = {},
24543			mergedArray = [];
24544
24545		for (var i=0, il=windowSettings.length; i<il; i++) {
24546			// get window settings
24547			var winSettings = windowSettings[i];
24548
24549			if (winSettings.data == null || typeof winSettings.data.params == 'undefined') {
24550				continue;
24551			}
24552
24553			// get window type info
24554			var winTypeName = winSettings.data.params.win,
24555				winTypeInfo = sofia.windowTypes.filter(function(winType) { return winType.param == winTypeName; })[0];
24556
24557			if (typeof winTypeInfo == 'undefined') {
24558				console.log('Cannot find', winTypeName, winTypeInfo, winSettings.data.params, winSettings.data.params.win);
24559				continue;
24560			}
24561
24562			// go through the params object
24563			// params: {'win': 'bible', 'fragmentid': 'JN1_1'}
24564			// shorten with paramKeys
24565			for (var paramName in winSettings.data.params) {
24566				var paramData = winSettings.data.params[paramName],
24567					paramShort = paramName == 'win' ? 'w' : winTypeInfo.paramKeys[paramName];
24568
24569				newParams[ paramShort + (i+1) ] = paramData;
24570				//console.log(paramName, paramData);
24571			}
24572
24573		}
24574
24575		// keep all parameters that aren't windowed ones
24576		for (var param in existingParams) {
24577			// check if it's blank or if it's a previously existing parameter (say the third window, and now there is only 2)
24578			if (param != '' && param.indexOf('win') != 0  && param.indexOf('textid') != 0  && param.indexOf('searchtext') != 0  && param.indexOf('fragmentid') != 0 ) {
24579				mergedParams[ param ] = existingParams[param];
24580			}
24581		}
24582
24583
24584		mergedParams = $.extend({}, mergedParams, newParams);
24585
24586		for (var param in mergedParams) {
24587			if (param != '' ) {
24588				mergedArray.push( param + '=' + encodeURIComponent( mergedParams[param] ) );
24589			}
24590		}
24591		//mergedArray.reverse();
24592
24593		url = location.protocol + '//' + location.host + location.pathname + '?' + mergedArray.join('&');
24594
24595		urlInput.val(url);
24596		urlDiv.html(url);
24597
24598		//console.log('URL', url);
24599	}
24600
24601
24602
24603	//base.height('300px');
24604};
24605
24606sofia.menuComponents.push('ConfigUrl');
24607
24608

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.