diff --git a/core/modules/node/css/node.preview.css b/core/modules/node/css/node.preview.css new file mode 100644 index 0000000..b648db7 --- /dev/null +++ b/core/modules/node/css/node.preview.css @@ -0,0 +1,21 @@ +/** + * @file + * Styles for node preview page. + */ + +.node-preview-container { + position: fixed; + z-index: 499; + width: 100%; + padding: 10px; +} +.node-preview-container .form-type-select { + margin-left: 25%; +} +.node-preview-backlink::before { + content: '<'; + display: inline-block; + width: 10px; + height: 10px; + color: #000; +} diff --git a/core/modules/node/node.libraries.yml b/core/modules/node/node.libraries.yml index ab1f935..42c0bb0 100644 --- a/core/modules/node/node.libraries.yml +++ b/core/modules/node/node.libraries.yml @@ -10,6 +10,9 @@ drupal.node: drupal.node.preview: version: VERSION + css: + theme: + css/node.preview.css: {} js: node.preview.js: {} dependencies: diff --git a/core/modules/node/node.module b/core/modules/node/node.module index d4f7db0..48d55b7 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -170,11 +170,6 @@ function node_theme() { 'file' => 'node.pages.inc', 'template' => 'node-add-list', ), - 'node_preview' => array( - 'variables' => array('node' => NULL), - 'file' => 'node.pages.inc', - 'template' => 'node-preview', - ), 'node_edit_form' => array( 'render element' => 'form', 'template' => 'node-edit-form', @@ -643,7 +638,7 @@ function template_preprocess_node(&$variables) { )); $variables['label'] = $variables['elements']['title']; unset($variables['elements']['title']); - $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node); + $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. $variables += array('content' => array()); @@ -685,7 +680,7 @@ function template_preprocess_node(&$variables) { if ($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'; } } @@ -1128,6 +1123,76 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) { } /** + * Implements hook_page_build(). + */ +function node_page_build(&$page) { + // Add 'Back to content edit editing' link on preview page. + $node = \Drupal::request()->attributes->get('node_preview'); + if ($node) { + $page['page_top']['node-preview'] = array( + '#type' => 'container', + '#attributes' => array( + 'class' => array('node-preview-container', 'container-inline') + ), + ); + + $form = drupal_get_form('node_preview_form_select', $node); + $page['page_top']['node-preview']['view-mode'] = $form; + } +} + +/** + * Get the preview form selection box. + */ +function node_preview_form_select(array $form, array $form_state, EntityInterface $node) { + + $view_mode = $node->preview_view_mode; + + $query_options = !$node->id() ? array('query' => array('uuid' => $node->uuid())) : array(); + $form['backlink'] = array( + '#type' => 'link', + '#title' => t('Back to content editing'), + '#href' => $node->id() ? 'node/' . $node->id() . '/edit' : 'node/add/' . $node->bundle(), + '#options' => array('attributes' => array('class' => array('node-preview-backlink button-action'))) + $query_options, + ); + + $view_mode_options = \Drupal::entityManager()->getViewModeOptions('node'); + foreach ($view_mode_options as $key => $value) { + $view_mode_options[$key] = t('@value view mode', array('@value' => $value)); + } + + $form['uuid'] = array( + '#type' => 'value', + '#value' => $node->uuid(), + ); + + $form['view_mode'] = array( + '#type' => 'select', + '#title' => t('Select a view mode'), + '#title_display' => 'invisible', + '#options' => $view_mode_options, + '#default_value' => $view_mode, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Switch'), + '#attributes' => array( + 'class' => array('node-preview-switch-button') + ) + ); + + return $form; +} + +/** + * Submit handler for the node preview view mode selection form. + */ +function node_preview_form_select_submit(array $form, array &$form_state) { + $form_state['redirect'] = array('node/preview/' . $form_state['values']['uuid'] . '/' . $form_state['values']['view_mode']); +} + +/** * Implements hook_form_FORM_ID_alter(). * * Alters the System module's site information settings form to add a global diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index 9757596..82431e2 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -36,68 +36,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, array &$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. You can insert the delimiter "<!--break-->" (without the quotes) to fine-tune where your post gets split.')); - $variables['preview_teaser'] = TRUE; - } - else { - $variables['preview_teaser'] = FALSE; - } -} diff --git a/core/modules/node/node.preview.js b/core/modules/node/node.preview.js index f27cbba..8f5755d 100644 --- a/core/modules/node/node.preview.js +++ b/core/modules/node/node.preview.js @@ -8,16 +8,16 @@ */ Drupal.behaviors.nodePreviewDestroyLinks = { attach: function (context) { - var $preview = $(context).find('.node').once('node-preview'); + var $preview = $(context).find('.page-node-preview').once('node-preview'); if ($preview.length) { - $preview.on('click.preview', 'a:not([href^=#])', function (e) { + $preview.on('click.preview', 'a:not([href^=#], #edit-backlink)', function (e) { e.preventDefault(); }); } }, detach: function (context, settings, trigger) { if (trigger === 'unload') { - var $preview = $(context).find('.node').removeOnce('node-preview'); + var $preview = $(context).find('.page-node-preview').removeOnce('node-preview'); if ($preview.length) { $preview.off('click.preview'); } @@ -25,4 +25,16 @@ } }; + /** + * Switch view mode. + */ + Drupal.behaviors.nodePreviewSwitchViewMode = { + attach: function (context) { + $('.node-preview-switch-button').hide(); + $('#edit-view-mode').change(function() { + $(this).closest('form').trigger('submit'); + }); + } + }; + })(jQuery, Drupal); diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml index 86278f2..bdfbf9f 100644 --- a/core/modules/node/node.routing.yml +++ b/core/modules/node/node.routing.yml @@ -37,6 +37,19 @@ node.add: options: _node_operation_route: TRUE +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_access: 'node.view' + node.view: path: '/node/{node}' defaults: diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml index 69cadb2..37e1e74 100644 --- a/core/modules/node/node.services.yml +++ b/core/modules/node/node.services.yml @@ -16,8 +16,18 @@ services: arguments: ['@entity.manager'] tags: - { 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: class: Drupal\node\EventSubscriber\NodeAdminRouteSubscriber arguments: ['@config.factory'] tags: - { name: event_subscriber } + node_preview: + class: Drupal\node\ParamConverter\NodepreviewConverter + arguments: ['@user.tempstore'] + tags: + - { name: paramconverter } diff --git a/core/modules/node/src/Access/NodePreviewAccessCheck.php b/core/modules/node/src/Access/NodePreviewAccessCheck.php new file mode 100644 index 0000000..cf23cb7 --- /dev/null +++ b/core/modules/node/src/Access/NodePreviewAccessCheck.php @@ -0,0 +1,53 @@ +entityManager = $entity_manager; + } + + /** + * Checks access to the node preview page for the node type. + * + * @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) { + // TODO + return static::ALLOW; + } + +} diff --git a/core/modules/node/src/Controller/NodePreviewController.php b/core/modules/node/src/Controller/NodePreviewController.php new file mode 100644 index 0000000..276b05b --- /dev/null +++ b/core/modules/node/src/Controller/NodePreviewController.php @@ -0,0 +1,71 @@ +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']); + + // Do not use render cache for preview. + 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()); + } + +} diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php index 03ed3da..e61d83d 100644 --- a/core/modules/node/src/NodeForm.php +++ b/core/modules/node/src/NodeForm.php @@ -7,12 +7,13 @@ namespace Drupal\node; -use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Language\LanguageInterface; -use Drupal\Component\Utility\String; +use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\user\TempStoreFactory; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Form controller for the node edit forms. @@ -27,6 +28,36 @@ class NodeForm extends ContentEntityForm { protected $settings; /** + * The tempstore factory. + * + * @var \Drupal\user\TempStoreFactory + */ + protected $tempStoreFactory; + + /** + * Constructs a ContentEntityForm object. + * + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. + * @param \Drupal\user\TempStoreFactory $temp_store_factory + * The factory for the temp store object. + */ + public function __construct(EntityManagerInterface $entity_manager, TempStoreFactory $temp_store_factory) { + parent::__construct($entity_manager); + $this->tempStoreFactory = $temp_store_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager'), + $container->get('user.tempstore') + ); + } + + /** * {@inheritdoc} */ protected function prepareEntity() { @@ -47,6 +78,22 @@ protected function prepareEntity() { * Overrides Drupal\Core\Entity\EntityForm::form(). */ public function form(array $form, array &$form_state) { + + // Restore from preview. + $uuid = $this->entity->uuid(); + $store = $this->tempStoreFactory->get('node_preview'); + if ($request_uuid = \Drupal::request()->query->get('uuid')) { + $uuid = $request_uuid; + } + if ($preview = $store->get($uuid)) { + $form_state['values'] = $preview['values']; + $form_state['input'] = $preview['input']; + $form_state['rebuild'] = TRUE; + $this->entity = $preview['controller']->getEntity(); + unset($this->entity->in_preview); + $store->delete($uuid); + } + /** @var \Drupal\node\NodeInterface $node */ $node = $this->entity; @@ -55,15 +102,6 @@ public function form(array $form, array &$form_state) { } $user_config = \Drupal::config('user.settings'); - // Some special stuff when previewing a node. - if (isset($form_state['node_preview'])) { - $form['#prefix'] = $form_state['node_preview']; - $node->in_preview = TRUE; - $form['#title'] = $this->t('Preview'); - } - else { - unset($node->in_preview); - } // Override the default CSS class name, since the user-defined node type // name in 'TYPE-node-form' potentially clashes with third-party class @@ -355,6 +393,10 @@ public function submit(array $form, array &$form_state) { $function($node, $form, $form_state); } + // Remove + $store = $this->tempStoreFactory->get('node_preview'); + $store->delete($node->uuid()); + return $node; } @@ -367,11 +409,10 @@ public function submit(array $form, array &$form_state) { * A reference to a keyed array containing the current state of the form. */ public function preview(array $form, array &$form_state) { - // @todo Remove this: we should not have explicit includes in autoloaded - // classes. - module_load_include('inc', 'node', 'node.pages'); - $form_state['node_preview'] = node_preview($this->entity, $form_state); - $form_state['rebuild'] = TRUE; + $store = $this->tempStoreFactory->get('node_preview'); + $this->entity->in_preview = TRUE; + $store->set($this->entity->uuid(), $form_state); + $form_state['redirect'] = 'node/preview/' . $this->entity->uuid() . '/default'; } /** diff --git a/core/modules/node/src/ParamConverter/NodepreviewConverter.php b/core/modules/node/src/ParamConverter/NodepreviewConverter.php new file mode 100644 index 0000000..6c838b3 --- /dev/null +++ b/core/modules/node/src/ParamConverter/NodepreviewConverter.php @@ -0,0 +1,58 @@ +tempStoreFactory = $temp_store_factory; + } + + /** + * {@inheritdoc} + */ + public function convert($value, $definition, $name, array $defaults, Request $request) { + $store = $this->tempStoreFactory->get('node_preview'); + if ($form_state = $store->get($value)) { + return $form_state['controller']->getEntity(); + } + } + + /** + * {@inheritdoc} + */ + public function applies($definition, $name, Route $route) { + if (!empty($definition['type']) && $definition['type'] == 'node_preview') { + return TRUE; + } + return FALSE; + } + +} diff --git a/core/modules/node/templates/node-preview.html.twig b/core/modules/node/templates/node-preview.html.twig deleted file mode 100644 index 1bc3441..0000000 --- a/core/modules/node/templates/node-preview.html.twig +++ /dev/null @@ -1,23 +0,0 @@ -{# -/** - * @file - * Default theme implementation for a node preview. - * - * This display may be used during node creation and editing. - * - * Available variables: - * - preview_teaser: Flag indicating whether to show a trimmed teaser version. - * - teaser: Trimmed teaser version of the node. - * - full: Full version of the node. - * - * @see template_preprocess_node_preview() - * - * @ingroup themeable - */ -#} -{% if preview_teaser %} -

{{ "Preview trimmed version"|t }}

- {{ teaser }} -

{{ "Preview full version"|t }}

-{% endif %} -{{ full }} diff --git a/core/modules/node/templates/node.html.twig b/core/modules/node/templates/node.html.twig index c80eecb..a08b064 100644 --- a/core/modules/node/templates/node.html.twig +++ b/core/modules/node/templates/node.html.twig @@ -39,7 +39,6 @@ * - node--view-mode-[view_mode]: The View Mode of the node; for example, a * teaser would result in: "node--view-mode-teaser", and * full: "node--view-mode-full". - * - node--preview: Whether a node is in preview mode. * The following are controlled through the node publishing options. * - node--promoted: Appears on nodes promoted to the front page. * - node--sticky: Appears on nodes ordered above other non-sticky nodes in diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css index 4fca3de..9544340 100644 --- a/core/modules/system/css/system.theme.css +++ b/core/modules/system/css/system.theme.css @@ -9,9 +9,6 @@ .node--unpublished { background-color: #fff4f4; } -.node--preview { - background-color: #ffffea; -} /** * Markup generated by theme_tablesort_indicator(). diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css index 119ef78..07bea82 100644 --- a/core/themes/bartik/css/style.css +++ b/core/themes/bartik/css/style.css @@ -847,6 +847,59 @@ ul.links { border-left: 1px solid #fff4f4; border-right: 1px solid #fff4f4; } +.node-preview-container { + background: #d1e8f5; + background-image: -webkit-linear-gradient(top, #d1e8f5, #d3e8f4); + background-image: -moz-linear-gradient(top, #d1e8f5, #d3e8f4); + background-image: -o-linear-gradient(top, #d1e8f5, #d3e8f4); + background-image: linear-gradient(to bottom, #d1e8f5, #d3e8f4); + -webkit-box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.3333); + font-family: "Source Sans Pro", "Lucida Grande", Verdana, sans-serif; + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.3333); + position: fixed; + z-index: 499; + width: 100%; + padding: 10px; +} +.node-preview-backlink { + background: #419ff1; + background-image: -webkit-linear-gradient(top, #419ff1, #1076d5); + background-image: -moz-linear-gradient(top, #419ff1, #1076d5); + background-image: -o-linear-gradient(top, #419ff1, #1076d5); + background-image: linear-gradient(to bottom, #419ff1, #1076d5); + border: 1px solid #0048c8; + border-radius: .4em; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4); + color: #fff; + font-size: 0.9em; + line-height: normal; + margin: 0; + padding: 4px 1em 4px 0.6em; + text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5); +} +.node-preview-backlink:focus, +.node-preview-backlink:hover { + background-color: #419cf1; + background-image: -webkit-linear-gradient(top, #59abf3, #2a90ef); + background-image: -moz-linear-gradient(top, #59abf3, #2a90ef); + background-image: -o-linear-gradient(top, #59abf3, #2a90ef); + background-image: linear-gradient(to bottom, #59abf3, #2a90ef); + border: 1px solid #0048c8; + text-decoration: none; + color: #fff; +} +.node-preview-backlink:active { + background-color: #0e69be; + background-image: -webkit-linear-gradient(top, #0e69be, #2a93ef); + background-image: -moz-linear-gradient(top, #0e69be, #2a93ef); + background-image: -o-linear-gradient(top, #0e69be, #2a93ef); + background-image: -ms-linear-gradient(top, #0e69be, #2a93ef); + background-image: linear-gradient(to bottom, #0e69be, #2a93ef); + border: 1px solid #0048c8; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .25); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .25); +} /* ----------------- Comments ----------------- */