');
				overlay.html(highlight_box).append(lnk_span);
			}else{
				lnk_span.stop(true,true).show().removeClass('gp_hover');
			}
			highlight_box.stop(true,true).hide().css({'height':(loc.h+5),'width':(loc.w+4)});
			SpanPosition();
			//add the edit links
			edit_links = edit_links.clone(true)
				.removeClass('ExtraEditLink');
			//
			lnk_span.html('
')
				.append(edit_links);
			ResetMenu();
		}
		/**
		 * Reset Context Menu
		 *
		 */
		function ResetMenu(){
			fixed_pos = false;
			lnk_span
				.css({'left':'auto','top':0,'right':0,'position':'absolute'})
				.removeClass('gp_hover')
				.off('mouseenter touchstart')
				.one('mouseenter touchstart',function(){
					if( edit_area.hasClass('gp_no_overlay') ){
						return;
					}
					ShowMenu();
				});
		}
		/**
		 * Display the editable area options
		 *
		 */
		function ShowMenu(){
			lnk_span
				.addClass('gp_hover')
				.stop(true,true)
				.show();
			//show the overlay
			highlight_box.stop(true,true).fadeIn();
		}
		/**
		 * Left click to show menu
		 * This may not always work. Some template/user js may cancel event bubbling
		 * Clicking on links still works
		 *
		 */
		$gp.$doc.on('click.gp','.editable_area, #gp_edit_overlay',function(evt){
			if( ShowableMenu(evt) ){
				MenuPos(evt);
			}
		});
		/**
		 * Position link at cursor
		 *
		 */
		function MenuPos(evt){
			fixed_pos	= true;
			var left	= evt.pageX-$gp.$win.scrollLeft();
			var diff	= left + lnk_span.width() - $gp.$win.width();
			var top		= evt.pageY - $gp.$win.scrollTop();
			if( diff > 0 ){
				left -= diff;
			}
			lnk_span.show().stop(true,true).css({'top':top,'left':left,'right':'auto','position':'fixed'});
		}
		/**
		 * Return true if we can show the Typesetter context menu
		 *
		 */
		function ShowableMenu(evt){
			if( evt.ctrlKey || evt.altKey || evt.shiftKey ){
				return;
			}
			if( !edit_area || edit_area.hasClass('gp_editing') || edit_area.hasClass('gp_no_overlay') || !lnk_span ){
				return;
			}
			return true;
		}
	} /* end EditOutlines */
	function UIEffects(){
		if( !$('html').hasClass('admin_body') ){
			SimpleDrag('#simplepanel .toolbar, #simplepanel .toolbar a', '#simplepanel', 'fixed', function(newpos){
				gpui.tx = newpos.left;
				gpui.ty = newpos.top;
				$gp.SaveGPUI();
			},true);
		}
		//keep expanding areas within the viewable window
		$('.in_window').parent().on('mouseenter touchstart',function(){
			var $this = $(this);
			var panel = $this.children('.in_window').css({'right':'auto','left':'100%','top':0});
			window.setTimeout(function(){
				var pos = panel.offset();
				var right = pos.left + panel.width();
				var bottom = pos.top + panel.height();
				if( right > $gp.$win.width() + $gp.$win.scrollLeft() ){
					panel.css({'right':'100%','left':'auto'});
				}
				var winbottom = $gp.$win.height() + $gp.$win.scrollTop();
				if( bottom > winbottom ){
					var diff = winbottom +  - bottom - 10;
					panel.css({'top':diff});
				}
			},1);
		});
	}
	/**
	 * Reduce a list of titles by search criteria entered in gpsearch areas
	 *
	 */
	$(document).on('keyup','input.gpsearch',function(){
		var search = this.value.toLowerCase();
		$(this.form).find('.gp_scrolllist > div > *').each(function(){
			var $this = $(this);
			if( $this.text().toLowerCase().indexOf(search) == -1 ){
				$this.addClass('filtered');
			}else{
				$this.removeClass('filtered');
			}
		});
	});
	/**
	 * Configuration -> Settings
	 * Disable minifyjs when combinejs is unchecked
	 *
	 */
	function CheckCombineJs(){
		var checked = $('#admincontent_inner input[type="checkbox"][name="combinejs"]').prop("checked");
		$('#admincontent_inner input[type="checkbox"][name="minifyjs"]').prop("disabled", !checked);
	}
	$('#admincontent_inner input[type="checkbox"][name="combinejs"]').on('change', CheckCombineJs);
	CheckCombineJs();
	/**
	 * Modifier key names based on UI language and OS
	 *
	 */
	$gp.mod_keys = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ?
		{
			// Apple
			ctrlKey:	'˄ control',
			shiftKey:	'⇑ shift',
			altKey:		'⌥ option',
			metaKey:	'⌘ command'
		} :
		{
			// others
			ctrlKey:	gplang.ctrlKey,
			shiftKey:	'⇑ ' + gplang.shiftKey,
			altKey:		gplang.altKey,
			metaKey:	'Meta'
		};
	/**
	 * Get visual representation of modifier keys in a 
	 * @param array of keys, possible values: ctrlKey, shiftKey, altKey, metaKey
	 * @return html
	 *
	 */
	$gp.GetModkeys = function(mod_keys){
		if( !mod_keys.length ){
			return '';
		}
		var html = '';
		$.each(mod_keys, function(i, k){
			html += '' + $gp.mod_keys[k] + ' + ';
		});
		return '' + html + '';
	};
	/**
	 * Insert modifier keys pepresentation to an element
	 * @param stringa valid jQuery selector
	 * @param string 'before' or 'after''
	 * @param array of keys, modifier keys and regular key, e.g. ['ctrlKey','H'], optional
	 *
	 */
	$gp.InsertModkeys = function(selector, where, mod_keys){
		$(selector).siblings('.show_modkeys').remove();
		if( typeof(mod_keys) == 'undefined' ){
			// get from hideAdminUIcfg
			var mod_keys = hideAdminUIcfg.hotkey_modkeys;
		}
		var modkeys_html = $gp.GetModkeys(mod_keys);
		if( where == 'before' ){
			$(modkeys_html).insertBefore(selector);
		}else{
			$(modkeys_html).insertAfter(selector);
		}
	};
	/**
	 * Get hotkey hint
	 * @param array of keys, modifier keys and regular key, e.g. ['ctrlKey','H']
	 * @return string e.g. for title attr
	 *
	 */
	$gp.GetHotkeyHint = function(keys){
		var hint = '';
		if( typeof(keys) == 'undefinded' || !keys.length ){
			return '';
		}
		$.each(keys, function(i, k){
			switch(k){
				case 'ctrlKey':
				case 'shiftKey':
				case 'altKey':
				case 'metaKey':
					hint += '[' + $gp.mod_keys[k] + ']+';
					break;
				default:
					hint += '[' + k + ']';
					break;
			}
		});
		return hint;
	};
	/*
	 * Hide Admin UI
	 * Configuration -> Settings
	 * input capture hotkey combination
	 */
	$('#admin_ui_hotkey').on('focus', function(){
			$(this).select();
		}).on('keydown', function(evt){
			var key_stroke	= $.inArray(evt.key, ['Control', 'Shift', 'Alt', 'AltGraph', 'Meta']) != -1 ? '' : evt.key.toUpperCase();
			var key_which	= $.inArray(evt.key, ['Control', 'Shift', 'Alt', 'AltGraph', 'Meta']) != -1 ? '' : evt.which;
			var mod_keys = [];
			evt.ctrlKey		&& mod_keys.push('ctrlKey');
			evt.shiftKey	&& mod_keys.push('shiftKey');
			evt.altKey		&& mod_keys.push('altKey');
			evt.metaKey		&& mod_keys.push('metaKey');
			// show the modifier keys
			$gp.InsertModkeys('#admin_ui_hotkey', 'before', mod_keys);
			var key_code =
				(evt.ctrlKey  ? 'ctrlKey+'	: '') +
				(evt.shiftKey ? 'shiftKey+'	: '') +
				(evt.altKey   ? 'altKey+'	: '') +
				(evt.metaKey  ? 'metaKey+'	: '') +
				key_which;
			if( key_stroke == '' ||
				key_stroke == ' ' ||
				key_stroke == 'DEAD' ||
				key_stroke == 'DELETE' ||
				key_stroke == 'BACKSPACE'
				){
				key_stroke = '';
				key_code = '';
			}
			$(this).val(key_stroke);
			$('#admin_ui_hotkey_code').val(key_code);
			evt.stopPropagation();
			evt.preventDefault();
		}).on('keyup', function(evt){
			var code_val = $('#admin_ui_hotkey_code').val();
			var has_modifier_key = /ctrlKey\+|shiftKey\+|altKey\+|metaKey\+/g.test(code_val);
			if( code_val == '' || !has_modifier_key ){
				$(this).val('');
				$('#admin_ui_hotkey_code').val('');
				$gp.InsertModkeys('#admin_ui_hotkey', 'before', []);
			}
		});
	// make the input smaller and show the modifier keys on load
	if( $('#admin_ui_hotkey').length ){
		$('#admin_ui_hotkey').width(96);
		$gp.InsertModkeys('#admin_ui_hotkey', 'before');
	}
}); /* end on DOM ready */
/**
 * Hide Admin UI
 *
 */
$gp.HideAdminUI = {
	init: function(){
		$gp.HideAdminUI.hotkey_hint = '';
		if( hideAdminUIcfg.hotkey != '' && hideAdminUIcfg.hotkey_code != '' ){
			var hotkey_arr = hideAdminUIcfg.hotkey_modkeys;
			hotkey_arr.push(hideAdminUIcfg.hotkey);
			$gp.HideAdminUI.hotkey_hint = ' ' + $gp.GetHotkeyHint(hotkey_arr);
		}
		$('
')
		.on('click', function(){
			$gp.HideAdminUI.toggle(false);
		}).appendTo('body');
		$('.admin-link-hide-ui')
			.attr('title', $('.admin-link-hide-ui').attr('title') + $gp.HideAdminUI.hotkey_hint);
		if( hideAdminUIcfg.autohide_below ){
			$gp.HideAdminUI.ww = $gp.$win.width();
			$gp.$win.on('load', function(evt){
				var ww = $gp.$win.width();
				if( ww < hideAdminUIcfg.autohide_below ){
					$gp.HideAdminUI.toggle(true);
					$gp.HideAdminUI.ww = ww;
				}
			}).on('resize', function(evt){
				var ww = $gp.$win.width();
				var threshold = hideAdminUIcfg.autohide_below;
				if( ww < threshold && $gp.HideAdminUI.ww >= threshold ){
					$gp.HideAdminUI.toggle(true);
					$gp.HideAdminUI.ww = ww;
				}else if( ww >= threshold && $gp.HideAdminUI.ww < threshold ){
					$gp.HideAdminUI.toggle(false);
					$gp.HideAdminUI.ww = ww;
				}
			});
		}
		if( hideAdminUIcfg.hotkey_which != '' ){
			$gp.$doc.on('keydown.hideAdminUI', function(evt){
				var modkeys_pressed = true;
				$.each(hideAdminUIcfg.hotkey_modkeys, function(i, key){
					if( evt[key] === false ){
						modkeys_pressed = false;
						return false;
					}
				});
				if( modkeys_pressed && evt.which == hideAdminUIcfg.hotkey_which ){
					evt.preventDefault();
					$gp.HideAdminUI.toggle();
				}
				if( evt.which == 27 ){ /* 27 [Esc] key always exits hidden state */
					$gp.HideAdminUI.toggle(false);
				}
			});
		}
	},
	toggle: function(show_hide){
		if( typeof(show_hide) == 'boolean' ){
			$("html").toggleClass("override_admin_style", show_hide);
		}else{
			$("html").toggleClass("override_admin_style");
		}
	},
};
(function() { // IIFE for scoping
  /**
   * A simple drag function for use with Typesetter admin areas
   * Works with absolute and fixed positioned elements
   * Different from other drag script in that the mouse will not trigger any mouseover/mousenter events because the drag box will be under the mouse
   *
   * @param {string} selector  Selector for the element that triggers the drag (e.g., '.drag-handle')
   * @param {string} drag_area Selector for the element to be dragged (e.g., '#draggable-div')
   * @param {string} positioning 'absolute', 'relative', or 'fixed'
   * @param {function} callback_done  Function to call once the drag 'n drop is done.  Receives (element, position, event)
   */
  window.SimpleDrag = function(selector, drag_area, positioning, callback_done) {  // Making it a global function explicitly
    if (typeof jQuery === 'undefined') {
      console.error("SimpleDrag: jQuery is required but not loaded.");
      return; // Exit if jQuery is missing
    }
    if (typeof $gp === 'undefined' || !$gp.$doc || !$gp.$win || typeof $gp.div !== 'function') {
      console.error("SimpleDrag: $gp object is required and must have $doc, $win and div properties/methods.");
      return;  // Exit if $gp is missing necessary properties
    }
    var tolerance = -10;
    var $drag_area = $(drag_area);
    var dragNamespace = '.simpleDrag_' + Math.random().toString(36).substring(7);  // Unique namespace
    if ($drag_area.length === 0) {
      console.warn("SimpleDrag: No element found with selector '" + drag_area + "'. Drag functionality will not be enabled.");
      return; // Exit if no element to drag
    }
    // dragging
    $gp.$doc.off('mousedown' + dragNamespace, selector).on('mousedown' + dragNamespace, selector, function(e) {
      if (e.which !== 1) {
        return; // Only left mouse button
      }
      var box, click_offsetx, click_offsety;
      e.preventDefault();
      init();
      function init() {
        try {
          var pos = $drag_area.offset();
          click_offsetx = e.clientX - pos.left + $gp.$win.scrollLeft();
          click_offsety = e.clientY - pos.top + $gp.$win.scrollTop();
        } catch (err) {
          console.error("SimpleDrag: Error in init function: ", err);
          return;
        }
      }
      $gp.$doc.on('mousemove' + dragNamespace, function(e) {
        try {
          // initiate the box
          if (!box) {
            var pos = $drag_area.offset();
            var w = $drag_area.width();
            var h = $drag_area.height();
            box = $gp.div('admin_drag_box')
              .css({
                'top': pos.top,
                'left': pos.left,
                'width': w,
                'height': h
              });
          }
          box.css({
            'left': Math.max(tolerance, e.clientX - click_offsetx),
            'top': Math.max(tolerance, e.clientY - click_offsety)
          });
          e.preventDefault();
          return false;
        } catch (err) {
          console.error("SimpleDrag: Error in mousemove handler: ", err);
          return false;  // Stop propagation
        }
      });
      $gp.$doc.off('mouseup' + dragNamespace).on('mouseup' + dragNamespace, function(e) {
        try {
          var newposleft, newpostop, pos_obj;
          $gp.$doc.off('mousemove' + dragNamespace + ' mouseup' + dragNamespace); // Remove both handlers
          if (!box) {
            return false;
          }
          e.preventDefault();
          //clean
          box.remove();
          box = false;
          //new
          newposleft = e.clientX - click_offsetx;
          newpostop = e.clientY - click_offsety;
          //add scroll back in for absolute position
          if (positioning === 'absolute') {
            newposleft += $gp.$win.scrollLeft();
            newpostop += $gp.$win.scrollTop();
          }
          newposleft = Math.max(tolerance, newposleft);
          newpostop = Math.max(tolerance, newpostop);
          pos_obj = {
            'left': newposleft,
            'top': newpostop
          };
          $drag_area.css(pos_obj).data({
            'gp_left': newposleft,
            'gp_top': newpostop
          });
          if (typeof(callback_done) === 'function') {
            callback_done.call($drag_area, $drag_area, pos_obj, e); //Pass element as first param
          }
          $drag_area.trigger('dragstop');
          return false;
        } catch (err) {
          console.error("SimpleDrag: Error in mouseup handler: ", err);
          return false;  // Stop propagation
        }
      });
      return false;
    });
    if ($drag_area.css('position') === 'fixed' || $drag_area.parent().css('position') === 'fixed') {
      KeepViewable($drag_area.addClass('keep_viewable'), true);
    }
    function KeepViewable($elem, init) {
      try {
        if (!$elem.hasClass('keep_viewable')) {
          return;
        }
        var gp_left,
          css = {},
          pos = $elem.position();
        //move back to the right if $elem has been moved left
        if (init) {
          $elem.data({
            'gp_left': pos.left,
            'gp_top': pos.top
          });
        } else if (gp_left = $elem.data('gp_left')) {
          pos.left = css.left = gp_left;
          pos.top = css.top = $elem.data('gp_top');
        }
        var width = $elem.width();
        //keep the top of the area from being placed too high in the window
        var winbottom = $gp.$win.height();
        if (pos.top < tolerance) {
          css.top = tolerance;
          //keep the top of the area from being placed too low
        } else if (pos.top > winbottom) {
          css.top = winbottom + 2 * tolerance; //tolerance is negative
        }
        //right
        var checkright = $gp.$win.width() - width - tolerance;
        if (pos.left > checkright) {
          css.left = checkright;
        }
        if (css.left || css.top) {
          $elem.css(css);
        }
      } catch (err) {
        console.error("SimpleDrag: Error in KeepViewable: ", err);
      }
    }
    $gp.$win.on('resize' + dragNamespace, function() {
      $('.keep_viewable').each(function() {
        KeepViewable($(this), false);
      });
    });
  }; // End SimpleDrag function
})(); // End IIFE
/**
 * Initialize functionality for the rename/details dialog
 *
 */
$gp.response.renameprep = function(){
	var $form			= $('#gp_rename_form');
	var old_title		= $('#old_title').val().toLowerCase();
	var $new_title		= $form.find('input.new_title').on('keyup change',ShowRedirect);
	var space_char		= $('#gp_space_char').val();
	$('input:disabled').each(function(a,b){
		$(b).fadeTo(400,0.6);
	});
	$('input.title_label').on('keyup change', SyncSlug).trigger('change');
	$gp.links.showmore = function(){
		$('#gp_rename_table tr').css('display','table-row');
		$(this).parent().remove();
	};
	/**
	 * Toggle synchronization/customization of a field
	 *
	 */
	$gp.links.ToggleSync = function(evt){
		var td		= $(this).closest('td');
		var vis		= td.find('a:visible');
		td.find('a').show();
		vis.hide();
		vis			= td.find('a:visible');
		if( vis.length ){
			if( vis.hasClass('slug_edit') ){
				td.find('input').addClass('sync_label').prop('readonly',true).fadeTo(400,0.6);
				SyncSlug();
			}else{
				td.find('input').removeClass('sync_label').prop('readonly',false).fadeTo(400,1);
			}
		}
	}
	/**
	 * Show the redirct option if the new slug doesn't match the old slug
	 *
	 */
	function ShowRedirect(){
		var new_val = $new_title.val().replace(/ /g,space_char).toLowerCase();
		if( new_val !== old_title ){
			$('#gp_rename_redirect').show(500);
		}else{
			$('#gp_rename_redirect').hide(300);
		}
	}
	/**
	 * Update the value of the slug/url field with the title
	 *
	 */
	function SyncSlug(){
		var label = $('input.title_label').val();
		$new_title.filter('.sync_label').val( LabelToSlug(label) );
		$('input.browser_title.sync_label').val(label);
		ShowRedirect();
		return true;
	}
	/**
	 * Convert a label to a slug
	 *
	 */
	function LabelToSlug(str){
		// Remove control characters
		str = str.replace(/[\x00-\x1F\x7F]/g,'');
		//illegal characters
		//str = str.replace(/("|'|\?|\*|:|#)/g,'');
		str = str.replace(/(\?|\*|:|#)/g,'');
		//after removing tags, unescape special characters
		str = strip_tags(str);
		str = SlugSlashes(str);
		//all spaces should be underscores
		return str.replace(/ /g,space_char);
	}
	/**
	 * Remove html tags from a string
	 *
	 */
	function strip_tags(str){
		return str.replace(/(<(\/?[a-zA-Z0-9][^<>]*)>)/ig,"");
	}
	/**
	 * Take care of slashes and periods
	 *
	 */
	function SlugSlashes(str){
		//replace \
		str = str.replace(/[\\]/g,'/');
		//remove trailing "/."
		str = str.replace(/^\.+[\/\/]/,'/');
		//remove trailing "/."
		str = str.replace(/[\/\/]\.+$/,'/');
		//remove any "/./"
		str = str.replace(/[\/\/]\.+[\/\/]/g,'/');
		//remove consecutive slashes
		str = str.replace(/[\/\/]+/g,'/');
		if( str === '.' ){
			return '';
		}
		//left trim /
		str = str.replace(/^\/\/*/g,'');
		return str;
	}
};