'+options.text+'
').animate(options.inEffect, options.inEffectDuration).wrap(noticeItemOuter);\n\t\t\tnoticeItemClose\t= jQuery('').addClass('notice-item-close').prependTo(noticeItemInner).html('x').click(function() { jQuery.noticeRemove(noticeItemInner) });\n\t\t\t\n\t\t\tnoticeItemInner.hover(function() {\n\t\t\t\thover = true;\n\t\t\t}, function () {\n\t\t\t\thover = false;\n\t\t\t});\n\n\t\t\tif (!options.stay) {\n\t\t\t\tsetTimeout( function () {\n\t\t\t\t\tvar noticeHover = setInterval(function () {\n\t\t\t\t\t\tif(!hover) {\n\t\t\t\t\t\t\tjQuery.noticeRemove(noticeItemInner);\n\t\t\t\t\t\t\tclearInterval(noticeHover);\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 1000);\n\t\t\t\t}, options.stayTime);\n\t\t\t}\n\t\t},\n\t\t\n\t\tnoticeRemove: function(obj)\n\t\t{\n\t\t\tobj.animate({opacity: '0'}, 600, function()\n\t\t\t{\n\t\t\t\tobj.parent().animate({height: '0px'}, 300, function()\n\t\t\t\t{\n\t\t\t\t\tobj.parent().remove();\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t});\n})(jQuery);","/**\n * @preserve JSizes - JQuery plugin v0.33\n *\n * Licensed under the revised BSD License.\n * Copyright 2008-2010 Bram Stein\n * All rights reserved.\n */\n/*global jQuery*/\n(function ($) {\n\t'use strict';\n\tvar num = function (value) {\n\t\t\treturn parseInt(value, 10) || 0;\n\t\t};\n\n\t/**\n\t * Sets or gets the values for min-width, min-height, max-width\n\t * and max-height.\n\t */\n\t$.each(['min', 'max'], function (i, name) {\n\t\t$.fn[name + 'Size'] = function (value) {\n\t\t\tvar width, height;\n\t\t\tif (value) {\n\t\t\t\tif (value.width !== undefined) {\n\t\t\t\t\tthis.css(name + '-width', value.width);\n\t\t\t\t}\n\t\t\t\tif (value.height !== undefined) {\n\t\t\t\t\tthis.css(name + '-height', value.height);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twidth = this.css(name + '-width');\n\t\t\t\theight = this.css(name + '-height');\n\t\t\t\t// Apparently:\n\t\t\t\t// * Opera returns -1px instead of none\n\t\t\t\t// * IE6 returns undefined instead of none\n\t\t\t\treturn {'width': (name === 'max' && (width === undefined || width === 'none' || num(width) === -1) && Number.MAX_VALUE) || num(width), \n\t\t\t\t\t\t'height': (name === 'max' && (height === undefined || height === 'none' || num(height) === -1) && Number.MAX_VALUE) || num(height)};\n\t\t\t}\n\t\t\treturn this;\n\t\t};\n\t});\n\n\t/**\n\t * Returns whether or not an element is visible.\n\t */\n\t$.fn.isVisible = function () {\n\t\treturn this.is(':visible');\n\t};\n\n\t/**\n\t * Sets or gets the values for border, margin and padding.\n\t */\n\t$.each(['border', 'margin', 'padding'], function (i, name) {\n\t\t$.fn[name] = function (value) {\n\t\t\tif (value) {\n\t\t\t\tif (value.top !== undefined) {\n\t\t\t\t\tthis.css(name + '-top' + (name === 'border' ? '-width' : ''), value.top);\n\t\t\t\t}\n\t\t\t\tif (value.bottom !== undefined) {\n\t\t\t\t\tthis.css(name + '-bottom' + (name === 'border' ? '-width' : ''), value.bottom);\n\t\t\t\t}\n\t\t\t\tif (value.left !== undefined) {\n\t\t\t\t\tthis.css(name + '-left' + (name === 'border' ? '-width' : ''), value.left);\n\t\t\t\t}\n\t\t\t\tif (value.right !== undefined) {\n\t\t\t\t\tthis.css(name + '-right' + (name === 'border' ? '-width' : ''), value.right);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn {top: num(this.css(name + '-top' + (name === 'border' ? '-width' : ''))),\n\t\t\t\t\t\tbottom: num(this.css(name + '-bottom' + (name === 'border' ? '-width' : ''))),\n\t\t\t\t\t\tleft: num(this.css(name + '-left' + (name === 'border' ? '-width' : ''))),\n\t\t\t\t\t\tright: num(this.css(name + '-right' + (name === 'border' ? '-width' : '')))};\n\t\t\t}\n\t\t\treturn this;\n\t\t};\n\t});\n}(jQuery));\n","import $ from './jQuery';\n\n$.fn.extend({\n\tssDatepicker: function(opts) {\n\t\treturn $(this).each(function() {\n\t\t\tif($(this).data('datepicker')) return; // already applied\n\n\t\t\t$(this).siblings(\"button\").addClass(\"ui-icon ui-icon-calendar\");\n\t\t\t\n\t\t\tvar holder = $(this).parents('.field.date:first'), \n\t\t\t\tconfig = $.extend(opts || {}, $(this).data(), $(this).data('jqueryuiconfig'), {});\n\t\t\tif(!config.showcalendar) return;\n\n\t\t\tif(config.locale && $.datepicker.regional[config.locale]) {\n\t\t\t\tconfig = $.extend(config, $.datepicker.regional[config.locale], {});\n\t\t\t}\n\n\t\t\tif(config.min) config.minDate = $.datepicker.parseDate('yy-mm-dd', config.min);\n\t\t\tif(config.max) config.maxDate = $.datepicker.parseDate('yy-mm-dd', config.max);\n\n\t\t\t// Initialize and open a datepicker \n\t\t\t// live() doesn't have \"onmatch\", and jQuery.entwine is a bit too heavyweight for this, so we need to do this onclick.\n\t\t\tconfig.dateFormat = config.jquerydateformat;\n\t\t\t$(this).datepicker(config);\n\t\t});\n\t}\n});\n\n$(document).on(\"click\", \".field.date input.text,input.text.date\", function() {\n\t$(this).ssDatepicker();\n\n\tif($(this).data('datepicker')) {\n\t\t$(this).datepicker('show');\n\t}\n});\n","import $ from './jQuery';\nimport i18n from './i18n';\n\n$.entwine('ss', function($) {\n\t$('.ss-gridfield').entwine({\n\t\t/**\n\t\t * @param {Object} Additional options for jQuery.ajax() call\n\t\t * @param {successCallback} callback to call after reloading succeeded.\n\t\t */\n\n\t\treload: function(ajaxOpts, successCallback) {\n\t\t\tvar self = this, form = this.closest('form'),\n\t\t\t\tfocusedElName = this.find(':input:focus').attr('name'), // Save focused element for restoring after refresh\n\t\t\t\tdata = form.find(':input').serializeArray();\n\n\t\t\tif(!ajaxOpts) ajaxOpts = {};\n\t\t\tif(!ajaxOpts.data) ajaxOpts.data = [];\n\t\t\tajaxOpts.data = ajaxOpts.data.concat(data);\n\n\n\t\t\t// Include any GET parameters from the current URL, as the view state might depend on it.\n\t\t\t// For example, a list prefiltered through external search criteria might be passed to GridField.\n\t\t\tif(window.location.search) {\n\t\t\t\tajaxOpts.data = window.location.search.replace(/^\\?/, '') + '&' + $.param(ajaxOpts.data);\n\t\t\t}\n\n\t\t\tform.addClass('loading');\n\n\t\t\t$.ajax($.extend({}, {\n\t\t\t\theaders: {\"X-Pjax\" : 'CurrentField'},\n\t\t\t\ttype: \"POST\",\n\t\t\t\turl: this.data('url'),\n\t\t\t\tdataType: 'html',\n\t\t\t\tsuccess: function(data) {\n\t\t\t\t\t// Replace the grid field with response, not the form.\n\t\t\t\t\t// TODO Only replaces all its children, to avoid replacing the current scope\n\t\t\t\t\t// of the executing method. Means that it doesn't retrigger the onmatch() on the main container.\n\t\t\t\t\tself.empty().append($(data).children());\n\n\t\t\t\t\t// Refocus previously focused element. Useful e.g. for finding+adding\n\t\t\t\t\t// multiple relationships via keyboard.\n\t\t\t\t\tif(focusedElName) self.find(':input[name=\"' + focusedElName + '\"]').focus();\n\n\t\t\t\t\t// Update filter\n\t\t\t\t\tif(self.find('.filter-header').length) {\n\t\t\t\t\t\tvar content;\n\t\t\t\t\t\tif(ajaxOpts.data[0].filter==\"show\") {\n\t\t\t\t\t\t\tcontent = '';\n\t\t\t\t\t\t\tself.addClass('show-filter').find('.filter-header').show();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontent = '';\n\t\t\t\t\t\t\tself.removeClass('show-filter').find('.filter-header').hide();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tself.find('.sortable-header th:last').html(content);\n\t\t\t\t\t}\n\n\t\t\t\t\tform.removeClass('loading');\n\t\t\t\t\tif(successCallback) successCallback.apply(this, arguments);\n\t\t\t\t\tself.trigger('reload', self);\n\t\t\t\t},\n\t\t\t\terror: function(e) {\n\t\t\t\t\talert(i18n._t('GRIDFIELD.ERRORINTRANSACTION'));\n\t\t\t\t\tform.removeClass('loading');\n\t\t\t\t}\n\t\t\t}, ajaxOpts));\n\t\t},\n\t\tshowDetailView: function(url) {\n\t\t\twindow.location.href = url;\n\t\t},\n\t\tgetItems: function() {\n\t\t\treturn this.find('.ss-gridfield-item');\n\t\t},\n\t\t/**\n\t\t * @param {String}\n\t\t * @param {Mixed}\n\t\t */\n\t\tsetState: function(k, v) {\n\t\t\tvar state = this.getState();\n\t\t\tstate[k] = v;\n\t\t\tthis.find(':input[name=\"' + this.data('name') + '[GridState]\"]').val(JSON.stringify(state));\n\t\t},\n\t\t/**\n\t\t * @return {Object}\n\t\t */\n\t\tgetState: function() {\n\t\t\treturn JSON.parse(this.find(':input[name=\"' + this.data('name') + '[GridState]\"]').val());\n\t\t}\n\t});\n\n\t$('.ss-gridfield *').entwine({\n\t\tgetGridField: function() {\n\t\t\treturn this.closest('.ss-gridfield');\n\t\t}\n\t});\n\n\n\n\t$('.ss-gridfield :button[name=showFilter]').entwine({\n\t\tonclick: function(e) {\n\t\t\t$('.filter-header')\n\t\t\t\t.show('slow') // animate visibility\n\t\t\t\t.find(':input:first').focus(); // focus first search field\n\t\t\tthis.closest('.ss-gridfield').addClass('show-filter');\n\t\t\tthis.parent().html('');\n\t\t\te.preventDefault();\n\t\t}\n\t});\n\n\n\t$('.ss-gridfield .ss-gridfield-item').entwine({\n\t\tonclick: function(e) {\n\t\t\tif($(e.target).closest('.action').length) {\n\t\t\t\tthis._super(e);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvar editLink = this.find('.edit-link');\n\t\t\tif(editLink.length) this.getGridField().showDetailView(editLink.prop('href'));\n\t\t},\n\t\tonmouseover: function() {\n\t\t\tif(this.find('.edit-link').length) this.css('cursor', 'pointer');\n\t\t},\n\t\tonmouseout: function() {\n\t\t\tthis.css('cursor', 'default');\n\t\t}\n\t});\n\n\t$('.ss-gridfield .action').entwine({\n\t\tonclick: function(e){\n\t\t\tvar filterState='show'; //filterstate should equal current state.\n\n\t\t\t// If the button is disabled, do nothing.\n\t\t\tif (this.button('option', 'disabled')) {\n\t\t\t\te.preventDefault();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif(this.hasClass('ss-gridfield-button-close') || !(this.closest('.ss-gridfield').hasClass('show-filter'))){\n\t\t\t\tfilterState='hidden';\n\t\t\t}\n\n\t\t\tthis.getGridField().reload({data: [{name: this.attr('name'), value: this.val(), filter: filterState}]});\n\t\t\te.preventDefault();\n\t\t},\n\t\t/**\n\t\t * Get the url this action should submit to\n\t\t */\n\t\tactionurl: function() {\n\t\t\tvar btn = this.closest(':button'), grid = this.getGridField(),\n\t\t\t\tform = this.closest('form'), data = form.find(':input.gridstate').serialize(),\n\t\t\t\tcsrf = form.find('input[name=\"SecurityID\"]').val();\n\n\t\t\t// Add current button\n\t\t\tdata += \"&\" + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val());\n\n\t\t\t// Add csrf\n\t\t\tif(csrf) {\n\t\t\t\tdata += \"&SecurityID=\" + encodeURIComponent(csrf);\n\t\t\t}\n\n\t\t\t// Include any GET parameters from the current URL, as the view\n\t\t\t// state might depend on it. For example, a list pre-filtered\n\t\t\t// through external search criteria might be passed to GridField.\n\t\t\tif(window.location.search) {\n\t\t\t\tdata = window.location.search.replace(/^\\?/, '') + '&' + data;\n\t\t\t}\n\n\t\t\t// decide whether we should use ? or & to connect the URL\n\t\t\tvar connector = grid.data('url').indexOf('?') == -1 ? '?' : '&';\n\n\t\t\treturn $.path.makeUrlAbsolute(\n\t\t\t\tgrid.data('url') + connector + data,\n\t\t\t\t$('base').attr('href')\n\t\t\t);\n\t\t}\n\n\t});\n\n\t/**\n\t * Don't allow users to submit empty values in grid field auto complete inputs.\n\t */\n\t$('.ss-gridfield .add-existing-autocompleter').entwine({\n\t\tonbuttoncreate: function () {\n\t\t\tvar self = this;\n\n\t\t\tthis.toggleDisabled();\n\n\t\t\tthis.find('input[type=\"text\"]').on('keyup', function () {\n\t\t\t\tself.toggleDisabled();\n\t\t\t});\n\t\t},\n\t\tonunmatch: function () {\n\t\t\tthis.find('input[type=\"text\"]').off('keyup');\n\t\t},\n\t\ttoggleDisabled: function () {\n\t\t\tvar $button = this.find('.ss-ui-button'),\n\t\t\t\t$input = this.find('input[type=\"text\"]'),\n\t\t\t\tinputHasValue = $input.val() !== '',\n\t\t\t\tbuttonDisabled = $button.is(':disabled');\n\n\t\t\tif ((inputHasValue && buttonDisabled) || (!inputHasValue && !buttonDisabled)) {\n\t\t\t\t$button.button(\"option\", \"disabled\", !buttonDisabled);\n\t\t\t}\n\t\t}\n\t});\n\n\t// Covers both tabular delete button, and the button on the detail form\n\t$('.ss-gridfield .col-buttons .action.gridfield-button-delete, .cms-edit-form .Actions button.action.action-delete').entwine({\n\t\tonclick: function(e){\n\t\t\tif(!confirm(i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE'))) {\n\t\t\t\te.preventDefault();\n\t\t\t\treturn false;\n\t\t\t} else {\n\t\t\t\tthis._super(e);\n\t\t\t}\n\t\t}\n\t});\n\n\t$('.ss-gridfield .action.gridfield-button-print').entwine({\n\t\tUUID: null,\n\t\tonmatch: function() {\n\t\t\tthis._super();\n\t\t\tthis.setUUID(new Date().getTime());\n\t\t},\n\t\tonunmatch: function() {\n\t\t\tthis._super();\n\t\t},\n\t\tonclick: function(e){\n\t\t\tvar url = this.actionurl();\n\t\t\twindow.open(url);\n\t\t\te.preventDefault();\n\t\t\treturn false;\n\t\t}\n\t});\n\n\t$('.ss-gridfield-print-iframe').entwine({\n\t\tonmatch: function(){\n\t\t\tthis._super();\n\n\t\t\tthis.hide().bind('load', function() {\n\t\t\t\tthis.focus();\n\t\t\t\tvar ifWin = this.contentWindow || this;\n\t\t\t\tifWin.print();\n\t\t\t});\n\t\t},\n\t\tonunmatch: function() {\n\t\t\tthis._super();\n\t\t}\n\t});\n\n\t/**\n\t * Prevents actions from causing an ajax reload of the field.\n\t *\n\t * Useful e.g. for actions which rely on HTTP response headers being\n\t * interpreted natively by the browser, like file download triggers.\n\t */\n\t$('.ss-gridfield .action.no-ajax').entwine({\n\t\tonclick: function(e){\n\t\t\twindow.location.href = this.actionurl();\n\t\t\te.preventDefault();\n\t\t\treturn false;\n\t\t}\n\t});\n\n\t$('.ss-gridfield .action-detail').entwine({\n\t\tonclick: function() {\n\t\t\tthis.getGridField().showDetailView($(this).prop('href'));\n\t\t\treturn false;\n\t\t}\n\t});\n\n\t/**\n\t * Allows selection of one or more rows in the grid field.\n\t * Purely clientside at the moment.\n\t */\n\t$('.ss-gridfield[data-selectable]').entwine({\n\t\t/**\n\t\t * @return {jQuery} Collection\n\t\t */\n\t\tgetSelectedItems: function() {\n\t\t\treturn this.find('.ss-gridfield-item.ui-selected');\n\t\t},\n\t\t/**\n\t\t * @return {Array} Of record IDs\n\t\t */\n\t\tgetSelectedIDs: function() {\n\t\t\treturn $.map(this.getSelectedItems(), function(el) {return $(el).data('id');});\n\t\t}\n\t});\n\t$('.ss-gridfield[data-selectable] .ss-gridfield-items').entwine({\n\t\tonadd: function() {\n\t\t\tthis._super();\n\n\t\t\t// TODO Limit to single selection\n\t\t\tthis.selectable();\n\t\t},\n\t\tonremove: function() {\n\t\t\tthis._super();\n\t\t\tif (this.data('selectable')) this.selectable('destroy');\n\t\t}\n\t});\n\n\t/**\n\t * Catch submission event in filter input fields, and submit the correct button\n\t * rather than the whole form.\n\t */\n\t$('.ss-gridfield .filter-header :input').entwine({\n\t\tonmatch: function() {\n\t\t\tvar filterbtn = this.closest('.fieldgroup').find('.ss-gridfield-button-filter'),\n\t\t\t\tresetbtn = this.closest('.fieldgroup').find('.ss-gridfield-button-reset');\n\n\t\t\tif(this.val()) {\n\t\t\t\tfilterbtn.addClass('filtered');\n\t\t\t\tresetbtn.addClass('filtered');\n\t\t\t}\n\t\t\tthis._super();\n\t\t},\n\t\tonunmatch: function() {\n\t\t\tthis._super();\n\t\t},\n\t\tonkeydown: function(e) {\n\t\t\t// Skip reset button events, they should trigger default submission\n\t\t\tif(this.closest('.ss-gridfield-button-reset').length) return;\n\n\t\t\tvar filterbtn = this.closest('.fieldgroup').find('.ss-gridfield-button-filter'),\n\t\t\t\tresetbtn = this.closest('.fieldgroup').find('.ss-gridfield-button-reset');\n\n\t\t\tif(e.keyCode == '13') {\n\t\t\t\tvar btns = this.closest('.filter-header').find('.ss-gridfield-button-filter');\n\t\t\t\tvar filterState='show'; //filterstate should equal current state.\n\t\t\t\tif(this.hasClass('ss-gridfield-button-close')||!(this.closest('.ss-gridfield').hasClass('show-filter'))){\n\t\t\t\t\tfilterState='hidden';\n\t\t\t\t}\n\n\t\t\t\tthis.getGridField().reload({data: [{name: btns.attr('name'), value: btns.val(), filter: filterState}]});\n\t\t\t\treturn false;\n\t\t\t}else{\n\t\t\t\tfilterbtn.addClass('hover-alike');\n\t\t\t\tresetbtn.addClass('hover-alike');\n\t\t\t}\n\t\t}\n\t});\n\n\t$(\".ss-gridfield .relation-search\").entwine({\n\t\tonfocusin: function (event) {\n\t\t\tthis.autocomplete({\n\t\t\t\tsource: function(request, response){\n\t\t\t\t\tvar searchField = $(this.element);\n\t\t\t\t\tvar form = $(this.element).closest(\"form\");\n\t\t\t\t\t$.ajax({\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\"X-Pjax\" : 'Partial'\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttype: \"GET\",\n\t\t\t\t\t\turl: $(searchField).data('searchUrl'),\n\t\t\t\t\t\tdata: encodeURIComponent(searchField.attr('name'))+'='+encodeURIComponent(searchField.val()),\n\t\t\t\t\t\tsuccess: function(data) {\n\t\t\t\t\t\t\tresponse( $.map(JSON.parse(data), function( name, id ) {\n\t\t\t\t\t\t\t\treturn { label: name, value: name, id: id };\n\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t},\n\t\t\t\t\t\terror: function(e) {\n\t\t\t\t\t\t\talert(i18n._t('GRIDFIELD.ERRORINTRANSACTION', 'An error occured while fetching data from the server\\n Please try again later.'));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tselect: function(event, ui) {\n\t\t\t\t\t$(this).closest(\".ss-gridfield\").find(\"#action_gridfield_relationfind\").replaceWith(\n\t\t\t\t\t\t''\n\t\t\t\t\t);\n\t\t\t\t\tvar addbutton = $(this).closest(\".ss-gridfield\").find(\"#action_gridfield_relationadd\");\n\t\t\t\t\tif(addbutton.data('button')){\n\t\t\t\t\t\taddbutton.button('enable');\n\t\t\t\t\t}else{\n\t\t\t\t\t\taddbutton.removeAttr('disabled');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t});\n\n\t$(\".ss-gridfield .pagination-page-number input\").entwine({\n\t\tonkeydown: function(event) {\n\t\t\tif(event.keyCode == 13) {\n\t\t\t\tvar newpage = parseInt($(this).val(), 10);\n\n\t\t\t\tvar gridfield = $(this).getGridField();\n\t\t\t\tgridfield.setState('GridFieldPaginator', {currentPage: newpage});\n\t\t\t\tgridfield.reload();\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t});\n});\n","/**\n * Functions for HtmlEditorFields in the back end.\n * Includes the JS for the ImageUpload forms.\n *\n * Relies on the jquery.form.js plugin to power the\n * ajax / iframe submissions\n */\n\nimport $ from './jQuery';\nimport i18n from './i18n';\n\nvar ss = typeof window.ss !== 'undefined' ? window.ss : {};\n\n/**\n * Wrapper for HTML WYSIWYG libraries, which abstracts library internals\n * from interface concerns like inserting and editing links.\n * Caution: Incomplete and unstable API.\n */\nss.editorWrappers = {};\nss.editorWrappers.tinyMCE = (function() {\n\n\t// ID of editor this is assigned to\n\tvar editorID;\n\n\treturn {\n\t\t/**\n\t\t * Initialise the editor\n\t\t *\n\t\t * @param {String} ID of parent textarea domID\n */\n\t\tinit: function(ID) {\n\t\t\teditorID = ID;\n\n\t\t\tthis.create();\n\t\t},\n\n\t\t/**\n\t\t * Remove the editor and cleanup\n\t\t */\n\t\tdestroy: function() {\n\t\t\ttinymce.EditorManager.execCommand('mceRemoveEditor', false, editorID);\n\t\t},\n\n\t\t/**\n\t\t * Get TinyMCE Editor instance\n\t\t *\n\t\t * @returns Editor\n */\n\t\tgetInstance: function() {\n\t\t\treturn tinymce.EditorManager.get(editorID);\n\t\t},\n\n\t\t/**(\n\t\t * Invoked when a content-modifying UI is opened.\n\t\t */\n\t\tonopen: function() {\n\t\t\t// NOOP\n\t\t},\n\n\t\t/**(\n\t\t * Invoked when a content-modifying UI is closed.\n\t\t */\n\t\tonclose: function() {\n\t\t\t// NOOP\n\t\t},\n\n\t\t/**\n\t\t * Get config for this data\n\t\t *\n\t\t * @returns array\n */\n\t\tgetConfig: function() {\n\t\t\tvar selector = \"#\" + editorID,\n\t\t\t\tconfig = $(selector).data('config'),\n\t\t\t\tself = this;\n\n\t\t\t// Add instance specific data to config\n\t\t\tconfig.selector = selector;\n\n\t\t\t// Ensure save events write back to textarea\n\t\t\tconfig.setup = function(ed) {\n\t\t\t\ted.on('change', function() {\n\t\t\t\t\tself.save();\n\t\t\t\t});\n\t\t\t};\n\t\t\treturn config;\n\t\t},\n\n\t\t/**\n\t\t * Write the HTML back to the original text area field.\n\t\t */\n\t\tsave: function() {\n\t\t\tvar instance = this.getInstance();\n\t\t\tinstance.save();\n\n\t\t\t// Update change detection\n\t\t\t$(instance.getElement()).trigger(\"change\");\n\t\t},\n\n\t\t/**\n\t\t * Create a new instance based on a textarea field.\n\t\t */\n\t\tcreate: function() {\n\t\t\tvar config = this.getConfig();\n\t\t\t// hack to set baseURL safely\n\t\t\tif(typeof config.baseURL !== 'undefined') {\n\t\t\t\ttinymce.EditorManager.baseURL = config.baseURL;\n\t\t\t}\n\t\t\ttinymce.init(config);\n\t\t},\n\n\t\t/**\n\t\t * Request an update to editor content\n\t\t */\n\t\trepaint: function() {\n\t\t\t// NOOP\n\t\t},\n\n\t\t/**\n\t\t * @return boolean\n\t\t */\n\t\tisDirty: function() {\n\t\t\treturn this.getInstance().isDirty();\n\t\t},\n\n\t\t/**\n\t\t * HTML representation of the edited content.\n\t\t *\n\t\t * Returns: {String}\n\t\t */\n\t\tgetContent: function() {\n\t\t\treturn this.getInstance().getContent();\n\t\t},\n\n\t\t/**\n\t\t * DOM tree of the edited content\n\t\t *\n\t\t * Returns: DOMElement\n\t\t */\n\t\tgetDOM: function() {\n\t\t\treturn this.getInstance().getElement();\n\t\t},\n\n\t\t/**\n\t\t * Returns: DOMElement\n\t\t */\n\t\tgetContainer: function() {\n\t\t\treturn this.getInstance().getContainer();\n\t\t},\n\n\t\t/**\n\t\t * Get the closest node matching the current selection.\n\t\t *\n\t\t * Returns: {jQuery} DOMElement\n\t\t */\n\t\tgetSelectedNode: function() {\n\t\t\treturn this.getInstance().selection.getNode();\n\t\t},\n\n\t\t/**\n\t\t * Select the given node within the editor DOM\n\t\t *\n\t\t * Parameters: {DOMElement}\n\t\t */\n\t\tselectNode: function(node) {\n\t\t\tthis.getInstance().selection.select(node);\n\t\t},\n\n\t\t/**\n\t\t * Replace entire content\n\t\t *\n\t\t * @param {String} html\n\t\t * @param {Object} opts\n\t\t */\n\t\tsetContent: function(html, opts) {\n\t\t\tthis.getInstance().setContent(html, opts);\n\t\t},\n\n\t\t/**\n\t\t * Insert content at the current caret position\n\t\t *\n\t\t * @param {String} html\n\t\t * @param {Object} opts\n\t\t */\n\t\tinsertContent: function(html, opts) {\n\t\t\tthis.getInstance().insertContent(html, opts);\n\t\t},\n\t\t/**\n\t\t * Replace currently selected content\n\t\t *\n\t\t * @param {String} html\n\t\t */\n\t\treplaceContent: function(html, opts) {\n\t\t\tthis.getInstance().execCommand('mceReplaceContent', false, html, opts);\n\t\t},\n\t\t/**\n\t\t * Insert or update a link in the content area (based on current editor selection)\n\t\t *\n\t\t * Parameters: {Object} attrs\n\t\t */\n\t\tinsertLink: function(attrs, opts) {\n\t\t\tthis.getInstance().execCommand(\"mceInsertLink\", false, attrs, opts);\n\t\t},\n\t\t/**\n\t\t * Remove the link from the currently selected node (if any).\n\t\t */\n\t\tremoveLink: function() {\n\t\t\tthis.getInstance().execCommand('unlink', false);\n\t\t},\n\t\t/**\n\t\t * Strip any editor-specific notation from link in order to make it presentable in the UI.\n\t\t *\n\t\t * Parameters:\n\t\t * {Object}\n\t\t * {DOMElement}\n\t\t */\n\t\tcleanLink: function(href, node) {\n\t\t\tvar settings = this.getConfig,\n\t\t\t\tcb = settings['urlconverter_callback'];\n\t\t\tif(cb) href = eval(cb + \"(href, node, true);\");\n\n\t\t\t// Turn into relative\n\t\t\tif(href.match(new RegExp('^' + tinyMCE.settings['document_base_url'] + '(.*)$'))) {\n\t\t\t\thref = RegExp.$1;\n\t\t\t}\n\n\t\t\t// Get rid of TinyMCE's temporary URLs\n\t\t\tif(href.match(/^javascript:\\s*mctmp/)) href = '';\n\n\t\t\treturn href;\n\t\t},\n\t\t/**\n\t\t * Creates a bookmark for the currently selected range,\n\t\t * which can be used to reselect this range at a later point.\n\t\t * @return {mixed}\n\t\t */\n\t\tcreateBookmark: function() {\n\t\t\treturn this.getInstance().selection.getBookmark();\n\t\t},\n\t\t/**\n\t\t * Selects a bookmarked range previously saved through createBookmark().\n\t\t * @param {mixed} bookmark\n\t\t */\n\t\tmoveToBookmark: function(bookmark) {\n\t\t\tthis.getInstance().selection.moveToBookmark(bookmark);\n\t\t\tthis.getInstance().focus();\n\t\t},\n\t\t/**\n\t\t * Removes any selection & de-focuses this editor\n\t\t */\n\t\tblur: function() {\n\t\t\tthis.getInstance().selection.collapse();\n\t\t},\n\t\t/**\n\t\t * Add new undo point with the current DOM content.\n\t\t */\n\t\taddUndo: function() {\n\t\t\tthis.getInstance().undoManager.add();\n\t\t}\n\t};\n});\n// Override this to switch editor wrappers\nss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;\n\n$.entwine('ss', function($) {\n\n\t/**\n\t * Class: textarea.htmleditor\n\t *\n\t * Add tinymce to HtmlEditorFields within the CMS. Works in combination\n\t * with a TinyMCE.init() call which is prepopulated with the used HTMLEditorConfig settings,\n\t * and included in the page as an inline