Build a blog with Drupal 8: Part 3

By Ronald van Belzen | March 25, 2016

I installed Bootstrap 3 for Drupal as the theme for my blog, but I want to change and add some minor things to that theme. For that purpose Drupal 8 offers the sub-theming mechanism. A sub-theme is a theme based upon another theme. Because I will base my sub-theme upon Bootstrap, I will call my sub-theme "My Bootstrap".

My Bootstrap sub-theme

In the directory "themes", in which I had already installed the Bootstrap theme, I create a new subdirectory "themes\mybootstrap". In that subdirectory I create a new empty file "mybootstrap.theme" and a file "mybootstrap.info.yml" with the following content:

name: 'My Bootstrap'
type: theme
description: 'My adaptation of the Bootstrap 3 theme.'
core: 8.x
base theme: bootstrap

regions:
  navigation: 'Navigation'
  navigation_collapsible: 'Navigation (Collapsible)'
  header: 'Top Bar'
  highlighted: 'Highlighted'
  help: 'Help'
  content: 'Content'
  sidebar_first: 'Primary'
  sidebar_second: 'Secondary'
  footer: 'Footer'
  page_top: 'Page top'
  page_bottom: 'Page bottom'

The line "base theme: bootstrap" tells Drupal that this theme is a sub-theme of bootstrap. The regions contain exactly the same regions as the Bootstrap theme on which it is based. This sub-theme is now an exact copy of Bootstrap.

The first thing I want to change is to add some CSS files to the sub-theme. For this I downloaded and saved the fontawesome toolkit to sub-theme subdirectory and in the new subdirectory "themes\mybootstrap\css" I placed a new empty file "mybootstrap.css" for later use.

To make fontawesome and my (still empty) css file available to the sub-theme I add the following to the "mybootstrap.info.yml" file:

libraries:
  - 'mybootstrap/font-awesome'
  - 'mybootstrap/mybootstrap-theme'

The locations of these libraries are defined in the file "mybootstrap.libraries.yml" in the subdirectory "themes\mybootstrap":

font-awesome:
  css:
    theme:
      css/font-awesome.css: {}
mybootstrap-theme:
  css:
    theme:
      css/mybootstrap.css: {}

We are going to use these css files to facilitate some of the changes in the sub-theme.

RSS feed link

The first thing I want to change is the way that the Bootstrap theme displays the RSS feed links. I want to change the text of the link into an icon. For this we need to find were this link is created and override the template by a template of the sub-theme. That will be the template file "feed-icon.html.twig" that needs to be places in the subdirectory "themes\mybootstrap\templates\system" in accordance to the location of this template in the core ("core\modules\system\templates"). The content of this file will be:

{#
/**
 * @file
 * Default theme implementation for a feed icon.
 *
 * Available variables:
 * - url: An internal system path or a fully qualified external URL of the feed.
 * - attributes: Remaining HTML attributes for the feed link.
 *   - title: A descriptive title of the feed link.
 *   - class: HTML classes to be applied to the feed link.
 *
 * @ingroup themeable
 */
#}
{% if title is empty %}
    <a href="{{ url }}"{{ attributes.addClass('feed-icon') }} title="RSS">
{% else %}
    <a href="{{ url }}"{{ attributes.addClass('feed-icon') }} title="{{ 'Subscribe to @title'|t({'@title': title}) }}">
{% endif %}
    <span class="fa-stack fa-lg">
        <i class="fa fa-square-o fa-stack-2x"></i>
        <i class="fa fa-rss fa-stack-1x"></i>
    </span> RSS
</a>

The classes fa-stack, fa-lg, etc. are fontawesome classes that create a rss icon in the display.

Comment links

Next I want to change the way how the content links (Edit/Delete/Reply) are displayed. For this we need to override the "core\modules\system\templates\links.html.twig", but only for comments. For this I create the file "links-comment.html.twig" in the subdirectory "themes\mybootstrap\templates\system" with the following content:

{#
/**
 * @file
 * Theme override for a set of links.
 *
 * Available variables:
 * - attributes: Attributes for the UL containing the list of links.
 * - links: Links to be output.
 *   Each link will have the following elements:
 *   - title: The link text.
 *   - href: The link URL. If omitted, the 'title' is shown as a plain text
 *     item in the links list. If 'href' is supplied, the entire link is passed
 *     to l() as its $options parameter.
 *   - attributes: (optional) HTML attributes for the anchor, or for the <span>
 *     tag if no 'href' is supplied.
 *   - link_key: The link CSS class.
 * - heading: (optional) A heading to precede the links.
 *   - text: The heading text.
 *   - level: The heading level (e.g. 'h2', 'h3').
 *   - attributes: (optional) A keyed list of attributes for the heading.
 *   If the heading is a string, it will be used as the text of the heading and
 *   the level will default to 'h2'.
 *
 *   Headings should be used on navigation menus and any list of links that
 *   consistently appears on multiple pages. To make the heading invisible use
 *   the 'visually-hidden' CSS class. Do not use 'display:none', which
 *   removes it from screen readers and assistive technology. Headings allow
 *   screen reader and keyboard only users to navigate to or skip the links.
 *   See http://juicystudio.com/article/screen-readers-display-none.php and
 *   http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
 *
 * @see template_preprocess_links()
 */
#}
{% if links -%}
  {%- if heading -%}
    {%- if heading.level -%}
      <{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}</{{ heading.level }}>
    {%- else -%}
      <h2{{ heading.attributes }}>{{ heading.text }}</h2>
    {%- endif -%}
  {%- endif -%}
  {%- if attributes.hasClass('inline') -%}
  <ul{{ attributes.addClass('list-inline') }}>
  {%- else -%}
  <ul{{ attributes }}>
  {%- endif -%}
    {%- for key, item in links -%}
      <li{{ item.attributes.addClass(key|clean_class) }}>
        {%- if item.link -%}
          <span class="btn btn-default btn-sm">{{ item.link }}</span>
        {%- elseif item.text_attributes -%}
          <span{{ item.text_attributes }}>{{ item.text }}</span>
        {%- else -%}
          {{ item.text }}
        {%- endif -%}
      </li>
    {%- endfor -%}
  </ul>
{%- endif %}

The classes btn, btn-default and btn-sm are bootstrap 3 classes.

Reference links

The content type Article comes with the reference fields for Tags, Topics and "Further reading". I want to change the look of these fields. For that I need to override the bootstrap theme template "field.html.twig" and I therefore copied this file to the mybootstrap theme (to subdirectory "themes\mybootstrap\templates\field") and made some changes to it:

{#
/**
 * @file
 * Theme override for a field.
 *
 * To override output, copy the "field.html.twig" from the templates directory
 * to your theme's directory and customize it, just like customizing other
 * Drupal templates such as page.html.twig or node.html.twig.
 *
 * Instead of overriding the theming for all fields, you can also just override
 * theming for a subset of fields using
 * @link themeable Theme hook suggestions. @endlink For example,
 * here are some theme hook suggestions that can be used for a field_foo field
 * on an article node type:
 * - field--node--field-foo--article.html.twig
 * - field--node--field-foo.html.twig
 * - field--node--article.html.twig
 * - field--field-foo.html.twig
 * - field--text-with-summary.html.twig
 * - field.html.twig
 *
 * Available variables:
 * - attributes: HTML attributes for the containing element.
 * - label_hidden: Whether to show the field label or not.
 * - title_attributes: HTML attributes for the title.
 * - label: The label for the field.
 * - multiple: TRUE if a field can contain multiple items.
 * - items: List of all the field items. Each item contains:
 *   - attributes: List of HTML attributes for each item.
 *   - content: The field item's content.
 * - entity_type: The entity type to which the field belongs.
 * - field_name: The name of the field.
 * - field_type: The type of the field.
 * - label_display: The display settings for the label.
 *
 *
 * @see template_preprocess_field()
 */
#}
{%
  set classes = [
    'field',
    'field--name-' ~ field_name|clean_class,
    'field--type-' ~ field_type|clean_class,
    'field--label-' ~ label_display,
  ]
%}
{%
  set title_classes = [
    'field--label',
    label_display == 'visually_hidden' ? 'sr-only',
  ]
%}
{%
	set button_classes = ['btn','btn-info','btn-sm','field--item']
%}


{% if label_hidden %}
  {% if multiple %}
    <div{{ attributes.addClass(classes, 'field--items') }}>
      {% for item in items %}
		<div{% if field_type == 'entity_reference' %} 
			{{ item.attributes.addClass(button_classes) }}
		{% else %} 
			{{ item.attributes.addClass('field--item') }} 
		{% endif%}>{{ item.content }}</div>
      {% endfor %}
    </div>
  {% else %}
    {% for item in items %}
      <div{{ attributes.addClass(classes, 'field--item') }}>{{ item.content }}</div>
    {% endfor %}
  {% endif %}
{% else %}
  <div{{ attributes.addClass(classes) }}>
    <div{{ title_attributes.addClass(title_classes) }}>{{ label }}</div>
    {% if multiple %}
      <div class="field__items">
    {% endif %}
    {% for item in items %}
		<div{% if field_type == 'entity_reference' %} 
			{{ item.attributes.addClass(button_classes) }}
		{% else %} 
			{{ item.attributes.addClass('field--item') }} 
		{% endif%}>{{ item.content }}</div>
    {% endfor %}
    {% if multiple %}
      </div>
    {% endif %}
  </div>
{% endif %}

In the file I added the variable "button_classes" and in the for-loop for the display of the links I added a test for the field_type, and in the case of an entity_reference I added the bootstrap button classes together with the field--item class.

But, because I did use the bootstrap class btn-info, which creates a darker background I need to change the link txt color from black to white to make it better readable. For this I added the following to my still empty "mybootstrap.css" file:

div.btn.btn-info.field--item > a {
    color: #fff;
    font-weight: bold;
}

Blog post author line

The next thing I want to change is the line of a blog post that shows the author and the date and time of posting the blog. The file that produces that information is "node.html.twig" that is also present in the bootstrap theme and for that reason I copy this file to the location "themes\mybootstrap\templates\node" and in that file I just change the following line:

{% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %}

I replace it with the following:

<strong>By {{ author_name }} | {{ node.created.value|date("F j, Y") }}</strong>

The {% trans %} is removed because it interferes with the filtering of the date.

Comments

The last thing I want to change is the way that the comments are displayed, which includes the order in which the different elements of a comment are displayed. I added a hook to the "mybootstrap.module" that changed the display of some fields:

<?php 
use Drupal\Core\Url;

function mybootstrap_preprocess_comment(&$variables) {
  $comment = $variables['elements']['#comment'];

  if (isset($comment->in_preview)) {
    $uri = new Url('<front>');
    $attributes = array('class' => array('btn', 'btn-default', 'btn-sm', 'align-right', 'permalink'), 'rel' => 'bookmark', 'title' => 'Permalink');
    $uri->setOption('attributes', $attributes);
    $variables['permalink'] = \Drupal::l(t('<i class="fa fa-link"></i>'), $uri);
  } else {
    $uri = $comment->permalink();
    $attributes = $uri->getOption('attributes') ?: array();
    $attributes += array('class' => array('btn', 'btn-default', 'btn-sm', 'align-right', 'permalink'), 'rel' => 'bookmark', 'title' => 'Permalink');
    $uri->setOption('attributes', $attributes);
    $variables['permalink'] = \Drupal::l(t('<i class="fa fa-link"></i>'), $uri);
  }
	
  $account = $comment->getOwner();
  $username = array(
    '#theme' => 'username',
    '#account' => $account,
  );
  $variables['author'] = drupal_render($username);
  $variables['created'] = format_date($comment->getCreatedTime());
  $variables['submitted'] = t('<strong>@username</strong> wrote on @datetime', array('@username' => $variables['author'], '@datetime' => $variables['created']));
}

The first thing that is changed is the display of the permalink, that was displayed as a text and now will be displayed as a bootstrap button containing a fontawesome icon. The submitted variable is slightly changed too.

Next I change the "comment.html.twig" file, that I copy from the bootstrap theme into the mybootstrap theme (at themes\mybootstrap\templates\comment) quite a bit:

{#
/**
 * @file
 * Default theme implementation for comments.
 *
 * Available variables:
 * - author: Comment author. Can be a link or plain text.
 * - content: The content-related items for the comment display. Use
 *   {{ content }} to print them all, or print a subset such as
 *   {{ content.field_example }}. Use the following code to temporarily suppress
 *   the printing of a given child element:
 *   @code
 *   {{ content|without('field_example') }}
 *   @endcode
 * - created: Formatted date and time for when the comment was created.
 *   Preprocess functions can reformat it by calling format_date() with the
 *   desired parameters on the 'comment.created' variable.
 * - changed: Formatted date and time for when the comment was last changed.
 *   Preprocess functions can reformat it by calling format_date() with the
 *   desired parameters on the 'comment.changed' variable.
 * - permalink: Comment permalink.
 * - submitted: Submission information created from author and created
 *   during template_preprocess_comment().
 * - user_picture: The comment author`s profile picture.
 * - status: Comment status. Possible values are:
 *   unpublished, published, or preview.
 * - title: Comment title, linked to the comment.
 * - attributes: HTML attributes for the containing element.
 *   The attributes.class may contain one or more of the following classes:
 *   - comment: The current template type; for instance, 'theming hook'.
 *   - by-anonymous: Comment by an unregistered user.
 *   - by-{entity-type}-author: Comment by the author of the parent entity,
 *     eg. by-node-author.
 *   - preview: When previewing a new or edited comment.
 *   The following applies only to viewers who are registered users:
 *   - unpublished: An unpublished comment visible only to administrators.
 * - title_prefix: Additional output populated by modules, intended to be
 *   displayed in front of the main title tag that appears in the template.
 * - title_suffix: Additional output populated by modules, intended to be
 *   displayed after the main title tag that appears in the template.
 * - content_attributes: List of classes for the styling of the comment content.
 * - title_attributes: Same as attributes, except applied to the main title
 *   tag that appears in the template.
 * - threaded: A flag indicating whether the comments are threaded or not.
 *
 * These variables are provided to give context about the parent comment (if
 * any):
 * - comment_parent: Full parent comment entity (if any).
 * - parent_author: Equivalent to author for the parent comment.
 * - parent_created: Equivalent to created for the parent comment.
 * - parent_changed: Equivalent to changed for the parent comment.
 * - parent_title: Equivalent to title for the parent comment.
 * - parent_permalink: Equivalent to permalink for the parent comment.
 * - parent: A text string of parent comment submission information created from
 *   parent_author and parent_created during template_preprocess_comment().
 *   This information is presented to help screen readers follow lengthy
 *   discussion threads. You can hide this from sighted users using the class
 *   visually-hidden.
 *
 * These two variables are provided for context:
 * - comment: Full comment object.
 * - entity: Entity the comments are attached to.
 *
 * @see template_preprocess_comment()
 *
 * @ingroup themeable
 */
#}

<article{{ attributes.addClass('js-comment') }}>
  {#
    Hide the "new" indicator by default, let a piece of JavaScript ask the
    server which comments are new for the user. Rendering the final "new"
    indicator here would break the render cache.
  #}

  <footer>
    {#
      Indicate the semantic relationship between parent and child comments for
      accessibility. The list is difficult to navigate in a screen reader
      without this information.
    #}
    {% if parent %}
      <p class="visually-hidden">{{ parent }}</p>
    {% endif %}
  </footer>

  <div{{ content_attributes }}>
    {% if title %}
      {{ title_prefix }}
	  <mark class="hidden btn btn-info btn-sm align-right" data-comment-timestamp="{{ new_indicator_timestamp }}"></mark>
	  {{ permalink }}
      <h4{{ title_attributes }}>{{ title }}</h4> 
      {{ title_suffix }}
    {% endif %}
    {{ user_picture }}
    <p>{{ submitted }}</p>
    <div class="well well-sm">
      {{ content.comment_body }}
    </div>
    {{ content|without('comment_body') }}
  </div>
</article>

Noteworthy is that I put the comment body inside a bootstrap 3 well, and for this I isolated the body of the comment from the rest of the comment by using "content.comment_body" and "content|without('comment_body')" to display everything of the comment excluding the comment body.

Next to that I turned the <mark> element into a bootstrap button.

The last change was made to the (already mentioned) "node.html.twig", in which the links display is repositioned. The bootstrap theme shows the links (that include the "Add new comment") below the add comment form. This was change by replacing:

  <div{{ content_attributes.addClass('content') }}>
    {{ content }}
  </div>

By:

  <div{{ content_attributes.addClass('content') }}>
     {{ content.field_image }}
     {{ content.body }}
     <p>{{ content.links }}<p>
     {{ content|without('field_image','body','links') }}
  </div>

 

Add new comment