diff --git a/admin/code/CMSMenu.php b/admin/code/CMSMenu.php index e13e7eff1..becb5261d 100755 --- a/admin/code/CMSMenu.php +++ b/admin/code/CMSMenu.php @@ -208,10 +208,12 @@ class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider // checks on if($menuItem->controller) { $controllerObj = singleton($menuItem->controller); - // Necessary for canView() to have request data available, - // e.g. to check permissions against LeftAndMain->currentPage() - $controllerObj->setRequest(Controller::curr()->getRequest()); - if(!$controllerObj->canView($member)) continue; + if(Controller::has_curr()) { + // Necessary for canView() to have request data available, + // e.g. to check permissions against LeftAndMain->currentPage() + $controllerObj->setRequest(Controller::curr()->getRequest()); + if(!$controllerObj->canView($member)) continue; + } } $viewableMenuItems[$code] = $menuItem; diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 20cb96b4c..5b79b4205 100755 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -69,6 +69,7 @@ class LeftAndMain extends Controller { */ static $allowed_actions = array( 'index', + 'save', 'savetreenode', 'getitem', 'getsubtree', @@ -241,6 +242,9 @@ class LeftAndMain extends Controller { Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/jsizes/lib/jquery.sizes.js'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jlayout.border.js'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jquery.jlayout.js'); + Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.js'); + Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.html4.js'); + Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.adapter.jquery.js'); Requirements::javascript(THIRDPARTY_DIR . '/behaviour/behaviour.js'); Requirements::javascript(THIRDPARTY_DIR . '/jquery-cookie/jquery.cookie.js'); @@ -254,8 +258,12 @@ class LeftAndMain extends Controller { Requirements::css(THIRDPARTY_DIR . '/jstree/themes/apple/style.css'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.js'); + Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Panel.js'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Tree.js'); + Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Ping.js'); + Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Content.js'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.EditForm.js'); + Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Menu.js'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.AddForm.js'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Preview.js'); Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.BatchActions.js'); @@ -296,6 +304,9 @@ class LeftAndMain extends Controller { SAPPHIRE_ADMIN_DIR . '/thirdparty/jsizes/lib/jquery.sizes.js', SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jlayout.border.js', SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jquery.jlayout.js', + SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.js', + SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.adapter.jquery.js', + SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.html4.js', THIRDPARTY_DIR . '/jstree/jquery.jstree.js', SAPPHIRE_ADMIN_DIR . '/javascript/jquery-changetracker/lib/jquery.changetracker.js', SAPPHIRE_DIR . '/javascript/TreeDropdownField.js', @@ -310,8 +321,12 @@ class LeftAndMain extends Controller { 'leftandmain.js', array( SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.js', + SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Panel.js', SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Tree.js', + SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Ping.js', + SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Content.js', SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.EditForm.js', + SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Menu.js', SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.AddForm.js', SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Preview.js', SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.BatchActions.js', @@ -325,6 +340,20 @@ class LeftAndMain extends Controller { // TableListField.ss or Form.ss. SSViewer::set_theme(null); } + + function handleRequest($request) { + $title = $this->Title(); + + $response = parent::handleRequest($request); + $response->addHeader('X-Controller', $this->class); + $response->addHeader('X-Title', $title); + + return $response; + } + + function index($request) { + return ($this->isAjax()) ? $this->show($request) : $this->getViewer('index')->process($this); + } /** @@ -369,17 +398,20 @@ class LeftAndMain extends Controller { if(!$title) $title = preg_replace('/Admin$/', '', $class); return $title; } - + public function show($request) { // TODO Necessary for TableListField URLs to work properly if($request->param('ID')) $this->setCurrentPageID($request->param('ID')); if($this->isAjax()) { - SSViewer::setOption('rewriteHashlinks', false); - $form = $this->getEditForm($request->param('ID')); - $content = $form->formHtmlContent(); + if($request->getVar('cms-view-form')) { + $form = $this->getEditForm(); + $content = $form->forTemplate(); + } else { + // Rendering is handled by template, which will call EditForm() eventually + $content = $this->renderWith($this->getTemplatesWithSuffix('_Content')); + } } else { - // Rendering is handled by template, which will call EditForm() eventually $content = $this->renderWith($this->getViewer('show')); } @@ -534,7 +566,7 @@ class LeftAndMain extends Controller { $titleEval = ' "
  • ID\" data-id=\"$child->ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" . " " . - "Link(),0,-1), "show", $child->ID) . "\" title=\"' + "Link("show"), $child->ID) . "\" title=\"' . _t('LeftAndMain.PAGETYPE','Page type: ') . '".$child->class."\" > " . ($child->TreeTitle) . "" @@ -628,7 +660,7 @@ class LeftAndMain extends Controller { // write process might've changed the record, so we reload before returning $form = $this->getEditForm($record->ID); - return $form->formHtmlContent(); + return $form->forTemplate(); } public function delete($data, $form) { @@ -640,7 +672,7 @@ class LeftAndMain extends Controller { $record->delete(); if($this->isAjax()) { - return $this->EmptyForm()->formHtmlContent(); + return $this->EmptyForm()->forTemplate(); } else { $this->redirectBack(); } @@ -946,7 +978,7 @@ class LeftAndMain extends Controller { return $record->ID; } else if($this->isAjax()) { $form = $this->getEditForm($record->ID); - return $form->formHtmlContent(); + return $form->forTemplate(); } else { return $this->redirect(Controller::join_links($this->Link('show'), $record->ID)); } @@ -1070,12 +1102,17 @@ class LeftAndMain extends Controller { } /** - * Get the staus of a certain page and version. - * - * This function is used for concurrent editing, and providing alerts - * when multiple users are editing a single page. It echoes a json - * encoded string to the UA. + * URL to a previewable record which is shown through this controller. + * The controller might not have any previewable content, in which case + * this method returns FALSE. + * + * @return String|boolean */ + public function PreviewLink() { + $record = $this->getRecord($this->currentPageID()); + $baseLink = ($record && $record instanceof Page) ? $record->Link('?stage=Stage') : Director::absoluteBaseURL(); + return Controller::join_links($baseLink, '?cms-preview-disabled=1'); + } /** * Return the version number of this application. @@ -1142,6 +1179,13 @@ class LeftAndMain extends Controller { function getApplicationName() { return self::$application_name; } + + /** + * @return String + */ + function Title() { + return sprintf('%s | %s', $this->getApplicationName(), $this->SectionTitle()); + } /** * Return the title of the current section, as shown on the main menu @@ -1162,6 +1206,21 @@ class LeftAndMain extends Controller { return MCE_ROOT; } + /** + * Same as {@link ViewableData->CSSClasses()}, but with a changed name + * to avoid problems when using {@link ViewableData->customise()} + * (which always returns "ArrayData" from the $original object). + * + * @return String + */ + function BaseCSSClasses() { + return $this->CSSClasses(); + } + + function IsPreviewExpanded() { + return ($this->request->getVar('cms-preview-expanded')); + } + /** * Register the given javascript file as required in the CMS. * Filenames should be relative to the base, eg, SAPPHIRE_DIR . '/javascript/loader.js' diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index dae73578b..2ac480841 100755 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -664,7 +664,7 @@ class ModelAdmin_CollectionController extends Controller { $msg = _t('ModelAdmin.NORESULTS',"Your search didn't return any matching items"); } return new SS_HTTPResponse( - $resultsForm->formHtmlContent(), + $resultsForm->forTemplate(), 200, $msg ); @@ -794,7 +794,7 @@ class ModelAdmin_CollectionController extends Controller { */ function add($request) { return new SS_HTTPResponse( - $this->AddForm()->formHtmlContent(), + $this->AddForm()->forTemplate(), 200, sprintf( _t('ModelAdmin.ADDFORM', "Fill out this form to add a %s to the database."), @@ -845,6 +845,7 @@ class ModelAdmin_CollectionController extends Controller { $form = new Form($this, "AddForm", $fields, $actions, $validator); $form->loadDataFrom($newRecord); + $form->addExtraClass('cms-edit-form'); return $form; } @@ -862,7 +863,7 @@ class ModelAdmin_CollectionController extends Controller { $class = $this->parentController->getRecordControllerClass($this->getModelClass()); $recordController = new $class($this, $request, $model->ID); return new SS_HTTPResponse( - $recordController->EditForm()->formHtmlContent(), + $recordController->EditForm()->forTemplate(), 200, sprintf( _t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."), @@ -912,7 +913,7 @@ class ModelAdmin_RecordController extends Controller { function edit($request) { if ($this->currentRecord) { if($this->isAjax()) { - $this->response->setBody($this->EditForm()->formHtmlContent()); + $this->response->setBody($this->EditForm()->forTemplate()); $this->response->setStatusCode( 200, sprintf( @@ -924,10 +925,10 @@ class ModelAdmin_RecordController extends Controller { } else { // This is really quite ugly; to fix will require a change in the way that customise() works. :-( return $this->parentController->parentController->customise(array( - 'Right' => $this->parentController->parentController->customise(array( + 'Content' => $this->parentController->parentController->customise(array( 'EditForm' => $this->EditForm() - ))->renderWith(array("{$this->class}_right",'LeftAndMain_right')) - ))->renderWith(array('ModelAdmin','LeftAndMain')); + ))->renderWith(array("{$this->class}_Content",'ModelAdmin_Content', 'LeftAndMain_Content')) + ))->renderWith(array('ModelAdmin', 'LeftAndMain')); } } else { return _t('ModelAdmin.ITEMNOTFOUND', "I can't find that item"); @@ -963,6 +964,7 @@ class ModelAdmin_RecordController extends Controller { $form = new Form($this, "EditForm", $fields, $actions, $validator); $form->loadDataFrom($this->currentRecord); + $form->addExtraClass('cms-edit-form'); return $form; } @@ -1017,7 +1019,7 @@ class ModelAdmin_RecordController extends Controller { function view($request) { if($this->currentRecord) { $form = $this->ViewForm(); - return $form->formHtmlContent(); + return $form->forTemplate(); } else { return _t('ModelAdmin.ITEMNOTFOUND'); } diff --git a/admin/css/layout.css b/admin/css/layout.css index 4a4136147..773c6cc08 100755 --- a/admin/css/layout.css +++ b/admin/css/layout.css @@ -17,18 +17,21 @@ a img { border: none; } @charset "UTF-8"; html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; } -.cms-preview { width: 1px; overflow: hidden; } +.cms-preview { width: 1px; } .cms-preview .cms-preview-toggle { width: 10px; } .cms-preview iframe { width: 100%; height: 100%; } .cms-container { height: 100%; } .cms-menu { width: 250px; overflow: auto; } +.cms-menu .cms-panel-content { width: 250px; } +.cms-menu.collapsed { width: 40px; } -.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form { display: -moz-inline-box; -moz-box-orient: vertical; display: inline-block; vertical-align: middle; *vertical-align: auto; } -.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form { *display: inline; } +.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form, .cms-edit-form { display: -moz-inline-box; -moz-box-orient: vertical; display: inline-block; vertical-align: middle; *vertical-align: auto; } +.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form, .cms-edit-form { *display: inline; } -.cms-content-tools { width: 230px; padding: 10px; overflow: auto; } +.cms-content-tools { width: 230px; overflow: auto; } +.cms-content-tools .cms-panel-header, .cms-content-tools .cms-panel-content { padding: 10px; } .cms-content-form { overflow: auto; } @@ -38,6 +41,8 @@ html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; .cms-content-actions { padding: 10px; } +.cms-header { padding: 7px 0 7px 7px; } + .cms-logo { height: 30px; overflow: hidden; vertical-align: middle; } .cms-login-status { height: 30px; overflow: hidden; vertical-align: middle; } diff --git a/admin/css/screen.css b/admin/css/screen.css index 119229857..0ad99dcc2 100755 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -1,292 +1,638 @@ /** This file is the central collection of included modules, links to custom SCSS files, and any global SCSS variable definitions. DO NOT ADD stylesheet rules to this file directly! Note: By prefixing files with an underscore, they won't create individual CSS files. */ /** ----------------------------- Core Compass Libraries ------------------------------ */ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; outline: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } +/* line 17, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } -body { line-height: 1; color: black; background: white; } +/* line 20, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +body { line-height: 1; } +/* line 22, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ ol, ul { list-style: none; } -table { border-collapse: separate; border-spacing: 0; vertical-align: middle; } +/* line 24, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +table { border-collapse: collapse; border-spacing: 0; } +/* line 26, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ caption, th, td { text-align: left; font-weight: normal; vertical-align: middle; } -q, blockquote { quotes: "" ""; } -q:before, q:after, blockquote:before, blockquote:after { content: ""; } +/* line 28, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +q, blockquote { quotes: none; } +/* line 101, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +q:before, q:after, blockquote:before, blockquote:after { content: ""; content: none; } +/* line 30, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ a img { border: none; } -@charset "UTF-8"; +/* line 115, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } + /** ----------------------------- Theme ------------------------------ */ -/** This file contains the default theme definitions for the admin interface. @package sapphire @subpackage admin */ +/** This file contains the default theme definitions for the admin interface. Please put mostly SCSS variable definitions in here, and leave the actual styling to _style.scss and auxilliary files. */ /** ----------------------------------------------- Colours ------------------------------------------------ */ /** ----------------------------------------------- Typography ------------------------------------------------ */ /** ----------------------------------------------- Application Logo (CMS Logo) ------------------------------------------------ */ /** ----------------------------- CMS Components ------------------------------ */ /** This file defines the jstree base styling (see http://jstree.com), as well as any customizations (see bottom of file). The styles are usually added through jstree.js on DOM load, but we need it earlier in order to correctly display the uninitialized tree. */ +/* line 10, ../scss/_tree.scss */ .jstree ul { display: block; margin: 0 0 0 0; padding: 0 0 0 0; list-style-type: none; } +/* line 16, ../scss/_tree.scss */ .jstree li { display: block; margin: 0 0 0 0; padding: 0 0 0 0; list-style-type: none; display: block; min-height: 18px; line-height: 18px; white-space: nowrap; margin-left: 18px; min-width: 18px; } -.jstree ins { display: inline-block; text-decoration: none; width: 18px; height: 18px; margin: 0 0 0 0; padding: 0; } +/* line 28, ../scss/_tree.scss */ +.jstree ins { display: inline-block; text-decoration: none; width: 18px; height: 18px; margin: 0 0 0 0; padding: 0; float: left; } +/* line 37, ../scss/_tree.scss */ .jstree a { display: inline-block; line-height: 16px; height: 16px; color: black; white-space: nowrap; text-decoration: none; padding: 1px 2px; margin: 0; } +/* line 46, ../scss/_tree.scss */ .jstree a:focus { outline: none; } +/* line 50, ../scss/_tree.scss */ .jstree a > ins { height: 16px; width: 16px; } +/* line 54, ../scss/_tree.scss */ .jstree a > .jstree-icon { margin-right: 3px; } +/* line 62, ../scss/_tree.scss */ .jstree li.jstree-open > ul { display: block; } +/* line 65, ../scss/_tree.scss */ .jstree li.jstree-closed > ul { display: none; } +/* line 70, ../scss/_tree.scss */ .jstree li.disabled a { color: #aaaaaa; } +/* line 77, ../scss/_tree.scss */ .jstree-rtl a > .jstree-icon { margin-left: 3px; margin-right: 0; } +/* line 81, ../scss/_tree.scss */ .jstree-rtl li { margin-left: 0; margin-right: 18px; } +/* line 88, ../scss/_tree.scss */ .jstree-rtl > ul > li { margin-right: 0px; } +/* line 92, ../scss/_tree.scss */ .jstree > ul > li { margin-left: 0px; } +/* line 96, ../scss/_tree.scss */ #vakata-dragged { display: block; margin: 0 0 0 0; padding: 4px 4px 4px 24px; position: absolute; top: -2000px; line-height: 16px; z-index: 10000; } +/* line 105, ../scss/_tree.scss */ #vakata-contextmenu { display: block; visibility: hidden; left: 0; top: -200px; position: absolute; margin: 0; padding: 0; min-width: 180px; background: #ebebeb; border: 1px solid silver; z-index: 10000; *width: 180px; } +/* line 118, ../scss/_tree.scss */ #vakata-contextmenu ul { min-width: 180px; *width: 180px; } +/* line 121, ../scss/_tree.scss */ #vakata-contextmenu ul, #vakata-contextmenu li { margin: 0; padding: 0; list-style-type: none; display: block; } +/* line 127, ../scss/_tree.scss */ #vakata-contextmenu li { line-height: 20px; min-height: 20px; position: relative; padding: 0px; } +/* line 133, ../scss/_tree.scss */ #vakata-contextmenu li a { padding: 1px 6px; line-height: 17px; display: block; text-decoration: none; margin: 1px 1px 0 1px; } +/* line 140, ../scss/_tree.scss */ #vakata-contextmenu li ins { float: left; width: 16px; height: 16px; text-decoration: none; margin-right: 2px; } +/* line 147, ../scss/_tree.scss */ #vakata-contextmenu li a:hover, #vakata-contextmenu li.vakata-hover > a { background: gray; color: white; } +/* line 151, ../scss/_tree.scss */ #vakata-contextmenu li ul { display: none; position: absolute; top: -2px; left: 100%; background: #ebebeb; border: 1px solid gray; } +/* line 159, ../scss/_tree.scss */ #vakata-contextmenu .right { right: 100%; left: auto; } +/* line 163, ../scss/_tree.scss */ #vakata-contextmenu .bottom { bottom: -1px; top: auto; } +/* line 167, ../scss/_tree.scss */ #vakata-contextmenu li.vakata-separator { min-height: 0; height: 1px; line-height: 1px; font-size: 1px; overflow: hidden; margin: 0 2px; background: silver; /* border-top:1px solid #fefefe; */ padding: 0; } +/* line 177, ../scss/_tree.scss */ .jstree ul, .jstree li { display: block; margin: 0 0 0 0; padding: 0 0 0 0; list-style-type: none; } +/* line 183, ../scss/_tree.scss */ .jstree li { display: block; min-height: 18px; line-height: 18px; white-space: nowrap; margin-left: 18px; min-width: 18px; } +/* line 191, ../scss/_tree.scss */ .jstree-rtl li { margin-left: 0; margin-right: 18px; } +/* line 195, ../scss/_tree.scss */ .jstree > ul > li { margin-left: 0px; } +/* line 198, ../scss/_tree.scss */ .jstree-rtl > ul > li { margin-right: 0px; } +/* line 201, ../scss/_tree.scss */ .jstree ins { display: inline-block; text-decoration: none; width: 18px; height: 18px; margin: 0 0 0 0; padding: 0; } +/* line 209, ../scss/_tree.scss */ .jstree a { display: inline-block; line-height: 16px; height: 16px; color: black; white-space: nowrap; text-decoration: none; padding: 1px 2px; margin: 0; } +/* line 219, ../scss/_tree.scss */ .jstree a:focus { outline: none; } +/* line 222, ../scss/_tree.scss */ .jstree a > ins { height: 16px; width: 16px; } +/* line 226, ../scss/_tree.scss */ .jstree a > .jstree-icon { margin-right: 3px; } +/* line 229, ../scss/_tree.scss */ .jstree-rtl a > .jstree-icon { margin-left: 3px; margin-right: 0; } +/* line 233, ../scss/_tree.scss */ li.jstree-open > ul { display: block; } +/* line 236, ../scss/_tree.scss */ li.jstree-closed > ul { display: none; } +/* line 239, ../scss/_tree.scss */ #vakata-dragged ins { display: block; text-decoration: none; width: 16px; height: 16px; margin: 0 0 0 0; padding: 0; position: absolute; top: 4px; left: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-border-radius: 4px; } +/* line 253, ../scss/_tree.scss */ #vakata-dragged .jstree-ok { background: green; } +/* line 256, ../scss/_tree.scss */ #vakata-dragged .jstree-invalid { background: red; } +/* line 259, ../scss/_tree.scss */ #jstree-marker { padding: 0; margin: 0; font-size: 12px; overflow: hidden; height: 12px; width: 8px; position: absolute; top: -30px; z-index: 10001; background-repeat: no-repeat; display: none; background-color: transparent; text-shadow: 1px 1px 1px white; color: black; line-height: 10px; } +/* line 276, ../scss/_tree.scss */ #jstree-marker-line { padding: 0; margin: 0; line-height: 0%; font-size: 1px; overflow: hidden; height: 1px; width: 100px; position: absolute; top: -30px; z-index: 10000; background-repeat: no-repeat; display: none; background-color: #456c43; cursor: pointer; border: 1px solid #eeeeee; border-left: 0; -moz-box-shadow: 0px 0px 2px #666; -webkit-box-shadow: 0px 0px 2px #666; box-shadow: 0px 0px 2px #666; -moz-border-radius: 1px; border-radius: 1px; -webkit-border-radius: 1px; } +/* line 300, ../scss/_tree.scss */ .jstree .jstree-real-checkbox { display: none; } +/* line 303, ../scss/_tree.scss */ .jstree-themeroller .ui-icon { overflow: visible; } +/* line 306, ../scss/_tree.scss */ .jstree-themeroller a { padding: 0 2px; } +/* line 309, ../scss/_tree.scss */ .jstree-themeroller .jstree-no-icon { display: none; } +/* line 312, ../scss/_tree.scss */ .jstree .jstree-wholerow-real { position: relative; z-index: 1; } +/* line 316, ../scss/_tree.scss */ .jstree .jstree-wholerow-real li { cursor: pointer; } +/* line 319, ../scss/_tree.scss */ .jstree .jstree-wholerow-real a { border-left-color: transparent !important; border-right-color: transparent !important; } +/* line 323, ../scss/_tree.scss */ .jstree .jstree-wholerow { position: relative; z-index: 0; height: 0; } +/* line 328, ../scss/_tree.scss */ .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li { width: 100%; } +/* line 331, ../scss/_tree.scss */ .jstree .jstree-wholerow, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li, .jstree .jstree-wholerow a { margin: 0 !important; padding: 0 !important; } +/* line 335, ../scss/_tree.scss */ .jstree .jstree-wholerow, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li { background: transparent !important; } +/* line 338, ../scss/_tree.scss */ .jstree .jstree-wholerow ins, .jstree .jstree-wholerow span, .jstree .jstree-wholerow input { display: none !important; } +/* line 341, ../scss/_tree.scss */ .jstree .jstree-wholerow a, .jstree .jstree-wholerow a:hover { text-indent: -9999px !important; width: 100%; padding: 0 !important; border-right-width: 0px !important; border-left-width: 0px !important; } +/* line 348, ../scss/_tree.scss */ .jstree .jstree-wholerow-span { position: absolute; left: 0; margin: 0px; padding: 0; height: 18px; border-width: 0; padding: 0; z-index: 0; } +/* line 361, ../scss/_tree.scss */ .cms .jstree-apple.jstree-focused { background: none; } +/* line 364, ../scss/_tree.scss */ .cms .jstree-apple > ul { background: none; } +/* line 369, ../scss/_tree.scss */ +.jstree li { line-height: 25px; } + +/* line 373, ../scss/_tree.scss */ +.cms-tree.jstree-apple { /* comment speech bubble - ccs3 only - source: http://nicolasgallagher.com/pure-css-speech-bubbles/demo/ */ } +/* line 376, ../scss/_tree.scss */ +.cms-tree.jstree-apple li.Root strong { font-weight: bold; padding-left: 1px; } +/* line 381, ../scss/_tree.scss */ +.cms-tree.jstree-apple li.Root > a .jstree-icon { background-position: -56px -36px; } +/* line 386, ../scss/_tree.scss */ +.cms-tree.jstree-apple a, .cms-tree.jstree-apple a:link { color: #1556b2; padding: 3px 6px 3px 3px; border: none; display: inline-block; margin-right: 5px; } +/* line 394, ../scss/_tree.scss */ +.cms-tree.jstree-apple a span.status:after, .cms-tree.jstree-apple a:link span.status:after { clear: both; text-transform: uppercase; display: inline-block; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-right: 6px; margin-top: -1px; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; -o-border-radius: 2px / 2px; -ms-border-radius: 2px / 2px; -khtml-border-radius: 2px / 2px; border-radius: 2px / 2px; } +/* line 408, ../scss/_tree.scss */ +.cms-tree.jstree-apple span.modified:after { content: "draft"; color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; } +/* line 415, ../scss/_tree.scss */ +.cms-tree.jstree-apple span.new:after { content: "new"; color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; } +/* line 422, ../scss/_tree.scss */ +.cms-tree.jstree-apple span.private:after { content: "private"; color: #636363; border: 1px solid #E49393; background-color: #F2DADB; } +/* line 429, ../scss/_tree.scss */ +.cms-tree.jstree-apple span.workflow-approval:after { content: "awaiting approval"; color: #56660C; border: 1px solid #7C8816; background-color: #DAE79A; } +/* line 437, ../scss/_tree.scss */ +.cms-tree.jstree-apple span.comment-count { clear: both; position: relative; text-transform: uppercase; display: inline-block; overflow: visible; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-right: 6px; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; -o-border-radius: 2px / 2px; -ms-border-radius: 2px / 2px; -khtml-border-radius: 2px / 2px; border-radius: 2px / 2px; color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; } +/* line 454, ../scss/_tree.scss */ +.cms-tree.jstree-apple span.comment-count:before { content: ""; position: absolute; bottom: -4px; /* value = - border-top-width - border-bottom-width */ left: 3px; /* controls horizontal position */ border-width: 4px 4px 0; border-style: solid; border-color: #C9B800 transparent; /* reduce the damage in FF3.0 */ display: block; width: 0; } +/* line 467, ../scss/_tree.scss */ +.cms-tree.jstree-apple span.comment-count:after { content: ""; position: absolute; bottom: -3px; /* value = - border-top-width - border-bottom-width */ left: 4px; /* value = (:before left) + (:before border-left) - (:after border-left) */ border-width: 3px 3px 0; border-style: solid; border-color: #FFF0BC transparent; /* reduce the damage in FF3.0 */ display: block; width: 0; } +/* line 480, ../scss/_tree.scss */ +.cms-tree.jstree-apple .jstree-hovered { text-shadow: none; text-decoration: none; } +/* line 485, ../scss/_tree.scss */ +.cms-tree.jstree-apple li { padding: 0px; clear: left; } +/* line 490, ../scss/_tree.scss */ +.cms-tree.jstree-apple li, .cms-tree.jstree-apple ins { background-color: transparent; background-image: url(../images/sitetree_ss_default_icons.png); } +/* line 495, ../scss/_tree.scss */ +.cms-tree.jstree-apple li.jstree-checked a, .cms-tree.jstree-apple li.jstree-checked a:link { background-color: #efe999; } + +/* line 500, ../scss/_tree.scss */ +.jstree-apple #record-0.jstree-open > ins { display: none; } + +/* line 504, ../scss/_tree.scss */ +a .jstree-pageicon { display: block; float: left; width: 16px; height: 16px; margin-right: 4px; background-color: transparent; background-image: url(../images/sitetree_ss_pageclass_icons_default.png); background-repeat: no-repeat; } + +/* line 515, ../scss/_tree.scss */ +li.class-HomePage a .jstree-pageicon { background-position: 0 -48px; } + +/* line 519, ../scss/_tree.scss */ +li.class-RedirectorPage a .jstree-pageicon { background-position: 0 -16px; } + +/* line 523, ../scss/_tree.scss */ +li.class-VirtualPage a .jstree-pageicon { background-position: 0 -32px; } + +/* line 527, ../scss/_tree.scss */ +li.class-ErrorPage a .jstree-pageicon { background-position: 0 -112px; } + /** Styles for the left hand side menu @package sapphire @subpackage admin */ /** ------------------------------------------------------- CMS Menu Bar -------------------------------------------------------- */ -@charset "UTF-8"; -.cms-menu { z-index: 10; background: #c6d7df; border-right: 1px solid #8c99a1; width: 250px; overflow: auto; -moz-box-shadow: rgba(107, 120, 123, 0.5) 2px 0 6px 0; -webkit-box-shadow: rgba(107, 120, 123, 0.5) 2px 0 6px 0; -o-box-shadow: rgba(107, 120, 123, 0.5) 2px 0 6px 0; box-shadow: rgba(107, 120, 123, 0.5) 2px 0 6px 0; } +/* line 12, ../scss/_menu.scss */ +.cms-menu { z-index: 10; background: #c6d7df; border-right: 1px solid #8c99a1; width: 250px; overflow: auto; -moz-box-shadow: 2px 0 6px rgba(107, 120, 123, 0.5); -webkit-box-shadow: 2px 0 6px rgba(107, 120, 123, 0.5); -o-box-shadow: 2px 0 6px rgba(107, 120, 123, 0.5); box-shadow: 2px 0 6px rgba(107, 120, 123, 0.5); } +/* line 21, ../scss/_menu.scss */ .cms-menu a { text-decoration: none; } +/* line 25, ../scss/_menu.scss */ +.cms-menu .cms-panel-content { width: 250px; } +/* line 30, ../scss/_menu.scss */ +.cms-menu.collapsed { width: 40px; cursor: auto; } +/* line 36, ../scss/_menu.scss */ +.cms-menu.collapsed .cms-menu-list li span.text { display: none; } +/* line 39, ../scss/_menu.scss */ +.cms-menu.collapsed .cms-menu-list li ul { display: none; } +/* line 44, ../scss/_menu.scss */ +.cms-menu.collapsed.cms-panel .cms-panel-content { display: block; } -.cms-menu-list li a { display: block; height: 30px; line-height: 30px; vertical-align: middle; font-size: 13px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-shadow: #ced7dc 1px 1px 0; color: #1f1f1f; padding: 5px; background-color: #b0bec7; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #b0bec7), color-stop(100%, #98aab6)); background-image: -moz-linear-gradient(top, #b0bec7 0%, #98aab6 100%); background-image: linear-gradient(top, #b0bec7 0%, #98aab6 100%); border-top: 1px solid #ced7dc; border-bottom: 1px solid #748d9d; } -.cms-menu-list li a:hover { text-decoration: none; background-color: #b6c3cb; border-bottom: 1px solid #8399a7; color: #2c2c2c; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #bfcad2), color-stop(100%, #b0bec7)); background-image: -moz-linear-gradient(top, #bfcad2 0%, #b0bec7 100%); background-image: linear-gradient(top, #bfcad2 0%, #b0bec7 100%); } -.cms-menu-list li a:focus { border-top: 1px solid #a1b2bc; text-decoration: none; background-color: #a1b2bc; color: #393939; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #92a5b2), color-stop(100%, #a1b2bc)); background-image: -moz-linear-gradient(top, #92a5b2 0%, #a1b2bc 100%); background-image: linear-gradient(top, #92a5b2 0%, #a1b2bc 100%); } -.cms-menu-list li a .icon { display: block; float: left; margin-right: 4px; background: url('../images/icons-32.png') no-repeat; width: 32px; height: 32px; overflow: hidden; background-position: 0px 0px; } +/* line 54, ../scss/_menu.scss */ +.cms-menu-list li a { display: block; height: 30px; line-height: 30px; vertical-align: middle; font-size: 13px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-shadow: #bfcad2 1px 1px 0; color: #1f1f1f; padding: 5px 5px 5px 12px; background-color: #b0bec7; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b0bec7), color-stop(100%, #8ca1ae)); background-image: -webkit-linear-gradient(top, #b0bec7, #8ca1ae); background-image: -moz-linear-gradient(top, #b0bec7, #8ca1ae); background-image: -o-linear-gradient(top, #b0bec7, #8ca1ae); background-image: -ms-linear-gradient(top, #b0bec7, #8ca1ae); background-image: linear-gradient(top, #b0bec7, #8ca1ae); border-top: 1px solid #ced7dc; border-bottom: 1px solid #748d9d; } +/* line 76, ../scss/_menu.scss */ +.cms-menu-list li a:hover { text-decoration: none; background-color: #b6c3cb; border-bottom: 1px solid #8399a7; color: #2c2c2c; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #bfcad2), color-stop(100%, #b0bec7)); background-image: -webkit-linear-gradient(top, #bfcad2, #b0bec7); background-image: -moz-linear-gradient(top, #bfcad2, #b0bec7); background-image: -o-linear-gradient(top, #bfcad2, #b0bec7); background-image: -ms-linear-gradient(top, #bfcad2, #b0bec7); background-image: linear-gradient(top, #bfcad2, #b0bec7); } +/* line 87, ../scss/_menu.scss */ +.cms-menu-list li a:focus { border-top: 1px solid #a1b2bc; text-decoration: none; background-color: #a1b2bc; color: #393939; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #92a5b2), color-stop(100%, #a1b2bc)); background-image: -webkit-linear-gradient(top, #92a5b2, #a1b2bc); background-image: -moz-linear-gradient(top, #92a5b2, #a1b2bc); background-image: -o-linear-gradient(top, #92a5b2, #a1b2bc); background-image: -ms-linear-gradient(top, #92a5b2, #a1b2bc); background-image: linear-gradient(top, #92a5b2, #a1b2bc); } +/* line 99, ../scss/_menu.scss */ +.cms-menu-list li a .icon { display: block; float: left; margin-right: 4px; } +/* line 107, ../scss/_menu.scss */ .cms-menu-list li a .text { display: block; } -.cms-menu-list li.current a { color: white; text-shadow: #1e5270 0 -1px 0; border-top: 1px solid #55a4d2; border-bottom: 1px solid #1e5270; background-color: #338dc1; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #338dc1), color-stop(100%, #287099)); background-image: -moz-linear-gradient(top, #338dc1 0%, #287099 100%); background-image: linear-gradient(top, #338dc1 0%, #287099 100%); } +/* line 113, ../scss/_menu.scss */ +.cms-menu-list li.current a { color: white; text-shadow: #1e5270 0 -1px 0; border-top: 1px solid #55a4d2; border-bottom: 1px solid #1e5270; background-color: #338dc1; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #338dc1), color-stop(100%, #287099)); background-image: -webkit-linear-gradient(top, #338dc1, #287099); background-image: -moz-linear-gradient(top, #338dc1, #287099); background-image: -o-linear-gradient(top, #338dc1, #287099); background-image: -ms-linear-gradient(top, #338dc1, #287099); background-image: linear-gradient(top, #338dc1, #287099); } +/* line 125, ../scss/_menu.scss */ .cms-menu-list li.current ul { border-top: 1px solid #1e5270; } +/* line 129, ../scss/_menu.scss */ .cms-menu-list li.current li { background-color: #287099; } -.cms-menu-list li.current li a { font-size: 11px; padding: 0 10px 0 36px; height: 32px; line-height: 32px; color: #e2f0f7; background: none; border-top: 1px solid #338dc1; border-bottom: 1px solid #1e5270; } +/* line 132, ../scss/_menu.scss */ +.cms-menu-list li.current li a { font-size: 12px; padding: 0 10px 0 36px; height: 32px; line-height: 32px; color: #e2f0f7; background: none; border-top: 1px solid #338dc1; border-bottom: 1px solid #1e5270; } +/* line 143, ../scss/_menu.scss */ .cms-menu-list li.current li a.current, .cms-menu-list li.current li a:hover { background: #2e7ead; border-top: 1px solid #2e7ead; color: white; } +/* line 149, ../scss/_menu.scss */ .cms-menu-list li.current li a:focus { background: #236184; border-top: 1px solid #1e5270; color: white; } +/* line 157, ../scss/_menu.scss */ .cms-menu-list li.current li.current a { font-weight: bold; color: white; } +/* line 162, ../scss/_menu.scss */ .cms-menu-list li.current li.first a { border-top: none; } +/* line 169, ../scss/_menu.scss */ +.cms-menu-list li#Menu-CMSMain a .icon { background-position: 0px 0px; } +/* line 170, ../scss/_menu.scss */ +.cms-menu-list li#Menu-CMSMain.current a .icon, .cms-menu-list li#Menu-CMSMain a:hover .icon { background-position: -32px 0px; } +/* line 171, ../scss/_menu.scss */ +.cms-menu-list li#Menu-AssetAdmin a .icon { background-position: 0px -96px; } +/* line 172, ../scss/_menu.scss */ +.cms-menu-list li#Menu-AssetAdmin.current a .icon, .cms-menu-list li#Menu-AssetAdmin a:hover .icon { background-position: -32px -96px; } +/* line 173, ../scss/_menu.scss */ +.cms-menu-list li#Menu-SecurityAdmin a .icon { background-position: 0px -128px; } +/* line 174, ../scss/_menu.scss */ +.cms-menu-list li#Menu-SecurityAdmin.current a .icon, .cms-menu-list li#Menu-SecurityAdmin a:hover .icon { background-position: -32px -128px; } +/* line 175, ../scss/_menu.scss */ +.cms-menu-list li#Menu-CMSPagesController a .icon { background-position: 0px -32px; } +/* line 176, ../scss/_menu.scss */ +.cms-menu-list li#Menu-CMSPagesController.current a .icon, .cms-menu-list li#Menu-CMSPagesController a:hover .icon { background-position: -32px -32px; } +/* line 179, ../scss/_menu.scss */ +.cms-menu-list.collapsed li .text { display: none; } +/* line 183, ../scss/_menu.scss */ +.cms-menu-list.collapsed li > li { display: none; } /** This file defines common styles for form elements used throughout the CMS interface. It is an addition to the base styles defined in sapphire/css/Form.css. */ -.field { display: block; padding: 10px 0; border-bottom: 1px solid rgba(201, 205, 206, 0.8); } -.field label { float: left; width: 10em; } -.field .middleColumn { margin-left: 10em; } +/** ---------------------------------------------------- Basic form fields ---------------------------------------------------- */ +/* line 10, ../scss/_forms.scss */ +.field { display: block; padding: 10px 0; border-bottom: 1px solid rgba(201, 205, 206, 0.8); overflow: hidden; } +/* line 16, ../scss/_forms.scss */ +.field label.left { float: left; width: 170px; padding: 8px 20px 8px 4px; line-height: 16px; } +/* line 24, ../scss/_forms.scss */ +.field .middleColumn { margin-left: 15em; } +/* line 27, ../scss/_forms.scss */ .field .middleColumn .field { display: inline; padding: 0; border: none; } +/* line 33, ../scss/_forms.scss */ .field .middleColumn label { float: none; width: auto; } +/* line 42, ../scss/_forms.scss */ form.nostyle .field { display: inline; padding: 0; border: 0; } +/* line 48, ../scss/_forms.scss */ form.nostyle label { float: none; width: auto; } +/* line 53, ../scss/_forms.scss */ form.nostyle .middleColumn { margin-left: 0; } +/* line 59, ../scss/_forms.scss */ .field.nolabel .middleColumn { margin-left: 0; } -input, textarea { -moz-border-radius: 5px; -webkit-border-radius: 5px; -o-border-radius: 5px; -ms-border-radius: 5px; -khtml-border-radius: 5px; border-radius: 5px; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #eceff1), color-stop(50%, #ffffff), color-stop(100%, #eceff1)); background-image: -moz-linear-gradient(top, #eceff1 0%, #ffffff 50%, #eceff1 100%); background-image: linear-gradient(top, #eceff1 0%, #ffffff 50%, #eceff1 100%); border: 1px solid rgba(201, 205, 206, 0.8); padding: 3px; } +/* line 64, ../scss/_forms.scss */ +.field.text input, textarea { -moz-border-radius: 4px; -webkit-border-radius: 4px; -o-border-radius: 4px; -ms-border-radius: 4px; -khtml-border-radius: 4px; border-radius: 4px; background: #fff; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -webkit-linear-gradient(top, #efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -moz-linear-gradient(top, #efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -o-linear-gradient(top, #efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -ms-linear-gradient(top, #efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(top, #efefef, #ffffff 10%, #ffffff 90%, #efefef); border: 1px solid #b3b3b3; padding: 7px; } -input.loading, input.ui-state-default.loading, .ui-widget-content input.ui-state-default.loading, .ui-widget-header input.ui-state-default.loading { padding-left: 16px; background: #eceff1 url(../../images/network-save.gif) no-repeat center left; } - -.ss-ui-button.ss-ui-action-constructive, .ui-widget-content .ss-ui-button.ss-ui-action-constructive, .ui-widget-header .ss-ui-button.ss-ui-action-constructive { background: none; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #77b53f), color-stop(100%, #456925)); background-image: -moz-linear-gradient(top, #77b53f 0%, #456925 100%); background-image: linear-gradient(top, #77b53f 0%, #456925 100%); color: white; } - -.ss-ui-button.ss-ui-action-destructive, .ui-widget-content .ss-ui-button.ss-ui-action-destructive, .ui-widget-header .ss-ui-button.ss-ui-action-destructive { color: red; } - -.ss-ui-button.ss-ui-action-minor, .ui-widget-content .ss-ui-button.ss-ui-action-minor .ui-widget-header .ss-ui-button.ss-ui-action-minor { background: none; padding: 0; border: 0; color: #1f1f1f; text-decoration: underline; } +/** ---------------------------------------------------- Buttons ---------------------------------------------------- */ +/* line 84, ../scss/_forms.scss */ +.cms input.loading, .cms input.ui-state-default.loading, .cms .ui-widget-content input.ui-state-default.loading, .cms .ui-widget-header input.ui-state-default.loading { padding-left: 16px; background: #eceff1 url(../../images/network-save.gif) no-repeat center left; } +/* line 91, ../scss/_forms.scss */ +.cms .ss-ui-button.ss-ui-action-constructive, .cms .ui-widget-content .ss-ui-button.ss-ui-action-constructive, .cms .ui-widget-header .ss-ui-button.ss-ui-action-constructive { padding-left: 23px; color: white; border-color: #118021; background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #84be3f), color-stop(100%, #128945)); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -webkit-linear-gradient(#84be3f, #128945); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -moz-linear-gradient(#84be3f, #128945); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -o-linear-gradient(#84be3f, #128945); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -ms-linear-gradient(#84be3f, #128945); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, linear-gradient(#84be3f, #128945); background-color: #84be3f; text-shadow: #475964 1px 1px 0; -moz-box-shadow: #748d9d 1px 1px 2px; -webkit-box-shadow: #748d9d 1px 1px 2px; -o-box-shadow: #748d9d 1px 1px 2px; box-shadow: #748d9d 1px 1px 2px; } +/* line 106, ../scss/_forms.scss */ +.cms .ss-ui-button.ss-ui-action-constructive.ui-state-hover { background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #7fb63c), color-stop(100%, #107b3e)); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -webkit-linear-gradient(#7fb63c, #107b3e); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -moz-linear-gradient(#7fb63c, #107b3e); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -o-linear-gradient(#7fb63c, #107b3e); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, -ms-linear-gradient(#7fb63c, #107b3e); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px 6px, linear-gradient(#7fb63c, #107b3e); background-color: #128945; -moz-box-shadow: #92a5b2 1px 1px 1px; -webkit-box-shadow: #92a5b2 1px 1px 1px; -o-box-shadow: #92a5b2 1px 1px 1px; box-shadow: #92a5b2 1px 1px 1px; } +/* line 117, ../scss/_forms.scss */ +.cms .ss-ui-button.ss-ui-action-constructive.cms-page-add-button { background-position: 5px -155px; } +/* line 123, ../scss/_forms.scss */ +.cms .ss-ui-button.ss-ui-action-destructive, .cms .ui-widget-content .ss-ui-button.ss-ui-action-destructive, .cms .ui-widget-header .ss-ui-button.ss-ui-action-destructive { color: red; background-color: #f5f5f5; } +/* line 128, ../scss/_forms.scss */ +.cms .ss-ui-button.ss-ui-action-destructive.delete { padding-left: 23px; color: red; border-color: #ababab; background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f5f5f5), color-stop(100%, #c3c3c3)); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -webkit-linear-gradient(#f5f5f5, #c3c3c3); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -moz-linear-gradient(#f5f5f5, #c3c3c3); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -o-linear-gradient(#f5f5f5, #c3c3c3); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -ms-linear-gradient(#f5f5f5, #c3c3c3); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, linear-gradient(#f5f5f5, #c3c3c3); text-shadow: none; -moz-box-shadow: #aab9c3 1px 1px 2px; -webkit-box-shadow: #aab9c3 1px 1px 2px; -o-box-shadow: #aab9c3 1px 1px 2px; box-shadow: #aab9c3 1px 1px 2px; } +/* line 142, ../scss/_forms.scss */ +.cms .ss-ui-button.ss-ui-action-destructive.delete.ui-state-hover { background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f0f0f0), color-stop(100%, #bbbbbb)); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -webkit-linear-gradient(#f0f0f0, #bbbbbb); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -moz-linear-gradient(#f0f0f0, #bbbbbb); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -o-linear-gradient(#f0f0f0, #bbbbbb); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, -ms-linear-gradient(#f0f0f0, #bbbbbb); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 5px -26px, linear-gradient(#f0f0f0, #bbbbbb); background-color: #c3c3c3; -moz-box-shadow: #92a5b2 1px 1px 1px; -webkit-box-shadow: #92a5b2 1px 1px 1px; -o-box-shadow: #92a5b2 1px 1px 1px; box-shadow: #92a5b2 1px 1px 1px; } +/* line 155, ../scss/_forms.scss */ +.cms .ss-ui-button.ss-ui-action-minor, .cms .ui-widget-content .ss-ui-button.ss-ui-action-minor .ui-widget-header .ss-ui-button.ss-ui-action-minor { background: none; padding: 0; border: 0; color: #1f1f1f; text-decoration: underline; } +/* line 164, ../scss/_forms.scss */ .cms-edit-form { padding-bottom: 20px; } -.cms-edit-form .text input, .cms-edit-form textarea { width: 300px; } +/* line 167, ../scss/_forms.scss */ +.cms-edit-form .text input, .cms-edit-form textarea { width: 300px; font-family: Arial, sans-serif; font-size: 13px; } +/* line 173, ../scss/_forms.scss */ .cms-edit-form .Actions { text-align: right; } -.cms-content-tools .field label { float: none; width: auto; } -.cms-content-tools .field .middleColumn { margin-left: 0; } +/** ---------------------------------------------------- Specific field overrides ---------------------------------------------------- */ +/* line 183, ../scss/_forms.scss */ +.htmleditor label { display: block; float: none; padding-bottom: 10px; } +/* line 189, ../scss/_forms.scss */ +.htmleditor .middleColumn { margin-left: 0px; clear: left; } +/* line 196, ../scss/_forms.scss */ .field#ViewerGroups label, .field#EditorGroups label, .field#CreateTopLevelGroups label { display: none; } +/* line 201, ../scss/_forms.scss */ +.action-hidden { display: none; } + /** This file defines CMS-specific customizations to the jQuery UI theme. Every rule in this file should be wrapped in the '.cms' selector (to make it more specific), and contain ONLY overwritten jQuery UI rules (with 'ui-' prefix). This file should be fairly short, as we're using our own custom jQuery UI theme already. TODO Add theme reference Use _style.scss to add more generic style information, and read the jQuery UI theming API: http://jqueryui.com/docs/Theming/API */ +/* line 14, ../scss/_uitheme.scss */ .cms .ui-tabs { padding: 0; } +/* line 17, ../scss/_uitheme.scss */ .cms .ui-tabs .ui-widget-header { border: 0; background: none; } -.cms .ui-widget-content, .cms .ui-tabs .ui-tabs-panel { color: #444444; font-size: 1em; border: 0; background: #eceff1; } +/* line 22, ../scss/_uitheme.scss */ +.cms .ui-tabs .ui-tabs-nav { margin: 0; padding: 0; } +/* line 26, ../scss/_uitheme.scss */ +.cms .ui-tabs .ui-tabs-nav li { top: 0; } +/* line 29, ../scss/_uitheme.scss */ +.cms .ui-tabs .ui-tabs-nav li a { padding: 0 15px; } +/* line 34, ../scss/_uitheme.scss */ +.cms .ui-tabs .ui-tabs-nav.ui-state-active { border-color: gray; } +/* line 41, ../scss/_uitheme.scss */ +.cms .ui-widget-content, .cms .ui-tabs .ui-tabs-panel { color: #444444; font-size: 13px; border: 0; } +/* line 47, ../scss/_uitheme.scss */ .cms .ui-widget-header { background: #eceff1; border: 0; padding: 0; } +/* line 53, ../scss/_uitheme.scss */ .cms .ss-ui-button { padding: 5px; text-decoration: none; } +/* line 59, ../scss/_uitheme.scss */ +.cms .ui-state-hover { cursor: pointer; } +/* line 65, ../scss/_uitheme.scss */ +.cms .ss-ui-button, .cms .ui-widget-content .ss-ui-button, .cms .ui-widget-header .ss-ui-button { padding: 5px 7px 5px 7px; color: #1f1f1f; background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 999px 999px, -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f5f5f5), color-stop(100%, #c3c3c3)); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 999px 999px, -webkit-linear-gradient(#f5f5f5, #c3c3c3); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 999px 999px, -moz-linear-gradient(#f5f5f5, #c3c3c3); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 999px 999px, -o-linear-gradient(#f5f5f5, #c3c3c3); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 999px 999px, -ms-linear-gradient(#f5f5f5, #c3c3c3); background: url('../images/../images/btn_icons_sprite.png?1310018832') no-repeat 999px 999px, linear-gradient(#f5f5f5, #c3c3c3); background-color: #f5f5f5; -moz-box-shadow: #748d9d 1px 1px 2px; -webkit-box-shadow: #748d9d 1px 1px 2px; -o-box-shadow: #748d9d 1px 1px 2px; box-shadow: #748d9d 1px 1px 2px; } -/** This file defines the 'theme' of the CMS: Colors, fonts, backgrounds, and detailed alignments. Together with _layout.css it provides the presentational backbone to the CMS. Ideally a developer should be able to exchange this file with his own theme easily. Please don't put any dimension, display or float information on major structural components like '.cms-container' or '.cms-header' in here, use the _layout.scss file instead. Use SCSS variable definitions in screen.css to avoid repeating styles like background colours or padding dimensions. See _colours.scss to get started. To avoid this file getting too large and complicated, it is encouraged to create new SCSS files for larger components like the CMS menu or tree (see _tree.scss and _menu.scss). */ +/* line 80, ../scss/_uitheme.scss */ +.cms-content-form { overflow: auto; background: transparent url(../images/textures/bg_cms_main_content.png) repeat top left !important; } + +/** This file defines most styles of the CMS: Colors, fonts, backgrounds, alignments, dimensions. Use SCSS variable definitions in screen.css to avoid repeating styles like background colours or padding dimensions. See themes/_default.scss to get started. To avoid this file getting too large and complicated, it is encouraged to create new SCSS files for larger components like the CMS menu or tree (see _tree.scss and _menu.scss). */ /** ---------------------------------------------------- Core Styles ---------------------------------------------------- */ -html, body { width: 100%; height: 100%; overflow: hidden; font-size: 13px; font-family: Verdana, Arial, sans-serif; color: #444444; } -html html, html body, html div, html span, html applet, html object, html iframe, html h1, html h2, html h3, html h4, html h5, html h6, html p, html blockquote, html pre, html a, html abbr, html acronym, html address, html big, html cite, html code, html del, html dfn, html em, html font, html img, html ins, html kbd, html q, html s, html samp, html small, html strike, html strong, html sub, html sup, html tt, html var, html dl, html dt, html dd, html ol, html ul, html li, html fieldset, html form, html label, html legend, html table, html caption, html tbody, html tfoot, html thead, html tr, html th, html td, body html, body body, body div, body span, body applet, body object, body iframe, body h1, body h2, body h3, body h4, body h5, body h6, body p, body blockquote, body pre, body a, body abbr, body acronym, body address, body big, body cite, body code, body del, body dfn, body em, body font, body img, body ins, body kbd, body q, body s, body samp, body small, body strike, body strong, body sub, body sup, body tt, body var, body dl, body dt, body dd, body ol, body ul, body li, body fieldset, body form, body label, body legend, body table, body caption, body tbody, body tfoot, body thead, body tr, body th, body td { margin: 0; padding: 0; border: 0; outline: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } -html body, body body { line-height: 1; color: black; background: white; } +/* line 15, ../scss/_style.scss */ +html, body { width: 100%; height: 100%; overflow: hidden; font-size: 13px; font-family: Arial, sans-serif; color: #444444; } +/* line 17, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +html html, html body, html div, html span, html applet, html object, html iframe, html h1, html h2, html h3, html h4, html h5, html h6, html p, html blockquote, html pre, html a, html abbr, html acronym, html address, html big, html cite, html code, html del, html dfn, html em, html img, html ins, html kbd, html q, html s, html samp, html small, html strike, html strong, html sub, html sup, html tt, html var, html b, html u, html i, html center, html dl, html dt, html dd, html ol, html ul, html li, html fieldset, html form, html label, html legend, html table, html caption, html tbody, html tfoot, html thead, html tr, html th, html td, html article, html aside, html canvas, html details, html embed, html figure, html figcaption, html footer, html header, html hgroup, html menu, html nav, html output, html ruby, html section, html summary, html time, html mark, html audio, html video, body html, body body, body div, body span, body applet, body object, body iframe, body h1, body h2, body h3, body h4, body h5, body h6, body p, body blockquote, body pre, body a, body abbr, body acronym, body address, body big, body cite, body code, body del, body dfn, body em, body img, body ins, body kbd, body q, body s, body samp, body small, body strike, body strong, body sub, body sup, body tt, body var, body b, body u, body i, body center, body dl, body dt, body dd, body ol, body ul, body li, body fieldset, body form, body label, body legend, body table, body caption, body tbody, body tfoot, body thead, body tr, body th, body td, body article, body aside, body canvas, body details, body embed, body figure, body figcaption, body footer, body header, body hgroup, body menu, body nav, body output, body ruby, body section, body summary, body time, body mark, body audio, body video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } +/* line 20, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +html body, body body { line-height: 1; } +/* line 22, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ html ol, html ul, body ol, body ul { list-style: none; } -html table, body table { border-collapse: separate; border-spacing: 0; vertical-align: middle; } +/* line 24, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +html table, body table { border-collapse: collapse; border-spacing: 0; } +/* line 26, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ html caption, html th, html td, body caption, body th, body td { text-align: left; font-weight: normal; vertical-align: middle; } -html q, html blockquote, body q, body blockquote { quotes: "" ""; } -html q:before, html q:after, html blockquote:before, html blockquote:after, body q:before, body q:after, body blockquote:before, body blockquote:after { content: ""; } +/* line 28, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +html q, html blockquote, body q, body blockquote { quotes: none; } +/* line 101, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +html q:before, html q:after, html blockquote:before, html blockquote:after, body q:before, body q:after, body blockquote:before, body blockquote:after { content: ""; content: none; } +/* line 30, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ html a img, body a img { border: none; } +/* line 115, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +html article, html aside, html details, html figcaption, html figure, html footer, html header, html hgroup, html menu, html nav, html section, body article, body aside, body details, body figcaption, body figure, body footer, body header, body hgroup, body menu, body nav, body section { display: block; } +/* line 27, ../scss/_style.scss */ a { color: #3ebae0; text-decoration: none; } +/* line 32, ../scss/_style.scss */ a:hover, a:focus { text-decoration: underline; } -body .ui-widget { font-size: 13px; } +/* line 36, ../scss/_style.scss */ +body .ui-widget { font-family: Arial, sans-serif; font-size: 13px; } +/* line 41, ../scss/_style.scss */ .cms-container { height: 100%; } -.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form { display: -moz-inline-box; -moz-box-orient: vertical; display: inline-block; vertical-align: middle; *vertical-align: auto; } -.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form { *display: inline; } +/* line 51, ../scss/_style.scss */ +.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-fields, .cms-edit-form { display: -moz-inline-box; -moz-box-orient: vertical; display: inline-block; vertical-align: middle; *vertical-align: auto; } +/* line 7, ../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.11.4/frameworks/compass/stylesheets/compass/css3/_inline-block.scss */ +.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-fields, .cms-edit-form { *display: inline; } +/* line 56, ../scss/_style.scss */ strong { font-weight: bold; } -.cms-content-header { background-color: #b1bec6; padding: 8px; height: 32px; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #b1bec6), color-stop(100%, #94a5b0)); background-image: -moz-linear-gradient(top, #b1bec6 0%, #94a5b0 100%); background-image: linear-gradient(top, #b1bec6 0%, #94a5b0 100%); } -.cms-content-header h2 { float: left; padding: 8px; font-size: 14px; font-weight: bold; width: 230px; } +/** -------------------------------------------- Misc Panels -------------------------------------------- */ +/* line 64, ../scss/_style.scss */ +.cms-content-header { background-color: #b0bec7; padding: 8px 8px 6px 8px; height: 32px; border-bottom: 2px solid #8399a7; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dde3e7), color-stop(100%, #92a5b2)); background-image: -webkit-linear-gradient(top, #dde3e7, #92a5b2); background-image: -moz-linear-gradient(top, #dde3e7, #92a5b2); background-image: -o-linear-gradient(top, #dde3e7, #92a5b2); background-image: -ms-linear-gradient(top, #dde3e7, #92a5b2); background-image: linear-gradient(top, #dde3e7, #92a5b2); border-bottom: 1px solid #5c7382; padding: 10px; height: 32px; } +/* line 80, ../scss/_style.scss */ +.cms-content-header h2 { float: left; padding: 12px 0 0 8px; font-size: 13px; font-weight: bold; text-shadow: #ced7dc 1px 1px 0; width: 230px; } +/* line 89, ../scss/_style.scss */ .cms-content-header > div { width: 9999em; overflow: hidden; } +/* line 94, ../scss/_style.scss */ .cms-content-header .cms-content-header-tabs { float: left; } -.ui-tabs .cms-content-header .ui-tabs-nav li { height: 40px; } -.ui-tabs .cms-content-header .ui-tabs-nav li a { font-weight: bold; font-size: 11px; padding-top: 8px; } -.ui-tabs .cms-content-header .ui-state-default, .ui-tabs .cms-content-header .ui-widget-content .ui-state-default, .ui-tabs .cms-content-header .ui-widget-header .ui-state-default { background-color: #feffff; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #feffff), color-stop(100%, #d6d9da)); background-image: -moz-linear-gradient(top, #feffff 0%, #d6d9da 100%); background-image: linear-gradient(top, #feffff 0%, #d6d9da 100%); } +/* line 101, ../scss/_style.scss */ +.ui-tabs .cms-content-header .ui-tabs-nav li a { font-weight: bold; font-size: 13px; padding: 11px 15px 9px; border-bottom: 2px solid #b3b3b3; } +/* line 111, ../scss/_style.scss */ +.ui-tabs .cms-content-header .ui-state-default, .ui-tabs .cms-content-header .ui-widget-content .ui-state-default, .ui-tabs .cms-content-header .ui-widget-header .ui-state-default { background-color: #d9d9d9; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #d9d9d9), color-stop(100%, #c0c0c0)); background-image: -webkit-linear-gradient(top, #d9d9d9, #c0c0c0); background-image: -moz-linear-gradient(top, #d9d9d9, #c0c0c0); background-image: -o-linear-gradient(top, #d9d9d9, #c0c0c0); background-image: -ms-linear-gradient(top, #d9d9d9, #c0c0c0); background-image: linear-gradient(top, #d9d9d9, #c0c0c0); border-color: #a6a6a6; margin: 0 3px 0 0; text-shadow: white 0 1px 0; } +/* line 125, ../scss/_style.scss */ .ui-tabs .cms-content-header .ui-state-active, .ui-tabs .cms-content-header .ui-widget-content .ui-state-active, .ui-tabs .cms-content-header .ui-widget-header .ui-state-active { background: #eceff1; } +/* line 128, ../scss/_style.scss */ +.ui-tabs .cms-content-header .ui-state-active a, .ui-tabs .cms-content-header .ui-widget-content .ui-state-active a, .ui-tabs .cms-content-header .ui-widget-header .ui-state-active a { border-bottom: 2px solid #eceff1; } -.cms-content-tools { background-color: #dde3e6; padding: 10px; width: 230px; overflow: auto; } +/* line 134, ../scss/_style.scss */ +.cms-content-tools { background-color: #dde3e7; padding: 8px; width: 230px; overflow: auto; } +/* line 141, ../scss/_style.scss */ +.cms-content-tools .cms-panel-header, .cms-content-tools .cms-panel-content { padding: 10px; } + +/* line 147, ../scss/_style.scss */ +.cms-content.loading { background: url(../images/spinner.gif) no-repeat 50% 50%; } /** ------------------------------------------------------- Top Left Header and logo area -------------------------------------------------------- */ -.cms-header { background-color: #00111d; position: relative; padding: 16px 8px 8px; line-height: 24px; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #00111d), color-stop(50%, #003050), color-stop(100%, #00111d)); background-image: -moz-linear-gradient(top, #00111d 0%, #003050 50%, #00111d 100%); background-image: linear-gradient(top, #00111d 0%, #003050 50%, #00111d 100%); } +/* line 156, ../scss/_style.scss */ +.cms-header { background-color: #00111d; position: relative; padding: 16px 8px 8px; line-height: 24px; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #00111d), color-stop(50%, #003050), color-stop(100%, #00111d)); background-image: -webkit-linear-gradient(top, #00111d, #003050, #00111d); background-image: -moz-linear-gradient(top, #00111d, #003050, #00111d); background-image: -o-linear-gradient(top, #00111d, #003050, #00111d); background-image: -ms-linear-gradient(top, #00111d, #003050, #00111d); background-image: linear-gradient(top, #00111d, #003050, #00111d); } +/* line 168, ../scss/_style.scss */ .cms-header span { color: white; white-space: no-wrap; text-overflow: ellipsis; display: block; } +/* line 174, ../scss/_style.scss */ .cms-header span a { color: #3ebae0; display: inline; } +/* line 181, ../scss/_style.scss */ .cms-logo { border-bottom: 1px solid #03090c; height: 31px; overflow: hidden; padding: 0 4px; vertical-align: middle; } +/* line 188, ../scss/_style.scss */ .cms-logo .version { display: none; } -.cms-logo a { display: inline-block; height: 25px; width: 25px; float: left; margin-right: 10px; background: url('../images/logo_small.png?1305762003') no-repeat; text-indent: -9999em; } +/* line 192, ../scss/_style.scss */ +.cms-logo a { display: inline-block; height: 25px; width: 25px; float: left; margin-right: 10px; background: url('../images/logo_small.png?1309939638') no-repeat; text-indent: -9999em; } -.cms-login-status { border-top: 1px solid #19435c; height: 23px; padding: 8px 0 0 14px; overflow: hidden; line-height: 16px; font-size: 11px; } -.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin-right: 10px; background: url(../images/logout.png) no-repeat; text-indent: -9999em; } +/* line 203, ../scss/_style.scss */ +.cms-login-status { border-top: 1px solid #19435c; height: 23px; padding: 8px 4px 0 4px; overflow: hidden; line-height: 16px; font-size: 11px; } +/* line 211, ../scss/_style.scss */ +.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 15px 0 5px; background: url(../images/logout.png) no-repeat; text-indent: -9999em; } +/** ----------------------------------------------- Loading Screen ------------------------------------------------ */ +/* line 226, ../scss/_style.scss */ .ss-loading-screen, .ss-loading-screen .loading-logo { width: 100%; height: 100%; overflow: hidden; position: absolute; background: #fff; background: -moz-radial-gradient(50% 50% 180deg, circle cover, white, #efefef, #c7c7c7 100%); background: -webkit-gradient(radial, 50% 50%, 350, 50% 50%, 0, from(#e3e3e3), to(white)); z-index: 100000; margin: 0; padding: 0; } +/* line 241, ../scss/_style.scss */ .ss-loading-screen .loading-logo { background-url: url(../images/logo.gif); background-repeat: no-repeat; background-color: transparent; background-position: 50% 50%; } +/* line 247, ../scss/_style.scss */ .ss-loading-screen p { width: 100%; text-align: center; position: absolute; bottom: 80px; } +/* line 253, ../scss/_style.scss */ .ss-loading-screen p span.notice { display: inline-block; font-size: 14px; padding: 10px 20px; color: #dc7f00; border: none; -moz-border-radius: 5px; -webkit-border-radius: 5px; -o-border-radius: 5px; -ms-border-radius: 5px; -khtml-border-radius: 5px; border-radius: 5px; } +/* line 263, ../scss/_style.scss */ .ss-loading-screen .loading-animation { display: none; position: absolute; left: 49%; top: 75%; } -.cms-content-actions { padding: 8px; } +/** -------------------------------------------- Actions -------------------------------------------- */ +/* line 275, ../scss/_style.scss */ +.cms-content-actions { border-top: 1px solid #8399a7; padding: 8px; background: transparent url(../images/textures/bg_cms_main_content.png) repeat top left; } -/** Messages (see sapphire/css/Form.css) */ +/** -------------------------------------------- Messages -------------------------------------------- */ +/* line 285, ../scss/_style.scss */ .message { margin: 1em 0; padding: 0.5em; font-weight: bold; border: 1px black solid; } +/* line 291, ../scss/_style.scss */ .message.notice { background-color: #ffbe66; border-color: #ff9300; } +/* line 296, ../scss/_style.scss */ .message.warning { background-color: #ffbe66; border-color: #ff9300; } +/* line 300, ../scss/_style.scss */ .message.error { background-color: #ffbe66; border-color: #ff9300; } +/** -------------------------------------------- ModelAdmin -------------------------------------------- */ +/* line 311, ../scss/_style.scss */ .ModelAdmin .cms-content-tools { width: 300px; } +/* line 316, ../scss/_style.scss */ .ModelAdmin .ResultAssemblyBlock { display: none; } +/** -------------------------------------------- "Add page" dialog -------------------------------------------- */ +/* line 326, ../scss/_style.scss */ .cms-page-add-form-dialog #PageType li { clear: left; height: 40px; border-bottom: 1px solid rgba(107, 120, 123, 0.5); } +/* line 331, ../scss/_style.scss */ .cms-page-add-form-dialog #PageType li:hover, .cms-page-add-form-dialog #PageType li.selected { background-color: #ffff99; } +/* line 335, ../scss/_style.scss */ .cms-page-add-form-dialog #PageType li input, .cms-page-add-form-dialog #PageType li label, .cms-page-add-form-dialog #PageType li .icon, .cms-page-add-form-dialog #PageType li .title { float: left; } +/* line 339, ../scss/_style.scss */ .cms-page-add-form-dialog #PageType li .icon { width: 20px; } +/* line 343, ../scss/_style.scss */ .cms-page-add-form-dialog #PageType li .title { width: 100px; font-weight: bold; } +/* line 348, ../scss/_style.scss */ .cms-page-add-form-dialog #PageType li .description { font-style: italic; } -.cms-content-toolbar > * { display: inline-block; } +/** -------------------------------------------- Content toolbar -------------------------------------------- */ +/* line 358, ../scss/_style.scss */ +.cms-content-toolbar { overflow: hidden; *zoom: 1; display: block; padding: 10px 0; margin: 0 0 15px 0; border-bottom-width: 2px; border-bottom: 2px groove rgba(255, 255, 255, 0.8); -webkit-border-image: url(../images/textures/bg_fieldset_elements_border.png) 2 stretch stretch; border-image: url(../images/textures/bg_fieldset_elements_border.png) 2 stretch stretch; } +/* line 369, ../scss/_style.scss */ +.cms-content-toolbar > * { float: left; } +/* line 373, ../scss/_style.scss */ .cms-content-toolbar .cms-tree-view-modes * { display: inline-block; } +/* line 377, ../scss/_style.scss */ .cms-content-toolbar .cms-content-batchactions form > * { display: inline-block; } -.cms-preview { width: 1px; overflow: hidden; } +/* line 384, ../scss/_style.scss */ +.cms-content-tools .field label { float: none; width: auto; } +/* line 389, ../scss/_style.scss */ +.cms-content-tools .field .middleColumn { margin-left: 0; } + +/* line 395, ../scss/_style.scss */ +.cms-content-batchactions, .cms-content-constructive-actions { float: right; } + +/* line 399, ../scss/_style.scss */ +.cms-content-batchactions { float: right; position: relative; display: block; margin-right: 8px; } + +/* line 406, ../scss/_style.scss */ +form.cms-batch-actions { float: left; } + +/* line 410, ../scss/_style.scss */ +.cms-content-constructive-actions a { display: block; float: right; } + +/** -------------------------------------------- Preview header (remove before release) -------------------------------------------- */ +/* line 419, ../scss/_style.scss */ +.cms-preview { width: 1px; } +/* line 422, ../scss/_style.scss */ .cms-preview .cms-preview-toggle { width: 10px; } +/* line 426, ../scss/_style.scss */ .cms-preview iframe { width: 100%; height: 100%; } +/* line 432, ../scss/_style.scss */ .cms-preview-header { background-color: #FFBE66; padding: 10px; font-weight: bold; } +/** -------------------------------------------- Member Profile -------------------------------------------- */ +/* line 444, ../scss/_style.scss */ form.member-profile-form #CsvFile .middleColumn { background: none !important; } +/* line 448, ../scss/_style.scss */ form.member-profile-form .advanced h4 { margin-bottom: .5em; } +/* line 452, ../scss/_style.scss */ form.member-profile-form .Actions { text-align: left; border: 0; } +/* line 457, ../scss/_style.scss */ form.member-profile-form input.customFormat { border: 1px solid #ccc !important; padding: 3px; margin-left: 2px; } +/* line 462, ../scss/_style.scss */ form.member-profile-form .formattingHelpToggle { font-size: 11px; padding: 3px; } +/* line 466, ../scss/_style.scss */ form.member-profile-form .formattingHelpText { margin: 5px auto; color: #333; padding: 5px 10px; width: 90%; background: #fff; border: 1px solid #ccc; } +/* line 474, ../scss/_style.scss */ form.member-profile-form .formattingHelpText ul { padding: 0; } +/* line 477, ../scss/_style.scss */ form.member-profile-form .formattingHelpText li { font-size: 11px; color: #333; margin-bottom: 2px; } -.cms-content-form { overflow: auto; } +/* line 485, ../scss/_style.scss */ +.cms-content-fields { overflow: auto; background: transparent url(../images/textures/bg_cms_main_content.png) repeat top left; } +/** -------------------------------------------- Panels -------------------------------------------- */ +/* line 494, ../scss/_style.scss */ +.cms-panel { overflow: hidden; } +/* line 499, ../scss/_style.scss */ +.cms-panel .toggle-expand, .cms-panel .toggle-collapse { display: block; position: absolute; bottom: 0; text-align: right; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b0bec7), color-stop(100%, #92a5b2)); background-image: -webkit-linear-gradient(top, #b0bec7, #92a5b2); background-image: -moz-linear-gradient(top, #b0bec7, #92a5b2); background-image: -o-linear-gradient(top, #b0bec7, #92a5b2); background-image: -ms-linear-gradient(top, #b0bec7, #92a5b2); background-image: linear-gradient(top, #b0bec7, #92a5b2); text-decoration: none; } +/* line 510, ../scss/_style.scss */ +.cms-panel .toggle-expand span, .cms-panel .toggle-collapse span { display: inline-block; margin: 5px; color: #1f1f1f; font-size: 16px; } +/* line 518, ../scss/_style.scss */ +.cms-panel .toggle-collapse { width: 100%; } +/* line 522, ../scss/_style.scss */ +.cms-panel .toggle-expand { width: 40px; } +/* line 528, ../scss/_style.scss */ +.cms-panel.collapsed .cms-panel-content { display: none; } +/* line 532, ../scss/_style.scss */ +.cms-panel.collapsed .cms-panel-header { -moz-transform: rotate(-90deg); -webkit-transform: rotate(-90deg); -o-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); position: relative; top: 100px; } + +/* line 542, ../scss/_style.scss */ +.cms-content .cms-panel.collapsed { cursor: pointer; } + +/** -------------------------------------------- Other -------------------------------------------- */ +/* line 550, ../scss/_style.scss */ .cms-preview { background-color: #b0bec7; } +/* line 553, ../scss/_style.scss */ .cms-preview .cms-preview-toggle { cursor: pointer; } -.cms-preview .cms-preview-toggle a { color: white; font-weight: bold; text-decoration: none; } +/* line 556, ../scss/_style.scss */ +.cms-preview .cms-preview-toggle a { display: block; width: 15px; height: 15px; position: relative; left: 10px; top: 48%; background-color: #b0bec7; color: white; font-weight: bold; text-decoration: none; z-index: 2000; } +/* line 573, ../scss/_style.scss */ +.cms-preview.is-collapsed .cms-preview-toggle a { left: -15px; } +/* line 581, ../scss/_style.scss */ .cms-switch-view a { padding-right: 1em; } diff --git a/admin/images/btn_arrow_down_grey.png b/admin/images/btn_arrow_down_grey.png new file mode 100644 index 000000000..1d769955a Binary files /dev/null and b/admin/images/btn_arrow_down_grey.png differ diff --git a/admin/images/btn_icons_sprite.png b/admin/images/btn_icons_sprite.png new file mode 100644 index 000000000..2f0196a1e Binary files /dev/null and b/admin/images/btn_icons_sprite.png differ diff --git a/admin/images/mainmenu/current-indicator.png b/admin/images/mainmenu/current-indicator.png new file mode 100644 index 000000000..22b7b3008 Binary files /dev/null and b/admin/images/mainmenu/current-indicator.png differ diff --git a/admin/images/sitetree_ss_default_icons.png b/admin/images/sitetree_ss_default_icons.png new file mode 100644 index 000000000..04c3cf88c Binary files /dev/null and b/admin/images/sitetree_ss_default_icons.png differ diff --git a/admin/images/sitetree_ss_pageclass_icons_default.png b/admin/images/sitetree_ss_pageclass_icons_default.png new file mode 100644 index 000000000..1d65c24d8 Binary files /dev/null and b/admin/images/sitetree_ss_pageclass_icons_default.png differ diff --git a/admin/images/textures/bg_cms_main_content.png b/admin/images/textures/bg_cms_main_content.png new file mode 100644 index 000000000..ce265dba1 Binary files /dev/null and b/admin/images/textures/bg_cms_main_content.png differ diff --git a/admin/images/textures/bg_fieldset_elements_border b/admin/images/textures/bg_fieldset_elements_border new file mode 100644 index 000000000..dae63ba4d Binary files /dev/null and b/admin/images/textures/bg_fieldset_elements_border differ diff --git a/admin/images/textures/bg_fieldset_elements_border.png b/admin/images/textures/bg_fieldset_elements_border.png new file mode 100644 index 000000000..c22e2273c Binary files /dev/null and b/admin/images/textures/bg_fieldset_elements_border.png differ diff --git a/admin/javascript/LeftAndMain.AddForm.js b/admin/javascript/LeftAndMain.AddForm.js index 896f0e47b..8f9176cf8 100755 --- a/admin/javascript/LeftAndMain.AddForm.js +++ b/admin/javascript/LeftAndMain.AddForm.js @@ -91,8 +91,9 @@ data.push({name:button.attr('name'),value:button.val()}); // TODO Should be set by hiddenfield already - jQuery('.cms-edit-form').entwine('ss').loadForm( + jQuery('.cms-content').entwine('ss').loadForm( this.attr('action'), + null, function() { // Tree updates are triggered by Form_EditForm load events button.removeClass('loading'); @@ -129,7 +130,7 @@ dropdown.find('option').remove(); //Use tree hints to find allowed children for this node - if (className && typeof siteTreeHints !== 'undefined') { + if (className && siteTreeHints) { disallowed = siteTreeHints[className].disallowedChildren; } diff --git a/admin/javascript/LeftAndMain.Content.js b/admin/javascript/LeftAndMain.Content.js new file mode 100644 index 000000000..0533d9b56 --- /dev/null +++ b/admin/javascript/LeftAndMain.Content.js @@ -0,0 +1,266 @@ +(function($) { + + $.entwine('ss', function($){ + + $('.cms-content, .cms-content *').entwine({ + /** + * Triggered before a new URL is loaded, typically via ajax. + * Loading itself is handled by $('.cms-container') and window.history. + * + * @param {String} + */ + beforeLoad: function(url) { + this.addClass('loading'); + this.cleanup(); + }, + + /** + * Triggered after an ajax request with new HTML data. + * + * @param {String} + * @param {String} + * @param {XMLHTTPRequest} + */ + afterLoad: function(data, status, xhr) { + this.removeClass('loading'); + this.replaceWith(data); + }, + + cleanup: function() { + this.empty(); + } + }); + + /** + * The "content" area contains all of the section specific UI (excluding the menu). + * This area can be a form itself, as well as contain one or more forms. + * For example, a page edit form might fill the whole area, + * while a ModelAdmin layout shows a search form on the left, and edit form on the right. + */ + $('.cms-content').entwine({ + + onmatch: function() { + var self = this; + + // Listen to tree selection events + this.find('.cms-tree').bind('select_node.jstree', function(e, data) { + var node = data.rslt.obj, loadedNodeID = self.find(':input[name=ID]').val(), origEvent = data.args[2]; + + // Don't trigger unless coming from a click event. + // Avoids problems with automated section switches from tree to detail view + // when JSTree auto-selects elements on first load. + if(!origEvent) return false; + + // Don't allow checking disabled nodes + if($(node).hasClass('disabled')) return false; + + // Don't allow reloading of currently selected node, + // mainly to avoid doing an ajax request on initial page load + if($(node).data('id') == loadedNodeID) return; + + var url = $(node).find('a:first').attr('href'); + if(url && url != '#') { + window.History.pushState({}, '', url); + } else { + self.removeForm(); + } + }); + + this._super(); + }, + + onunmatch: function() { + this._super(); + }, + + /** + * Function: loadForm + * + * See $('.cms-container').handleStateChange() on a frequently used alternative + * to direct ajax loading of content, with support for the window.History object. + * + * Parameters: + * (String) url - .. + * (Function) callback - (Optional) Called after the form content as been loaded + * (Object) ajaxOptions - Object literal merged into the jQuery.ajax() call (Optional) + * + * Returns: + * (XMLHTTPRequest) + */ + loadForm: function(url, form, callback, ajaxOptions) { + var self = this; + if(!form || !form.length) var form = $('.cms-content-fields form:first'); + + // Alert when unsaved changes are present + if(form._checkChangeTracker(true) == false) return false; + + // hide existing form - shown again through _loadResponse() + form.addClass('loading'); + + this.trigger('loadform', {form: form, url: url}); + + form.cleanup(); + + return jQuery.ajax(jQuery.extend({ + url: url, + // Ensure that form view is loaded (rather than whole "Content" template) + data: {'cms-view-form': 1}, + complete: function(xmlhttp, status) { + self.loadForm_responseHandler(form, xmlhttp.responseText, status, xmlhttp); + if(callback) callback.apply(self, arguments); + }, + dataType: 'html' + }, ajaxOptions)); + }, + + loadForm_responseHandler: function(oldForm, html, status, xmlhttp) { + oldForm.replaceWith(html); // triggers onmatch() on form + + // set status message based on response + var _statusMessage = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText; + }, + + /** + * Function: ajaxSubmit + * + * Parameters: + * {DOMElement} button - The pressed button (optional) + * {Function} callback - Called in complete() handler of jQuery.ajax() + * {Object} ajaxOptions - Object literal to merge into $.ajax() call + * {boolean} loadResponse - Render response through _loadResponse() (Default: true) + * + * Returns: + * (boolean) + */ + submitForm: function(form, button, callback, ajaxOptions, loadResponse) { + var self = this; + + // look for save button + if(!button) button = this.find('.Actions :submit[name=action_save]'); + // default to first button if none given - simulates browser behaviour + if(!button) button = this.find('.Actions :submit:first'); + + this.trigger('submitform', {form: form, button: button}); + + // set button to "submitting" state + $(button).addClass('loading'); + + // @todo TinyMCE coupling + if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave(); + + // validate if required + if(!form.validate()) { + // TODO Automatically switch to the tab/position of the first error + statusMessage("Validation failed.", "bad"); + + $(button).removeClass('loading'); + + return false; + } + + // save tab selections in order to reconstruct them later + var selectedTabs = []; + form.find('.ss-tabset').each(function(i, el) { + if($(el).attr('id')) selectedTabs.push({id:$(el).attr('id'), selected:$(el).tabs('option', 'selected')}); + }); + + // get all data from the form + var formData = form.serializeArray(); + // add button action + formData.push({name: $(button).attr('name'), value:'1'}); + jQuery.ajax(jQuery.extend({ + url: form.attr('action'), + data: formData, + type: 'POST', + complete: function(xmlhttp, status) { + $(button).removeClass('loading'); + + // TODO This should be using the plugin API + form.removeClass('changed'); + + if(callback) callback(xmlhttp, status); + + // pass along original form data to enable old/new comparisons + if(loadResponse !== false) { + self.submitForm_responseHandler(form, xmlhttp.responseText, status, xmlhttp, formData); + } + + // Re-init tabs (in case the form tag itself is a tabset) + if(self.hasClass('ss-tabset')) self.removeClass('ss-tabset').addClass('ss-tabset'); + + // re-select previously saved tabs + $.each(selectedTabs, function(i, selectedTab) { + form.find('#' + selectedTab.id).tabs('select', selectedTab.selected); + }); + }, + dataType: 'html' + }, ajaxOptions)); + + return false; + }, + + /** + * Function: _loadResponse + * + * Parameters: + * {String} data - Either HTML for straight insertion, or eval'ed JavaScript. + * If passed as HTML, it is assumed that everying inside the
    tag is replaced, + * but the old tag itself stays intact. + * {String} status + * {XMLHTTPRequest} xmlhttp - .. + * {Array} origData - The original submitted data, useful to do comparisons of changed + * values in new form output, e.g. to detect a URLSegment being changed on the serverside. + * Array in jQuery serializeArray() notation. + */ + submitForm_responseHandler: function(oldForm, data, status, xmlhttp, origData) { + if(status == 'success') { + var form = this.replaceForm(oldForm, data); + + Behaviour.apply(); // refreshes ComplexTableField + + this.trigger('loadnewpage', {form: form, origData: origData, xmlhttp: xmlhttp}); + } + + // set status message based on response + var _statusMessage = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText; + }, + + /** + * @return {jQuery} New form element + */ + replaceForm: function(form, html) { + form.cleanup(); + if(html) { + var parent = form.parent(), id = form.attr('id'); + form.replaceWith(html); + // Try to get the new form by ID (assuming they're identical), otherwise fall back to the first form in the parent + return id ? $('#' + id) : parent.children('form:first'); + } else { + this.removeForm(form); + return null; + } + }, + + /** + * Function: removeForm + * + * Remove everying inside the tag + * with a custom HTML fragment. Useful e.g. for deleting a page in the CMS. + * Checks for unsaved changes before removing the form + * + * Parameters: + * {String} placeholderHtml - Short note why the form has been removed, displayed in

    tags. + * Falls back to the default RemoveText() option (Optional) + */ + removeForm: function(form, placeholderHtml) { + if(!placeholderHtml) placeholderHtml = this.getPlaceholderHtml(); + // Alert when unsaved changes are present + if(form._checkChangeTracker(true) == false) return; + this.trigger('removeform'); + this.html(placeholderHtml); + // TODO This should be using the plugin API + this.removeClass('changed'); + } + }); + }); +})(jQuery); \ No newline at end of file diff --git a/admin/javascript/LeftAndMain.EditForm.js b/admin/javascript/LeftAndMain.EditForm.js index de192dfb2..62837e70b 100755 --- a/admin/javascript/LeftAndMain.EditForm.js +++ b/admin/javascript/LeftAndMain.EditForm.js @@ -40,33 +40,64 @@ var self = this; this._setupChangeTracker(); - - $('.cms-tree').bind('select_node.jstree', function(e, data) { - var node = data.rslt.obj, loadedNodeID = self.find(':input[name=ID]').val() - - // Don't allow checking disabled nodes - if($(node).hasClass('disabled')) return false; - - // Don't allow reloading of currently selected node, - // mainly to avoid doing an ajax request on initial page load - if($(node).data('id') == loadedNodeID) return; - - var url = $(node).find('a:first').attr('href'); - if(url && url != '#') { - var xmlhttp = self.loadForm( - url, - function(response) {} - ); - } else { - self.removeForm(); - } - }); // Can't bind this through jQuery window.onbeforeunload = function(e) {return self._checkChangeTracker(false);}; + + // focus input on first form element + this.find(':input:visible:first').focus(); + + // Optionally get the form attributes from embedded fields, see Form->formHtmlContent() + for(var overrideAttr in {'action':true,'method':true,'enctype':true,'name':true}) { + var el = this.find(':input[name='+ '_form_' + overrideAttr + ']'); + if(el) { + this.attr(overrideAttr, el.val()); + el.remove(); + } + } + + // TODO + // // Rewrite # links + // html = html.replace(/(]+href *= *")#/g, '$1' + window.location.href.replace(/#.*$/,'') + '#'); + // + // // Rewrite iframe links (for IE) + // html = html.replace(/(]*src=")([^"]+)("[^>]*>)/g, '$1' + $('base').attr('href') + '$2$3'); + + // Show validation errors if necessary + if(this.hasClass('validationerror')) { + // TODO validation shouldnt need a special case + statusMessage(ss.i18n._t('ModelAdmin.VALIDATIONERROR', 'Validation Error'), 'bad'); + } this._super(); }, + + onunmatch: function() { + // Prepare iframes for removal, otherwise we get loading bugs + this.find('iframe').each(function() { + this.contentWindow.location.href = 'about:blank'; + $(this).remove(); + }); + + // Remove all TinyMCE instances + if((typeof tinymce != 'undefined') && tinymce.editors) { + $(tinymce.editors).each(function() { + if(typeof(this.remove) == 'function') this.remove(); + }); + } + + this._super(); + }, + + beforeLoad: function(url) { + this.addClass('loading'); + this.cleanup(); + }, + + afterLoad: function(data, status, xhr) { + this.removeClass('loading'); + this.replaceWith(data); + }, /** * Function: _setupChangeTracker @@ -114,90 +145,11 @@ * Suppress submission unless it is handled through ajaxSubmit(). */ onsubmit: function(e) { - this.ajaxSubmit(); + this.parents('.cms-content').submitForm(this); return false; }, - /** - * Function: ajaxSubmit - * - * Parameters: - * {DOMElement} button - The pressed button (optional) - * {Function} callback - Called in complete() handler of jQuery.ajax() - * {Object} ajaxOptions - Object literal to merge into $.ajax() call - * {boolean} loadResponse - Render response through _loadResponse() (Default: true) - * - * Returns: - * (boolean) - */ - ajaxSubmit: function(button, callback, ajaxOptions, loadResponse) { - var self = this; - - // look for save button - if(!button) button = this.find('.Actions :submit[name=action_save]'); - // default to first button if none given - simulates browser behaviour - if(!button) button = this.find('.Actions :submit:first'); - - this.trigger('ajaxsubmit', {button: button}); - - // set button to "submitting" state - $(button).addClass('loading'); - - // @todo TinyMCE coupling - if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave(); - - // validate if required - if(!this.validate()) { - // TODO Automatically switch to the tab/position of the first error - statusMessage("Validation failed.", "bad"); - - $(button).removeClass('loading'); - - return false; - } - - // save tab selections in order to reconstruct them later - var selectedTabs = []; - this.find('.ss-tabset').each(function(i, el) { - if($(el).attr('id')) selectedTabs.push({id:$(el).attr('id'), selected:$(el).tabs('option', 'selected')}); - }); - - // get all data from the form - var formData = this.serializeArray(); - // add button action - formData.push({name: $(button).attr('name'), value:'1'}); - jQuery.ajax(jQuery.extend({ - url: this.attr('action'), - data: formData, - type: 'POST', - complete: function(xmlhttp, status) { - $(button).removeClass('loading'); - - // TODO This should be using the plugin API - self.removeClass('changed'); - - if(callback) callback(xmlhttp, status); - - // pass along original form data to enable old/new comparisons - if(loadResponse !== false) { - self._loadResponse(xmlhttp.responseText, status, xmlhttp, formData); - } - - // Re-init tabs (in case the form tag itself is a tabset) - if(self.hasClass('ss-tabset')) self.removeClass('ss-tabset').addClass('ss-tabset'); - - // re-select previously saved tabs - $.each(selectedTabs, function(i, selectedTab) { - self.find('#' + selectedTab.id).tabs('select', selectedTab.selected); - }); - }, - dataType: 'html' - }, ajaxOptions)); - - return false; - }, - /** * Function: validate * @@ -215,151 +167,6 @@ this.trigger('validate', {isValid: isValid}); return isValid; - }, - - /** - * Function: loadForm - * - * Parameters: - * (String) url - .. - * (Function) callback - (Optional) Called after the form content as been loaded - * (Object) ajaxOptions - Object literal merged into the jQuery.ajax() call (Optional) - * - * Returns: - * (XMLHTTPRequest) - */ - loadForm: function(url, callback, ajaxOptions) { - var self = this; - - // Alert when unsaved changes are present - if(this._checkChangeTracker(true) == false) return false; - - // hide existing form - shown again through _loadResponse() - this.addClass('loading'); - - this.trigger('load', {url: url}); - - this.cleanup(); - - return jQuery.ajax(jQuery.extend({ - url: url, - complete: function(xmlhttp, status) { - // TODO This should be using the plugin API - self.removeClass('changed'); - - self._loadResponse(xmlhttp.responseText, status, xmlhttp); - - self.removeClass('loading'); - - if(callback) callback.apply(self, arguments); - }, - dataType: 'html' - }, ajaxOptions)); - }, - - /** - * Function: removeForm - * - * Remove everying inside the tag - * with a custom HTML fragment. Useful e.g. for deleting a page in the CMS. - * Checks for unsaved changes before removing the form - * - * Parameters: - * {String} placeholderHtml - Short note why the form has been removed, displayed in

    tags. - * Falls back to the default RemoveText() option (Optional) - */ - removeForm: function(placeholderHtml) { - if(!placeholderHtml) placeholderHtml = this.getPlaceholderHtml(); - // Alert when unsaved changes are present - if(this._checkChangeTracker(true) == false) return; - this.trigger('removeform'); - this.html(placeholderHtml); - // TODO This should be using the plugin API - this.removeClass('changed'); - }, - - /** - * Function: cleanup - * - * Remove all the currently active TinyMCE editors. - * Note: Everything that calls this externally has an inappropriate coupling to TinyMCE. - */ - cleanup: function() { - if((typeof tinymce != 'undefined') && tinymce.editors) { - $(tinymce.editors).each(function() { - if(typeof(this.remove) == 'function') { - this.remove(); - } - }); - } - }, - - /** - * Function: _loadResponse - * - * Parameters: - * {String} data - Either HTML for straight insertion, or eval'ed JavaScript. - * If passed as HTML, it is assumed that everying inside the tag is replaced, - * but the old tag itself stays intact. - * {String} status - * {XMLHTTPRequest} xmlhttp - .. - * {Array} origData - The original submitted data, useful to do comparisons of changed - * values in new form output, e.g. to detect a URLSegment being changed on the serverside. - * Array in jQuery serializeArray() notation. - */ - _loadResponse: function(data, status, xmlhttp, origData) { - if(status == 'success') { - this.cleanup(); - - var html = data; - - // Rewrite # links - html = html.replace(/(]+href *= *")#/g, '$1' + window.location.href.replace(/#.*$/,'') + '#'); - - // Rewrite iframe links (for IE) - html = html.replace(/(]*src=")([^"]+)("[^>]*>)/g, '$1' + $('base').attr('href') + '$2$3'); - - // Prepare iframes for removal, otherwise we get loading bugs - this.find('iframe').each(function() { - this.contentWindow.location.href = 'about:blank'; - $(this).remove(); - }); - - // update form content - if(html) { - this.html(html); - } else { - this.removeForm(); - } - - // If the form itself is a tabset, force re-rendering - if(this.hasClass('ss-tabset')) this.tabs('destroy').tabs(); - - this._setupChangeTracker(); - - // Optionally get the form attributes from embedded fields, see Form->formHtmlContent() - for(var overrideAttr in {'action':true,'method':true,'enctype':true,'name':true}) { - var el = this.find(':input[name='+ '_form_' + overrideAttr + ']'); - if(el) { - this.attr(overrideAttr, el.val()); - el.remove(); - } - } - - Behaviour.apply(); // refreshes ComplexTableField - - // focus input on first form element - this.find(':input:visible:first').focus(); - - this.trigger('loadnewpage', {data: data, origData: origData, xmlhttp: xmlhttp}); - } - - // set status message based on response - var _statusMessage = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText; - if(this.hasClass('validationerror')) { - // TODO validation shouldnt need a special case - statusMessage(ss.i18n._t('ModelAdmin.VALIDATIONERROR', 'Validation Error'), 'bad'); - } } }); @@ -376,7 +183,7 @@ * Function: onclick */ onclick: function(e) { - jQuery('.cms-edit-form').entwine('ss').ajaxSubmit(this); + $('.cms-content').submitForm(this.parents('form'), this); return false; } }); diff --git a/admin/javascript/LeftAndMain.Menu.js b/admin/javascript/LeftAndMain.Menu.js new file mode 100644 index 000000000..06e76cb75 --- /dev/null +++ b/admin/javascript/LeftAndMain.Menu.js @@ -0,0 +1,114 @@ +(function($) { + + $.entwine('ss', function($){ + + /** + * Vertical CMS menu with two levels, built from a nested unordered list. + * The (optional) second level is collapsible, hiding its children. + * The whole menu (including second levels) is collapsible as well, + * exposing only a preview for every menu item in order to save space. + * In this "preview/collapsed" mode, the secondary menu hovers over the menu item, + * rather than expanding it. + * + * Example: + * + *

    + */ + $('.cms-menu-list').entwine({ + onmatch: function() { + var self = this; + + // TODO Fix icon etc. + // this.children('li').each(function() { + // $(this).find('a:first').append('o'); + // }); + + $('.cms-container').bind('afterstatechange', function(e, data) { + var controller = data.xhr.getResponseHeader('X-Controller'); + if(controller) self.find('li#Menu-' + controller).select(); + }); + + // Sync collapsed state with parent panel + this.parents('.cms-panel:first').bind('toggle', function(e) { + self.toggleClass('collapsed', $(this).hasClass('collapsed')); + }); + + this._super(); + } + }); + + $('.cms-menu-list .toggle').entwine({ + onclick: function(e) { + this.getMenuItem().toggle(); + e.preventDefault(); + } + }); + + $('.cms-menu-list li').entwine({ + toggle: function() { + this[this.hasClass('opened') ? 'close' : 'open'](); + }, + open: function() { + var parent = this.getMenuItem(); + if(parent) parent.open(); + this.addClass('opened').find('ul').show(); + }, + close: function() { + this.removeClass('opened').find('ul').hide(); + }, + select: function() { + var parent = this.getMenuItem(); + this.addClass('current').open(); + // Remove "current" class from all siblings and their children + this.siblings().removeClass('current').close(); + this.siblings().find('li').removeClass('current'); + if(parent) parent.addClass('current').siblings().removeClass('current'); + } + }); + + $('.cms-menu-list li *').entwine({ + getMenuItem: function() { + return this.parents('li:first'); + } + }); + + /** + * Both primary and secondary nav. + */ + $('.cms-menu-list li a').entwine({ + onclick: function(e) { + // Only catch left clicks, in order to allow opening in tabs. + // Ignore external links, fallback to standard link behaviour + if(e.which > 1 || this.is(':external')) return; + e.preventDefault(); + + // Expand this, and collapse all other items + var item = this.getMenuItem(); + item.select(); + + var children = item.find('li'); + if(children.length) { + children.first().find('a').click(); + } else { + // Active menu item is set based on X-Controller ajax header, + // which matches one class on the menu + window.History.pushState({}, '', this.attr('href')); + } + } + }); + + }); + + // Internal Helper + $.expr[':'].internal = function(obj){return obj.href.match(/^mailto\:/) || (obj.hostname == location.hostname);}; + $.expr[':'].external = function(obj){return !$(obj).is(':internal')}; +}(jQuery)); \ No newline at end of file diff --git a/admin/javascript/LeftAndMain.Panel.js b/admin/javascript/LeftAndMain.Panel.js new file mode 100644 index 000000000..b4d12f878 --- /dev/null +++ b/admin/javascript/LeftAndMain.Panel.js @@ -0,0 +1,108 @@ +(function($) { + + $.entwine('ss', function($){ + + // setup jquery.entwine + $.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE; + + /** + * Vertically collapsible panel. Generic enough to work with CMS menu as well as various "filter" panels. + * + * A panel consists of the following parts: + * - Container div: The outer element, with class ".cms-panel" + * - Header (optional) + * - Content + * - Expand and collapse toggle anchors (optional) + * + * Sample HTML: + *
    + *
    your header
    + *
    your content here
    + * your toggle text + * your toggle text + *
    + */ + $('.cms-panel').entwine({ + + WidthExpanded: null, + + WidthCollapsed: null, + + onmatch: function() { + if(!this.find('.cms-panel-content').length) throw new Exception('Content panel for ".cms-panel" not found'); + + // Create default controls unless they already exist + if(!this.find('.toggle-expand').length) this.append('»'); + if(!this.find('.toggle-collapse').length) this.append('«'); + + // Set panel width same as the content panel it contains. Assumes the panel has overflow: hidden. + this.setWidthExpanded(this.find('.cms-panel-content').width()); + + // Assumes the collasped width is indicated by the toggle, or by an optional collapsed view + var collapsedContent = this.find('.cms-panel-content-collapsed'); + this.setWidthCollapsed(collapsedContent.length ? collapsedContent.widht() : this.find('.toggle-expand').width()); + + this.togglePanel(!jQuery(this).hasClass('collapsed')); + + this._super(); + }, + + onclick: function(e) { + // By default, the whole collapsed area serves as a trigger + if(this.data('expandOnClick') && jQuery(this).hasClass('collapsed')) { + e.preventDefault(); + this.expandPanel(); + } + }, + + togglePanel: function(bool) { + // if((!bool && this.hasClass('collapsed')) || (bool && !this.hasClass('collapsed'))) return; + + this.toggleClass('collapsed', !bool); + var newWidth = bool ? this.getWidthExpanded() : this.getWidthCollapsed(); + + this.trigger('beforetoggle'); + this.width(newWidth); // the content panel width always stays in "expanded state" to avoid floating elements + this.find('.toggle-collapse')[bool ? 'show' : 'hide'](); + this.find('.toggle-expand')[bool ? 'hide' : 'show'](); + + // If an alternative collapsed view exists, toggle it as well + var collapsedContent = this.find('.cms-panel-content-collapsed'); + if(collapsedContent.length) { + this.find('.cms-panel-content')[bool ? 'show' : 'hide'](); + this.find('.cms-panel-content-collapsed')[bool ? 'hide' : 'show'](); + } + + this.trigger('toggle'); + }, + + expandPanel: function() { + this.togglePanel(true); + }, + + collapsePanel: function() { + this.togglePanel(false); + } + }); + + $('.cms-panel *').entwine({ + getPanel: function() { + return this.parents('.cms-panel:first'); + } + }); + + $('.cms-panel .toggle-expand').entwine({ + onclick: function(e) { + e.preventDefault(); + this.getPanel().expandPanel(); + } + }); + + $('.cms-panel .toggle-collapse').entwine({ + onclick: function(e) { + this.getPanel().collapsePanel(); + return false; + } + }); + }); +}(jQuery)); \ No newline at end of file diff --git a/admin/javascript/LeftAndMain.Ping.js b/admin/javascript/LeftAndMain.Ping.js new file mode 100644 index 000000000..768b00744 --- /dev/null +++ b/admin/javascript/LeftAndMain.Ping.js @@ -0,0 +1,50 @@ +/** + * File: LeftAndMain.EditForm.js + */ +(function($) { + $.entwine('ss', function($){ + + $('.cms-container').entwine(/** @lends ss.Form_EditForm */{ + /** + * Variable: PingIntervalSeconds + * (Number) Interval in which /Security/ping will be checked for a valid login session. + */ + PingIntervalSeconds: 5*60, + + // onmatch: function() { + // this._super(); + // + // this._setupPinging(); + // }, + + /** + * Function: _setupPinging + * + * This function is called by prototype when it receives notification that the user was logged out. + * It uses /Security/ping for this purpose, which should return '1' if a valid user session exists. + * It redirects back to the login form if the URL is either unreachable, or returns '0'. + */ + _setupPinging: function() { + var onSessionLost = function(xmlhttp, status) { + if(xmlhttp.status > 400 || xmlhttp.responseText == 0) { + // TODO will pile up additional alerts when left unattended + if(window.open('Security/login')) { + alert("Please log in and then try again"); + } else { + alert("Please enable pop-ups for this site"); + } + } + }; + + // setup pinging for login expiry + setInterval(function() { + jQuery.ajax({ + url: "Security/ping", + global: false, + complete: onSessionLost + }); + }, this.getPingIntervalSeconds() * 1000); + } + }); + }); +}(jQuery)); \ No newline at end of file diff --git a/admin/javascript/LeftAndMain.Preview.js b/admin/javascript/LeftAndMain.Preview.js index a6ab43c28..6c858345b 100755 --- a/admin/javascript/LeftAndMain.Preview.js +++ b/admin/javascript/LeftAndMain.Preview.js @@ -1,7 +1,10 @@ (function($) { + $.entwine('ss', function($){ - $('.LeftAndMain .cms-preview').entwine({ + + $('.cms-preview').entwine({ + // Minimum width to keep the CMS operational SharedWidth: null, onmatch: function() { @@ -17,18 +20,31 @@ this.setSharedWidth(500); // Create layout and controls - this.prepend(''); + this.prepend(''); this.find('iframe').addClass('center'); this.layout({type: 'border'}); - this.find('iframe').bind('load', function() {self._fixIframeLinks();}); + this.find('iframe').bind('load', function() { + self._fixIframeLinks(); + self.loadCurrentPage(); + }); self._fixIframeLinks(); + // Limit to CMS forms for the moment $('.cms-edit-form').bind('loadnewpage', function(e, ui) { // var url = ui.xmlhttp.getResponseHeader('x-frontend-url'); var url = $(this).find(':input[name=StageURLSegment]').val(); - if(url) self.loadUrl(url); + if(url) self.loadUrl(url + '&cms-preview-disabled=1'); }); + + $('.cms-container').bind('afterstatechange', function(e) { + // var url = ui.xmlhttp.getResponseHeader('x-frontend-url'); + var url = $('.cms-edit-form').find(':input[name=StageURLSegment]').val(); + if(url) self.loadUrl(url + '&cms-preview-disabled=1'); + }); + + if(this.hasClass('is-expanded')) this.expand(); + else this.collapse(); this._super(); }, @@ -37,6 +53,22 @@ this.find('iframe').attr('src', url); }, + loadCurrentPage: function() { + var doc = this.find('iframe')[0].contentDocument, + containerEl = this.getLayoutContainer(), + contentEl = containerEl.find('.cms-content'); + + // Only load if we're in the "edit page" view + if(!contentEl.hasClass('CMSMain') || contentEl.hasClass('CMSPagesController') || contentEl.hasClass('CMSSettingsController')) return; + + // Load this page in the admin interface if appropriate + var id = $(doc).find('meta[name=x-page-id]').attr('content'), contentPanel = $('.cms-content'); + // TODO Remove hardcoding + if(id && contentPanel.find(':input[name=ID]').val() != id) { + window.History.pushState({}, '', 'admin/page/edit/show/' + id); + } + }, + _fixIframeLinks: function() { var doc = this.find('iframe')[0].contentDocument; @@ -46,64 +78,63 @@ var href = links[i].getAttribute('href'); if (href && href.match(/^http:\/\//)) { links[i].setAttribute('href', 'javascript:false'); + } else { + links[i].setAttribute('href', href + '?cms-preview-disabled=1'); } } - - // Load this page in the admin interface if appropriate - var id = $(doc).find('meta[name=x-page-id]').attr('content'), form = $('.cms-edit-form'); - // TODO Remove hardcoding - if(id && form.find(':input[name=ID]').val() != id) form.loadForm('admin/page/edit/show/' + id); + }, + + expand: function() { + var self = this, containerEl = this.getLayoutContainer(), contentEl = containerEl.find('.cms-content'); + this.removeClass('east').addClass('center').removeClass('is-collapsed'); + // this.css('overflow', 'auto'); + contentEl.removeClass('center').hide(); + this.find('iframe').show(); + containerEl.find('.cms-menu').collapsePanel(); + this.find('.cms-preview-toggle a').html('»'); + containerEl.redraw(); + }, + + collapse: function() { + var self = this, containerEl = this.getLayoutContainer(), contentEl = containerEl.find('.cms-content'); + this.addClass('east').removeClass('center').addClass('is-collapsed').width(10); + // this.css('overflow', 'hidden'); + contentEl.addClass('center').show(); + this.find('iframe').hide(); + containerEl.find('.cms-menu').expandPanel(); + this.find('.cms-preview-toggle a').html('«'); + containerEl.redraw(); + }, + + getLayoutContainer: function() { + return this.parents('.cms-container'); }, toggle: function(bool) { - var self = this, - width = this.width(), - relayout = function() {$('.cms-container').layout({resize: false});}, - minWidth = this.find('.cms-preview-toggle').width(), - wasCollapsed = (bool === true || bool === false) ? bool : (width <= minWidth), - newWidth = wasCollapsed ? this.getSharedWidth() : minWidth, - newOverflow = wasCollapsed ? 'auto' : 'hidden'; - - this.css('overflow', newOverflow).width(newWidth); - this.toggleClass('collapsed', !wasCollapsed).toggleClass('expanded', wasCollapsed); - this.find('iframe').toggle(wasCollapsed); - relayout(); - - // this.css('overflow', newOverflow).animate( - // {width: newWidth+'px'}, - // { - // duration: 500, - // complete: function() { - // relayout(); - // self.toggleClass('collapsed', !wasCollapsed).toggleClass('expanded', wasCollapsed); - // self.find('iframe').toggle(wasCollapsed); - // }, - // step: relayout - // } - // ); + this[this.hasClass('is-collapsed') ? 'expand' : 'collapse'](); } }); - $('.LeftAndMain .cms-preview.collapsed').entwine({ + $('.cms-preview.collapsed').entwine({ onmatch: function() { this.find('a').text('<'); } }); - $('.LeftAndMain .cms-preview.expanded').entwine({ + $('.cms-preview.expanded').entwine({ onmatch: function() { this.find('a').text('>'); } }); - $('.LeftAndMain .cms-preview .cms-preview-toggle').entwine({ + $('.cms-preview .cms-preview-toggle').entwine({ onclick: function(e) { e.preventDefault(); this.parents('.cms-preview').toggle(); } }); - $('.LeftAndMain .cms-switch-view a').entwine({ + $('.cms-switch-view a').entwine({ onclick: function(e) { e.preventDefault(); var preview = $('.cms-preview'); @@ -111,5 +142,16 @@ preview.loadUrl($(e.target).attr('href')); } }); + + $('.cms-menu li').entwine({ + onclick: function(e) { + // Prevent reloading of interface when opening the edit panel + if(this.hasClass('Menu-CMSMain')) { + var preview = $('.cms-preview'); + preview.toggle(true); + e.preventDefault(); + } + } + }); }); }(jQuery)); \ No newline at end of file diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index 16345f309..b69ae9f61 100755 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -31,8 +31,6 @@ }); /** - * Class: .LeftAndMain - * * Main LeftAndMain interface with some control panel and an edit form. * * Events: @@ -40,20 +38,16 @@ * validate - ... * loadnewpage - ... */ - $('.LeftAndMain').entwine({ - - /** - * Variable: PingIntervalSeconds - * (Number) Interval in which /Security/ping will be checked for a valid login session. - */ - PingIntervalSeconds: 5*60, - + $('.cms-container').entwine({ + + CurrentXHR: null, + /** * Constructor: onmatch */ onmatch: function() { var self = this; - + // Browser detection if($.browser.msie && parseInt($.browser.version, 10) < 7) { $('.ss-loading-screen').append( @@ -65,66 +59,96 @@ } // Initialize layouts, inner to outer - var doInnerLayout = function() {$('.cms-content').layout();} - var outer = $('.cms-container'); - var doOuterLayout = function() {outer.layout({resize: false});} - doInnerLayout(); - doOuterLayout(); - $(window).resize(doOuterLayout); + this.redraw(); + $(window).resize(function() {self.redraw()}); // Remove loading screen $('.ss-loading-screen').hide(); $('body').removeClass('loading'); $(window).unbind('resize', positionLoadingSpinner); - this._setupPinging(); - - $('.cms-edit-form').live('loadnewpage', function() { - doInnerLayout(); - doOuterLayout(); + $('.cms-edit-form').live('loadnewpage', function() {self.redraw()}); + + History.Adapter.bind(window,'statechange',function(){ + self.handleStateChange(); }); this._super(); }, - + + redraw: function() { + // Not all edit forms are layouted + var editForm = $('.cms-edit-form[data-layout]').layout(); + $('.cms-content').layout(); + $('.cms-container').layout({resize: false}) + }, + /** - * Function: _setupPinging + * Handles ajax loading of new panels through the window.History object. + * To trigger loading, pass a new URL to window.History.pushState(). * - * This function is called by prototype when it receives notification that the user was logged out. - * It uses /Security/ping for this purpose, which should return '1' if a valid user session exists. - * It redirects back to the login form if the URL is either unreachable, or returns '0'. + * Due to the nature of history management, no callbacks are allowed. + * Use the 'beforestatechange' and 'afterstatechange' events instead, + * or overwrite the beforeLoad() and afterLoad() methods on the + * DOM element you're loading the new content into. + * Although you can pass data into pushState(), it shouldn't contain + * DOM elements or callback closures. + * + * The passed URL should allow reconstructing important interface state + * without additional parameters, in the following use cases: + * - Explicit loading through History.pushState() + * - Implicit loading through browser navigation event triggered by the user (forward or back) + * - Full window refresh without ajax + * For example, a ModelAdmin search event should contain the search terms + * as URL parameters, and the result display should automatically appear + * if the URL is loaded without ajax. + * + * Alternatively, you can load new content via $('.cms-content').loadForm(). + * In this case, the action won't be recorded in the browser history. */ - _setupPinging: function() { - var onSessionLost = function(xmlhttp, status) { - if(xmlhttp.status > 400 || xmlhttp.responseText == 0) { - // TODO will pile up additional alerts when left unattended - if(window.open('Security/login')) { - alert("Please log in and then try again"); - } else { - alert("Please enable pop-ups for this site"); - } + handleStateChange: function() { + var self = this, h = window.History, state = h.getState(); + + // Don't allow parallel loading to avoid edge cases + if(this.getCurrentXHR()) this.getCurrentXHR().abort(); + + var contentEl = $(state.data.selector || '.cms-content'); + this.trigger('beforestatechange', {state: state}); + contentEl.beforeLoad(state.url); + + var xhr = $.ajax({ + url: state.url, + success: function(data, status, xhr) { + // Update title + var title = xhr.getResponseHeader('X-Title'); + if(title) document.title = title; + + // Update panels + contentEl.afterLoad(data, status, xhr); + self.redraw(); + + self.trigger('afterstatechange', {data: data, status: status, xhr: xhr}); } - }; - - // setup pinging for login expiry - setInterval(function() { - jQuery.ajax({ - url: "Security/ping", - global: false, - complete: onSessionLost - }); - }, this.getPingIntervalSeconds() * 1000); + }); + this.setCurrentXHR(xhr); + } + }); + + /** + * Monitor all panels for layout changes + */ + $('.cms-panel').entwine({ + ontoggle: function(e) { + this.parents('.cms-container').redraw(); } }); /** - * Class: .LeftAndMain :submit, .LeftAndMain button, .LeftAndMain :reset - * * Make all buttons "hoverable" with jQuery theming. * Also sets the clicked button on a form submission, making it available through * a new 'clickedButton' property on the form DOM element. */ - $('.LeftAndMain :submit, .LeftAndMain button, .LeftAndMain :reset').entwine({ + $('.cms-container :submit, .cms-container button, .cms-container :reset').entwine({ onmatch: function() { // TODO Adding classes in onmatch confuses entwine var self = this; @@ -139,7 +163,7 @@ * * Link for editing the profile for a logged-in member through a modal dialog. */ - $('.LeftAndMain .profile-link').entwine({ + $('.cms-container .profile-link').entwine({ /** * Constructor: onmatch @@ -281,7 +305,7 @@ * the DOM element on creation, rather than onclick - which allows us to decorate * the field with a calendar icon */ - $('.LeftAndMain .field.date input.text').entwine({ + $('.cms-container .field.date input.text').entwine({ onmatch: function() { var holder = $(this).parents('.field.date:first'), config = holder.metadata({type: 'class'}); if(!config.showcalendar) return; @@ -299,7 +323,8 @@ } }) - }); + }); + }(jQuery)); // Backwards compatibility diff --git a/admin/javascript/ModelAdmin.History.js b/admin/javascript/ModelAdmin.History.js index ef249c6a1..177231b72 100755 --- a/admin/javascript/ModelAdmin.History.js +++ b/admin/javascript/ModelAdmin.History.js @@ -66,6 +66,8 @@ redraw: function() { this.find('.historyNav .forward').toggle(Boolean(this.getFuture().length > 0)); this.find('.historyNav .back').toggle(Boolean(this.getHistory().length > 1)); + + this._super(); }, /** @@ -116,7 +118,7 @@ this.trigger('historyGoBack', {url:previousPage}); // load new location - $('.cms-edit-form').loadForm(previousPage); + $('.cms-content').loadForm(previousPage); this.redraw(); } @@ -136,7 +138,7 @@ this.trigger('historyGoForward', {url:nextPage}); // load new location - $('.cms-edit-form').loadForm(nextPage); + $('.cms-content').loadForm(nextPage); this.redraw(); } diff --git a/admin/javascript/ModelAdmin.js b/admin/javascript/ModelAdmin.js index 7f78fa66c..293a7e955 100755 --- a/admin/javascript/ModelAdmin.js +++ b/admin/javascript/ModelAdmin.js @@ -71,8 +71,9 @@ var btn = $(this[0].clickedButton); btn.addClass('loading'); - $('.cms-edit-form').loadForm( + $('.cms-content').loadForm( this.attr('action'), + null, function() { btn.removeClass('loading'); }, @@ -135,7 +136,8 @@ onclick: function(e) { var firstLink = this.find('a[href]'); if(!firstLink) return; - $('.cms-edit-form').loadForm(firstLink.attr('href')); + + window.History.pushState({selector: '.cms-content-fields form:first'}, '', firstLink.attr('href')); return false; } }); @@ -153,8 +155,9 @@ className = $('select option:selected', this).val(); requestPath = this.attr('action').replace('ManagedModelsSelect', className + '/add'); var $button = $(':submit', this); - $('.cms-edit-form').loadForm( + $('.cms-content').loadForm( requestPath, + null, function() { $button.removeClass('loading'); $button = null; diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index 72099a4ea..c68537785 100755 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -3,19 +3,26 @@ * It is an addition to the base styles defined in sapphire/css/Form.css. */ +/** ---------------------------------------------------- + * Basic form fields + * ---------------------------------------------------- */ + .field { display: block; padding: 10px 0; border-bottom: 1px solid $color-shadow-light; - - label { + overflow: hidden; + + label.left { float: left; - width: 10em; + width: 170px; + padding: 8px 20px 8px 4px; + line-height: $font-base-lineheight; } // Don't float inner/contained fields .middleColumn { - margin-left: 10em; + margin-left: 15em; .field { display: inline; @@ -54,50 +61,104 @@ form.nostyle { } } -input, textarea { - @include border-radius(5px); +.field.text input, textarea { + @include border-radius(4px); + background: #fff; @include linear-gradient(color-stops( - $color-widget-bg, - lighten($color-widget-bg, 10%), - $color-widget-bg + #efefef, + #fff 10%, + #fff 90%, + #efefef )); - border: 1px solid $color-shadow-light; - padding: 3px; + border: 1px solid lighten($color-medium-separator, 20%); + padding: 7px; } -input.loading, -input.ui-state-default.loading, .ui-widget-content input.ui-state-default.loading, -.ui-widget-header input.ui-state-default.loading { - padding-left: 16px; - background: $color-widget-bg url(../../images/network-save.gif) no-repeat center left; -} +/** ---------------------------------------------------- + * Buttons + * ---------------------------------------------------- */ -// Buttons -.ss-ui-button.ss-ui-action-constructive, -.ui-widget-content .ss-ui-button.ss-ui-action-constructive, -.ui-widget-header .ss-ui-button.ss-ui-action-constructive { - background: none; // avoid overwritten gradient from jQuery UI styles - @include linear-gradient(color-stops( - $color-button-constructive, - darken($color-button-constructive, 20%) - )); - color: $color-text-light; -} +.cms { + input.loading, + input.ui-state-default.loading, .ui-widget-content input.ui-state-default.loading, + .ui-widget-header input.ui-state-default.loading { + padding-left: 16px; + background: $color-widget-bg url(../../images/network-save.gif) no-repeat center left; + } -.ss-ui-button.ss-ui-action-destructive, -.ui-widget-content .ss-ui-button.ss-ui-action-destructive, -.ui-widget-header .ss-ui-button.ss-ui-action-destructive { - color: $color-button-destructive; -} + .ss-ui-button.ss-ui-action-constructive, + .ui-widget-content .ss-ui-button.ss-ui-action-constructive, + .ui-widget-header .ss-ui-button.ss-ui-action-constructive { + padding-left: 23px; + color: $color-text-light; + border-color: $color-button-constructive-border; + @include background(image-url("../images/btn_icons_sprite.png") no-repeat 5px 6px, + linear-gradient(color-stops( + $color-button-constructive-light, + $color-button-constructive-dark + )) + ); + background-color: $color-button-constructive-light; + @include text-shadow(darken($color-base, 40%) 1px 1px 0); + @include box-shadow(darken($color-base, 20%) 1px 1px 2px); + } -.ss-ui-button.ss-ui-action-minor, -.ui-widget-content .ss-ui-button.ss-ui-action-minor -.ui-widget-header .ss-ui-button.ss-ui-action-minor { - background: none; - padding: 0; - border: 0; - color: $color-text-dark; - text-decoration: underline; + .ss-ui-button.ss-ui-action-constructive.ui-state-hover { + @include background(image-url("../images/btn_icons_sprite.png") no-repeat 5px 6px, + linear-gradient(color-stops( + darken($color-button-constructive-light, 2%), + darken($color-button-constructive-dark, 3%) + )) + ); + background-color: $color-button-constructive-dark; + @include box-shadow(darken($color-base, 10%) 1px 1px 1px); + } + + .ss-ui-button.ss-ui-action-constructive.cms-page-add-button { + background-position: 5px -155px; + } + + .ss-ui-button.ss-ui-action-destructive, + .ui-widget-content .ss-ui-button.ss-ui-action-destructive, + .ui-widget-header .ss-ui-button.ss-ui-action-destructive { + color: $color-button-destructive; + background-color: $color-button-generic-light; + } + + .ss-ui-button.ss-ui-action-destructive.delete { + padding-left: 23px; + color: $color-button-destructive; + border-color: $color-button-generic-border; + @include background(image-url("../images/btn_icons_sprite.png") no-repeat 5px -26px, + linear-gradient(color-stops( + $color-button-generic-light, + $color-button-generic-dark + )) + ); + @include text-shadow(none); + @include box-shadow(darken($color-base, 2%) 1px 1px 2px); + } + + .ss-ui-button.ss-ui-action-destructive.delete.ui-state-hover { + @include background(image-url("../images/btn_icons_sprite.png") no-repeat 5px -26px, + linear-gradient(color-stops( + darken($color-button-generic-light, 2%), + darken($color-button-generic-dark, 3%) + )) + ); + background-color: $color-button-generic-dark; + @include box-shadow(darken($color-base, 10%) 1px 1px 1px); + } + + .ss-ui-button.ss-ui-action-minor, + .ui-widget-content .ss-ui-button.ss-ui-action-minor + .ui-widget-header .ss-ui-button.ss-ui-action-minor { + background: none; + padding: 0; + border: 0; + color: $color-text-dark; + text-decoration: underline; + } } .cms-edit-form { @@ -105,24 +166,29 @@ input.ui-state-default.loading, .ui-widget-content input.ui-state-default.loadin .text input, textarea { width: 300px; + font-family: $font-family; + font-size: $font-base-size; } - // TODO Unclear if "button bar" concept is edit form specific .Actions { text-align: right; } } -.cms-content-tools { - .field { - label { - float: none; - width: auto; - } - - .middleColumn { - margin-left: 0; - } +/** ---------------------------------------------------- + * Specific field overrides + * ---------------------------------------------------- */ + +.htmleditor { + label { + display: block; + float: none; + padding-bottom: 10px; + } + + .middleColumn { + margin-left: 0px; + clear: left; } } @@ -130,4 +196,8 @@ input.ui-state-default.loading, .ui-widget-content input.ui-state-default.loadin label { display: none; } +} + +.action-hidden { + display: none; } \ No newline at end of file diff --git a/admin/scss/_menu.scss b/admin/scss/_menu.scss index 9d3b88792..ddf8ad2de 100755 --- a/admin/scss/_menu.scss +++ b/admin/scss/_menu.scss @@ -21,9 +21,34 @@ a { text-decoration: none; } + + .cms-panel-content { + width: 250px; // avoids floating of elements when collapsed + } + + // toggled via JS + &.collapsed { + + width: 40px; + cursor: auto; + + .cms-menu-list { + li span.text { + display: none; + } + li ul { + display: none; + } + } + + &.cms-panel .cms-panel-content { + display: block; // override panel defaults + } + } } .cms-menu-list { + li { a { @@ -35,14 +60,14 @@ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - text-shadow: lighten($color-base, 10%) 1px 1px 0; + text-shadow: lighten($color-base, 5%) 1px 1px 0; color: $color-text-dark; - padding: 5px; + padding: 5px 5px 5px 12px; //left aligning with SS logo at top background-color: $color-base; @include linear-gradient(color-stops( $color-base, - darken($color-base, 8%) + darken($color-base, 12%) )); border-top: 1px solid lighten($color-base, 10%); @@ -75,8 +100,8 @@ display: block; float: left; margin-right: 4px; - @include sprite-background("icons-32.png"); - @include sprite-position(1, 1); + //@include sprite-background("icons-32.png"); + //@include sprite-position(1, 1); } .text { @@ -105,7 +130,7 @@ background-color: darken($color-menu-button, 10%); a { - font-size: $font-small-size; + font-size: $font-base-size - 1; padding: 0 10px 0 36px; height: 32px; line-height: 32px; @@ -140,4 +165,23 @@ } } } + + li#Menu-CMSMain a .icon {@include sprite-position(1, 1);} + li#Menu-CMSMain.current a .icon, li#Menu-CMSMain a:hover .icon {@include sprite-position(2, 1);} + li#Menu-AssetAdmin a .icon {@include sprite-position(1, 4);} + li#Menu-AssetAdmin.current a .icon, li#Menu-AssetAdmin a:hover .icon {@include sprite-position(2, 4);} + li#Menu-SecurityAdmin a .icon {@include sprite-position(1, 5);} + li#Menu-SecurityAdmin.current a .icon, li#Menu-SecurityAdmin a:hover .icon {@include sprite-position(2, 5);} + li#Menu-CMSPagesController a .icon {@include sprite-position(1, 2);} + li#Menu-CMSPagesController.current a .icon, li#Menu-CMSPagesController a:hover .icon {@include sprite-position(2, 2);} + + &.collapsed { + li .text { + display: none; + } + + li > li { + display: none; + } + } } \ No newline at end of file diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss index 1e0fd04c0..f18d6a86c 100755 --- a/admin/scss/_style.scss +++ b/admin/scss/_style.scss @@ -1,13 +1,8 @@ /** - * This file defines the 'theme' of the CMS: Colors, fonts, backgrounds, and detailed alignments. - * Together with _layout.css it provides the presentational backbone to the CMS. - * Ideally a developer should be able to exchange this file with his own theme easily. - * - * Please don't put any dimension, display or float information on major structural components - * like '.cms-container' or '.cms-header' in here, use the _layout.scss file instead. + * This file defines most styles of the CMS: Colors, fonts, backgrounds, alignments, dimensions. * * Use SCSS variable definitions in screen.css to avoid repeating styles like background colours - * or padding dimensions. See _colours.scss to get started. + * or padding dimensions. See themes/_default.scss to get started. * * To avoid this file getting too large and complicated, it is encouraged to create new SCSS files * for larger components like the CMS menu or tree (see _tree.scss and _menu.scss). @@ -39,6 +34,7 @@ a { } } body .ui-widget { + font-family: $font-family; font-size: $font-base-size; } @@ -51,7 +47,8 @@ body .ui-widget { .cms-content, .cms-content-header, .cms-content-tools, -.cms-content-form { +.cms-content-fields, +.cms-edit-form { @include inline-block; } @@ -60,24 +57,32 @@ strong { font-weight: bold; } -// ######################### Misc Panels ######################### +/** -------------------------------------------- + * Misc Panels + * -------------------------------------------- */ .cms-content-header { background-color: darken($color-widget-bg, 20%); - padding: 8px; + padding: 8px 8px 6px 8px; height: 32px; + border-bottom: 2px solid darken($color-widget-bg, 35%); @include linear-gradient(color-stops( - darken($color-widget-bg, 20%), + darken($color-widget-bg, 5%), darken($color-widget-bg, 30%) )); + + border-bottom: 1px solid darken($color-widget-bg, 50%); + padding: 10px; + height: 32px; h2 { float: left; - padding: 8px; - font-size: 14px; + padding: 12px 0 0 8px; + font-size: $font-base-size; font-weight: bold; + text-shadow: lighten($color-base, 10%) 1px 1px 0; width: 250px - (10px*2); } @@ -93,39 +98,56 @@ strong { .ui-tabs .cms-content-header { .ui-tabs-nav li { - height: 40px; - a { font-weight: bold; - font-size: 11px; - padding-top: 8px; + font-size: $font-base-size; + padding: 11px 15px 9px; + border-bottom: 2px solid darken($color-tab, 15%); } } .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { - background-color: lighten($color-shadow-light, 20%); + background-color: $color-tab; @include linear-gradient(color-stops( - lighten($color-shadow-light, 20%), - lighten($color-shadow-light, 5%) + $color-tab, + darken($color-tab, 10%) )); + + border-color: darken($color-tab, 20%); + margin: 0 3px 0 0; + text-shadow: lighten($color-tab, 60%) 0 1px 0; } .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { background: $color-widget-bg; + + a { + border-bottom: 2px solid $color-widget-bg; + } } } .cms-content-tools { background-color: darken($color-widget-bg, 5%); - padding: 10px; + padding: 8px; width: 230px; overflow: auto; + + .cms-panel-header, + .cms-panel-content { + padding: 10px; + } } +.cms-content { + &.loading { + background: url(../images/spinner.gif) no-repeat 50% 50%; + } +} /** ------------------------------------------------------- * Top Left Header and logo area @@ -181,24 +203,25 @@ strong { .cms-login-status { border-top: 1px solid $color-dark-separator; height: 23px; - padding: 8px 0 0 14px; + padding: 8px 4px 0 4px; overflow: hidden; line-height: 16px; - font-size: 11px; + font-size: $font-base-size - 2; .logout-link { display: inline-block; height: 16px; width: 16px; float: left; - margin-right: 10px; + margin: 0 15px 0 5px; background: url(../images/logout.png) no-repeat; text-indent: -9999em; } } -// ######################### Loading Screen ######################### - +/** ----------------------------------------------- + * Loading Screen + * ------------------------------------------------ */ .ss-loading-screen, .ss-loading-screen .loading-logo { width: 100%; @@ -245,17 +268,20 @@ strong { } } -// ######################### Actions ######################### +/** -------------------------------------------- + * Actions + * -------------------------------------------- */ .cms-content-actions { + border-top: 1px solid darken($color-widget-bg, 35%); padding: 8px; + background: transparent url(../images/textures/bg_cms_main_content.png) repeat top left; } -// ######################### Messages ######################### +/** -------------------------------------------- + * Messages + * -------------------------------------------- */ -/** - * Messages (see sapphire/css/Form.css) - */ .message { margin: 1em 0; padding: 0.5em; @@ -277,7 +303,9 @@ strong { } } -// ######################### ModelAdmin ######################### +/** -------------------------------------------- + * ModelAdmin + * -------------------------------------------- */ .ModelAdmin { .cms-content-tools { @@ -290,7 +318,10 @@ strong { } } -// ######################### "Add page" dialog ######################### +/** -------------------------------------------- + * "Add page" dialog + * -------------------------------------------- */ + .cms-page-add-form-dialog { #PageType li { clear: left; @@ -320,10 +351,23 @@ strong { } } -// ######################### Content toolbar ######################### +/** -------------------------------------------- + * Content toolbar + * -------------------------------------------- */ + .cms-content-toolbar { + @include clearfix; + display: block; + padding: 10px 0; + margin: 0 0 15px 0; + border-bottom-width: 2px; + border-bottom: 2px groove lighten($color-shadow-light, 95%); + -webkit-border-image: url(../images/textures/bg_fieldset_elements_border.png) 2 stretch stretch; + border-image: url(../images/textures/bg_fieldset_elements_border.png) 2 stretch stretch; + //TODO: use single border line with shadow instead:: http://daverupert.com/2011/06/two-tone-borders-with-css3/ + & > * { - display: inline-block; + float: left; } .cms-tree-view-modes * { @@ -335,10 +379,45 @@ strong { } } -// ######################### Preview header (remove before release) ######################### +.cms-content-tools { + .field { + label { + float: none; + width: auto; + } + + .middleColumn { + margin-left: 0; + } + } +} + +.cms-content-batchactions, .cms-content-constructive-actions { + float: right; +} + +.cms-content-batchactions { + float: right; + position: relative; + display: block; + margin-right: 8px; +} + +form.cms-batch-actions { + float: left; +} + +.cms-content-constructive-actions a { + display: block; + float: right; +} + +/** -------------------------------------------- + * Preview header (remove before release) + * -------------------------------------------- */ + .cms-preview { width: 1px; // collapsed by default - overflow: hidden; .cms-preview-toggle { width: 10px; @@ -356,7 +435,9 @@ strong { font-weight: bold; } -// ######################### Member Profile ######################### +/** -------------------------------------------- + * Member Profile + * -------------------------------------------- */ form.member-profile-form { @@ -401,10 +482,71 @@ form.member-profile-form { } -.cms-content-form { +.cms-content-fields { overflow: auto; + background: transparent url(../images/textures/bg_cms_main_content.png) repeat top left; } +/** -------------------------------------------- + * Panels + * -------------------------------------------- */ + +.cms-panel { + + overflow: hidden; + + .toggle-expand, + .toggle-collapse { + display: block; + position: absolute; + bottom: 0; + text-align: right; + @include linear-gradient(color-stops( + darken($color-widget-bg, 20%), + darken($color-widget-bg, 30%) + )); + text-decoration: none; + + span { + display: inline-block; + margin: 5px; + color: $color-text-dark; + font-size: 16px; + } + } + + .toggle-collapse { + width: 100%; + } + + .toggle-expand { + width: 40px; // will set the collapsed width + } + + &.collapsed { + + .cms-panel-content { + display: none; + } + + .cms-panel-header { + @include rotate(-90deg); + // TODO How to reflow container to correct height? + position: relative; + top: 100px; + } + } + +} + +.cms-content .cms-panel.collapsed { + cursor: pointer; +} + +/** -------------------------------------------- + * Other + * -------------------------------------------- */ + .cms-preview { background-color: $color-base; @@ -412,9 +554,25 @@ form.member-profile-form { cursor: pointer; a { + display: block; + width: 15px; + height: 15px; + position: relative; + left: 10px; // point right (width of toggle) + top: 48%; + background-color: $color-base; color: $color-text-light; font-weight: bold; text-decoration: none; + z-index: 2000; + } + } + + &.is-collapsed { + .cms-preview-toggle { + a { + left: -15px; // point left + } } } } diff --git a/admin/scss/_tree.scss b/admin/scss/_tree.scss index 015c97345..69932f4c7 100755 --- a/admin/scss/_tree.scss +++ b/admin/scss/_tree.scss @@ -32,6 +32,7 @@ height: 18px; margin: 0 0 0 0; padding: 0; + float: left; } a { display: inline-block; @@ -364,3 +365,165 @@ li.jstree-closed > ul { background: none; } } + +.jstree li { + line-height: 25px; +} + +.cms-tree.jstree-apple { + + & li.Root { + & strong { + font-weight: bold; + padding-left: 1px; + } + + & > a .jstree-icon { + background-position: -56px -36px; + } + } + + & a, a:link { + color: $color-text-blue-link; + padding: 3px 6px 3px 3px; + border: none; + display: inline-block; + margin-right: 5px; + + + & span.status:after { + clear: both; + text-transform: uppercase; + display: inline-block; + padding: 0px 3px; + font-size: 0.75em; + line-height: 1em; + margin-left: 3px; + margin-right: 6px; + margin-top: -1px; + @include border-radius(2px, 2px); + } + } + + & span.modified:after { + content: "draft"; + color: #7E7470; + border: 1px solid #C9B800; + background-color: #FFF0BC; + } + + & span.new:after { + content: "new"; + color: #7E7470; + border: 1px solid #C9B800; + background-color: #FFF0BC; + } + + & span.private:after { + content: "private"; + color: #636363; + border: 1px solid #E49393; + background-color: #F2DADB; + } + + & span.workflow-approval:after { + content: "awaiting approval"; + color: #56660C; + border: 1px solid #7C8816; + background-color: #DAE79A; + } + + /* comment speech bubble - ccs3 only - source: http://nicolasgallagher.com/pure-css-speech-bubbles/demo/ */ + & span.comment-count { + clear: both; + position: relative; + text-transform: uppercase; + display: inline-block; + overflow: visible; + padding: 0px 3px; + font-size: 0.75em; + line-height: 1em; + margin-left: 3px; + margin-right: 6px; + @include border-radius(2px, 2px); + color: #7E7470; + border: 1px solid #C9B800; + background-color: #FFF0BC; + } + + & span.comment-count:before { + content:""; + position:absolute; + bottom:-4px; /* value = - border-top-width - border-bottom-width */ + left:3px; /* controls horizontal position */ + border-width:4px 4px 0; + border-style:solid; + border-color:#C9B800 transparent; + /* reduce the damage in FF3.0 */ + display:block; + width:0; + } + + & span.comment-count:after { + content:""; + position:absolute; + bottom:-3px; /* value = - border-top-width - border-bottom-width */ + left:4px; /* value = (:before left) + (:before border-left) - (:after border-left) */ + border-width:3px 3px 0; + border-style:solid; + border-color:#FFF0BC transparent; + /* reduce the damage in FF3.0 */ + display:block; + width:0; + } + + .jstree-hovered { + text-shadow: none; + text-decoration: none; + } + + & li { + padding: 0px; + clear: left; + } + + & li, ins { + background-color: transparent; + background-image: url(../images/sitetree_ss_default_icons.png); + } + + & li.jstree-checked a, li.jstree-checked a:link { + background-color: $color-cms-batchactions-menu-selected-background; + } +} + +.jstree-apple #record-0.jstree-open > ins { + display: none; +} + +a .jstree-pageicon { + display: block; + float: left; + width: 16px; + height: 16px; + margin-right: 4px; + background-color: transparent; + background-image: url(../images/sitetree_ss_pageclass_icons_default.png); + background-repeat: no-repeat; +} + +li.class-HomePage a .jstree-pageicon { + background-position: 0 -48px; +} + +li.class-RedirectorPage a .jstree-pageicon { + background-position: 0 -16px; +} + +li.class-VirtualPage a .jstree-pageicon { + background-position: 0 -32px; +} + +li.class-ErrorPage a .jstree-pageicon { + background-position: 0 -112px; +} \ No newline at end of file diff --git a/admin/scss/_uitheme.scss b/admin/scss/_uitheme.scss index a4b026f7b..815cb59b7 100755 --- a/admin/scss/_uitheme.scss +++ b/admin/scss/_uitheme.scss @@ -18,16 +18,32 @@ border: 0; background: none; } + + .ui-tabs-nav { + margin: 0; + padding: 0; + + li { + top: 0; + + a { + padding: 0 15px; + } + } + + &.ui-state-active { + border-color: $color-medium-separator; + } + } } .ui-widget-content, .ui-tabs .ui-tabs-panel { color: $color-text; - font-size: 1em; + font-size: $font-base-size; border: 0; - background: $color-widget-bg; } - + .ui-widget-header { background: $color-widget-bg; border: 0; @@ -38,4 +54,30 @@ padding: 5px; text-decoration: none; } + + // Buttons + .ui-state-hover { + cursor: pointer; + } + + .ss-ui-button, + .ui-widget-content .ss-ui-button, + .ui-widget-header .ss-ui-button { + padding: 5px 7px 5px 7px; + color: $color-text-dark; + @include background(image-url("../images/btn_icons_sprite.png") no-repeat 999px 999px, + linear-gradient(color-stops( + $color-button-generic-light, + $color-button-generic-dark + )) + ); + background-color: $color-button-generic-light; + @include box-shadow(darken($color-base, 20%) 1px 1px 2px); + } + +} + +.cms-content-form { + overflow: auto; + background: transparent url(../images/textures/bg_cms_main_content.png) repeat top left !important; } \ No newline at end of file diff --git a/admin/scss/screen.scss b/admin/scss/screen.scss index e30e89466..7476578dc 100755 --- a/admin/scss/screen.scss +++ b/admin/scss/screen.scss @@ -13,6 +13,7 @@ @import "compass/reset"; @import "compass/css3"; @import "compass/utilities/sprites/sprite-img"; +@import "compass/utilities/general"; /** ----------------------------- * Theme diff --git a/admin/scss/themes/_default.scss b/admin/scss/themes/_default.scss index 62b7cc0b8..67144679c 100755 --- a/admin/scss/themes/_default.scss +++ b/admin/scss/themes/_default.scss @@ -1,8 +1,7 @@ /** * This file contains the default theme definitions for the admin interface. - * - * @package sapphire - * @subpackage admin + * Please put mostly SCSS variable definitions in here, + * and leave the actual styling to _style.scss and auxilliary files. */ /** ----------------------------------------------- @@ -14,6 +13,9 @@ $color-widget-bg: lighten($color-base, 20%); $color-dark-bg: #003050; $color-dark-separator: #19435c; +$color-medium-separator: #808080; + +$color-tab: #d9d9d9; $color-shadow-light: rgba(201, 205, 206, 0.8); $color-shadow-dark: rgba(107, 120, 123, 0.5); @@ -29,20 +31,29 @@ $color-text-light: white; $color-text-light-link: white; $color-text-dark: #1f1f1f; $color-text-dark-link: #3EBAE0; +$color-text-blue-link:#1556B2; -$color-button-constructive: #77b53f; +$color-button-generic-dark: #c3c3c3; +$color-button-generic-light:#f5f5f5; +$color-button-generic-border: #ABABAB; +$color-button-constructive-dark: #128945; +$color-button-constructive-light:#84BE3F; +$color-button-constructive-border: #118021; $color-button-destructive: #f00; $color-warning: #FF9300; $color-error: #FF9300; $color-notice: #FF9300; +$color-cms-batchactions-menu-background: #f5f5f5; +$color-cms-batchactions-menu-selected-background: #efe999; + + /** ----------------------------------------------- * Typography * ------------------------------------------------ */ -$font-family: Verdana, Arial, sans-serif; +$font-family: Arial, sans-serif; $font-base-size: 13px; -$font-small-size: 11px; $font-base-lineheight: 16px; /** ----------------------------------------------- diff --git a/admin/scss/themes/_grayscale.scss b/admin/scss/themes/_grayscale.scss index 7d6d9c18d..ec18c1209 100755 --- a/admin/scss/themes/_grayscale.scss +++ b/admin/scss/themes/_grayscale.scss @@ -1,33 +1,49 @@ /** - * This file contains the grayscale theme definitions for the admin interface. - * - * - * @package sapphire - * @subpackage admin + * Sample alternative theme without colors (grayscale only). */ /** ----------------------------------------------- * Colours * ------------------------------------------------ */ -$color-base: grayscale(#B0BFC6); +$color-base: grayscale(#b0bec7); + $color-widget-bg: grayscale(lighten($color-base, 20%)); + $color-dark-bg: grayscale(#003050); -$color-shadow-light: grayscale(#aaa); -$color-shadow-dark: grayscale(#333); +$color-dark-separator: grayscale(#19435c); + +$color-shadow-light: grayscale(rgba(201, 205, 206, 0.8)); +$color-shadow-dark: grayscale(rgba(107, 120, 123, 0.5)); + $color-highlight: grayscale(#FFFF66); -$color-menu-button: grayscale(#338DC1); -$color-text: grayscale(#444); + +$color-menu-button: grayscale(#338DC1); +$color-menu-background: grayscale(#c6d7df); +$color-menu-border: grayscale(#8c99a1); + +$color-text: #444; $color-text-light: white; $color-text-light-link: white; -$color-text-dark: grayscale(#333); +$color-text-dark: #1f1f1f; $color-text-dark-link: grayscale(#3EBAE0); -$color-button-constructive: grayscale(#77b53f); -$color-button-destructive: grayscale(#f00); -$color-warning: grayscale(#FF9300); -$color-error: grayscale(#FF9300); -$color-notice: grayscale(#FF9300); + +$color-button-constructive: #77b53f; +$color-button-destructive: #f00; + +$color-warning: #FF9300; +$color-error: #FF9300; +$color-notice: #FF9300; /** ----------------------------------------------- * Typography * ------------------------------------------------ */ -$default-fonts: Verdana, Arial, sans-serif; \ No newline at end of file +$font-family: Arial, sans-serif; +$font-base-size: 13px; +$font-base-lineheight: 16px; + +/** ----------------------------------------------- + * Application Logo (CMS Logo) + * ------------------------------------------------ */ +$application-logo-small: image-url("logo_small.png"); +$application-logo-small-ysize: 25px; +$application-logo-small-xsize: 25px; \ No newline at end of file diff --git a/admin/templates/Includes/LeftAndMain_EditForm.ss b/admin/templates/Includes/LeftAndMain_EditForm.ss index d42c8bca2..f02c5a5b2 100755 --- a/admin/templates/Includes/LeftAndMain_EditForm.ss +++ b/admin/templates/Includes/LeftAndMain_EditForm.ss @@ -2,56 +2,56 @@ <% end_if %> -
    -
    -

    My Page Title

    - <% if Fields.hasTabset %> - <% with Fields.fieldByName('Root') %> -
    -
      - <% control Tabs %> -
    • $Title
    • - <% end_control %> -
    - <% end_with %> -
    +
    +
    +

    My Page Title

    + <% if Fields.hasTabset %> + <% with Fields.fieldByName('Root') %> +
    +
      + <% control Tabs %> +
    • $Title
    • + <% end_control %> +
    + <% end_with %> +
    + <% end_if %> + + +
    +
    + +
    + + + + <% if Message %> +

    $Message

    + <% else %> + <% end_if %> + +
    + <% if Legend %>$Legend<% end_if %> + <% control Fields %> + $FieldHolder + <% end_control %> +
    +
    -
    -
    - - -
    - - <% if Message %> -

    $Message

    - <% else %> - - <% end_if %> - -
    - <% if Legend %>$Legend<% end_if %> - <% control Fields %> - $FieldHolder - <% end_control %> -
    -
    - -
    - -
    - <% if Actions %> -
    - <% control Actions %> - $Field - <% end_control %> +
    + <% if Actions %> +
    + <% control Actions %> + $Field + <% end_control %> +
    + <% end_if %>
    - <% end_if %> -
    <% if IncludeFormTag %> diff --git a/admin/templates/Includes/LeftAndMain_Menu.ss b/admin/templates/Includes/LeftAndMain_Menu.ss index da5537edd..870308cb5 100755 --- a/admin/templates/Includes/LeftAndMain_Menu.ss +++ b/admin/templates/Includes/LeftAndMain_Menu.ss @@ -1,50 +1,54 @@ -
    +
    + +
    -
    - - - - - +
    \ No newline at end of file diff --git a/admin/templates/Includes/ModelAdmin_Content.ss b/admin/templates/Includes/ModelAdmin_Content.ss index c8643d761..b12229713 100755 --- a/admin/templates/Includes/ModelAdmin_Content.ss +++ b/admin/templates/Includes/ModelAdmin_Content.ss @@ -1,4 +1,4 @@ -
    +
    @@ -6,39 +6,42 @@
    - -
    - -
    - <% if SearchClassSelector = tabs %> -
      - <% control ModelForms %> -
    • $Title
    • - <% end_control %> -
    - <% end_if %> - - <% if SearchClassSelector = dropdown %> -
    - Search for: - -
    - <% end_if %> + + <% end_if %> - <% control ModelForms %> -
    - $Content -
    - <% end_control %> + <% if SearchClassSelector = dropdown %> +
    + Search for: + +
    + <% end_if %> + + <% control ModelForms %> +
    + $Content +
    + <% end_control %> +
    -
    +
    $EditForm
    diff --git a/admin/templates/Includes/SecurityAdmin_Content.ss b/admin/templates/Includes/SecurityAdmin_Content.ss index a1c72be87..b9317f895 100755 --- a/admin/templates/Includes/SecurityAdmin_Content.ss +++ b/admin/templates/Includes/SecurityAdmin_Content.ss @@ -1,4 +1,4 @@ -
    +
    @@ -31,7 +31,7 @@
    -
    +
    $EditForm
    diff --git a/admin/templates/LeftAndMain.ss b/admin/templates/LeftAndMain.ss index 165b9938a..d2f56081a 100755 --- a/admin/templates/LeftAndMain.ss +++ b/admin/templates/LeftAndMain.ss @@ -4,10 +4,10 @@ <% base_tag %> -$ApplicationName | $SectionTitle +$Title - + <% include CMSLoadingScreen %> @@ -25,11 +25,9 @@ $Content - <% if currentPage %> -
    - +
    +
    - <% end_if %>
    diff --git a/admin/thirdparty/history-js/.piston.yml b/admin/thirdparty/history-js/.piston.yml new file mode 100644 index 000000000..480cb6a25 --- /dev/null +++ b/admin/thirdparty/history-js/.piston.yml @@ -0,0 +1,8 @@ +--- +format: 1 +handler: + commit: 861b4b1f7240b9d5e50d560ba5b94de78aa439e5 + branch: master +lock: false +repository_class: Piston::Git::Repository +repository_url: https://github.com/balupton/history.js.git diff --git a/admin/thirdparty/history-js/README.md b/admin/thirdparty/history-js/README.md new file mode 100644 index 000000000..5eeee3286 --- /dev/null +++ b/admin/thirdparty/history-js/README.md @@ -0,0 +1,311 @@ +Welcome to History.js (v1.7.0 - April 01 2011) +================== + + +This project is the successor of [jQuery History](http://balupton.com/projects/jquery-history), it aims to: + +- Follow the [HTML5 History API](https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history) as much as possible +- Provide a cross-compatible experience for all HTML5 Browsers (they all implement the HTML5 History API a little bit differently causing different behaviours and sometimes bugs - History.js fixes this ensuring the experience is as expected / the same / great throughout the HTML5 browsers) +- Provide a backwards-compatible experience for all HTML4 Browsers using a hash-fallback (including continued support for the HTML5 History API's `data`, `title`, `pushState` and `replaceState`) with the option to [remove HTML4 support if it is not right for your application](https://github.com/balupton/History.js/wiki/Intelligent-State-Handling) +- Provide a forwards-compatible experience for HTML4 States to HTML5 States (so if a hash-fallbacked url is accessed by a HTML5 browser it is naturally transformed into its non-hashed url equivalent) +- Provide support for as many javascript frameworks as possible via adapters; especially [jQuery](http://jquery.com/), [MooTools](http://mootools.net/), [Prototype](http://www.prototypejs.org/) and [Zepto](http://zeptojs.com/) + +Licensed under the [New BSD License](http://creativecommons.org/licenses/BSD/) +Copyright 2011 [Benjamin Arthur Lupton](http://balupton.com) + + +## Usage + +### Working with History.js: + + (function(window,undefined){ + + // Prepare + var History = window.History; // Note: We are using a capital H instead of a lower h + if ( !History.enabled ) { + // History.js is disabled for this browser. + // This is because we can optionally choose to support HTML4 browsers or not. + return false; + } + + // Bind to StateChange Event + History.Adapter.bind(window,'statechange',function(){ // Note: We are using statechange instead of popstate + var State = History.getState(); // Note: We are using History.getState() instead of event.state + History.log(State.data, State.title, State.url); + }); + + // Change our States + History.pushState({state:1}, "State 1", "?state=1"); // logs {state:1}, "State 1", "?state=1" + History.pushState({state:2}, "State 2", "?state=2"); // logs {state:2}, "State 2", "?state=2" + History.replaceState({state:3}, "State 3", "?state=3"); // logs {state:3}, "State 3", "?state=3" + History.pushState(null, null, "?state=4"); // logs {}, '', "?state=4" + History.back(); // logs {state:3}, "State 3", "?state=3" + History.back(); // logs {state:1}, "State 1", "?state=1" + History.back(); // logs {}, "Home Page", "?" + History.go(2); // logs {state:3}, "State 3", "?state=3" + + })(window); + +To ajaxify your entire website with the HTML5 History API, History.js and jQuery [this snippet of code](https://gist.github.com/854622) is all you need. It's that easy. + +### How would the above operations look in a HTML5 Browser? + +1. www.mysite.com +1. www.mysite.com/?state=1 +1. www.mysite.com/?state=2 +1. www.mysite.com/?state=3 +1. www.mysite.com/?state=4 +1. www.mysite.com/?state=3 +1. www.mysite.com/?state=1 +1. www.mysite.com +1. www.mysite.com/?state=3 + +> Note: These urls also work in HTML4 browsers and Search Engines. So no need for the hashbang (`#!`) fragment-identifier that google ["recommends"](https://github.com/balupton/History.js/wiki/Intelligent-State-Handling). + +### How would they look in a HTML4 Browser? + +1. www.mysite.com +1. www.mysite.com/#?state=1&_suid=1 +1. www.mysite.com/#?state=2&_suid=2 +1. www.mysite.com/#?state=3&_suid=3 +1. www.mysite.com/#?state=4 +1. www.mysite.com/#?state=3&_suid=3 +1. www.mysite.com/#?state=1&_suid=1 +1. www.mysite.com +1. www.mysite.com/#?state=3&_suid=3 + +> Note 1: These urls also work in HTML5 browsers - we use `replaceState` to transform these HTML4 states into their HTML5 equivalents so the user won't even notice :-) +> +> Note 2: These urls will be automatically url-encoded in IE6 to prevent certain browser-specific bugs. +> +> Note 3: Support for HTML4 browsers (this hash fallback) is optional [- why supporting HTML4 browsers could be either good or bad based on my app's use cases](https://github.com/balupton/History.js/wiki/Intelligent-State-Handling) + +### What's the deal with the SUIDs used in the HTML4 States? + +- SUIDs (State Unique Identifiers) are used when we utilise a `title` and/or `data` in our state. Adding a SUID allows us to associate particular states with data and titles while keeping the urls as simple as possible (don't worry it's all tested, working and a lot smarter than I'm making it out to be). +- If you aren't utilising `title` or `data` then we don't even include a SUID (as there is no need for it) - as seen by State 4 above :-) +- We also shrink the urls to make sure that the smallest url will be used. For instance we will adjust `http://www.mysite.com/#http://www.mysite.com/projects/History.js` to become `http://www.mysite.com/#/projects/History.js` automatically. (again tested, working, and smarter). +- It works with domains, subdomains, subdirectories, whatever - doesn't matter where you put it. It's smart. +- Safari 5 will also have a SUID appended to the URL, it is entirely transparent but just a visible side-effect. It is required to fix a bug with Safari 5. + +### Is there a working demo? + +- Sure is, give it a download and navigate to the demo directory in your browser :-) +- If you are after something a bit more adventurous than a end-user demo, open up the tests directory in your browser and editor - it'll rock your world and show all the vast use cases that History.js supports. + + +## Download & Installation + +1. Download History.js and upload it to your webserver. Download links: [tar.gz](https://github.com/balupton/History.js/tarball/master) or [zip](https://github.com/balupton/History.js/zipball/master) + +2. Include [JSON2](http://www.json.org/js.html) for HTML4 Browsers Only *(replace www.yourwebsite.com)* + + + +3. Include [Amplify.js Store](http://amplifyjs.com/) for Data Persistance and Synchronisation Support (optional but recommended) + + + +4. Include the Adapter for your Framework: + + - [jQuery](http://jquery.com/) v1.3+ + + + + - [Mootools](http://mootools.net/) v1.3+ + + + + - [Prototype](http://www.prototypejs.org/) v1.7+ (does not support versions of IE prior to 9 due to a bug in the prototype library) + + + + - [Zepto](http://zeptojs.com/) v0.5+ + + + + - _Would you like to support another framework? No problem! It's very easy to create adapters, and I'll be happy to include them or help out if you [let me know](https://github.com/balupton/history.js/issues) :-)_ + +5. Include History.js + + + + +> Note: If you want to only support HTML5 Browsers and not HTML4 Browsers (so no hash fallback support) then just remove the `history.html4.js` file include in step #5 and the JSON2 (`json2.js`) file include in step #2 [- why supporting HTML4 browsers could be either good or bad based on my app's use cases](https://github.com/balupton/History.js/wiki/Intelligent-State-Handling) + + +## Subscribe to Updates + +- For Email Updates: + - You can subscribe via the subscription form included in the demo page +- For Commit RSS/Atom Updates: + - You can subscribe via the [GitHub Commit Atom Feed](https://github.com/balupton/History.js/commits/master.atom) +- For GitHub News Feed Updates: + - You can click the "watch" button up the top right of History.js's [GitHub Project Page](https://github.com/balupton/History.js) + + +## Getting Support + +History.js is an actively developed, supported and maintained project. You can grab support via its [GitHub Issue Tracker](https://github.com/balupton/History.js/issues). Alternatively you can reach [Benjamin Lupton](http://balupton.com) (the core developer) via [twitter](http://twitter.com/balupton), skype (balupton) or email (contact@balupton.com). + + +## Giving Support + +If you'd love to give some support back and make a difference; here are a few great ways you can give back! + +- Give it your honest rating on its [jQuery Plugin's Page](http://plugins.jquery.com/project/history-js) and its [Ohloh Page](https://www.ohloh.net/p/history-js) +- If you have any feedback or suggestions let me know via its [Issue Tracker](https://github.com/balupton/History.js/issues) - so that I can ensure you get the best experience! +- Spread the word via tweets, blogs, tumblr, whatever - the more people talking about it the better! +- Donate via the donation form at the bottom right of [balupton.com](http://balupton.com) - every cent truly does help! +- Make it easier for me to let you know about future releases and updates by subscribing via the signup form inside the demo page +- Watch it via clicking the "watch" button up the top of its [Project Page](https://github.com/balupton/History.js) + +Thanks! every bit of help really does make a difference. Again thank you. + + +## Browsers: Tested and Working In + +### HTML5 Browsers + +- Chrome 8,9,10 +- Firefox 4 +- Safari 5 +- Safari iOS 4.3 + +### HTML4 Browsers + +- IE 6,7,8,9 +- Firefox 3 +- Opera 10,11 +- Safari 4 +- Safari iOS prior to version 4.3 + + +## Exposed API + +### Functions + +- `History.pushState(data,title,url)`
    Pushes a new state to the browser; `data` can be null or an object, `title` can be null or a string, `url` must be a string +- `History.replaceState(data,title,url)`
    Replaces the existing state with a new state to the browser; `data` can be null or an object, `title` can be null or a string, `url` must be a string +- `History.getState()`
    Gets the current state of the browser, returns an object with `data`, `title` and `url` +- `History.getHash()`
    Gets the current hash of the browser +- `History.Adapter.bind(element,event,callback)`
    A framework independent event binder, you may either use this or your framework's native event binder. +- `History.Adapter.trigger(element,event)`
    A framework independent event trigger, you may either use this or your framework's native event trigger. +- `History.Adapter.onDomLoad(callback)`
    A framework independent onDomLoad binder, you may either use this or your framework's native onDomLoad binder. +- `History.back()`
    Go back once through the history (same as hitting the browser's back button) +- `History.forward()`
    Go forward once through the history (same as hitting the browser's forward button) +- `History.go(X)`
    If X is negative go back through history X times, if X is positive go forwards through history X times +- `History.log(...)`
    Logs messages to the console, the log element, and fallbacks to alert if neither of those two exist +- `History.debug(...)`
    Same as `History.log` but only runs if `History.debug.enable === true` + +### Events + +- `window.onstatechange`
    Fired when the state of the page changes (does not include hash changes) +- `window.onanchorchange`
    Fired when the anchor of the page changes (does not include state hashes) + + +## Notes on Compatibility + +- History.js **solves** the following browser bugs: + - HTML5 Browsers + - Chrome 8 sometimes does not contain the correct state data when traversing back to the initial state + - Safari 5, Safari iOS 4 and Firefox 3 and 4 do not fire the `onhashchange` event when the page is loaded with a hash + - Safari 5 and Safari iOS 4 do not fire the `onpopstate` event when the hash has changed unlike the other browsers + - Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call / [bug report](https://bugs.webkit.org/show_bug.cgi?id=56249) + - Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions / [bug report](https://bugs.webkit.org/show_bug.cgi?id=42940) + - Google Chrome 8,9,10 and Firefox 4 prior to the RC will always fire `onpopstate` once the page has loaded / [change recommendation](http://hacks.mozilla.org/2011/03/history-api-changes-in-firefox-4/) + - Safari iOS 4.0, 4.1, 4.2 have a working HTML5 History API - although the actual back buttons of the browsers do not work, therefore we treat them as HTML4 browsers + - None of the HTML5 browsers actually utilise the `title` argument to the `pushState` and `replaceState` calls + - HTML4 Browsers + - Old browsers like MSIE 6,7 and Firefox 2 do not have a `onhashchange` event + - MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function) + - Non-Opera HTML4 browsers sometimes do not apply the hash when the hash is not `urlencoded` + - All Browsers + - State data and titles do not persist once the site is left and then returned (includes page refreshes) + - State titles are never applied to the `document.title` +- ReplaceState functionality is emulated in HTML4 browsers by discarding the replaced state, so when the discarded state is accessed it is skipped using the appropriate `History.back()` / `History.forward()` call +- Data persistance and synchronisation works like so: Every second or so, the SUIDs and URLs of the states will synchronise between the store and the local session. When a new session opens a familiar state (via the SUID or the URL) and it is not found locally then it will attempt to load the last known stored state with that information. +- URLs will be unescaped to the maximum, so for instance the URL `?key=a%20b%252c` will become `?key=a b c`. This is to ensure consistency between browser url encodings. +- Changing the hash of the page causes `onpopstate` to fire (this is expected/standard functionality). To ensure correct compatibility between HTML5 and HTML4 browsers the following events have been created: + - `window.onstatechange`: this is the same as the `onpopstate` event except it does not fire for traditional anchors + - `window.onanchorchange`: this is the same as the `onhashchange` event except it does not fire for states +- Known Issues + - Opera 11 fails to create history entries when under stressful loads (events fire perfectly, just the history events fail) - there is nothing we can do about this + - Mercury iOS fails to apply url changes (hashes and HTML5 History API states) - there is nothing we can do about this + + +## Changelog + +- v1.7.0 - April 01 2011 + - Added `History.enabled` property (refer to usage section). This reflects whether or not History.js is enabled for our particular browser. For instance, if we have not included support for a HTML4 browser and we are accessing through a HTML4 browser then `History.enabled` will be `false`. + - Added (optional but recommended) Data Persistance and Synchronisation Support thanks to [AppendTo's](http://appendto.com/) [Amplify.js](http://amplifyjs.com/) (refer to installation and compatibility sections for details) + - Made HTML5 SUIDs more transparent - [Reported](https://github.com/balupton/History.js/issues#issue/34) by [azago](https://github.com/azago) and [Mark Jaquith](http://markjaquith.com/) + - Fixed Session Storage Issue - Reported by a whole bunch of different people; [one](https://github.com/balupton/History.js/issues#issue/36), [two](https://github.com/balupton/History.js/issues#issue/37), [three](http://getsatisfaction.com/balupton/topics/history_js_1_6_losing_state_after_manual_page_reload) + - Fixed URL Encoding Issue - [Reported](https://github.com/balupton/history.js/issues/#issue/33) by [Rob Madole](http://robmadole.com/) + - Disabled support for IE6,7,8 when using the Prototype Adapter (there is nothing we can do about this, it is due to a bug in the prototype library) - [Reported](https://github.com/balupton/history.js/issues#issue/39) by [Sindre Wimberger](http://sindre.at/) + - URLs in the State Hashes for HTML4 Browsers are now even shorter - [Discussion](https://github.com/balupton/history.js/issues#issue/28) + - Fixed a issue with the MooTools Adapter and JSON with IE7 and IE8 + +- v1.6.0 - March 22 2011 + - Added Zepto adapter thanks to [Matt Garrett](http://twitter.com/#!/matthewgarrett) + - The readme now references the supported versions of the libraries we use + - Updated vendors to the most recent versions. jQuery 1.5.1 and Mootools 1.3.1 + - Reverted versions of Safari iOS prior to version 4.3 to be HTML4 browsers, Safari iOS 4.3 is a HTML5 browser + - Refined code in History.js and its adapters + - Fixed issue with extra state being inserted on Safari 5 requiring an extra click on the back button to go home - [Reported](https://github.com/balupton/history.js/issues#issue/17) by [Rob Madole](http://robmadole.com/) + - Fixed issue with Safari 5 and Safari iOS 4 sometimes failing to apply the state change under busy conditions - Solution conceived with [Matt Garrett](http://twitter.com/matthewgarrett) + - Fixed issue with HTML4 browsers requiring a query-string in the urls of states - [Reported](https://github.com/balupton/history.js/issues#issue/26) by [azago](https://github.com/azago) + - Fixed issue with HTML4 browsers requiring title in the states in order to use state data - [Reported](https://github.com/balupton/history.js/issues#issue/25) by [Jonathan McLaughlin](http://system-werks.com/) + - Fixed issue with HTML4 browsers failing is a state is pushed/replaced twice in a row - [Reported](https://github.com/balupton/history.js/issues#issue/17) by [Joey Baker](http://byjoeybaker.com/) + - **B/C BREAK:** The `statechange` event now only fires if the state has changed; it no longer fires on page initialisation. This is following the [Firefox 4 History API Changes](http://hacks.mozilla.org/2011/03/history-api-changes-in-firefox-4/) which we agree with - this breaks standard, but makes more sense. + +- v1.5.0 - February 12 2011 + - Moved to UglifyJS instead of Google Closure + - Split HTML4 functionality from HTML5 functionality + - Installation details have changed (the filenames are different) + +- v1.4.1 - February 10 2011 + - Added HTML History API Support for Safari 5 and Safari iOS 4.2.1 + - Cleaned code a bit (mostly with unit tests) + +- v1.4.0 - February 10 2011 + - Unit Testing now uses [QUnit](http://docs.jquery.com/Qunit) + - Corrected Safari 5 Support + - Now uses queues instead of timeouts + - This means the API works exactly as expected, no more need to wrap calls in timeouts + - Included a Subscribe Form in the Demo for Version Updates via Email + - Small updates to Documentation + +- v1.3.1 - February 04 2011 + - Improved Documentation + +- v1.3.0 - January 31 2011 + - Support for cleaner HTML4 States + +- v1.2.1 - January 30 2011 + - Fixed History.log always being called - [reported by dlee](https://github.com/balupton/History.js/issues/#issue/2) + - Re-Added `History.go(index)` support + +- v1.2.0 - January 25 2011 + - Support for HTML4 States in HTML5 Browsers (added test) + - Updates of Documentation + +- v1.1.0 - January 24 2011 + - Developed a series of automated test cases + - Fixed issue with traditional anchors + - Fixed issue with differing replaceState functionality in HTML4 Browsers + - Fixed issue with Google Chrome artefacts being carried over to the initial state + - Provided `onstatechange` and `onanchorchange` events + +- v1.0.0 - January 22 2011 + - Supported `History.pushState` and `History.replaceState` degradation + - Supported jQuery, MooTools and Prototype Frameworks + + +## Todo for Upcoming Releases + +- Allow for url to be optional in `pushState` and `replaceState` calls +- Add an Ajax extension to succeed the [jQuery Ajaxy](http://balupton.com/projects/jquery-ajaxy) project +- Add a compilation test to ensure `.debug = false` and no `History.log` or `console.xxx` calls exist. + +It's likely these features and/or others have been included in the latest [dev branch](https://github.com/balupton/History.js/tree/dev). If you are wanting to fork and help out, then be sure to work on the dev branch and not master. diff --git a/admin/thirdparty/history-js/license.txt b/admin/thirdparty/history-js/license.txt new file mode 100644 index 000000000..647bfd26d --- /dev/null +++ b/admin/thirdparty/history-js/license.txt @@ -0,0 +1,10 @@ +Copyright (c) 2011, Benjamin Arthur Lupton +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + • Neither the name of Benjamin Arthur Lupton nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/admin/thirdparty/history-js/package.json b/admin/thirdparty/history-js/package.json new file mode 100644 index 000000000..18ba6c559 --- /dev/null +++ b/admin/thirdparty/history-js/package.json @@ -0,0 +1,74 @@ +{ + "name": "history.js", + "version": "1.6.0", + "description": "History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles, replaceState. Supports jQuery, MooTools and Prototype. For HTML5 browsers this means that you can modify the URL directly, without needing to use hashes anymore. For HTML4 browsers it will revert back to using the old onhashchange functionality.", + "homepage": "https://github.com/balupton/history.js", + "keywords": [ + "javascript", + "html5 history api", + "hashchange", + "popstate", + "pushstate", + "replacestate", + "hashes", + "hashbang" + ], + "author": { + "name": "Benjamin Lupton", + "email": "b@lupton.cc", + "web": "http://balupton.com" + }, + "maintainers": [ + { + "name": "Benjamin Lupton", + "email": "b@lupton.cc", + "web": "http://balupton.com" + } + ], + "contributors": [ + { + "name": "Benjamin Lupton", + "email": "b@lupton.cc", + "web": "http://balupton.com" + } + ], + "bugs": { + "web": "https://github.com/balupton/history.js/issues" + }, + "licenses": [ + { + "type": "New-BSD", + "url": "http://creativecommons.org/licenses/BSD/" + } + ], + "repository" : { + "type" : "git", + "url" : "http://github.com/balupton/history.js.git" + }, + "dependencies": { + }, + "engines" : { + }, + "directories": { + "out": "./scripts/compressed", + "src": "./scripts/uncompressed" + }, + "buildr": { + "compress": { + "js": true, + "css": false, + "img": false, + "html": false + }, + "bundle": false, + "directories": { + "out": "./scripts/compressed", + "src": "./scripts/uncompressed" + }, + "files": { + "js": true, + "css": false, + "img": false + } + } +} diff --git a/admin/thirdparty/history-js/scripts/compressed/amplify.store.js b/admin/thirdparty/history-js/scripts/compressed/amplify.store.js new file mode 100644 index 000000000..4ae9f2b65 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/amplify.store.js @@ -0,0 +1 @@ +(function(a,b){function d(a,d){var e=d.__amplify__?JSON.parse(d.__amplify__):{};c.addType(a,function(f,g,h){var i=g,j=(new Date).getTime(),k,l;if(!f){i={};for(f in e)k=d[f],l=k?JSON.parse(k):{expires:-1},l.expires&&l.expires<=j?(delete d[f],delete e[f]):i[f.replace(/^__amplify__/,"")]=l.data;d.__amplify__=JSON.stringify(e);return i}f="__amplify__"+f;if(g===b){if(e[f]){k=d[f],l=k?JSON.parse(k):{expires:-1};if(l.expires&&l.expires<=j)delete d[f],delete e[f];else return l.data}}else if(g===null)delete d[f],delete e[f];else{l=JSON.stringify({data:g,expires:h.expires?j+h.expires:null});try{d[f]=l,e[f]=!0}catch(m){c[a]();try{d[f]=l,e[f]=!0}catch(m){throw c.error()}}}d.__amplify__=JSON.stringify(e);return i})}JSON.stringify=JSON.stringify||JSON.encode,JSON.parse=JSON.parse||JSON.decode;var c=a.store=function(a,b,d,e){var e=c.type;d&&d.type&&d.type in c.types&&(e=d.type);return c.types[e](a,b,d||{})};c.types={},c.type=null,c.addType=function(a,b){c.type||(c.type=a),c.types[a]=b,c[a]=function(b,d,e){e=e||{},e.type=a;return c(b,d,e)}},c.error=function(){return"amplify.store quota exceeded"};for(var e in{localStorage:1,sessionStorage:1})try{window[e].getItem&&d(e,window[e])}catch(f){}window.globalStorage&&(d("globalStorage",window.globalStorage[window.location.hostname]),c.type==="sessionStorage"&&(c.type="globalStorage")),function(){var a=document.createElement("div"),d="amplify",e;a.style.display="none",document.getElementsByTagName("head")[0].appendChild(a),a.addBehavior&&(a.addBehavior("#default#userdata"),a.load(d),e=a.getAttribute(d)?JSON.parse(a.getAttribute(d)):{},c.addType("userData",function(f,g,h){var i=g,j=(new Date).getTime(),k,l,m;if(!f){i={};for(f in e)k=a.getAttribute(f),l=k?JSON.parse(k):{expires:-1},l.expires&&l.expires<=j?(a.removeAttribute(f),delete e[f]):i[f]=l.data;a.setAttribute(d,JSON.stringify(e)),a.save(d);return i}f=f.replace(/[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g,"-");if(g===b){if(f in e){k=a.getAttribute(f),l=k?JSON.parse(k):{expires:-1};if(l.expires&&l.expires<=j)a.removeAttribute(f),delete e[f];else return l.data}}else g===null?(a.removeAttribute(f),delete e[f]):(m=a.getAttribute(f),l=JSON.stringify({data:g,expires:h.expires?j+h.expires:null}),a.setAttribute(f,l),e[f]=!0);a.setAttribute(d,JSON.stringify(e));try{a.save(d)}catch(n){m===null?(a.removeAttribute(f),delete e[f]):a.setAttribute(f,m),c.userData();try{a.setAttribute(f,l),e[f]=!0,a.save(d)}catch(n){m===null?(a.removeAttribute(f),delete e[f]):a.setAttribute(f,m);throw c.error()}}return i}))}(),d("memory",{})})(this.amplify=this.amplify||{}) \ No newline at end of file diff --git a/admin/templates/Includes/ModelAdmin.ss b/admin/thirdparty/history-js/scripts/compressed/history.adapter.dojo.js old mode 100755 new mode 100644 similarity index 100% rename from admin/templates/Includes/ModelAdmin.ss rename to admin/thirdparty/history-js/scripts/compressed/history.adapter.dojo.js diff --git a/admin/thirdparty/history-js/scripts/compressed/history.adapter.jquery.js b/admin/thirdparty/history-js/scripts/compressed/history.adapter.jquery.js new file mode 100644 index 000000000..c72a99f22 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/history.adapter.jquery.js @@ -0,0 +1 @@ +(function(a,b){var c=a.History=a.History||{},d=a.jQuery;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){d(a).bind(b,c)},trigger:function(a,b){d(a).trigger(b)},onDomLoad:function(a){d(a)}},typeof c.init!="undefined"&&c.init()})(window) \ No newline at end of file diff --git a/admin/thirdparty/history-js/scripts/compressed/history.adapter.mootools.js b/admin/thirdparty/history-js/scripts/compressed/history.adapter.mootools.js new file mode 100644 index 000000000..a31be1c30 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/history.adapter.mootools.js @@ -0,0 +1 @@ +(function(a,b){var c=a.History=a.History||{},d=a.MooTools;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");Object.append(Element.NativeEvents,{popstate:2,hashchange:2}),c.Adapter={bind:function(a,b,c){var d=typeof a=="string"?document.id(a):a;d.addEvent(b,c)},trigger:function(a,b){var c=typeof a=="string"?document.id(a):a;c.fireEvent(b)},onDomLoad:function(b){a.addEvent("domready",b)}},typeof c.init!="undefined"&&c.init()})(window) \ No newline at end of file diff --git a/admin/thirdparty/history-js/scripts/compressed/history.adapter.prototype.js b/admin/thirdparty/history-js/scripts/compressed/history.adapter.prototype.js new file mode 100644 index 000000000..b6f656d33 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/history.adapter.prototype.js @@ -0,0 +1 @@ +(function(a,b){var c=a.History=a.History||{},d=a.Prototype,e=a.Element,f=a.Event,g=a.$;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");typeof a.fireEvent=="undefined"&&typeof a.dispatchEvent=="undefined"?c.enable=!1:(function(){var a={HTMLEvents:/^(?:load|unload|abort|error|select|hashchange|popstate|change|submit|reset|focus|blur|resize|scroll)$/,MouseEvents:/^(?:click|mouse(?:down|up|over|move|out))$/},b={pointerX:0,pointerY:0,button:0,ctrlKey:!1,altKey:!1,shiftKey:!1,metaKey:!1,bubbles:!0,cancelable:!0};f.hasNativeEvent=function(b,c){var d=null,e;b=g(b);for(var f in a)if(a[f].test(c)){d=f;break}e=d?!0:!1;return e},f.bind=function(a,b,c){a=g(a);return e.hasNativeEvent(a,b)?e.observe(a,b,c):e.observe(a,"custom:"+b,c)},f.trigger=function(c,d){var f=Object.extend(b,arguments[2]||{}),h,i=null;c=g(c);var j;for(j in a)if(a[j].test(d)){i=j;break}if(!i)return e.fire(c,"custom:"+d);document.createEvent?(h=document.createEvent(i),i==="HTMLEvents"?h.initEvent(d,f.bubbles,f.cancelable):i&&h.initMouseEvent(d,f.bubbles,f.cancelable,document.defaultView,f.button,f.pointerX,f.pointerY,f.pointerX,f.pointerY,f.ctrlKey,f.altKey,f.shiftKey,f.metaKey,f.button,c)):document.createEventObject&&(f.clientX=f.pointerX,f.clientY=f.pointerY,h=Object.extend(document.createEventObject(),f));if(c.fireEvent)c.fireEvent("on"+d,h);else if(c.dispatchEvent)c.dispatchEvent(h);else throw new Error("Cannot dispatch the event");return c},e.addMethods({simulate:f.trigger,trigger:f.trigger,bind:f.bind,hasNativeEvent:f.hasNativeEvent})}(),c.Adapter={bind:function(a,b,c){e.bind(a,b,c)},trigger:function(a,b){e.trigger(a,b)},onDomLoad:function(b){f.observe(a.document,"dom:loaded",b)}},typeof c.init!="undefined"&&c.init())})(window) \ No newline at end of file diff --git a/admin/thirdparty/history-js/scripts/compressed/history.adapter.yui.js b/admin/thirdparty/history-js/scripts/compressed/history.adapter.yui.js new file mode 100644 index 000000000..dbd5ccebf --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/history.adapter.yui.js @@ -0,0 +1 @@ +(function(a,b){var c=a.History=a.History||{},d=a.YUI;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){d().use("node-base",function(d){d.one(a).on(b,c)})},trigger:function(a,b){d().use("node-event-simulate",function(c){c.one(a).simulate(b)})},onDomLoad:function(a){d().use("event",function(b){b.on("domready",a)})}},typeof c.init!="undefined"&&c.init()})(window) \ No newline at end of file diff --git a/admin/thirdparty/history-js/scripts/compressed/history.adapter.zepto.js b/admin/thirdparty/history-js/scripts/compressed/history.adapter.zepto.js new file mode 100644 index 000000000..5fd1d3ac6 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/history.adapter.zepto.js @@ -0,0 +1 @@ +(function(a,b){var c=a.History=a.History||{},d=a.Zepto;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){d(a).bind(b,c)},trigger:function(a,b){d(a).trigger(b)},onDomLoad:function(a){d(a)}},typeof c.init!="undefined"&&c.init()})(window) \ No newline at end of file diff --git a/admin/thirdparty/history-js/scripts/compressed/history.html4.js b/admin/thirdparty/history-js/scripts/compressed/history.html4.js new file mode 100644 index 000000000..cd082cb16 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/history.html4.js @@ -0,0 +1 @@ +(function(a,b){"use strict";var c=a.document,d=a.setTimeout||d,e=a.clearTimeout||e,f=a.setInterval||f,g=a.History=a.History||{};if(typeof g.initHtml4!="undefined")throw new Error("History.js HTML4 Support has already been loaded...");g.initHtml4=function(){if(typeof g.initHtml4.initialized!="undefined")return!1;g.initHtml4.initialized=!0,g.enabled=!0,g.savedHashes=[],g.isLastHash=function(a){var b=g.getHashByIndex(),c=a===b;return c},g.saveHash=function(a){if(g.isLastHash(a))return!1;g.savedHashes.push(a);return!0},g.getHashByIndex=function(a){var b=null;typeof a=="undefined"?b=g.savedHashes[g.savedHashes.length-1]:a<0?b=g.savedHashes[g.savedHashes.length+a]:b=g.savedHashes[a];return b},g.discardedHashes={},g.discardedStates={},g.discardState=function(a,b,c){var d=g.getHashByState(a),e={discardedState:a,backState:c,forwardState:b};g.discardedStates[d]=e;return!0},g.discardHash=function(a,b,c){var d={discardedHash:a,backState:c,forwardState:b};g.discardedHashes[a]=d;return!0},g.discardedState=function(a){var b=g.getHashByState(a),c=g.discardedStates[b]||!1;return c},g.discardedHash=function(a){var b=g.discardedHashes[a]||!1;return b},g.recycleState=function(a){var b=g.getHashByState(a);g.discardedState(a)&&delete g.discardedStates[b];return!0},g.emulated.hashChange&&(g.hashChangeInit=function(){g.checkerFunction=null;var b="";if(g.isInternetExplorer()){var d="historyjs-iframe",e=c.createElement("iframe");e.setAttribute("id",d),e.style.display="none",c.body.appendChild(e),e.contentWindow.document.open(),e.contentWindow.document.close();var h="",i=!1;g.checkerFunction=function(){if(i)return!1;i=!0;var c=g.getHash()||"",d=g.unescapeHash(e.contentWindow.document.location.hash)||"";c!==b?(b=c,d!==c&&(h=d=c,e.contentWindow.document.open(),e.contentWindow.document.close(),e.contentWindow.document.location.hash=g.escapeHash(c)),g.Adapter.trigger(a,"hashchange")):d!==h&&(h=d,g.setHash(d,!1)),i=!1;return!0}}else g.checkerFunction=function(){var c=g.getHash();c!==b&&(b=c,g.Adapter.trigger(a,"hashchange"));return!0};f(g.checkerFunction,g.options.hashChangeInterval);return!0},g.Adapter.onDomLoad(g.hashChangeInit)),g.emulated.pushState&&(g.onHashChange=function(b){var d=b&&b.newURL||c.location.href,e=g.getHashByUrl(d),f=null,h=null,i=null;if(g.isLastHash(e)){g.busy(!1);return!1}g.doubleCheckComplete(),g.saveHash(e);if(e&&g.isTraditionalAnchor(e)){g.Adapter.trigger(a,"anchorchange"),g.busy(!1);return!1}f=g.extractState(g.getFullUrl(e||c.location.href,!1),!0);if(g.isLastSavedState(f)){g.busy(!1);return!1}h=g.getHashByState(f);var j=g.discardedState(f);if(j){g.getHashByIndex(-2)===g.getHashByState(j.forwardState)?g.back(!1):g.forward(!1);return!1}g.pushState(f.data,f.title,f.url,!1);return!0},g.Adapter.bind(a,"hashchange",g.onHashChange),g.pushState=function(b,d,e,f){if(g.getHashByUrl(e))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(f!==!1&&g.busy()){g.pushQueue({scope:g,callback:g.pushState,args:arguments,queue:f});return!1}g.busy(!0);var h=g.createStateObject(b,d,e),i=g.getHashByState(h),j=g.getState(!1),k=g.getHashByState(j),l=g.getHash();g.storeState(h),g.expectedStateId=h.id,g.recycleState(h),g.setTitle(h);if(i===k){g.busy(!1);return!1}if(i!==l&&i!==g.getShortUrl(c.location.href)){g.setHash(i,!1);return!1}g.saveState(h),g.Adapter.trigger(a,"statechange"),g.busy(!1);return!0},g.replaceState=function(a,b,c,d){if(g.getHashByUrl(c))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(d!==!1&&g.busy()){g.pushQueue({scope:g,callback:g.replaceState,args:arguments,queue:d});return!1}g.busy(!0);var e=g.createStateObject(a,b,c),f=g.getState(!1),h=g.getStateByIndex(-2);g.discardState(f,e,h),g.pushState(e.data,e.title,e.url,!1);return!0},g.getHash()&&!g.emulated.hashChange&&g.Adapter.onDomLoad(function(){g.Adapter.trigger(a,"hashchange")}))},g.init()})(window) \ No newline at end of file diff --git a/admin/thirdparty/history-js/scripts/compressed/history.js b/admin/thirdparty/history-js/scripts/compressed/history.js new file mode 100644 index 000000000..2ee495824 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/history.js @@ -0,0 +1 @@ +(function(a,b){"use strict";var c=a.console||b,d=a.document,e=a.navigator,f=a.amplify||!1,g=a.setTimeout,h=a.clearTimeout,i=a.setInterval,j=a.JSON,k=a.History=a.History||{},l=a.history;j.stringify=j.stringify||j.encode,j.parse=j.parse||j.decode;if(typeof k.init!="undefined")throw new Error("History.js Core has already been loaded...");k.init=function(){if(typeof k.Adapter=="undefined")return!1;typeof k.initCore!="undefined"&&k.initCore(),typeof k.initHtml4!="undefined"&&k.initHtml4();return!0},k.initCore=function(){if(typeof k.initCore.initialized!="undefined")return!1;k.initCore.initialized=!0,k.options=k.options||{},k.options.hashChangeInterval=k.options.hashChangeInterval||100,k.options.safariPollInterval=k.options.safariPollInterval||500,k.options.doubleCheckInterval=k.options.doubleCheckInterval||500,k.options.storeInterval=k.options.storeInterval||1e3,k.options.busyDelay=k.options.busyDelay||250,k.options.debug=k.options.debug||!1,k.options.initialTitle=k.options.initialTitle||d.title,k.debug=function(){(k.options.debug||!1)&&k.log.apply(k,arguments)},k.log=function(){var a=typeof c!="undefined"&&typeof c.log!="undefined"&&typeof c.log.apply!="undefined",b=d.getElementById("log"),e,f,g;if(a){var h=Array.prototype.slice.call(arguments);e=h.shift(),typeof c.debug!="undefined"?c.debug.apply(c,[e,h]):c.log.apply(c,[e,h])}else e="\n"+arguments[0]+"\n";for(f=1,g=arguments.length;f")&&c[0]);return a>4?a:!1}();return a},k.isInternetExplorer=function(){var a=k.isInternetExplorer.cached=typeof k.isInternetExplorer.cached!="undefined"?k.isInternetExplorer.cached:Boolean(k.getInternetExplorerMajorVersion());return a},k.emulated={pushState:!Boolean(a.history&&a.history.pushState&&a.history.replaceState&&!/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i.test(e.userAgent)&&!/AppleWebKit\/5([0-2]|3[0-2])/i.test(e.userAgent)),hashChange:Boolean(!("onhashchange"in a||"onhashchange"in d)||k.isInternetExplorer()&&k.getInternetExplorerMajorVersion()<8)},k.enabled=!k.emulated.pushState,k.bugs={setHash:Boolean(!k.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),safariPoll:Boolean(!k.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),ieDoubleCheck:Boolean(k.isInternetExplorer()&&k.getInternetExplorerMajorVersion()<8),hashEscape:Boolean(k.isInternetExplorer()&&k.getInternetExplorerMajorVersion()<7)},k.isEmptyObject=function(a){for(var b in a)return!1;return!0},k.cloneObject=function(a){var b,c;a?(b=j.stringify(a),c=j.parse(b)):c={};return c},k.getRootUrl=function(){var a=d.location.protocol+"//"+(d.location.hostname||d.location.host);if(d.location.port||!1)a+=":"+d.location.port;a+="/";return a},k.getBaseHref=function(){var a=d.getElementsByTagName("base"),b=null,c="";a.length===1&&(b=a[0],c=b.href.replace(/[^\/]+$/,"")),c=c.replace(/\/+$/,""),c&&(c+="/");return c},k.getBaseUrl=function(){var a=k.getBaseHref()||k.getBasePageUrl()||k.getRootUrl();return a},k.getPageUrl=function(){var a=k.getState(!1,!1),b=(a||{}).url||d.location.href,c=b.replace(/\/+$/,"").replace(/[^\/]+$/,function(a,b,c){return/\./.test(a)?a:a+"/"});return c},k.getBasePageUrl=function(){var a=d.location.href.replace(/[#\?].*/,"").replace(/[^\/]+$/,function(a,b,c){return/[^\/]$/.test(a)?"":a}).replace(/\/+$/,"")+"/";return a},k.getFullUrl=function(a,b){var c=a,d=a.substring(0,1);b=typeof b=="undefined"?!0:b,/[a-z]+\:\/\//.test(a)||(d==="/"?c=k.getRootUrl()+a.replace(/^\/+/,""):d==="#"?c=k.getPageUrl().replace(/#.*/,"")+a:d==="?"?c=k.getPageUrl().replace(/[\?#].*/,"")+a:b?c=k.getBaseUrl()+a.replace(/^(\.\/)+/,""):c=k.getBasePageUrl()+a.replace(/^(\.\/)+/,""));return c.replace(/\#$/,"")},k.getShortUrl=function(a){var b=a,c=k.getBaseUrl(),d=k.getRootUrl();k.emulated.pushState&&(b=b.replace(c,"")),b=b.replace(d,"/"),k.isTraditionalAnchor(b)&&(b="./"+b),b=b.replace(/^(\.\/)+/g,"./").replace(/\#$/,"");return b},k.store=f?f.store("History.store")||{}:{},k.store.idToState=k.store.idToState||{},k.store.urlToId=k.store.urlToId||{},k.store.stateToId=k.store.stateToId||{},k.idToState=k.idToState||{},k.stateToId=k.stateToId||{},k.urlToId=k.urlToId||{},k.storedStates=k.storedStates||[],k.savedStates=k.savedStates||[],k.getState=function(a,b){typeof a=="undefined"&&(a=!0),typeof b=="undefined"&&(b=!0);var c=k.getLastSavedState();!c&&b&&(c=k.createStateObject()),a&&(c=k.cloneObject(c),c.url=c.cleanUrl||c.url);return c},k.getIdByState=function(a){var b=k.extractId(a.url);if(!b){var c=k.getStateString(a);if(typeof k.stateToId[c]!="undefined")b=k.stateToId[c];else if(typeof k.store.stateToId[c]!="undefined")b=k.store.stateToId[c];else{for(;;){b=String(Math.floor(Math.random()*1e3));if(typeof k.idToState[b]=="undefined"&&typeof k.store.idToState[b]=="undefined")break}k.stateToId[c]=b,k.idToState[b]=a}}return b},k.normalizeState=function(a){if(!a||typeof a!="object")a={};if(typeof a.normalized!="undefined")return a;if(!a.data||typeof a.data!="object")a.data={};var b={};b.normalized=!0,b.title=a.title||"",b.url=k.getFullUrl(k.unescapeString(a.url||d.location.href)),b.hash=k.getShortUrl(b.url),b.data=k.cloneObject(a.data),b.id=k.getIdByState(b),b.cleanUrl=b.url.replace(/\??\&_suid.*/,""),b.url=b.cleanUrl;var c=!k.isEmptyObject(b.data);if(b.title||c)b.hash=k.getShortUrl(b.url).replace(/\??\&_suid.*/,""),/\?/.test(b.hash)||(b.hash+="?"),b.hash+="&_suid="+b.id;b.hashedUrl=k.getFullUrl(b.hash),(k.emulated.pushState||k.bugs.safariPoll)&&k.hasUrlDuplicate(b)&&(b.url=b.hashedUrl);return b},k.createStateObject=function(a,b,c){var d={data:a,title:b,url:c};d=k.normalizeState(d);return d},k.getStateById=function(a){a=String(a);var c=k.idToState[a]||k.store.idToState[a]||b;return c},k.getStateString=function(a){var b=k.normalizeState(a),c={data:b.data,title:a.title,url:a.url},d=j.stringify(c);return d},k.getStateId=function(a){var b=k.normalizeState(a),c=b.id;return c},k.getHashByState=function(a){var b,c=k.normalizeState(a);b=c.hash;return b},k.extractId=function(a){var b,c,d;c=/(.*)\&_suid=([0-9]+)$/.exec(a),d=c?c[1]||a:a,b=c?String(c[2]||""):"";return b||!1},k.isTraditionalAnchor=function(a){var b=!/[\/\?\.]/.test(a);return b},k.extractState=function(a,b){var c=null;b=b||!1;var d=k.extractId(a);d&&(c=k.getStateById(d));if(!c){var e=k.getFullUrl(a);d=k.getIdByUrl(e)||!1,d&&(c=k.getStateById(d)),!c&&b&&!k.isTraditionalAnchor(a)&&(c=k.createStateObject(null,null,e))}return c},k.getIdByUrl=function(a){var c=k.urlToId[a]||k.store.urlToId[a]||b;return c},k.getLastSavedState=function(){return k.savedStates[k.savedStates.length-1]||b},k.getLastStoredState=function(){return k.storedStates[k.storedStates.length-1]||b},k.hasUrlDuplicate=function(a){var b=!1,c=k.extractState(a.url);b=c&&c.id!==a.id;return b},k.storeState=function(a){k.urlToId[a.url]=a.id,k.storedStates.push(k.cloneObject(a));return a},k.isLastSavedState=function(a){var b=!1;if(k.savedStates.length){var c=a.id,d=k.getLastSavedState(),e=d.id;b=c===e}return b},k.saveState=function(a){if(k.isLastSavedState(a))return!1;k.savedStates.push(k.cloneObject(a));return!0},k.getStateByIndex=function(a){var b=null;typeof a=="undefined"?b=k.savedStates[k.savedStates.length-1]:a<0?b=k.savedStates[k.savedStates.length+a]:b=k.savedStates[a];return b},k.getHash=function(){var a=k.unescapeHash(d.location.hash);return a},k.unescapeString=function(b){var c=b,d;for(;;){d=a.unescape(c);if(d===c)break;c=d}return c},k.unescapeHash=function(a){var b=k.normalizeHash(a);b=k.unescapeString(b);return b},k.normalizeHash=function(a){var b=a.replace(/[^#]*#/,"").replace(/#.*/,"");return b},k.setHash=function(a,b){if(b!==!1&&k.busy()){k.pushQueue({scope:k,callback:k.setHash,args:arguments,queue:b});return!1}var c=k.escapeHash(a);k.busy(!0);var e=k.extractState(a,!0);if(e&&!k.emulated.pushState)k.pushState(e.data,e.title,e.url,!1);else if(d.location.hash!==c)if(k.bugs.setHash){var f=k.getPageUrl();k.pushState(null,null,f+"#"+c,!1)}else d.location.hash=c;return k},k.escapeHash=function(b){var c=k.normalizeHash(b);c=a.escape(c),k.bugs.hashEscape||(c=c.replace(/\%21/g,"!").replace(/\%26/g,"&").replace(/\%3D/g,"=").replace(/\%3F/g,"?"));return c},k.getHashByUrl=function(a){var b=String(a).replace(/([^#]*)#?([^#]*)#?(.*)/,"$2");b=k.unescapeHash(b);return b},k.setTitle=function(a){var b=a.title;if(!b){var c=k.getStateByIndex(0);c&&c.url===a.url&&(b=c.title||k.options.initialTitle)}try{d.getElementsByTagName("title")[0].innerHTML=b.replace("<","<").replace(">",">").replace(" & "," & ")}catch(e){}d.title=b;return k},k.queues=[],k.busy=function(a){typeof a!="undefined"?k.busy.flag=a:typeof k.busy.flag=="undefined"&&(k.busy.flag=!1);if(!k.busy.flag){h(k.busy.timeout);var b=function(){if(!k.busy.flag)for(var a=k.queues.length-1;a>=0;--a){var c=k.queues[a];if(c.length===0)continue;var d=c.shift();k.fireQueueItem(d),k.busy.timeout=g(b,k.options.busyDelay)}};k.busy.timeout=g(b,k.options.busyDelay)}return k.busy.flag},k.fireQueueItem=function(a){return a.callback.apply(a.scope||k,a.args||[])},k.pushQueue=function(a){k.queues[a.queue||0]=k.queues[a.queue||0]||[],k.queues[a.queue||0].push(a);return k},k.queue=function(a,b){typeof a=="function"&&(a={callback:a}),typeof b!="undefined"&&(a.queue=b),k.busy()?k.pushQueue(a):k.fireQueueItem(a);return k},k.clearQueue=function(){k.busy.flag=!1,k.queues=[];return k},k.stateChanged=!1,k.doubleChecker=!1,k.doubleCheckComplete=function(){k.stateChanged=!0,k.doubleCheckClear();return k},k.doubleCheckClear=function(){k.doubleChecker&&(h(k.doubleChecker),k.doubleChecker=!1);return k},k.doubleCheck=function(a){k.stateChanged=!1,k.doubleCheckClear(),k.bugs.ieDoubleCheck&&(k.doubleChecker=g(function(){k.doubleCheckClear(),k.stateChanged||a();return!0},k.options.doubleCheckInterval));return k},k.safariStatePoll=function(){var b=k.extractState(d.location.href),c;if(!k.isLastSavedState(b))c=b;else return;c||(c=k.createStateObject()),k.Adapter.trigger(a,"popstate");return k},k.back=function(a){if(a!==!1&&k.busy()){k.pushQueue({scope:k,callback:k.back,args:arguments,queue:a});return!1}k.busy(!0),k.doubleCheck(function(){k.back(!1)}),l.go(-1);return!0},k.forward=function(a){if(a!==!1&&k.busy()){k.pushQueue({scope:k,callback:k.forward,args:arguments,queue:a});return!1}k.busy(!0),k.doubleCheck(function(){k.forward(!1)}),l.go(1);return!0},k.go=function(a,b){var c;if(a>0)for(c=1;c<=a;++c)k.forward(b);else{if(!(a<0))throw new Error("History.go: History.go requires a positive or negative integer passed.");for(c=-1;c>=a;--c)k.back(b)}return k},k.saveState(k.storeState(k.extractState(d.location.href,!0))),f&&(k.onUnload=function(){var a=f.store("History.store")||{},b;a.idToState=a.idToState||{},a.urlToId=a.urlToId||{},a.stateToId=a.stateToId||{};for(b in k.idToState){if(!k.idToState.hasOwnProperty(b))continue;a.idToState[b]=k.idToState[b]}for(b in k.urlToId){if(!k.urlToId.hasOwnProperty(b))continue;a.urlToId[b]=k.urlToId[b]}for(b in k.stateToId){if(!k.stateToId.hasOwnProperty(b))continue;a.stateToId[b]=k.stateToId[b]}k.store=a,f.store("History.store",a)},i(k.onUnload,k.options.storeInterval),k.Adapter.bind(a,"beforeunload",k.onUnload),k.Adapter.bind(a,"unload",k.onUnload));if(k.emulated.pushState){var m=function(){};k.pushState=k.pushState||m,k.replaceState=k.replaceState||m}else{k.onPopState=function(b){k.doubleCheckComplete();var c=k.getHash();if(c){var e=k.extractState(c||d.location.href,!0);e?k.replaceState(e.data,e.title,e.url,!1):(k.Adapter.trigger(a,"anchorchange"),k.busy(!1)),k.expectedStateId=!1;return!1}var f=!1;b=b||{},typeof b.state=="undefined"&&(typeof b.originalEvent!="undefined"&&typeof b.originalEvent.state!="undefined"?b.state=b.originalEvent.state||!1:typeof b.event!="undefined"&&typeof b.event.state!="undefined"&&(b.state=b.event.state||!1)),b.state=b.state||!1,b.state?f=k.getStateById(b.state):k.expectedStateId?f=k.getStateById(k.expectedStateId):f=k.extractState(d.location.href),f||(f=k.createStateObject(null,null,d.location.href)),k.expectedStateId=!1;if(k.isLastSavedState(f)){k.busy(!1);return!1}k.storeState(f),k.saveState(f),k.setTitle(f),k.Adapter.trigger(a,"statechange"),k.busy(!1);return!0},k.Adapter.bind(a,"popstate",k.onPopState),k.pushState=function(b,c,d,e){if(k.getHashByUrl(d)&&k.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&k.busy()){k.pushQueue({scope:k,callback:k.pushState,args:arguments,queue:e});return!1}k.busy(!0);var f=k.createStateObject(b,c,d);k.isLastSavedState(f)?k.busy(!1):(k.storeState(f),k.expectedStateId=f.id,l.pushState(f.id,f.title,f.url),k.Adapter.trigger(a,"popstate"));return!0},k.replaceState=function(b,c,d,e){if(k.getHashByUrl(d)&&k.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&k.busy()){k.pushQueue({scope:k,callback:k.replaceState,args:arguments,queue:e});return!1}k.busy(!0);var f=k.createStateObject(b,c,d);k.isLastSavedState(f)?k.busy(!1):(k.storeState(f),k.expectedStateId=f.id,l.replaceState(f.id,f.title,f.url),k.Adapter.trigger(a,"popstate"));return!0},k.bugs.safariPoll&&i(k.safariStatePoll,k.options.safariPollInterval);if(e.vendor==="Apple Computer, Inc."||(e.appCodeName||"")==="Mozilla")k.Adapter.bind(a,"hashchange",function(){k.Adapter.trigger(a,"popstate")}),k.getHash()&&k.Adapter.onDomLoad(function(){k.Adapter.trigger(a,"hashchange")})}},k.init()})(window) \ No newline at end of file diff --git a/admin/thirdparty/history-js/scripts/compressed/json2.js b/admin/thirdparty/history-js/scripts/compressed/json2.js new file mode 100644 index 000000000..704b8812d --- /dev/null +++ b/admin/thirdparty/history-js/scripts/compressed/json2.js @@ -0,0 +1 @@ +var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c + * @copyright 2010-2011 Benjamin Arthur Lupton + * @license New BSD License + */ + +// Closure +(function(window,undefined){ + // Localise Globals + var + History = window.History = window.History||{}, + jQuery = window.jQuery; + + // Check Existence + if ( typeof History.Adapter !== 'undefined' ) { + throw new Error('History.js Adapter has already been loaded...'); + } + + // Add the Adapter + History.Adapter = { + /** + * History.Adapter.bind(el,event,callback) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @param {Function} callback + * @return + */ + bind: function(el,event,callback){ + jQuery(el).bind(event,callback); + }, + + /** + * History.Adapter.trigger(el,event) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @return + */ + trigger: function(el,event){ + jQuery(el).trigger(event); + }, + + /** + * History.Adapter.trigger(el,event,data) + * @param {Function} callback + * @return + */ + onDomLoad: function(callback) { + jQuery(callback); + } + }; + + // Try and Initialise History + if ( typeof History.init !== 'undefined' ) { + History.init(); + } + +})(window); diff --git a/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.mootools.js b/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.mootools.js new file mode 100644 index 000000000..5a07da30e --- /dev/null +++ b/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.mootools.js @@ -0,0 +1,66 @@ +/** + * History.js jQuery Adapter + * @author Benjamin Arthur Lupton + * @copyright 2010-2011 Benjamin Arthur Lupton + * @license New BSD License + */ + +// Closure +(function(window,undefined){ + // Localise Globals + var + History = window.History = window.History||{}, + MooTools = window.MooTools; + + // Check Existence + if ( typeof History.Adapter !== 'undefined' ) { + throw new Error('History.js Adapter has already been loaded...'); + } + + // Make MooTools aware of History.js Events + Object.append(Element.NativeEvents,{ + 'popstate':2, + 'hashchange':2 + }); + + // Add the Adapter + History.Adapter = { + /** + * History.Adapter.bind(el,event,callback) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @param {Function} callback + * @return + */ + bind: function(el,event,callback){ + var El = typeof el === 'string' ? document.id(el) : el; + El.addEvent(event,callback); + }, + + /** + * History.Adapter.trigger(el,event) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @return + */ + trigger: function(el,event){ + var El = typeof el === 'string' ? document.id(el) : el; + El.fireEvent(event); + }, + + /** + * History.Adapter.trigger(el,event) + * @param {Function} callback + * @return + */ + onDomLoad: function(callback) { + window.addEvent('domready',callback); + } + }; + + // Try and Initialise History + if ( typeof History.init !== 'undefined' ) { + History.init(); + } + +})(window); diff --git a/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.prototype.js b/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.prototype.js new file mode 100644 index 000000000..58d1fc28a --- /dev/null +++ b/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.prototype.js @@ -0,0 +1,197 @@ +/** + * History.js Prototype Adapter + * @author Benjamin Arthur Lupton + * @copyright 2010-2011 Benjamin Arthur Lupton + * @license New BSD License + */ + +// Closure +(function(window,undefined){ + // Localise Globals + var + History = window.History = window.History||{}, + Prototype = window.Prototype, + Element = window.Element, + Event = window.Event, + $ = window.$; + + // Check Existence + if ( typeof History.Adapter !== 'undefined' ) { + throw new Error('History.js Adapter has already been loaded...'); + } + + // Prototype does not support event binding to the window element in IE6-8 + if ( typeof window.fireEvent === 'undefined' && typeof window.dispatchEvent === 'undefined' ) { + History.enable = false; + return; + } + + /** + * Bind and Trigger custom and native events in Prototype + * @author Juriy Zaytsev (kangax) + * @author Benjamin Arthur Lupton + * @copyright MIT license + */ + (function(){ + // Prepare + var + eventMatchers = { + 'HTMLEvents': /^(?:load|unload|abort|error|select|hashchange|popstate|change|submit|reset|focus|blur|resize|scroll)$/, + 'MouseEvents': /^(?:click|mouse(?:down|up|over|move|out))$/ + }, + defaultOptions = { + pointerX: 0, + pointerY: 0, + button: 0, + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + bubbles: true, + cancelable: true + }; + + // Check for Native Event + Event.hasNativeEvent = function(element, eventName) { + // Prepare + var eventType = null, result; + element = $(element); + + // Cycle + for (var name in eventMatchers) { + if ( eventMatchers[name].test(eventName) ) { + eventType = name; + break; + } + } + + // Evaluate + result = eventType ? true : false; + + // Return result + return result; + }; + + // Bind a Native or Custom Event + Event.bind = function(element, eventName, eventHandler) { + // Prepare + element = $(element); + + // Native Event? + if ( Element.hasNativeEvent(element,eventName) ) { + return Element.observe(element,eventName,eventHandler); + } + + // Custom Event? + else { + return Element.observe(element,'custom:'+eventName,eventHandler); + } + + // Return element + return element; + }; + + // Trigger + Event.trigger = function(element, eventName) { + // Prepare + var options = Object.extend(defaultOptions, arguments[2] || { }); + var oEvent, eventType = null; + element = $(element); + + // Check for Native Event + var name; for (name in eventMatchers) { + if (eventMatchers[name].test(eventName)) { eventType = name; break; } + } + + // Custom Event? + if ( !eventType ) { + return Element.fire(element,'custom:'+eventName); + } + + // Create Event + if ( document.createEvent ) { + // Firefox + Others + oEvent = document.createEvent(eventType); + + // Normal Event? + if ( eventType === 'HTMLEvents' ) { + oEvent.initEvent(eventName, options.bubbles, options.cancelable); + } + // Mouse Event? + else if ( eventType ) { + oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView, + options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element); + } + } + else if ( document.createEventObject ) { + // Internet Explorer + options.clientX = options.pointerX; + options.clientY = options.pointerY; + oEvent = Object.extend(document.createEventObject(), options); + } + + // Fire Event + if ( element.fireEvent ) { + element.fireEvent('on'+eventName,oEvent); + } + else if ( element.dispatchEvent ) { + element.dispatchEvent(oEvent); + } + else { + throw new Error('Cannot dispatch the event'); + } + + // Return + return element; + }; + + // Amend Element Prototype + Element.addMethods({ + simulate: Event.trigger, + trigger: Event.trigger, + bind: Event.bind, + hasNativeEvent: Event.hasNativeEvent + }); + })(); + + // Add the Adapter + History.Adapter = { + + /** + * History.Adapter.bind(el,event,callback) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @param {Function} callback + * @return + */ + bind: function(el,event,callback){ + Element.bind(el,event,callback); + }, + + /** + * History.Adapter.trigger(el,event) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @return + */ + trigger: function(el,event){ + Element.trigger(el,event); + }, + + /** + * History.Adapter.trigger(el,event,data) + * @param {Function} callback + * @return + */ + onDomLoad: function(callback) { + Event.observe(window.document, 'dom:loaded', callback); + } + }; + + // Try and Initialise History + if ( typeof History.init !== 'undefined' ) { + History.init(); + } + +})(window); diff --git a/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.yui.js b/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.yui.js new file mode 100644 index 000000000..a488584e8 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.yui.js @@ -0,0 +1,64 @@ +/** + * History.js YUI Adapter [NOT WORKING] + * @author Benjamin Arthur Lupton + * @copyright 2010-2011 Benjamin Arthur Lupton + * @license New BSD License + */ + +// Closure +(function(window,undefined){ + // Localise Globals + var + History = window.History = window.History||{}, + YUI = window.YUI; + + // Check Existence + if ( typeof History.Adapter !== 'undefined' ) { + throw new Error('History.js Adapter has already been loaded...'); + } + + // Add the Adapter + History.Adapter = { + /** + * History.Adapter.bind(el,event,callback) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @param {Function} callback + * @return {element} + */ + bind: function(el,event,callback){ + YUI().use('node-base', function(Y){ + Y.one(el).on(event,callback); + }); + }, + + /** + * History.Adapter.trigger(el,event) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @return {element} + */ + trigger: function(el,event){ + YUI().use('node-event-simulate', function(Y){ + Y.one(el).simulate(event); + }); + }, + + /** + * History.Adapter.trigger(el,event,data) + * @param {Function} callback + * @return {true} + */ + onDomLoad: function(callback) { + YUI().use('event', function(Y){ + Y.on('domready', callback); + }); + } + }; + + // Try and Initialise History + if ( typeof History.init !== 'undefined' ) { + History.init(); + } + +})(window); diff --git a/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.zepto.js b/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.zepto.js new file mode 100644 index 000000000..790ea0b48 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/uncompressed/history.adapter.zepto.js @@ -0,0 +1,58 @@ +/** + * History.js Zepto Adapter + * @author Benjamin Arthur Lupton + * @copyright 2010-2011 Benjamin Arthur Lupton + * @license New BSD License + */ + +// Closure +(function(window,undefined){ + // Localise Globals + var + History = window.History = window.History||{}, + Zepto = window.Zepto; + + // Check Existence + if ( typeof History.Adapter !== 'undefined' ) { + throw new Error('History.js Adapter has already been loaded...'); + } + + // Add the Adapter + History.Adapter = { + /** + * History.Adapter.bind(el,event,callback) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @param {Function} callback + * @return + */ + bind: function(el,event,callback){ + Zepto(el).bind(event,callback); + }, + + /** + * History.Adapter.trigger(el,event) + * @param {Element|Selector} el + * @param {String} event - custom and standard events + * @return + */ + trigger: function(el,event){ + Zepto(el).trigger(event); + }, + + /** + * History.Adapter.trigger(el,event,data) + * @param {Function} callback + * @return + */ + onDomLoad: function(callback) { + Zepto(callback); + } + }; + + // Try and Initialise History + if ( typeof History.init !== 'undefined' ) { + History.init(); + } + +})(window); diff --git a/admin/thirdparty/history-js/scripts/uncompressed/history.html4.js b/admin/thirdparty/history-js/scripts/uncompressed/history.html4.js new file mode 100644 index 000000000..8f2822a03 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/uncompressed/history.html4.js @@ -0,0 +1,606 @@ +/** + * History.js HTML4 Support + * Depends on the HTML5 Support + * @author Benjamin Arthur Lupton + * @copyright 2010-2011 Benjamin Arthur Lupton + * @license New BSD License + */ + +(function(window,undefined){ + "use strict"; + + // -------------------------------------------------------------------------- + // Initialise + + // Localise Globals + var + document = window.document, // Make sure we are using the correct document + setTimeout = window.setTimeout||setTimeout, + clearTimeout = window.clearTimeout||clearTimeout, + setInterval = window.setInterval||setInterval, + History = window.History = window.History||{}; // Public History Object + + // Check Existence + if ( typeof History.initHtml4 !== 'undefined' ) { + throw new Error('History.js HTML4 Support has already been loaded...'); + } + + // -------------------------------------------------------------------------- + // Initialise HTML4 Support + + // Initialise HTML4 Support + History.initHtml4 = function(){ + // Initialise + if ( typeof History.initHtml4.initialized !== 'undefined' ) { + // Already Loaded + return false; + } + else { + History.initHtml4.initialized = true; + } + + // ---------------------------------------------------------------------- + // Properties + + /** + * History.enabled + * Is History enabled? + */ + History.enabled = true; + + + // ---------------------------------------------------------------------- + // Hash Storage + + /** + * History.savedHashes + * Store the hashes in an array + */ + History.savedHashes = []; + + /** + * History.isLastHash(newHash) + * Checks if the hash is the last hash + * @param {string} newHash + * @return {boolean} true + */ + History.isLastHash = function(newHash){ + // Prepare + var oldHash = History.getHashByIndex(); + + // Check + var isLast = newHash === oldHash; + + // Return isLast + return isLast; + }; + + /** + * History.saveHash(newHash) + * Push a Hash + * @param {string} newHash + * @return {boolean} true + */ + History.saveHash = function(newHash){ + // Check Hash + if ( History.isLastHash(newHash) ) { + return false; + } + + // Push the Hash + History.savedHashes.push(newHash); + + // Return true + return true; + }; + + /** + * History.getHashByIndex() + * Gets a hash by the index + * @param {integer} index + * @return {string} + */ + History.getHashByIndex = function(index){ + // Prepare + var hash = null; + + // Handle + if ( typeof index === 'undefined' ) { + // Get the last inserted + hash = History.savedHashes[History.savedHashes.length-1]; + } + else if ( index < 0 ) { + // Get from the end + hash = History.savedHashes[History.savedHashes.length+index]; + } + else { + // Get from the beginning + hash = History.savedHashes[index]; + } + + // Return hash + return hash; + }; + + // ---------------------------------------------------------------------- + // Discarded States + + /** + * History.discardedHashes + * A hashed array of discarded hashes + */ + History.discardedHashes = {}; + + /** + * History.discardedStates + * A hashed array of discarded states + */ + History.discardedStates = {}; + + /** + * History.discardState(State) + * Discards the state by ignoring it through History + * @param {object} State + * @return {true} + */ + History.discardState = function(discardedState,forwardState,backState){ + //History.debug('History.discardState', arguments); + // Prepare + var discardedStateHash = History.getHashByState(discardedState); + + // Create Discard Object + var discardObject = { + 'discardedState': discardedState, + 'backState': backState, + 'forwardState': forwardState + }; + + // Add to DiscardedStates + History.discardedStates[discardedStateHash] = discardObject; + + // Return true + return true; + }; + + /** + * History.discardHash(hash) + * Discards the hash by ignoring it through History + * @param {string} hash + * @return {true} + */ + History.discardHash = function(discardedHash,forwardState,backState){ + //History.debug('History.discardState', arguments); + // Create Discard Object + var discardObject = { + 'discardedHash': discardedHash, + 'backState': backState, + 'forwardState': forwardState + }; + + // Add to discardedHash + History.discardedHashes[discardedHash] = discardObject; + + // Return true + return true; + }; + + /** + * History.discardState(State) + * Checks to see if the state is discarded + * @param {object} State + * @return {bool} + */ + History.discardedState = function(State){ + // Prepare + var StateHash = History.getHashByState(State); + + // Check + var discarded = History.discardedStates[StateHash]||false; + + // Return true + return discarded; + }; + + /** + * History.discardedHash(hash) + * Checks to see if the state is discarded + * @param {string} State + * @return {bool} + */ + History.discardedHash = function(hash){ + // Check + var discarded = History.discardedHashes[hash]||false; + + // Return true + return discarded; + }; + + /** + * History.recycleState(State) + * Allows a discarded state to be used again + * @param {object} data + * @param {string} title + * @param {string} url + * @return {true} + */ + History.recycleState = function(State){ + //History.debug('History.recycleState', arguments); + // Prepare + var StateHash = History.getHashByState(State); + + // Remove from DiscardedStates + if ( History.discardedState(State) ) { + delete History.discardedStates[StateHash]; + } + + // Return true + return true; + }; + + // ---------------------------------------------------------------------- + // HTML4 HashChange Support + + if ( History.emulated.hashChange ) { + /* + * We must emulate the HTML4 HashChange Support by manually checking for hash changes + */ + + /** + * History.hashChangeInit() + * Init the HashChange Emulation + */ + History.hashChangeInit = function(){ + // Define our Checker Function + History.checkerFunction = null; + + // Define some variables that will help in our checker function + var + lastDocumentHash = ''; + + // Handle depending on the browser + if ( History.isInternetExplorer() ) { + // IE6 and IE7 + // We need to use an iframe to emulate the back and forward buttons + + // Create iFrame + var + iframeId = 'historyjs-iframe', + iframe = document.createElement('iframe'); + + // Adjust iFarme + iframe.setAttribute('id', iframeId); + iframe.style.display = 'none'; + + // Append iFrame + document.body.appendChild(iframe); + + // Create initial history entry + iframe.contentWindow.document.open(); + iframe.contentWindow.document.close(); + + // Define some variables that will help in our checker function + var + lastIframeHash = '', + checkerRunning = false; + + // Define the checker function + History.checkerFunction = function(){ + // Check Running + if ( checkerRunning ) { + return false; + } + + // Update Running + checkerRunning = true; + + // Fetch + var + documentHash = History.getHash()||'', + iframeHash = History.unescapeHash(iframe.contentWindow.document.location.hash)||''; + + // The Document Hash has changed (application caused) + if ( documentHash !== lastDocumentHash ) { + // Equalise + lastDocumentHash = documentHash; + + // Create a history entry in the iframe + if ( iframeHash !== documentHash ) { + //History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash); + + // Equalise + lastIframeHash = iframeHash = documentHash; + + // Create History Entry + iframe.contentWindow.document.open(); + iframe.contentWindow.document.close(); + + // Update the iframe's hash + iframe.contentWindow.document.location.hash = History.escapeHash(documentHash); + } + + // Trigger Hashchange Event + History.Adapter.trigger(window,'hashchange'); + } + + // The iFrame Hash has changed (back button caused) + else if ( iframeHash !== lastIframeHash ) { + //History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash); + + // Equalise + lastIframeHash = iframeHash; + + // Update the Hash + History.setHash(iframeHash,false); + } + + // Reset Running + checkerRunning = false; + + // Return true + return true; + }; + } + else { + // We are not IE + // Firefox 1 or 2, Opera + + // Define the checker function + History.checkerFunction = function(){ + // Prepare + var documentHash = History.getHash(); + + // The Document Hash has changed (application caused) + if ( documentHash !== lastDocumentHash ) { + // Equalise + lastDocumentHash = documentHash; + + // Trigger Hashchange Event + History.Adapter.trigger(window,'hashchange'); + } + + // Return true + return true; + }; + } + + // Apply the checker function + History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval)); + + // Done + return true; + }; // History.hashChangeInit + + // Bind hashChangeInit + History.Adapter.onDomLoad(History.hashChangeInit); + + } // History.emulated.hashChange + + + // ---------------------------------------------------------------------- + // HTML5 State Support + + if ( History.emulated.pushState ) { + /* + * We must emulate the HTML5 State Management by using HTML4 HashChange + */ + + /** + * History.onHashChange(event) + * Trigger HTML5's window.onpopstate via HTML4 HashChange Support + */ + History.onHashChange = function(event){ + //History.debug('History.onHashChange', arguments); + + // Prepare + var + currentUrl = ((event && event.newURL) || document.location.href), + currentHash = History.getHashByUrl(currentUrl), + currentState = null, + currentStateHash = null, + currentStateHashExits = null; + + // Check if we are the same state + if ( History.isLastHash(currentHash) ) { + // There has been no change (just the page's hash has finally propagated) + //History.debug('History.onHashChange: no change'); + History.busy(false); + return false; + } + + // Reset the double check + History.doubleCheckComplete(); + + // Store our location for use in detecting back/forward direction + History.saveHash(currentHash); + + // Expand Hash + if ( currentHash && History.isTraditionalAnchor(currentHash) ) { + //History.debug('History.onHashChange: traditional anchor', currentHash); + // Traditional Anchor Hash + History.Adapter.trigger(window,'anchorchange'); + History.busy(false); + return false; + } + + // Create State + currentState = History.extractState(History.getFullUrl(currentHash||document.location.href,false),true); + + // Check if we are the same state + if ( History.isLastSavedState(currentState) ) { + //History.debug('History.onHashChange: no change'); + // There has been no change (just the page's hash has finally propagated) + History.busy(false); + return false; + } + + // Create the state Hash + currentStateHash = History.getHashByState(currentState); + + // Check if we are DiscardedState + var discardObject = History.discardedState(currentState); + if ( discardObject ) { + // Ignore this state as it has been discarded and go back to the state before it + if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) { + // We are going backwards + //History.debug('History.onHashChange: go backwards'); + History.back(false); + } else { + // We are going forwards + //History.debug('History.onHashChange: go forwards'); + History.forward(false); + } + return false; + } + + // Push the new HTML5 State + //History.debug('History.onHashChange: success hashchange'); + History.pushState(currentState.data,currentState.title,currentState.url,false); + + // End onHashChange closure + return true; + }; + History.Adapter.bind(window,'hashchange',History.onHashChange); + + /** + * History.pushState(data,title,url) + * Add a new State to the history object, become it, and trigger onpopstate + * We have to trigger for HTML4 compatibility + * @param {object} data + * @param {string} title + * @param {string} url + * @return {true} + */ + History.pushState = function(data,title,url,queue){ + //History.debug('History.pushState: called', arguments); + + // Check the State + if ( History.getHashByUrl(url) ) { + throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); + } + + // Handle Queueing + if ( queue !== false && History.busy() ) { + // Wait + Push to Queue + //History.debug('History.pushState: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.pushState, + args: arguments, + queue: queue + }); + return false; + } + + // Make Busy + History.busy(true); + + // Fetch the State Object + var + newState = History.createStateObject(data,title,url), + newStateHash = History.getHashByState(newState), + oldState = History.getState(false), + oldStateHash = History.getHashByState(oldState), + html4Hash = History.getHash(); + + // Store the newState + History.storeState(newState); + History.expectedStateId = newState.id; + + // Recycle the State + History.recycleState(newState); + + // Force update of the title + History.setTitle(newState); + + // Check if we are the same State + if ( newStateHash === oldStateHash ) { + //History.debug('History.pushState: no change', newStateHash); + History.busy(false); + return false; + } + + // Update HTML4 Hash + if ( newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href) ) { + //History.debug('History.pushState: update hash', newStateHash, html4Hash); + History.setHash(newStateHash,false); + return false; + } + + // Update HTML5 State + History.saveState(newState); + + // Fire HTML5 Event + //History.debug('History.pushState: trigger popstate'); + History.Adapter.trigger(window,'statechange'); + History.busy(false); + + // End pushState closure + return true; + }; + + /** + * History.replaceState(data,title,url) + * Replace the State and trigger onpopstate + * We have to trigger for HTML4 compatibility + * @param {object} data + * @param {string} title + * @param {string} url + * @return {true} + */ + History.replaceState = function(data,title,url,queue){ + //History.debug('History.replaceState: called', arguments); + + // Check the State + if ( History.getHashByUrl(url) ) { + throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); + } + + // Handle Queueing + if ( queue !== false && History.busy() ) { + // Wait + Push to Queue + //History.debug('History.replaceState: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.replaceState, + args: arguments, + queue: queue + }); + return false; + } + + // Make Busy + History.busy(true); + + // Fetch the State Objects + var + newState = History.createStateObject(data,title,url), + oldState = History.getState(false), + previousState = History.getStateByIndex(-2); + + // Discard Old State + History.discardState(oldState,newState,previousState); + + // Alias to PushState + History.pushState(newState.data,newState.title,newState.url,false); + + // End replaceState closure + return true; + }; + + /** + * Ensure initial state is handled correctly + */ + if ( History.getHash() && !History.emulated.hashChange ) { + History.Adapter.onDomLoad(function(){ + History.Adapter.trigger(window,'hashchange'); + }); + } + + } // History.emulated.pushState + + }; // History.initHtml4 + + // Try and Initialise History + History.init(); + +})(window); diff --git a/admin/thirdparty/history-js/scripts/uncompressed/history.js b/admin/thirdparty/history-js/scripts/uncompressed/history.js new file mode 100644 index 000000000..af06c7e38 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/uncompressed/history.js @@ -0,0 +1,1867 @@ +/** + * History.js Core + * @author Benjamin Arthur Lupton + * @copyright 2010-2011 Benjamin Arthur Lupton + * @license New BSD License + */ + +(function(window,undefined){ + "use strict"; + + // -------------------------------------------------------------------------- + // Initialise + + // Localise Globals + var + console = window.console||undefined, // Prevent a JSLint complain + document = window.document, // Make sure we are using the correct document + navigator = window.navigator, // Make sure we are using the correct navigator + amplify = window.amplify||false, // Amplify.js + setTimeout = window.setTimeout, + clearTimeout = window.clearTimeout, + setInterval = window.setInterval, + clearInterval = window.clearInterval, + JSON = window.JSON, + History = window.History = window.History||{}, // Public History Object + history = window.history; // Old History Object + + // MooTools Compatibility + JSON.stringify = JSON.stringify||JSON.encode; + JSON.parse = JSON.parse||JSON.decode; + + // Check Existence + if ( typeof History.init !== 'undefined' ) { + throw new Error('History.js Core has already been loaded...'); + } + + // Initialise History + History.init = function(){ + // Check Load Status of Adapter + if ( typeof History.Adapter === 'undefined' ) { + return false; + } + + // Check Load Status of Core + if ( typeof History.initCore !== 'undefined' ) { + History.initCore(); + } + + // Check Load Status of HTML4 Support + if ( typeof History.initHtml4 !== 'undefined' ) { + History.initHtml4(); + } + + // Return true + return true; + }; + + // -------------------------------------------------------------------------- + // Initialise Core + + // Initialise Core + History.initCore = function(){ + // Initialise + if ( typeof History.initCore.initialized !== 'undefined' ) { + // Already Loaded + return false; + } + else { + History.initCore.initialized = true; + } + + // ---------------------------------------------------------------------- + // Options + + /** + * History.options + * Configurable options + */ + History.options = History.options||{}; + + /** + * History.options.hashChangeInterval + * How long should the interval be before hashchange checks + */ + History.options.hashChangeInterval = History.options.hashChangeInterval || 100; + + /** + * History.options.safariPollInterval + * How long should the interval be before safari poll checks + */ + History.options.safariPollInterval = History.options.safariPollInterval || 500; + + /** + * History.options.doubleCheckInterval + * How long should the interval be before we perform a double check + */ + History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500; + + /** + * History.options.storeInterval + * How long should we wait between store calls + */ + History.options.storeInterval = History.options.storeInterval || 1000; + + /** + * History.options.busyDelay + * How long should we wait between busy events + */ + History.options.busyDelay = History.options.busyDelay || 250; + + /** + * History.options.debug + * If true will enable debug messages to be logged + */ + History.options.debug = History.options.debug || false; + + /** + * History.options.initialTitle + * What is the title of the initial state + */ + History.options.initialTitle = History.options.initialTitle || document.title; + + + // ---------------------------------------------------------------------- + // Interval record + + /** + * History.intervalList + * List of intervals set, to be cleared when document is unloaded. + */ + History.intervalList = []; + + /** + * History.clearAllIntervals + * Clears all setInterval instances. + */ + History.clearAllIntervals = function(){ + var i, il = History.intervalList; + if (typeof il !== "undefined" && il !== null) { + for (i = 0; i < il.length; i++) { + clearInterval(il[i]); + } + History.intervalList = null; + } + }; + History.Adapter.bind(window,"beforeunload",History.clearAllIntervals); + History.Adapter.bind(window,"unload",History.clearAllIntervals); + + + // ---------------------------------------------------------------------- + // Debug + + /** + * History.debug(message,...) + * Logs the passed arguments if debug enabled + */ + History.debug = function(){ + if ( (History.options.debug||false) ) { + History.log.apply(History,arguments); + } + }; + + /** + * History.log(message,...) + * Logs the passed arguments + */ + History.log = function(){ + // Prepare + var + consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'), + textarea = document.getElementById('log'), + message, + i,n + ; + + // Write to Console + if ( consoleExists ) { + var args = Array.prototype.slice.call(arguments); + message = args.shift(); + if ( typeof console.debug !== 'undefined' ) { + console.debug.apply(console,[message,args]); + } + else { + console.log.apply(console,[message,args]); + } + } + else { + message = ("\n"+arguments[0]+"\n"); + } + + // Write to log + for ( i=1,n=arguments.length; i + * @author James Padolsey + */ + History.getInternetExplorerMajorVersion = function(){ + var result = History.getInternetExplorerMajorVersion.cached = + (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined') + ? History.getInternetExplorerMajorVersion.cached + : (function(){ + var v = 3, + div = document.createElement('div'), + all = div.getElementsByTagName('i'); + while ( (div.innerHTML = '') && all[0] ) {} + return (v > 4) ? v : false; + })() + ; + return result; + }; + + /** + * History.isInternetExplorer() + * Are we using Internet Explorer? + * @return {boolean} + * @license Public Domain + * @author Benjamin Arthur Lupton + */ + History.isInternetExplorer = function(){ + var result = + History.isInternetExplorer.cached = + (typeof History.isInternetExplorer.cached !== 'undefined') + ? History.isInternetExplorer.cached + : Boolean(History.getInternetExplorerMajorVersion()) + ; + return result; + }; + + /** + * History.emulated + * Which features require emulating? + */ + History.emulated = { + pushState: !Boolean( + window.history && window.history.pushState && window.history.replaceState + && !( + (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */ + || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */ + ) + ), + hashChange: Boolean( + !(('onhashchange' in window) || ('onhashchange' in document)) + || + (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8) + ) + }; + + /** + * History.enabled + * Is History enabled? + */ + History.enabled = !History.emulated.pushState; + + /** + * History.bugs + * Which bugs are present + */ + History.bugs = { + /** + * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call + * https://bugs.webkit.org/show_bug.cgi?id=56249 + */ + setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)), + + /** + * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions + * https://bugs.webkit.org/show_bug.cgi?id=42940 + */ + safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)), + + /** + * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function) + */ + ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8), + + /** + * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event + */ + hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7) + }; + + /** + * History.isEmptyObject(obj) + * Checks to see if the Object is Empty + * @param {Object} obj + * @return {boolean} + */ + History.isEmptyObject = function(obj) { + for ( var name in obj ) { + return false; + } + return true; + }; + + /** + * History.cloneObject(obj) + * Clones a object + * @param {Object} obj + * @return {Object} + */ + History.cloneObject = function(obj) { + var hash,newObj; + if ( obj ) { + hash = JSON.stringify(obj); + newObj = JSON.parse(hash); + } + else { + newObj = {}; + } + return newObj; + }; + + // ---------------------------------------------------------------------- + // URL Helpers + + /** + * History.getRootUrl() + * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com" + * @return {String} rootUrl + */ + History.getRootUrl = function(){ + // Create + var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host); + if ( document.location.port||false ) { + rootUrl += ':'+document.location.port; + } + rootUrl += '/'; + + // Return + return rootUrl; + }; + + /** + * History.getBaseHref() + * Fetches the `href` attribute of the `` element if it exists + * @return {String} baseHref + */ + History.getBaseHref = function(){ + // Create + var + baseElements = document.getElementsByTagName('base'), + baseElement = null, + baseHref = ''; + + // Test for Base Element + if ( baseElements.length === 1 ) { + // Prepare for Base Element + baseElement = baseElements[0]; + baseHref = baseElement.href.replace(/[^\/]+$/,''); + } + + // Adjust trailing slash + baseHref = baseHref.replace(/\/+$/,''); + if ( baseHref ) baseHref += '/'; + + // Return + return baseHref; + }; + + /** + * History.getBaseUrl() + * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first) + * @return {String} baseUrl + */ + History.getBaseUrl = function(){ + // Create + var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl(); + + // Return + return baseUrl; + }; + + /** + * History.getPageUrl() + * Fetches the URL of the current page + * @return {String} pageUrl + */ + History.getPageUrl = function(){ + // Fetch + var + State = History.getState(false,false), + stateUrl = (State||{}).url||document.location.href; + + // Create + var pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){ + return (/\./).test(part) ? part : part+'/'; + }); + + // Return + return pageUrl; + }; + + /** + * History.getBasePageUrl() + * Fetches the Url of the directory of the current page + * @return {String} basePageUrl + */ + History.getBasePageUrl = function(){ + // Create + var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){ + return (/[^\/]$/).test(part) ? '' : part; + }).replace(/\/+$/,'')+'/'; + + // Return + return basePageUrl; + }; + + /** + * History.getFullUrl(url) + * Ensures that we have an absolute URL and not a relative URL + * @param {string} url + * @param {Boolean} allowBaseHref + * @return {string} fullUrl + */ + History.getFullUrl = function(url,allowBaseHref){ + // Prepare + var fullUrl = url, firstChar = url.substring(0,1); + allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref; + + // Check + if ( /[a-z]+\:\/\//.test(url) ) { + // Full URL + } + else if ( firstChar === '/' ) { + // Root URL + fullUrl = History.getRootUrl()+url.replace(/^\/+/,''); + } + else if ( firstChar === '#' ) { + // Anchor URL + fullUrl = History.getPageUrl().replace(/#.*/,'')+url; + } + else if ( firstChar === '?' ) { + // Query URL + fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url; + } + else { + // Relative URL + if ( allowBaseHref ) { + fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,''); + } else { + fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,''); + } + // We have an if condition above as we do not want hashes + // which are relative to the baseHref in our URLs + // as if the baseHref changes, then all our bookmarks + // would now point to different locations + // whereas the basePageUrl will always stay the same + } + + // Return + return fullUrl.replace(/\#$/,''); + }; + + /** + * History.getShortUrl(url) + * Ensures that we have a relative URL and not a absolute URL + * @param {string} url + * @return {string} url + */ + History.getShortUrl = function(url){ + // Prepare + var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl(); + + // Trim baseUrl + if ( History.emulated.pushState ) { + // We are in a if statement as when pushState is not emulated + // The actual url these short urls are relative to can change + // So within the same session, we the url may end up somewhere different + shortUrl = shortUrl.replace(baseUrl,''); + } + + // Trim rootUrl + shortUrl = shortUrl.replace(rootUrl,'/'); + + // Ensure we can still detect it as a state + if ( History.isTraditionalAnchor(shortUrl) ) { + shortUrl = './'+shortUrl; + } + + // Clean It + shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,''); + + // Return + return shortUrl; + }; + + // ---------------------------------------------------------------------- + // State Storage + + /** + * History.store + * The store for all session specific data + */ + History.store = amplify ? (amplify.store('History.store')||{}) : {}; + History.store.idToState = History.store.idToState||{}; + History.store.urlToId = History.store.urlToId||{}; + History.store.stateToId = History.store.stateToId||{}; + + /** + * History.idToState + * 1-1: State ID to State Object + */ + History.idToState = History.idToState||{}; + + /** + * History.stateToId + * 1-1: State String to State ID + */ + History.stateToId = History.stateToId||{}; + + /** + * History.urlToId + * 1-1: State URL to State ID + */ + History.urlToId = History.urlToId||{}; + + /** + * History.storedStates + * Store the states in an array + */ + History.storedStates = History.storedStates||[]; + + /** + * History.savedStates + * Saved the states in an array + */ + History.savedStates = History.savedStates||[]; + + /** + * History.getState() + * Get an object containing the data, title and url of the current state + * @param {Boolean} friendly + * @param {Boolean} create + * @return {Object} State + */ + History.getState = function(friendly,create){ + // Prepare + if ( typeof friendly === 'undefined' ) { friendly = true; } + if ( typeof create === 'undefined' ) { create = true; } + + // Fetch + var State = History.getLastSavedState(); + + // Create + if ( !State && create ) { + State = History.createStateObject(); + } + + // Adjust + if ( friendly ) { + State = History.cloneObject(State); + State.url = State.cleanUrl||State.url; + } + + // Return + return State; + }; + + /** + * History.getIdByState(State) + * Gets a ID for a State + * @param {State} newState + * @return {String} id + */ + History.getIdByState = function(newState){ + + // Fetch ID + var id = History.extractId(newState.url); + if ( !id ) { + // Find ID via State String + var str = History.getStateString(newState); + if ( typeof History.stateToId[str] !== 'undefined' ) { + id = History.stateToId[str]; + } + else if ( typeof History.store.stateToId[str] !== 'undefined' ) { + id = History.store.stateToId[str]; + } + else { + // Generate a new ID + while ( true ) { + id = String(Math.floor(Math.random()*1000)); + if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) { + break; + } + } + + // Apply the new State to the ID + History.stateToId[str] = id; + History.idToState[id] = newState; + } + } + + // Return ID + return id; + }; + + /** + * History.normalizeState(State) + * Expands a State Object + * @param {object} State + * @return {object} + */ + History.normalizeState = function(oldState){ + // Prepare + if ( !oldState || (typeof oldState !== 'object') ) { + oldState = {}; + } + + // Check + if ( typeof oldState.normalized !== 'undefined' ) { + return oldState; + } + + // Adjust + if ( !oldState.data || (typeof oldState.data !== 'object') ) { + oldState.data = {}; + } + + // ---------------------------------------------------------------------- + + // Create + var newState = {}; + newState.normalized = true; + newState.title = oldState.title||''; + newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href)); + newState.hash = History.getShortUrl(newState.url); + newState.data = History.cloneObject(oldState.data); + + // Fetch ID + newState.id = History.getIdByState(newState); + + // ---------------------------------------------------------------------- + + // Clean the URL + newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,''); + newState.url = newState.cleanUrl; + + // Check to see if we have more than just a url + var dataNotEmpty = !History.isEmptyObject(newState.data); + + // Apply + if ( newState.title || dataNotEmpty ) { + // Add ID to Hash + newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,''); + if ( !/\?/.test(newState.hash) ) { + newState.hash += '?'; + } + newState.hash += '&_suid='+newState.id; + } + + // Create the Hashed URL + newState.hashedUrl = History.getFullUrl(newState.hash); + + // ---------------------------------------------------------------------- + + // Update the URL if we have a duplicate + if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) { + newState.url = newState.hashedUrl; + } + + // ---------------------------------------------------------------------- + + // Return + return newState; + }; + + /** + * History.createStateObject(data,title,url) + * Creates a object based on the data, title and url state params + * @param {object} data + * @param {string} title + * @param {string} url + * @return {object} + */ + History.createStateObject = function(data,title,url){ + // Hashify + var State = { + 'data': data, + 'title': title, + 'url': url + }; + + // Expand the State + State = History.normalizeState(State); + + // Return object + return State; + }; + + /** + * History.getStateById(id) + * Get a state by it's UID + * @param {String} id + */ + History.getStateById = function(id){ + // Prepare + id = String(id); + + // Retrieve + var State = History.idToState[id] || History.store.idToState[id] || undefined; + + // Return State + return State; + }; + + /** + * Get a State's String + * @param {State} passedState + */ + History.getStateString = function(passedState){ + // Prepare + var State = History.normalizeState(passedState); + + // Clean + var cleanedState = { + data: State.data, + title: passedState.title, + url: passedState.url + }; + + // Fetch + var str = JSON.stringify(cleanedState); + + // Return + return str; + }; + + /** + * Get a State's ID + * @param {State} passedState + * @return {String} id + */ + History.getStateId = function(passedState){ + // Prepare + var State = History.normalizeState(passedState); + + // Fetch + var id = State.id; + + // Return + return id; + }; + + /** + * History.getHashByState(State) + * Creates a Hash for the State Object + * @param {State} passedState + * @return {String} hash + */ + History.getHashByState = function(passedState){ + // Prepare + var hash, State = History.normalizeState(passedState); + + // Fetch + hash = State.hash; + + // Return + return hash; + }; + + /** + * History.extractId(url_or_hash) + * Get a State ID by it's URL or Hash + * @param {string} url_or_hash + * @return {string} id + */ + History.extractId = function ( url_or_hash ) { + // Prepare + var id; + + // Extract + var parts,url; + parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash); + url = parts ? (parts[1]||url_or_hash) : url_or_hash; + id = parts ? String(parts[2]||'') : ''; + + // Return + return id||false; + }; + + /** + * History.isTraditionalAnchor + * Checks to see if the url is a traditional anchor or not + * @param {String} url_or_hash + * @return {Boolean} + */ + History.isTraditionalAnchor = function(url_or_hash){ + // Check + var isTraditional = !(/[\/\?\.]/.test(url_or_hash)); + + // Return + return isTraditional; + }; + + /** + * History.extractState + * Get a State by it's URL or Hash + * @param {String} url_or_hash + * @return {State|null} + */ + History.extractState = function(url_or_hash,create){ + // Prepare + var State = null; + create = create||false; + + // Fetch SUID + var id = History.extractId(url_or_hash); + if ( id ) { + State = History.getStateById(id); + } + + // Fetch SUID returned no State + if ( !State ) { + // Fetch URL + var url = History.getFullUrl(url_or_hash); + + // Check URL + id = History.getIdByUrl(url)||false; + if ( id ) { + State = History.getStateById(id); + } + + // Create State + if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) { + State = History.createStateObject(null,null,url); + } + } + + // Return + return State; + }; + + /** + * History.getIdByUrl() + * Get a State ID by a State URL + */ + History.getIdByUrl = function(url){ + // Fetch + var id = History.urlToId[url] || History.store.urlToId[url] || undefined; + + // Return + return id; + }; + + /** + * History.getLastSavedState() + * Get an object containing the data, title and url of the current state + * @return {Object} State + */ + History.getLastSavedState = function(){ + return History.savedStates[History.savedStates.length-1]||undefined; + }; + + /** + * History.getLastStoredState() + * Get an object containing the data, title and url of the current state + * @return {Object} State + */ + History.getLastStoredState = function(){ + return History.storedStates[History.storedStates.length-1]||undefined; + }; + + /** + * History.hasUrlDuplicate + * Checks if a Url will have a url conflict + * @param {Object} newState + * @return {Boolean} hasDuplicate + */ + History.hasUrlDuplicate = function(newState) { + // Prepare + var hasDuplicate = false; + + // Fetch + var oldState = History.extractState(newState.url); + + // Check + hasDuplicate = oldState && oldState.id !== newState.id; + + // Return + return hasDuplicate; + }; + + /** + * History.storeState + * Store a State + * @param {Object} newState + * @return {Object} newState + */ + History.storeState = function(newState){ + // Store the State + History.urlToId[newState.url] = newState.id; + + // Push the State + History.storedStates.push(History.cloneObject(newState)); + + // Return newState + return newState; + }; + + /** + * History.isLastSavedState(newState) + * Tests to see if the state is the last state + * @param {Object} newState + * @return {boolean} isLast + */ + History.isLastSavedState = function(newState){ + // Prepare + var isLast = false; + + // Check + if ( History.savedStates.length ) { + var + newId = newState.id, + oldState = History.getLastSavedState(), + oldId = oldState.id; + + // Check + isLast = (newId === oldId); + } + + // Return + return isLast; + }; + + /** + * History.saveState + * Push a State + * @param {Object} newState + * @return {boolean} changed + */ + History.saveState = function(newState){ + // Check Hash + if ( History.isLastSavedState(newState) ) { + return false; + } + + // Push the State + History.savedStates.push(History.cloneObject(newState)); + + // Return true + return true; + }; + + /** + * History.getStateByIndex() + * Gets a state by the index + * @param {integer} index + * @return {Object} + */ + History.getStateByIndex = function(index){ + // Prepare + var State = null; + + // Handle + if ( typeof index === 'undefined' ) { + // Get the last inserted + State = History.savedStates[History.savedStates.length-1]; + } + else if ( index < 0 ) { + // Get from the end + State = History.savedStates[History.savedStates.length+index]; + } + else { + // Get from the beginning + State = History.savedStates[index]; + } + + // Return State + return State; + }; + + // ---------------------------------------------------------------------- + // Hash Helpers + + /** + * History.getHash() + * Gets the current document hash + * @return {string} + */ + History.getHash = function(){ + var hash = History.unescapeHash(document.location.hash); + return hash; + }; + + /** + * History.unescapeString() + * Unescape a string + * @param {String} str + * @return {string} + */ + History.unescapeString = function(str){ + // Prepare + var result = str; + + // Unescape hash + var tmp; + while ( true ) { + tmp = window.unescape(result); + if ( tmp === result ) { + break; + } + result = tmp; + } + + // Return result + return result; + }; + + /** + * History.unescapeHash() + * normalize and Unescape a Hash + * @param {String} hash + * @return {string} + */ + History.unescapeHash = function(hash){ + // Prepare + var result = History.normalizeHash(hash); + + // Unescape hash + result = History.unescapeString(result); + + // Return result + return result; + }; + + /** + * History.normalizeHash() + * normalize a hash across browsers + * @return {string} + */ + History.normalizeHash = function(hash){ + var result = hash.replace(/[^#]*#/,'').replace(/#.*/, ''); + + // Return result + return result; + }; + + /** + * History.setHash(hash) + * Sets the document hash + * @param {string} hash + * @return {History} + */ + History.setHash = function(hash,queue){ + // Handle Queueing + if ( queue !== false && History.busy() ) { + // Wait + Push to Queue + //History.debug('History.setHash: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.setHash, + args: arguments, + queue: queue + }); + return false; + } + + // Log + //History.debug('History.setHash: called',hash); + + // Prepare + var adjustedHash = History.escapeHash(hash); + + // Make Busy + Continue + History.busy(true); + + // Check if hash is a state + var State = History.extractState(hash,true); + if ( State && !History.emulated.pushState ) { + // Hash is a state so skip the setHash + //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments); + + // PushState + History.pushState(State.data,State.title,State.url,false); + } + else if ( document.location.hash !== adjustedHash ) { + // Hash is a proper hash, so apply it + + // Handle browser bugs + if ( History.bugs.setHash ) { + // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249 + + // Fetch the base page + var pageUrl = History.getPageUrl(); + + // Safari hash apply + History.pushState(null,null,pageUrl+'#'+adjustedHash,false); + } + else { + // Normal hash apply + document.location.hash = adjustedHash; + } + } + + // Chain + return History; + }; + + /** + * History.escape() + * normalize and Escape a Hash + * @return {string} + */ + History.escapeHash = function(hash){ + var result = History.normalizeHash(hash); + + // Escape hash + result = window.escape(result); + + // IE6 Escape Bug + if ( !History.bugs.hashEscape ) { + // Restore common parts + result = result + .replace(/\%21/g,'!') + .replace(/\%26/g,'&') + .replace(/\%3D/g,'=') + .replace(/\%3F/g,'?'); + } + + // Return result + return result; + }; + + /** + * History.getHashByUrl(url) + * Extracts the Hash from a URL + * @param {string} url + * @return {string} url + */ + History.getHashByUrl = function(url){ + // Extract the hash + var hash = String(url) + .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2') + ; + + // Unescape hash + hash = History.unescapeHash(hash); + + // Return hash + return hash; + }; + + /** + * History.setTitle(title) + * Applies the title to the document + * @param {State} newState + * @return {Boolean} + */ + History.setTitle = function(newState){ + // Prepare + var title = newState.title; + + // Initial + if ( !title ) { + var firstState = History.getStateByIndex(0); + if ( firstState && firstState.url === newState.url ) { + title = firstState.title||History.options.initialTitle; + } + } + + // Apply + try { + document.getElementsByTagName('title')[0].innerHTML = title.replace('<','<').replace('>','>').replace(' & ',' & '); + } + catch ( Exception ) { } + document.title = title; + + // Chain + return History; + }; + + // ---------------------------------------------------------------------- + // Queueing + + /** + * History.queues + * The list of queues to use + * First In, First Out + */ + History.queues = []; + + /** + * History.busy(value) + * @param {boolean} value [optional] + * @return {boolean} busy + */ + History.busy = function(value){ + // Apply + if ( typeof value !== 'undefined' ) { + //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length); + History.busy.flag = value; + } + // Default + else if ( typeof History.busy.flag === 'undefined' ) { + History.busy.flag = false; + } + + // Queue + if ( !History.busy.flag ) { + // Execute the next item in the queue + clearTimeout(History.busy.timeout); + var fireNext = function(){ + if ( History.busy.flag ) return; + for ( var i=History.queues.length-1; i >= 0; --i ) { + var queue = History.queues[i]; + if ( queue.length === 0 ) continue; + var item = queue.shift(); + History.fireQueueItem(item); + History.busy.timeout = setTimeout(fireNext,History.options.busyDelay); + } + }; + History.busy.timeout = setTimeout(fireNext,History.options.busyDelay); + } + + // Return + return History.busy.flag; + }; + + /** + * History.fireQueueItem(item) + * Fire a Queue Item + * @param {Object} item + * @return {Mixed} result + */ + History.fireQueueItem = function(item){ + return item.callback.apply(item.scope||History,item.args||[]); + }; + + /** + * History.pushQueue(callback,args) + * Add an item to the queue + * @param {Object} item [scope,callback,args,queue] + */ + History.pushQueue = function(item){ + // Prepare the queue + History.queues[item.queue||0] = History.queues[item.queue||0]||[]; + + // Add to the queue + History.queues[item.queue||0].push(item); + + // Chain + return History; + }; + + /** + * History.queue (item,queue), (func,queue), (func), (item) + * Either firs the item now if not busy, or adds it to the queue + */ + History.queue = function(item,queue){ + // Prepare + if ( typeof item === 'function' ) { + item = { + callback: item + }; + } + if ( typeof queue !== 'undefined' ) { + item.queue = queue; + } + + // Handle + if ( History.busy() ) { + History.pushQueue(item); + } else { + History.fireQueueItem(item); + } + + // Chain + return History; + }; + + /** + * History.clearQueue() + * Clears the Queue + */ + History.clearQueue = function(){ + History.busy.flag = false; + History.queues = []; + return History; + }; + + + // ---------------------------------------------------------------------- + // IE Bug Fix + + /** + * History.stateChanged + * States whether or not the state has changed since the last double check was initialised + */ + History.stateChanged = false; + + /** + * History.doubleChecker + * Contains the timeout used for the double checks + */ + History.doubleChecker = false; + + /** + * History.doubleCheckComplete() + * Complete a double check + * @return {History} + */ + History.doubleCheckComplete = function(){ + // Update + History.stateChanged = true; + + // Clear + History.doubleCheckClear(); + + // Chain + return History; + }; + + /** + * History.doubleCheckClear() + * Clear a double check + * @return {History} + */ + History.doubleCheckClear = function(){ + // Clear + if ( History.doubleChecker ) { + clearTimeout(History.doubleChecker); + History.doubleChecker = false; + } + + // Chain + return History; + }; + + /** + * History.doubleCheck() + * Create a double check + * @return {History} + */ + History.doubleCheck = function(tryAgain){ + // Reset + History.stateChanged = false; + History.doubleCheckClear(); + + // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does) + // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940 + if ( History.bugs.ieDoubleCheck ) { + // Apply Check + History.doubleChecker = setTimeout( + function(){ + History.doubleCheckClear(); + if ( !History.stateChanged ) { + //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments); + // Re-Attempt + tryAgain(); + } + return true; + }, + History.options.doubleCheckInterval + ); + } + + // Chain + return History; + }; + + // ---------------------------------------------------------------------- + // Safari Bug Fix + + /** + * History.safariStatePoll() + * Poll the current state + * @return {History} + */ + History.safariStatePoll = function(){ + // Poll the URL + + // Get the Last State which has the new URL + var + urlState = History.extractState(document.location.href), + newState; + + // Check for a difference + if ( !History.isLastSavedState(urlState) ) { + newState = urlState; + } + else { + return; + } + + // Check if we have a state with that url + // If not create it + if ( !newState ) { + //History.debug('History.safariStatePoll: new'); + newState = History.createStateObject(); + } + + // Apply the New State + //History.debug('History.safariStatePoll: trigger'); + History.Adapter.trigger(window,'popstate'); + + // Chain + return History; + }; + + // ---------------------------------------------------------------------- + // State Aliases + + /** + * History.back(queue) + * Send the browser history back one item + * @param {Integer} queue [optional] + */ + History.back = function(queue){ + //History.debug('History.back: called', arguments); + + // Handle Queueing + if ( queue !== false && History.busy() ) { + // Wait + Push to Queue + //History.debug('History.back: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.back, + args: arguments, + queue: queue + }); + return false; + } + + // Make Busy + Continue + History.busy(true); + + // Fix certain browser bugs that prevent the state from changing + History.doubleCheck(function(){ + History.back(false); + }); + + // Go back + history.go(-1); + + // End back closure + return true; + }; + + /** + * History.forward(queue) + * Send the browser history forward one item + * @param {Integer} queue [optional] + */ + History.forward = function(queue){ + //History.debug('History.forward: called', arguments); + + // Handle Queueing + if ( queue !== false && History.busy() ) { + // Wait + Push to Queue + //History.debug('History.forward: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.forward, + args: arguments, + queue: queue + }); + return false; + } + + // Make Busy + Continue + History.busy(true); + + // Fix certain browser bugs that prevent the state from changing + History.doubleCheck(function(){ + History.forward(false); + }); + + // Go forward + history.go(1); + + // End forward closure + return true; + }; + + /** + * History.go(index,queue) + * Send the browser history back or forward index times + * @param {Integer} queue [optional] + */ + History.go = function(index,queue){ + //History.debug('History.go: called', arguments); + + // Prepare + var i; + + // Handle + if ( index > 0 ) { + // Forward + for ( i=1; i<=index; ++i ) { + History.forward(queue); + } + } + else if ( index < 0 ) { + // Backward + for ( i=-1; i>=index; --i ) { + History.back(queue); + } + } + else { + throw new Error('History.go: History.go requires a positive or negative integer passed.'); + } + + // Chain + return History; + }; + + + // ---------------------------------------------------------------------- + // Initialise + + /** + * Create the initial State + */ + History.saveState(History.storeState(History.extractState(document.location.href,true))); + + /** + * Bind for Saving Store + */ + if ( amplify ) { + History.onUnload = function(){ + // Prepare + var + currentStore = amplify.store('History.store')||{}, + item; + + // Ensure + currentStore.idToState = currentStore.idToState || {}; + currentStore.urlToId = currentStore.urlToId || {}; + currentStore.stateToId = currentStore.stateToId || {}; + + // Sync + for ( item in History.idToState ) { + if ( !History.idToState.hasOwnProperty(item) ) { + continue; + } + currentStore.idToState[item] = History.idToState[item]; + } + for ( item in History.urlToId ) { + if ( !History.urlToId.hasOwnProperty(item) ) { + continue; + } + currentStore.urlToId[item] = History.urlToId[item]; + } + for ( item in History.stateToId ) { + if ( !History.stateToId.hasOwnProperty(item) ) { + continue; + } + currentStore.stateToId[item] = History.stateToId[item]; + } + + // Update + History.store = currentStore; + + // Store + amplify.store('History.store',currentStore); + }; + // For Internet Explorer + History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval)); + // For Other Browsers + History.Adapter.bind(window,'beforeunload',History.onUnload); + History.Adapter.bind(window,'unload',History.onUnload); + // Both are enabled for consistency + } + + + // ---------------------------------------------------------------------- + // HTML5 State Support + + if ( History.emulated.pushState ) { + /* + * Provide Skeleton for HTML4 Browsers + */ + + // Prepare + var emptyFunction = function(){}; + History.pushState = History.pushState||emptyFunction; + History.replaceState = History.replaceState||emptyFunction; + } + else { + /* + * Use native HTML5 History API Implementation + */ + + /** + * History.onPopState(event,extra) + * Refresh the Current State + */ + History.onPopState = function(event){ + // Reset the double check + History.doubleCheckComplete(); + + // Check for a Hash, and handle apporiatly + var currentHash = History.getHash(); + if ( currentHash ) { + // Expand Hash + var currentState = History.extractState(currentHash||document.location.href,true); + if ( currentState ) { + // We were able to parse it, it must be a State! + // Let's forward to replaceState + //History.debug('History.onPopState: state anchor', currentHash, currentState); + History.replaceState(currentState.data, currentState.title, currentState.url, false); + } + else { + // Traditional Anchor + //History.debug('History.onPopState: traditional anchor', currentHash); + History.Adapter.trigger(window,'anchorchange'); + History.busy(false); + } + + // We don't care for hashes + History.expectedStateId = false; + return false; + } + + // Prepare + var newState = false; + + // Prepare + event = event||{}; + if ( typeof event.state === 'undefined' ) { + // jQuery + if ( typeof event.originalEvent !== 'undefined' && typeof event.originalEvent.state !== 'undefined' ) { + event.state = event.originalEvent.state||false; + } + // MooTools + else if ( typeof event.event !== 'undefined' && typeof event.event.state !== 'undefined' ) { + event.state = event.event.state||false; + } + } + + // Ensure + event.state = (event.state||false); + + // Fetch State + if ( event.state ) { + // Vanilla: Back/forward button was used + newState = History.getStateById(event.state); + } + else if ( History.expectedStateId ) { + // Vanilla: A new state was pushed, and popstate was called manually + newState = History.getStateById(History.expectedStateId); + } + else { + // Initial State + newState = History.extractState(document.location.href); + } + + // The State did not exist in our store + if ( !newState ) { + // Regenerate the State + newState = History.createStateObject(null,null,document.location.href); + } + + // Clean + History.expectedStateId = false; + + // Check if we are the same state + if ( History.isLastSavedState(newState) ) { + // There has been no change (just the page's hash has finally propagated) + //History.debug('History.onPopState: no change', newState, History.savedStates); + History.busy(false); + return false; + } + + // Store the State + History.storeState(newState); + History.saveState(newState); + + // Force update of the title + History.setTitle(newState); + + // Fire Our Event + History.Adapter.trigger(window,'statechange'); + History.busy(false); + + // Return true + return true; + }; + History.Adapter.bind(window,'popstate',History.onPopState); + + /** + * History.pushState(data,title,url) + * Add a new State to the history object, become it, and trigger onpopstate + * We have to trigger for HTML4 compatibility + * @param {object} data + * @param {string} title + * @param {string} url + * @return {true} + */ + History.pushState = function(data,title,url,queue){ + //History.debug('History.pushState: called', arguments); + + // Check the State + if ( History.getHashByUrl(url) && History.emulated.pushState ) { + throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); + } + + // Handle Queueing + if ( queue !== false && History.busy() ) { + // Wait + Push to Queue + //History.debug('History.pushState: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.pushState, + args: arguments, + queue: queue + }); + return false; + } + + // Make Busy + Continue + History.busy(true); + + // Create the newState + var newState = History.createStateObject(data,title,url); + + // Check it + if ( History.isLastSavedState(newState) ) { + // Won't be a change + History.busy(false); + } + else { + // Store the newState + History.storeState(newState); + History.expectedStateId = newState.id; + + // Push the newState + history.pushState(newState.id,newState.title,newState.url); + + // Fire HTML5 Event + History.Adapter.trigger(window,'popstate'); + } + + // End pushState closure + return true; + }; + + /** + * History.replaceState(data,title,url) + * Replace the State and trigger onpopstate + * We have to trigger for HTML4 compatibility + * @param {object} data + * @param {string} title + * @param {string} url + * @return {true} + */ + History.replaceState = function(data,title,url,queue){ + //History.debug('History.replaceState: called', arguments); + + // Check the State + if ( History.getHashByUrl(url) && History.emulated.pushState ) { + throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); + } + + // Handle Queueing + if ( queue !== false && History.busy() ) { + // Wait + Push to Queue + //History.debug('History.replaceState: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.replaceState, + args: arguments, + queue: queue + }); + return false; + } + + // Make Busy + Continue + History.busy(true); + + // Create the newState + var newState = History.createStateObject(data,title,url); + + // Check it + if ( History.isLastSavedState(newState) ) { + // Won't be a change + History.busy(false); + } + else { + // Store the newState + History.storeState(newState); + History.expectedStateId = newState.id; + + // Push the newState + history.replaceState(newState.id,newState.title,newState.url); + + // Fire HTML5 Event + History.Adapter.trigger(window,'popstate'); + } + + // End replaceState closure + return true; + }; + + // Be aware, the following is only for native pushState implementations + // If you are wanting to include something for all browsers + // Then include it above this if block + + /** + * Setup Safari Fix + */ + if ( History.bugs.safariPoll ) { + History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval)); + } + + /** + * Ensure Cross Browser Compatibility + */ + if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) { + /** + * Fix Safari HashChange Issue + */ + + // Setup Alias + History.Adapter.bind(window,'hashchange',function(){ + History.Adapter.trigger(window,'popstate'); + }); + + // Initialise Alias + if ( History.getHash() ) { + History.Adapter.onDomLoad(function(){ + History.Adapter.trigger(window,'hashchange'); + }); + } + } + + } // !History.emulated.pushState + + }; // History.initCore + + // Try and Initialise History + History.init(); + +})(window); diff --git a/admin/thirdparty/history-js/scripts/uncompressed/json2.js b/admin/thirdparty/history-js/scripts/uncompressed/json2.js new file mode 100644 index 000000000..36d3dc393 --- /dev/null +++ b/admin/thirdparty/history-js/scripts/uncompressed/json2.js @@ -0,0 +1,480 @@ +/* + http://www.JSON.org/json2.js + 2011-01-18 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, strict: false, regexp: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON; +if (!JSON) { + JSON = {}; +} + +(function () { + "use strict"; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : gap ? + '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : gap ? + '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : + '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}());