Commit d72c0f98 authored by webchick's avatar webchick

Issue #1510544 by swentel, Bojhan, Gábor Hojtsy, merlinofchaos, Cottser, Wim...

Issue #1510544 by swentel, Bojhan, Gábor Hojtsy, merlinofchaos, Cottser, Wim Leers, plopesc, aspilicious, sannejanssen,  larowlan, tim.plunkett, nod_: Fixed Show previews in front-end theme, able to select different view modes.
parent 7fde4cf6
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M7.951 7.645c-.193.196-.193.516 0 .71l3.258 3.29c.193.193.191.519-.002.709l-1.371 1.371c-.193.192-.512.191-.707 0l-5.335-5.371c-.194-.194-.194-.514 0-.708l5.335-5.369c.195-.195.514-.195.707-.001l1.371 1.371c.193.194.195.513.002.709l-3.258 3.289z"/></svg>
...@@ -35,6 +35,7 @@ class BlockConfigSchemaTest extends KernelTestBase { ...@@ -35,6 +35,7 @@ class BlockConfigSchemaTest extends KernelTestBase {
// BlockManager->getModuleName() calls system_get_info(). // BlockManager->getModuleName() calls system_get_info().
'system', 'system',
'taxonomy', 'taxonomy',
'user',
); );
/** /**
......
...@@ -87,6 +87,7 @@ function testNodeDisplay() { ...@@ -87,6 +87,7 @@ function testNodeDisplay() {
$edit[$field_name . '[0][display]'] = FALSE; $edit[$field_name . '[0][display]'] = FALSE;
$edit[$field_name . '[1][display]'] = FALSE; $edit[$field_name . '[1][display]'] = FALSE;
$this->drupalPostForm("node/$nid/edit", $edit, t('Preview')); $this->drupalPostForm("node/$nid/edit", $edit, t('Preview'));
$this->clickLink(t('Back to content editing'));
$this->assertRaw($field_name . '[0][display]', 'First file appears as expected.'); $this->assertRaw($field_name . '[0][display]', 'First file appears as expected.');
$this->assertRaw($field_name . '[1][display]', 'Second file appears as expected.'); $this->assertRaw($field_name . '[1][display]', 'Second file appears as expected.');
} }
......
...@@ -234,7 +234,7 @@ function testFormatWidgetPermissions() { ...@@ -234,7 +234,7 @@ function testFormatWidgetPermissions() {
$this->assertText($edit[$body_value_key], 'Old body found in preview.'); $this->assertText($edit[$body_value_key], 'Old body found in preview.');
// Save and verify that only the title was changed. // Save and verify that only the title was changed.
$this->drupalPostForm(NULL, $new_edit, t('Save')); $this->drupalPostForm('node/' . $node->id() . '/edit', $new_edit, t('Save'));
$this->assertNoText($edit['title[0][value]'], 'Old title not found.'); $this->assertNoText($edit['title[0][value]'], 'Old title not found.');
$this->assertText($new_edit['title[0][value]'], 'New title found.'); $this->assertText($new_edit['title[0][value]'], 'New title found.');
$this->assertText($edit[$body_value_key], 'Old body found.'); $this->assertText($edit[$body_value_key], 'Old body found.');
......
/**
* @file
* Styles for node preview page.
*/
.node-preview-container {
position: fixed;
z-index: 499;
width: 100%;
padding: 10px;
}
@media only screen and (min-width: 36em) {
.node-preview-container .form-type-select {
margin-left: 25%;
}
}
...@@ -10,12 +10,16 @@ drupal.node: ...@@ -10,12 +10,16 @@ drupal.node:
drupal.node.preview: drupal.node.preview:
version: VERSION version: VERSION
css:
theme:
css/node.preview.css: {}
js: js:
node.preview.js: {} node.preview.js: {}
dependencies: dependencies:
- core/jquery - core/jquery
- core/jquery.once - core/jquery.once
- core/drupal - core/drupal
- core/drupal.form
drupal.content_types: drupal.content_types:
version: VERSION version: VERSION
......
...@@ -169,11 +169,6 @@ function node_theme() { ...@@ -169,11 +169,6 @@ function node_theme() {
'file' => 'node.pages.inc', 'file' => 'node.pages.inc',
'template' => 'node-add-list', 'template' => 'node-add-list',
), ),
'node_preview' => array(
'variables' => array('node' => NULL),
'file' => 'node.pages.inc',
'template' => 'node-preview',
),
'node_edit_form' => array( 'node_edit_form' => array(
'render element' => 'form', 'render element' => 'form',
'template' => 'node-edit-form', 'template' => 'node-edit-form',
...@@ -627,7 +622,10 @@ function template_preprocess_node(&$variables) { ...@@ -627,7 +622,10 @@ function template_preprocess_node(&$variables) {
)); ));
$variables['label'] = $variables['elements']['title']; $variables['label'] = $variables['elements']['title'];
unset($variables['elements']['title']); unset($variables['elements']['title']);
$variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node); // The 'page' variable is set to TRUE in two occasions:
// - The view mode is 'full' and we are on the 'node.view' route.
// - The node is in preview and view mode is either 'full' or 'default'.
$variables['page'] = ($variables['view_mode'] == 'full' && (node_is_page($node)) || (isset($node->in_preview) && in_array($node->preview_view_mode, array('full', 'default'))));
// Helpful $content variable for templates. // Helpful $content variable for templates.
$variables += array('content' => array()); $variables += array('content' => array());
...@@ -669,7 +667,7 @@ function template_preprocess_node(&$variables) { ...@@ -669,7 +667,7 @@ function template_preprocess_node(&$variables) {
if ($variables['view_mode']) { if ($variables['view_mode']) {
$variables['attributes']['class'][] = drupal_html_class('node--view-mode-' . $variables['view_mode']); $variables['attributes']['class'][] = drupal_html_class('node--view-mode-' . $variables['view_mode']);
} }
if (isset($variables['preview'])) { if (isset($node->preview)) {
$variables['attributes']['class'][] = 'node--preview'; $variables['attributes']['class'][] = 'node--preview';
} }
} }
...@@ -1044,6 +1042,25 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) { ...@@ -1044,6 +1042,25 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) {
return entity_view_multiple($nodes, $view_mode, $langcode); return entity_view_multiple($nodes, $view_mode, $langcode);
} }
/**
* Implements hook_page_build().
*/
function node_page_build(&$page) {
// Add 'Back to content editing' link on preview page.
$route_match = \Drupal::routeMatch();
if ($route_match->getRouteName() == 'entity.node.preview') {
$page['page_top']['node_preview'] = array(
'#type' => 'container',
'#attributes' => array(
'class' => array('node-preview-container', 'container-inline')
),
);
$form = \Drupal::formBuilder()->getForm('\Drupal\node\Form\NodePreviewForm', $route_match->getParameter('node_preview'));
$page['page_top']['node_preview']['view_mode'] = $form;
}
}
/** /**
* Implements hook_form_FORM_ID_alter(). * Implements hook_form_FORM_ID_alter().
* *
......
...@@ -37,68 +37,3 @@ function template_preprocess_node_add_list(&$variables) { ...@@ -37,68 +37,3 @@ function template_preprocess_node_add_list(&$variables) {
} }
} }
} }
/**
* Generates a node preview.
*
* @param \Drupal\node\NodeInterface $node
* The node to preview.
*
* @return
* An HTML-formatted string of a node preview.
*
* @see node_form_build_preview()
*/
function node_preview(NodeInterface $node, FormStateInterface $form_state) {
if ($node->access('create') || $node->access('update')) {
$node->changed = REQUEST_TIME;
// Display a preview of the node.
if (!form_get_errors($form_state)) {
$node->in_preview = TRUE;
$node_preview = array(
'#theme' => 'node_preview',
'#node' => $node,
);
$output = drupal_render($node_preview);
unset($node->in_preview);
}
return $output;
}
}
/**
* Prepares variables for node preview templates.
*
* Default template: node-preview.html.twig.
*
* @param array $variables
* An associative array containing:
* - node: The node entity which is being previewed.
*
* @see NodeForm::preview()
* @see node_preview()
*/
function template_preprocess_node_preview(&$variables) {
$node = $variables['node'];
// Render trimmed teaser version of the post.
$node_teaser = node_view($node, 'teaser');
$node_teaser['#attached']['library'][] = 'node/drupal.node.preview';
$variables['teaser'] = $node_teaser;
// Render full version of the post.
$node_full = node_view($node, 'full');
$variables['full'] = $node_full;
// Display a preview of the teaser only if the content of the teaser is
// different to the full post.
if ($variables['teaser'] != $variables['full']) {
drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication.<span class="no-js"> You can insert the delimiter "&lt;!--break--&gt;" (without the quotes) to fine-tune where your post gets split.</span>'));
$variables['preview_teaser'] = TRUE;
}
else {
$variables['preview_teaser'] = FALSE;
}
}
...@@ -8,16 +8,16 @@ ...@@ -8,16 +8,16 @@
*/ */
Drupal.behaviors.nodePreviewDestroyLinks = { Drupal.behaviors.nodePreviewDestroyLinks = {
attach: function (context) { attach: function (context) {
var $preview = $(context).find('.node').once('node-preview'); var $preview = $(context).find('.page-node-preview').once('node-preview');
if ($preview.length) { if ($preview.length) {
$preview.on('click.preview', 'a:not([href^=#])', function (e) { $preview.on('click.preview', 'a:not([href^=#], #edit-backlink, #toolbar-administration a)', function (e) {
e.preventDefault(); e.preventDefault();
}); });
} }
}, },
detach: function (context, settings, trigger) { detach: function (context, settings, trigger) {
if (trigger === 'unload') { if (trigger === 'unload') {
var $preview = $(context).find('.node').removeOnce('node-preview'); var $preview = $(context).find('.page-node-preview').removeOnce('node-preview');
if ($preview.length) { if ($preview.length) {
$preview.off('click.preview'); $preview.off('click.preview');
} }
...@@ -25,4 +25,18 @@ ...@@ -25,4 +25,18 @@
} }
}; };
/**
* Switch view mode.
*/
Drupal.behaviors.nodePreviewSwitchViewMode = {
attach: function (context) {
var $autosubmit = $(context).find('[data-drupal-autosubmit]').once('autosubmit');
if ($autosubmit.length) {
$autosubmit.on('formUpdated.preview', function() {
$(this.form).trigger('submit');
});
}
}
};
})(jQuery, Drupal); })(jQuery, Drupal);
node.multiple_delete_confirm: node.multiple_delete_confirm:
path: '/admin/content/node/delete' path: '/admin/content/node/delete'
defaults: defaults:
...@@ -37,6 +36,18 @@ node.add: ...@@ -37,6 +36,18 @@ node.add:
options: options:
_node_operation_route: TRUE _node_operation_route: TRUE
entity.node.preview:
path: '/node/preview/{node_preview}/{view_mode_id}'
defaults:
_content: '\Drupal\node\Controller\NodePreviewController::view'
_title_callback: '\Drupal\node\Controller\NodePreviewController::title'
requirements:
_node_preview_access: '{node_preview}'
options:
parameters:
node_preview:
type: 'node_preview'
entity.node.canonical: entity.node.canonical:
path: '/node/{node}' path: '/node/{node}'
defaults: defaults:
......
...@@ -19,8 +19,18 @@ services: ...@@ -19,8 +19,18 @@ services:
arguments: ['@entity.manager'] arguments: ['@entity.manager']
tags: tags:
- { name: access_check, applies_to: _node_add_access } - { name: access_check, applies_to: _node_add_access }
access_check.node.preview:
class: Drupal\node\Access\NodePreviewAccessCheck
arguments: ['@entity.manager']
tags:
- { name: access_check, applies_to: _node_preview_access }
node.admin_path.route_subscriber: node.admin_path.route_subscriber:
class: Drupal\node\EventSubscriber\NodeAdminRouteSubscriber class: Drupal\node\EventSubscriber\NodeAdminRouteSubscriber
arguments: ['@config.factory'] arguments: ['@config.factory']
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
node_preview:
class: Drupal\node\ParamConverter\NodePreviewConverter
arguments: ['@user.tempstore']
tags:
- { name: paramconverter }
<?php
/**
* @file
* Contains \Drupal\node\Access\NodePreviewAccessCheck.
*/
namespace Drupal\node\Access;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
/**
* Determines access to node previews.
*/
class NodePreviewAccessCheck implements AccessInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a EntityCreateAccessCheck object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* Checks access to the node preview page.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
* @param \Drupal\node\NodeInterface $node_preview
* The node that is being previewed.
*
* @return string
* A \Drupal\Core\Access\AccessInterface constant value.
*/
public function access(AccountInterface $account, NodeInterface $node_preview) {
if ($node_preview->isNew()) {
$access_controller = $this->entityManager->getAccessControlHandler('node');
return $access_controller->createAccess($node_preview->bundle(), $account) ? static::ALLOW : static::DENY;
}
else {
return $node_preview->access('update', $account) ? static::ALLOW : static::DENY;
}
}
}
<?php
/**
* @file
* Contains \Drupal\node\Controller\NodePreviewController.
*/
namespace Drupal\node\Controller;
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Controller\EntityViewController;
/**
* Defines a controller to render a single node in preview.
*/
class NodePreviewController extends EntityViewController {
/**
* {@inheritdoc}
*/
public function view(EntityInterface $node_preview, $view_mode_id = 'full', $langcode = NULL) {
// Do not cache this page.
drupal_page_is_cacheable(FALSE);
$node_preview->preview_view_mode = $view_mode_id;
$build = array('nodes' => parent::view($node_preview, $view_mode_id));
$build['#attached']['library'][] = 'node/drupal.node.preview';
$build['#title'] = $build['nodes']['#title'];
unset($build['nodes']['#title']);
// Don't render cache previews.
unset($build['nodes']['#cache']);
foreach ($node_preview->uriRelationships() as $rel) {
// Set the node path as the canonical URL to prevent duplicate content.
$build['#attached']['drupal_add_html_head_link'][] = array(
array(
'rel' => $rel,
'href' => $node_preview->url($rel),
)
, TRUE);
if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink.
$build['#attached']['drupal_add_html_head_link'][] = array(
array(
'rel' => 'shortlink',
'href' => $node_preview->url($rel, array('alias' => TRUE)),
)
, TRUE);
}
}
return $build;
}
/**
* The _title_callback for the page that renders a single node in preview.
*
* @param \Drupal\Core\Entity\EntityInterface $node_preview
* The current node.
*
* @return string
* The page title.
*/
public function title(EntityInterface $node_preview) {
return String::checkPlain($this->entityManager->getTranslationFromContext($node_preview)->label());
}
}
<?php
/**
* @file
* Contains \Drupal\node\Form\NodePreviewForm.
*/
namespace Drupal\node\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Contains a form for switching the view mode of a node during preview.
*/
class NodePreviewForm extends FormBase implements ContainerInjectionInterface {
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity.manager'), $container->get('config.factory'));
}
/**
* Constructs a new NodePreviewForm.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
*/
public function __construct(EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory) {
$this->entityManager = $entity_manager;
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'node_preview_form_select';
}
/**
* Form constructor.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param \Drupal\Core\Entity\EntityInterface $node
* The node being previews
*
* @return array
* The form structure.
*/
public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $node = NULL) {
$view_mode = $node->preview_view_mode;
$query_options = $node->isNew() ? array('query' => array('uuid' => $node->uuid())) : array();
$form['backlink'] = array(
'#type' => 'link',
'#title' => $this->t('Back to content editing'),
'#href' => $node->isNew() ? 'node/add/' . $node->bundle() : 'node/' . $node->id() . '/edit',
'#options' => array('attributes' => array('class' => array('node-preview-backlink'))) + $query_options,
);
$view_mode_options = $this->getViewModeOptions($node);
$form['uuid'] = array(
'#type' => 'value',
'#value' => $node->uuid(),
);
$form['view_mode'] = array(
'#type' => 'select',
'#title' => $this->t('View mode'),
'#options' => $view_mode_options,
'#default_value' => $view_mode,
'#attributes' => array(
'data-drupal-autosubmit' => TRUE,
)
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Switch'),
'#attributes' => array(
'class' => array('js-hide'),
),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->setRedirect('entity.node.preview', array(
'node_preview' => $form_state['values']['uuid'],
'view_mode_id' => $form_state['values']['view_mode'],
));
}
/**
* Retrieves the list of available view modes for the current node.
*
* @param EntityInterface $node
* The node being previewed.
*
* @return array
* List of available view modes for the current node.
*/
protected function getViewModeOptions(EntityInterface $node) {
$load_ids = array();
$view_mode_options = array();
// Load all the node's view modes.
$view_modes = $this->entityManager->getViewModes('node');
// Get the list of available view modes for the current node's bundle.
$ids = $this->configFactory->listAll('entity.view_display.node.' . $node->bundle());
foreach ($ids as $id) {
$config_id = str_replace('entity.view_display' . '.', '', $id);
$load_ids[] = $config_id;
}
$displays = entity_load_multiple('entity_view_display', $load_ids);