mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Move state to enwtine properties, provide API for preview.
Also the preview state is now kept between panel loads. We also use a redraw function to update appearance based on the state.
This commit is contained in:
parent
9312c70696
commit
8f5acd70b3
@ -1044,7 +1044,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
$fields->push(new HiddenField('ParentID'));
|
$fields->push(new HiddenField('ParentID'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
|
// Added in-line to the form, but plucked into different view by frontend scripts.
|
||||||
if(in_array('CMSPreviewable', class_implements($record))) {
|
if(in_array('CMSPreviewable', class_implements($record))) {
|
||||||
$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
|
$navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator());
|
||||||
$navField->setAllowHTML(true);
|
$navField->setAllowHTML(true);
|
||||||
|
@ -94,14 +94,6 @@
|
|||||||
$('.cms-container').clearCurrentTabState(); // clear state to avoid override later on
|
$('.cms-container').clearCurrentTabState(); // clear state to avoid override later on
|
||||||
firstTabWithErrors.closest('.tabset').tabs('select', firstTabWithErrors.attr('id'));
|
firstTabWithErrors.closest('.tabset').tabs('select', firstTabWithErrors.attr('id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move navigator to preview if one is available.
|
|
||||||
// If not, just leave the links in the form.
|
|
||||||
var previewEl = $('.cms-preview');
|
|
||||||
if(previewEl.length) {
|
|
||||||
// TODO Relies on DOM element order (the second .cms-navigator is the "old" one)
|
|
||||||
previewEl.find('.cms-preview-controls').html(this.find('.cms-navigator').detach());
|
|
||||||
}
|
|
||||||
|
|
||||||
this._super();
|
this._super();
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
(function($) {
|
(function($) {
|
||||||
$.entwine('ss', function($){
|
$.entwine('ss.preview', function($){
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a previewable website state alongside its editable version in backend UI,
|
* Shows a previewable website state alongside its editable version in backend UI,
|
||||||
* typically a page. This allows CMS users to seamlessly switch between preview and
|
* typically a page. This allows CMS users to seamlessly switch between preview and
|
||||||
* edit mode in the same browser window. The preview panel is embedded in the layout
|
* edit mode in the same browser window. The preview panel is embedded in the layout
|
||||||
* of the backend UI, and loads its content via an iframe.
|
* of the backend UI, and loads its content via an iframe.
|
||||||
*
|
|
||||||
* The admin UI itself is collapsible, leaving most screen space to this panel.
|
|
||||||
*
|
*
|
||||||
* Relies on the server responses to indicate if a preview URL is available for the
|
* Relies on the server responses to indicate if a preview URL is available for the
|
||||||
* currently loaded admin interface. If no preview is available, the panel is "blocked"
|
* currently loaded admin interface. If no preview is available, the panel is "blocked"
|
||||||
@ -17,7 +15,103 @@
|
|||||||
* while all external links are disabled (via JavaScript).
|
* while all external links are disabled (via JavaScript).
|
||||||
*/
|
*/
|
||||||
$('.cms-preview').entwine({
|
$('.cms-preview').entwine({
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of SilverStripeNavigator states (SilverStripeNavigatorItem classes) to search for.
|
||||||
|
* The order is significant - if the state is not available, preview will start searching the list
|
||||||
|
* from the beginning.
|
||||||
|
*/
|
||||||
|
AllowedStates: ['StageLink', 'LiveLink'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API
|
||||||
|
* Name of the current preview state - one of the "AllowedStates".
|
||||||
|
*/
|
||||||
|
CurrentStateName: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API
|
||||||
|
* Current size selection.
|
||||||
|
*/
|
||||||
|
CurrentSizeName: 'auto',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API
|
||||||
|
* Switch the preview to different state.
|
||||||
|
* stateName can be one of the "AllowedStates".
|
||||||
|
*/
|
||||||
|
changeState: function(stateName) {
|
||||||
|
this.setCurrentStateName(stateName);
|
||||||
|
this._updatePreview();
|
||||||
|
this.redraw();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API
|
||||||
|
* Change the preview mode.
|
||||||
|
* modeName can be: split, content, preview.
|
||||||
|
*/
|
||||||
|
changeMode: function(modeName) {
|
||||||
|
var container = $('.cms-container');
|
||||||
|
|
||||||
|
if (modeName == 'split') {
|
||||||
|
container.entwine('.ss').splitViewMode();
|
||||||
|
} else if (modeName == 'content') {
|
||||||
|
container.entwine('.ss').contentViewMode();
|
||||||
|
} else {
|
||||||
|
container.entwine('.ss').previewMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.redraw();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API
|
||||||
|
* Change the preview size.
|
||||||
|
* sizeName can be: auto, desktop, tablet, mobile.
|
||||||
|
*/
|
||||||
|
changeSize: function(sizeName) {
|
||||||
|
this.setCurrentSizeName(sizeName);
|
||||||
|
|
||||||
|
this.removeClass('auto desktop tablet mobile')
|
||||||
|
.addClass(sizeName);
|
||||||
|
|
||||||
|
this.redraw();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API
|
||||||
|
* Update the visual appearance to match the internal preview state.
|
||||||
|
*/
|
||||||
|
redraw: function() {
|
||||||
|
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
|
||||||
|
|
||||||
|
// Update preview state selector.
|
||||||
|
var currentStateName = this.getCurrentStateName();
|
||||||
|
if (currentStateName) {
|
||||||
|
this.find('.cms-preview-states').changeVisibleState(currentStateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update preview mode selectors.
|
||||||
|
var layoutOptions = $('.cms-container').entwine('.ss').getLayoutOptions();
|
||||||
|
if (layoutOptions) {
|
||||||
|
// There are two mode selectors that we need to keep in sync. Redraw both.
|
||||||
|
$('.preview-mode-selector').changeVisibleMode(layoutOptions.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update preview size selector.
|
||||||
|
var currentSizeName = this.getCurrentSizeName();
|
||||||
|
if (currentSizeName) {
|
||||||
|
this.find('.preview-size-selector').changeVisibleSize(this.getCurrentSizeName());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onadd: function() {
|
onadd: function() {
|
||||||
var self = this, layoutContainer = this.parent();
|
var self = this, layoutContainer = this.parent();
|
||||||
// this.resizable({
|
// this.resizable({
|
||||||
@ -34,7 +128,7 @@
|
|||||||
|
|
||||||
// Load edit view for new page, but only if the preview is activated at the moment.
|
// Load edit view for new page, but only if the preview is activated at the moment.
|
||||||
// This avoids e.g. force-redirections of the edit view on RedirectorPage instances.
|
// This avoids e.g. force-redirections of the edit view on RedirectorPage instances.
|
||||||
self.loadCurrentPage();
|
self._loadCurrentPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.data('cms-preview-initialized', true);
|
this.data('cms-preview-initialized', true);
|
||||||
@ -42,33 +136,70 @@
|
|||||||
// Preview might not be available in all admin interfaces - block/disable when necessary
|
// Preview might not be available in all admin interfaces - block/disable when necessary
|
||||||
this.append('<div class="cms-preview-overlay ui-widget-overlay-light"></div>');
|
this.append('<div class="cms-preview-overlay ui-widget-overlay-light"></div>');
|
||||||
this.find('.cms-preview-overlay-light').hide();
|
this.find('.cms-preview-overlay-light').hide();
|
||||||
$('.cms-preview-toggle-link')[this.canPreview() ? 'show' : 'hide']();
|
$('.cms-preview-toggle-link')[this._canPreview() ? 'show' : 'hide']();
|
||||||
|
|
||||||
self._fixIframeLinks();
|
self._fixIframeLinks();
|
||||||
this.updatePreview();
|
this._updatePreview();
|
||||||
|
|
||||||
this._super();
|
this._super();
|
||||||
},
|
},
|
||||||
|
|
||||||
loadUrl: function(url) {
|
/**
|
||||||
|
* Load the URL into the preview iframe.
|
||||||
|
*/
|
||||||
|
_loadUrl: function(url) {
|
||||||
this.find('iframe').attr('src', url);
|
this.find('iframe').attr('src', url);
|
||||||
},
|
},
|
||||||
|
|
||||||
updatePreview: function() {
|
/**
|
||||||
var url = $('.cms-edit-form').choosePreviewLink();
|
* Fetch available states from the current SilverStripeNavigator (SilverStripeNavigatorItems).
|
||||||
|
* Navigator is supplied by the backend and contains all state options for the current object.
|
||||||
|
*/
|
||||||
|
_getNavigatorStates: function() {
|
||||||
|
// Walk through available states and get the URLs.
|
||||||
|
var urlMap = $.map(this.getAllowedStates(), function(name) {
|
||||||
|
var stateLink = $('.cms-preview-states .switch-options a[name=' + name + ']');
|
||||||
|
return stateLink.length ? {name: name, url: stateLink.attr('href')} : null;
|
||||||
|
});
|
||||||
|
|
||||||
if(url) {
|
return urlMap;
|
||||||
this.loadUrl(url);
|
|
||||||
this.unblock();
|
|
||||||
} else {
|
|
||||||
this.block();
|
|
||||||
this.toggle();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateAfterXhr: function(){
|
/**
|
||||||
$('.cms-preview-toggle-link')[this.canPreview() ? 'show' : 'hide']();
|
* Reload the preview while keeping current state.
|
||||||
this.updatePreview();
|
* Fall back to first preferred state if state is no longer available.
|
||||||
|
*/
|
||||||
|
_updatePreview: function() {
|
||||||
|
var states = this._getNavigatorStates();
|
||||||
|
var currentStateName = this.getCurrentStateName();
|
||||||
|
var currentState = null;
|
||||||
|
|
||||||
|
// Find current state within currently available states.
|
||||||
|
if (states) {
|
||||||
|
currentState = $.grep(states, function(state, index) {
|
||||||
|
return currentStateName===state.name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentState[0]) {
|
||||||
|
// State is available.
|
||||||
|
this._loadUrl(currentState[0].url);
|
||||||
|
this._unblock();
|
||||||
|
} else if (states.length) {
|
||||||
|
// Fall back to first preferred state.
|
||||||
|
this.setCurrentStateName(states[0].name);
|
||||||
|
this._loadUrl(states[0].url);
|
||||||
|
this._unblock();
|
||||||
|
} else {
|
||||||
|
// No state available.
|
||||||
|
this._block();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateAfterXhr: function(){
|
||||||
|
$('.cms-preview-toggle-link')[this._canPreview() ? 'show' : 'hide']();
|
||||||
|
this._updatePreview();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,7 +207,7 @@
|
|||||||
*/
|
*/
|
||||||
'from .cms-container': {
|
'from .cms-container': {
|
||||||
onafterstatechange: function(){
|
onafterstatechange: function(){
|
||||||
this.updateAfterXhr();
|
this._updateAfterXhr();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -86,7 +217,7 @@
|
|||||||
*/
|
*/
|
||||||
'from .cms-container .cms-edit-form': {
|
'from .cms-container .cms-edit-form': {
|
||||||
onaftersubmitform: function(){
|
onaftersubmitform: function(){
|
||||||
this.updateAfterXhr();
|
this._updateAfterXhr();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -94,10 +225,10 @@
|
|||||||
* Loads the matching edit form for a page viewed in the preview iframe,
|
* Loads the matching edit form for a page viewed in the preview iframe,
|
||||||
* based on metadata sent along with this document.
|
* based on metadata sent along with this document.
|
||||||
*/
|
*/
|
||||||
loadCurrentPage: function() {
|
_loadCurrentPage: function() {
|
||||||
var doc = this.find('iframe')[0].contentDocument, containerEl = this.getLayoutContainer();
|
var doc = this.find('iframe')[0].contentDocument, containerEl = $('.cms-container');
|
||||||
|
|
||||||
if(!this.canPreview()) return;
|
if(!this._canPreview()) return;
|
||||||
|
|
||||||
// Load this page in the admin interface if appropriate
|
// Load this page in the admin interface if appropriate
|
||||||
var id = $(doc).find('meta[name=x-page-id]').attr('content');
|
var id = $(doc).find('meta[name=x-page-id]').attr('content');
|
||||||
@ -108,7 +239,7 @@
|
|||||||
// Ignore behaviour without history support (as we need ajax loading
|
// Ignore behaviour without history support (as we need ajax loading
|
||||||
// for the new form to load in the background)
|
// for the new form to load in the background)
|
||||||
if(window.History.enabled)
|
if(window.History.enabled)
|
||||||
$('.cms-container').loadPanel(editLink);
|
$('.cms-container').entwine('.ss').loadPanel(editLink);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -117,8 +248,8 @@
|
|||||||
*
|
*
|
||||||
* Returns: {boolean}
|
* Returns: {boolean}
|
||||||
*/
|
*/
|
||||||
canPreview: function() {
|
_canPreview: function() {
|
||||||
var contentEl = this.getLayoutContainer().find('.cms-content');
|
var contentEl = $('.cms-container .cms-content');
|
||||||
// Only load if we're in the "edit page" view
|
// Only load if we're in the "edit page" view
|
||||||
var blockedClasses = ['CMSPagesController', 'CMSPageHistoryController'];
|
var blockedClasses = ['CMSPagesController', 'CMSPageHistoryController'];
|
||||||
return !(contentEl.is('.' + blockedClasses.join(',.')));
|
return !(contentEl.is('.' + blockedClasses.join(',.')));
|
||||||
@ -146,27 +277,38 @@
|
|||||||
if (href.match(/^http:\/\//)) links[i].setAttribute('target', '_blank');
|
if (href.match(/^http:\/\//)) links[i].setAttribute('target', '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide duplicate navigator, as it replicates existing UI in the CMS
|
// Hide the navigator from the preview iframe and use only the CMS one.
|
||||||
var navi = doc.getElementById('SilverStripeNavigator');
|
var navi = doc.getElementById('SilverStripeNavigator');
|
||||||
if(navi) navi.style.display = 'none';
|
if(navi) navi.style.display = 'none';
|
||||||
var naviMsg = doc.getElementById('SilverStripeNavigatorMessage');
|
var naviMsg = doc.getElementById('SilverStripeNavigatorMessage');
|
||||||
if(naviMsg) naviMsg.style.display = 'none';
|
if(naviMsg) naviMsg.style.display = 'none';
|
||||||
},
|
},
|
||||||
|
|
||||||
block: function() {
|
_block: function() {
|
||||||
this.addClass('blocked');
|
this.addClass('blocked');
|
||||||
},
|
},
|
||||||
|
|
||||||
unblock: function() {
|
_unblock: function() {
|
||||||
this.removeClass('blocked');
|
this.removeClass('blocked');
|
||||||
},
|
}
|
||||||
|
});
|
||||||
getLayoutContainer: function() {
|
|
||||||
return this.parents('.cms-container');
|
$('.cms-edit-form').entwine({
|
||||||
},
|
/**
|
||||||
|
* Initialise the navigator - move it from the EditForm to the preview.
|
||||||
redraw: function() {
|
*/
|
||||||
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
|
onadd: function() {
|
||||||
|
var previewEl = $('.cms-preview .cms-preview-controls');
|
||||||
|
var navigatorEl = $('.cms-edit-form .cms-navigator');
|
||||||
|
|
||||||
|
if (navigatorEl.length && previewEl.length) {
|
||||||
|
// Preview is available - install the navigator.
|
||||||
|
previewEl.html($('.cms-edit-form .cms-navigator').detach());
|
||||||
|
$('.cms-preview').changeMode('split');
|
||||||
|
} else {
|
||||||
|
// Preview not available.
|
||||||
|
$('.cms-preview').changeMode('content');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -181,63 +323,69 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.switch-options a').entwine({
|
/**
|
||||||
onclick: function(e) {
|
* "Preview state" functions.
|
||||||
var preview = $('.cms-preview');
|
* -------------------------------------------------------------------
|
||||||
var loadSibling = $(this).siblings('a');
|
*/
|
||||||
var checkbox = $(this).closest('.cms-preview-states').find('input');
|
$('.cms-preview-states').entwine({
|
||||||
if(checkbox.attr('checked') !== undefined){
|
/**
|
||||||
checkbox.attr('checked', false);
|
* Change the displayed state.
|
||||||
}else{
|
*/
|
||||||
checkbox.attr('checked', true);
|
changeVisibleState: function(state) {
|
||||||
|
// Arbitrary mapping from checkbox state to the preview state.
|
||||||
|
if (state==='LiveLink') {
|
||||||
|
this.find('.cms-preview-checkbox').prop('checked', false);
|
||||||
|
} else {
|
||||||
|
this.find('.cms-preview-checkbox').prop('checked', true);
|
||||||
}
|
}
|
||||||
preview.loadUrl($(loadSibling).attr('href'));
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.cms-preview-states .switch-options a').entwine({
|
||||||
|
/**
|
||||||
|
* Reacts to the user changing the state of the preview.
|
||||||
|
* TODO Rewrite this function to ensure we can handle 1,2,3+ states.
|
||||||
|
*/
|
||||||
|
onclick: function(e) {
|
||||||
|
var targetStateName = $(this).siblings('a').attr('name');
|
||||||
|
|
||||||
|
// Reload preview with the selected state.
|
||||||
|
$('.cms-preview').changeState(targetStateName);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.preview-mode-selector select').entwine({
|
/**
|
||||||
onchange: function(e) {
|
* "Preview mode" functions
|
||||||
e.preventDefault();
|
* -------------------------------------------------------------------
|
||||||
|
*/
|
||||||
var container = $('.cms-container');
|
$('.preview-mode-selector').entwine({
|
||||||
var state = $(this).val();
|
/**
|
||||||
|
* Change the displayed mode.
|
||||||
if (state == 'split') {
|
*/
|
||||||
container.splitViewMode();
|
changeVisibleMode: function(mode) {
|
||||||
} else if (state == 'edit') {
|
this.find('select')
|
||||||
container.contentViewMode();
|
.val(mode)
|
||||||
} else {
|
|
||||||
container.previewMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addIcon();
|
|
||||||
|
|
||||||
// Synchronise other preview-mode selectors to display the same state.
|
|
||||||
$('.preview-mode-selector select').not(this)
|
|
||||||
.val(this.val())
|
|
||||||
.trigger('liszt:updated')
|
.trigger('liszt:updated')
|
||||||
.addIcon();
|
._addIcon();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.preview-size-selector select').entwine({
|
$('.preview-mode-selector select').entwine({
|
||||||
|
/**
|
||||||
|
* Reacts to the user changing the preview mode.
|
||||||
|
*/
|
||||||
onchange: function(e) {
|
onchange: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var preview = $('.cms-preview');
|
var targetStateName = $(this).val();
|
||||||
var size = $(this).val();
|
$('.cms-preview').changeMode(targetStateName);
|
||||||
|
|
||||||
preview
|
|
||||||
.removeClass('auto desktop tablet mobile')
|
|
||||||
.addClass(size);
|
|
||||||
|
|
||||||
this.addIcon();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React to state view mode changes by showing/hiding the preview-mode selector.
|
* Adjust the visibility of the preview-mode selector in the CMS part (hidden if preview is visible).
|
||||||
*/
|
*/
|
||||||
$('.cms-preview.column-hidden').entwine({
|
$('.cms-preview.column-hidden').entwine({
|
||||||
onmatch: function() {
|
onmatch: function() {
|
||||||
@ -251,7 +399,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise the CMS's preview-mode selector.
|
* Initialise the preview-mode selector in the CMS part (could be hidden if preview is visible).
|
||||||
*/
|
*/
|
||||||
$('#preview-mode-dropdown-in-content').entwine({
|
$('#preview-mode-dropdown-in-content').entwine({
|
||||||
onmatch: function() {
|
onmatch: function() {
|
||||||
@ -268,18 +416,51 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Preview size" functions
|
||||||
|
* -------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
$('.preview-size-selector').entwine({
|
||||||
|
/**
|
||||||
|
* Change the displayed size.
|
||||||
|
*/
|
||||||
|
changeVisibleSize: function(size) {
|
||||||
|
this.find('select')
|
||||||
|
.val(size)
|
||||||
|
.trigger('liszt:updated')
|
||||||
|
._addIcon();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.preview-size-selector select').entwine({
|
||||||
|
/**
|
||||||
|
* Trigger change in the preview size.
|
||||||
|
*/
|
||||||
|
onchange: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var targetSizeName = $(this).val();
|
||||||
|
$('.cms-preview').changeSize(targetSizeName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chosen plumbing.
|
||||||
|
* -------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a class to the chzn select trigger based on the currently
|
* Add a class to the chzn select trigger based on the currently
|
||||||
* selected option. Update as this changes
|
* selected option. Update as this changes
|
||||||
*/
|
*/
|
||||||
$('.preview-selector select.preview-dropdown').entwine({
|
$('.preview-selector select.preview-dropdown').entwine({
|
||||||
'onliszt:showing_dropdown': function() {
|
'onliszt:showing_dropdown': function() {
|
||||||
this.siblings().find('.chzn-drop').addClass('open').alignRight();
|
this.siblings().find('.chzn-drop').addClass('open')._alignRight();
|
||||||
},
|
},
|
||||||
'onliszt:hiding_dropdown': function() {
|
'onliszt:hiding_dropdown': function() {
|
||||||
this.siblings().find('.chzn-drop').removeClass('open').removeRightAlign();
|
this.siblings().find('.chzn-drop').removeClass('open')._removeRightAlign();
|
||||||
},
|
},
|
||||||
addIcon: function(){
|
_addIcon: function(){
|
||||||
var selected = this.find(':selected');
|
var selected = this.find(':selected');
|
||||||
var iconClass = selected.attr('data-icon');
|
var iconClass = selected.attr('data-icon');
|
||||||
|
|
||||||
@ -299,7 +480,7 @@
|
|||||||
*/
|
*/
|
||||||
$('.preview-selector a.chzn-single').entwine({
|
$('.preview-selector a.chzn-single').entwine({
|
||||||
onmatch: function() {
|
onmatch: function() {
|
||||||
this.closest('.preview-selector').find('select').addIcon();
|
this.closest('.preview-selector').find('select')._addIcon();
|
||||||
this._super();
|
this._super();
|
||||||
},
|
},
|
||||||
onunmatch: function() {
|
onunmatch: function() {
|
||||||
@ -309,7 +490,7 @@
|
|||||||
|
|
||||||
|
|
||||||
$('.preview-selector .chzn-drop').entwine({
|
$('.preview-selector .chzn-drop').entwine({
|
||||||
alignRight: function(){
|
_alignRight: function(){
|
||||||
var that = this;
|
var that = this;
|
||||||
$(this).hide();
|
$(this).hide();
|
||||||
/* Delay so styles applied after chosen applies css
|
/* Delay so styles applied after chosen applies css
|
||||||
@ -320,7 +501,7 @@
|
|||||||
$(that).show();
|
$(that).show();
|
||||||
}, 100);
|
}, 100);
|
||||||
},
|
},
|
||||||
removeRightAlign:function(){
|
_removeRightAlign:function(){
|
||||||
$(this).css({right:'auto'});
|
$(this).css({right:'auto'});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,41 +542,32 @@
|
|||||||
}
|
}
|
||||||
}); */
|
}); */
|
||||||
|
|
||||||
|
/**
|
||||||
$('.cms-edit-form').entwine({
|
* Recalculate the preview space to allow for horizontal scrollbar and the preview actions panel
|
||||||
/**
|
*/
|
||||||
* Choose applicable preview link based on form data,
|
|
||||||
* in a fixed order of priority: The PreviewURL field is used as an override,
|
|
||||||
* which falls back to stage or live URLs.
|
|
||||||
*
|
|
||||||
* @return String Absolute URL
|
|
||||||
*/
|
|
||||||
choosePreviewLink: function() {
|
|
||||||
var self = this, urls = $.map(['PreviewURL', 'StageLink', 'LiveLink'], function(name) {
|
|
||||||
var val = self.find(':input[name=' + name + ']').val();
|
|
||||||
return val ? val : null;
|
|
||||||
});
|
|
||||||
return urls ? urls[0] : false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Recalculate the preview space to allow for horizontal scrollbar and the preview actions panel
|
|
||||||
var toolbarSize = 53; // Height of the preview actions panel
|
|
||||||
$('.preview-scroll').entwine({
|
$('.preview-scroll').entwine({
|
||||||
redraw: function() {
|
/**
|
||||||
|
* Height of the preview actions panel
|
||||||
|
*/
|
||||||
|
ToolbarSize: 53,
|
||||||
|
|
||||||
|
_redraw: function() {
|
||||||
|
var toolbarSize = this.getToolbarSize();
|
||||||
|
|
||||||
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
|
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
|
||||||
var previewHeight = (this.height() - toolbarSize);
|
var previewHeight = (this.height() - toolbarSize);
|
||||||
this.height(previewHeight);
|
this.height(previewHeight);
|
||||||
},
|
},
|
||||||
|
|
||||||
onmatch: function() {
|
onmatch: function() {
|
||||||
this.redraw();
|
this._redraw();
|
||||||
this._super();
|
this._super();
|
||||||
},
|
},
|
||||||
|
|
||||||
onunmatch: function() {
|
onunmatch: function() {
|
||||||
this._super();
|
this._super();
|
||||||
}
|
}
|
||||||
// Todo: Need to recalculate on resize of browser
|
// TODO: Need to recalculate on resize of browser
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,12 +20,12 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="cms-preview-states switch-states">
|
<div class="cms-preview-states switch-states">
|
||||||
<input type="checkbox" name="cms-preview" class="state cms-preview" id="cms-preview-state" checked>
|
<input type="checkbox" name="cms-preview" class="state cms-preview-checkbox" id="cms-preview-state" checked>
|
||||||
<label for="cms-preview-state">
|
<label for="cms-preview-state">
|
||||||
<span class="switch-options">
|
<span class="switch-options">
|
||||||
<% loop Items %>
|
<% loop Items %>
|
||||||
$Items.count
|
$Items.count
|
||||||
<a href="$Link" class="$FirstLast <% if isActive %> active<% end_if %>">$Title</a>
|
<a href="$Link" name="$Name" class="$FirstLast <% if isActive %> active<% end_if %>">$Title</a>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</span>
|
</span>
|
||||||
<span class="switch"></span>
|
<span class="switch"></span>
|
||||||
|
@ -27,13 +27,17 @@ This causes the framework to:
|
|||||||
to the layout manager)
|
to the layout manager)
|
||||||
* trigger `redraw` on children which also cascades deeper into the hierarchy (this is framework activity)
|
* trigger `redraw` on children which also cascades deeper into the hierarchy (this is framework activity)
|
||||||
|
|
||||||
Caveat #1: `layout` is also triggered when a DOM element is replaced with AJAX in `LeftAndMain::handleAjaxResponse`. In
|
<div class="notice" markdown='1'>
|
||||||
|
Caveat: `layout` is also triggered when a DOM element is replaced with AJAX in `LeftAndMain::handleAjaxResponse`. In
|
||||||
this case it is triggered on the parent of the element being replaced so jLayout has a chance to rebuild its algorithms.
|
this case it is triggered on the parent of the element being replaced so jLayout has a chance to rebuild its algorithms.
|
||||||
Calling the top level `layout` is not enough as it will wrongly descend down the detached element's hierarchy.
|
Calling the top level `layout` is not enough as it will wrongly descend down the detached element's hierarchy.
|
||||||
|
</div>
|
||||||
|
|
||||||
Caveat #2: invocation order of the `redraws` is crucial here, generally going from innermost to outermost elements. For
|
<div class="notice" markdown='1'>
|
||||||
|
Caveat: invocation order of the `redraws` is crucial here, generally going from innermost to outermost elements. For
|
||||||
example, the tab panels have be applied in the CMS form before the form itself is layouted with its sibling panels to
|
example, the tab panels have be applied in the CMS form before the form itself is layouted with its sibling panels to
|
||||||
avoid incorrect dimensions.
|
avoid incorrect dimensions.
|
||||||
|
</div>
|
||||||
|
|
||||||
![Layout variations](_images/cms-architecture.png)
|
![Layout variations](_images/cms-architecture.png)
|
||||||
|
|
||||||
@ -70,12 +74,12 @@ panel to the CMS UI.
|
|||||||
The following methods are available as an interface to underlying _threeColumnCompressor_ algorithm on the
|
The following methods are available as an interface to underlying _threeColumnCompressor_ algorithm on the
|
||||||
`.cms-container` entwine:
|
`.cms-container` entwine:
|
||||||
|
|
||||||
* _getLayoutOptions_: get currently used _threeColumnCompressor_ options.
|
* **getLayoutOptions**: get currently used _threeColumnCompressor_ options.
|
||||||
* _updateLayoutOptions_: change specified options and trigger the laying out:
|
* **updateLayoutOptions**: change specified options and trigger the laying out:
|
||||||
`$('.cms-container').updateLayoutOptions({mode: 'split'});`
|
`$('.cms-container').updateLayoutOptions({mode: 'split'});`
|
||||||
* _splitViewMode_: enable side by side editing.
|
* **splitViewMode**: enable side by side editing.
|
||||||
* _contentViewMode_: only menu and content areas are shown.
|
* **contentViewMode**: only menu and content areas are shown.
|
||||||
* _previewMode_: only menu and preview areas are shown.
|
* **previewMode**: only menu and preview areas are shown.
|
||||||
|
|
||||||
### CSS classes
|
### CSS classes
|
||||||
|
|
||||||
@ -103,7 +107,7 @@ The parameters are as follows:
|
|||||||
* **column-spec-object**: object providing the _menu_, _content_ and _preview_ elements (all fields mandatory)
|
* **column-spec-object**: object providing the _menu_, _content_ and _preview_ elements (all fields mandatory)
|
||||||
* **options-object**: object providing the configuration (all fields mandatory, see options below)
|
* **options-object**: object providing the configuration (all fields mandatory, see options below)
|
||||||
|
|
||||||
### Available options
|
### Layout options
|
||||||
|
|
||||||
* _minContentWidth_: minimum size for the content display as long as the preview is visible
|
* _minContentWidth_: minimum size for the content display as long as the preview is visible
|
||||||
* _minPreviewWidth_: preview will not be displayed below this size
|
* _minPreviewWidth_: preview will not be displayed below this size
|
||||||
@ -112,4 +116,5 @@ The parameters are as follows:
|
|||||||
## Related
|
## Related
|
||||||
|
|
||||||
* [Reference: CMS Architecture](../reference/cms-architecture)
|
* [Reference: CMS Architecture](../reference/cms-architecture)
|
||||||
|
* [Reference: Preview](../reference/preview)
|
||||||
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)
|
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)
|
||||||
|
104
docs/en/reference/preview.md
Normal file
104
docs/en/reference/preview.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# CMS preview
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
With the addition of side-by-side editing, the preview has the ability to appear within the CMS window when editing
|
||||||
|
content in the _Pages_ section of the CMS. The site is rendered into an iframe. It will update itself whenever the
|
||||||
|
content is saved, and relevant pages will be loaded for editing when the user navigates around in the preview.
|
||||||
|
|
||||||
|
The root element for preview is `.cms-preview` which maintains the internal states neccessary for rendering. It provides
|
||||||
|
function calls for transitioning between these states and has the ability to redraw the area.
|
||||||
|
|
||||||
|
In terms of backend support, it relies on `SilverStripeNavigator` to be rendered into the `.cms-edit-form`.
|
||||||
|
_LeftAndMain_ will automatically take care of generating it as long as the `*_SilverStripeNavigator` template is found -
|
||||||
|
first segment has to match current _LeftAndMain_-derived class (e.g. `LeftAndMain_SilverStripeNavigator`).
|
||||||
|
|
||||||
|
<div class="notice" markdown='1'>
|
||||||
|
Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only support SiteTree objects that are
|
||||||
|
_Versioned_. They are not general enough for using on any other DataObject. That pretty much limits the extendability
|
||||||
|
of the feature.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
If the `SilverStripeNavigator` structure is found, it is detached and installed in the `.cms-preview-control` panel at
|
||||||
|
the bottom of the preview, and the preview is enabled into _split_ mode.
|
||||||
|
|
||||||
|
We use `ss.preview` entwine namespace for all preview-related entwines.
|
||||||
|
|
||||||
|
## Preview states
|
||||||
|
|
||||||
|
States are the site stages: _live_, _stage_ etc. Preview states are picked up from the `SilverStripeNavigator`.
|
||||||
|
You can invoke the state change by calling:
|
||||||
|
|
||||||
|
```js
|
||||||
|
$('.cms-preview').entwine('.ss.preview').changeState('StageLink');
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the state names come from `SilverStripeNavigatorItems` class names - thus the _Link_ in their names. This call will
|
||||||
|
also redraw the state selector to fit with the internal state. See `AllowedStates` in `.cms-preview` entwine for the
|
||||||
|
list of supported states.
|
||||||
|
|
||||||
|
You can get the current state by calling:
|
||||||
|
|
||||||
|
```js
|
||||||
|
$('.cms-preview').entwine('.ss.preview').getCurrentStateName();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preview sizes
|
||||||
|
|
||||||
|
This selector defines how the preview iframe is rendered, and try to emulate different device sizes. The options are
|
||||||
|
hardcoded. The option names map directly to CSS classes applied to the `.cms-preview` and are as follows:
|
||||||
|
|
||||||
|
* _auto_: responsive layout
|
||||||
|
* _desktop_
|
||||||
|
* _tablet_
|
||||||
|
* _mobile_
|
||||||
|
|
||||||
|
You can switch between different types of display sizes programmatically, which has the benefit of redrawing the
|
||||||
|
related selector and maintaining a consistent internal state:
|
||||||
|
|
||||||
|
```js
|
||||||
|
$('.cms-preview').entwine('.ss.preview').changeSize('auto');
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find out current size by calling:
|
||||||
|
|
||||||
|
```js
|
||||||
|
$('.cms-preview').entwine('.ss.preview').getCurrentSizeName();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preview modes
|
||||||
|
|
||||||
|
Preview modes map to the modes supported by the _threeColumnCompressor_ layout algorithm, see
|
||||||
|
[layout reference](../reference/layout) for more details. You can change modes by calling:
|
||||||
|
|
||||||
|
```js
|
||||||
|
$('.cms-preview').entwine('.ss.preview').changeMode('preview');
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently active mode is stored on the `.cms-container` along with related internal states of the layout. You can reach
|
||||||
|
it by calling:
|
||||||
|
|
||||||
|
```js
|
||||||
|
$('.cms-container').entwine('.ss').getLayoutOptions().mode;
|
||||||
|
```
|
||||||
|
|
||||||
|
<div class="notice" markdown='1'>
|
||||||
|
Caveat: the `.preview-mode-selector` appears twice, once in the preview and second time in the CMS actions area as
|
||||||
|
`#preview-mode-dropdown-in-cms`. This is done because the user should still have access to the mode selector even if
|
||||||
|
preview is not visible. Currently CMS Actions are a separate area to the preview option selectors, even if they try
|
||||||
|
to appear as one horizontal bar.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Preview API
|
||||||
|
|
||||||
|
Namespace `ss.preview`, selector `.cms-preview`:
|
||||||
|
|
||||||
|
* **getCurrentStateName**: get the name of the current state (e.g. _LiveLink_ or _StageLink_).
|
||||||
|
* **getCurrentSizeName**: get the name of the current device size.
|
||||||
|
* **changeState**: one of the `AllowedStates`.
|
||||||
|
* **changeSize**: one of _auto_, _desktop_, _tablet_, _mobile_.
|
||||||
|
* **changeMode**: maps to _threeColumnLayout_ modes - _split_, _preview_, _content_.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
* [Reference: Layout](../reference/layout)
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<option data-icon="icon-split" class="icon-split icon-view first" value="split"><% _t('SilverStripeNavigator.SplitView', 'Split mode') %></option>
|
<option data-icon="icon-split" class="icon-split icon-view first" value="split"><% _t('SilverStripeNavigator.SplitView', 'Split mode') %></option>
|
||||||
<option data-icon="icon-preview" class="icon-preview icon-view" value="preview"><% _t('SilverStripeNavigator.PreviewView', 'Preview mode') %></option>
|
<option data-icon="icon-preview" class="icon-preview icon-view" value="preview"><% _t('SilverStripeNavigator.PreviewView', 'Preview mode') %></option>
|
||||||
<option data-icon="icon-edit" class="icon-edit icon-view" value="edit"><% _t('SilverStripeNavigator.EditView', 'Edit mode') %></option>
|
<option data-icon="icon-edit" class="icon-edit icon-view last" value="content"><% _t('SilverStripeNavigator.EditView', 'Edit mode') %></option>
|
||||||
<!-- Dual window not implemented yet -->
|
<!-- Dual window not implemented yet -->
|
||||||
<!--
|
<!--
|
||||||
<option data-icon="icon-window" class="icon-window icon-view last" value="window"><% _t('SilverStripeNavigator.DualWindowView', 'Dual Window') %></option>
|
<option data-icon="icon-window" class="icon-window icon-view last" value="window"><% _t('SilverStripeNavigator.DualWindowView', 'Dual Window') %></option>
|
||||||
|
Loading…
Reference in New Issue
Block a user