Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 2.0.x
  • 8.x-1.x
  • 2.0.0
  • 2.0.0-beta1
  • 8.x-1.0
  • 8.x-1.0-alpha1
  • 8.x-1.0-alpha2
  • 8.x-1.0-alpha3
  • 8.x-1.0-alpha4
  • 8.x-1.0-beta1
  • 8.x-1.1
  • 8.x-1.2
  • 8.x-1.3
  • 8.x-1.4
  • 8.x-1.5
  • 8.x-1.6
  • 8.x-1.7
17 results

Target

Select target project
  • project/toc_api
  • issue/toc_api-3204991
  • issue/toc_api-3254404
  • issue/toc_api-3274613
  • issue/toc_api-3294155
  • issue/toc_api-3290082
  • issue/toc_api-2841748
  • issue/toc_api-3384686
  • issue/toc_api-3388382
  • issue/toc_api-3398231
  • issue/toc_api-3412644
  • issue/toc_api-3432479
  • issue/toc_api-3455312
  • issue/toc_api-3470701
  • issue/toc_api-2905420
  • issue/toc_api-3487659
  • issue/toc_api-3493738
  • issue/toc_api-2789105
  • issue/toc_api-3417862
  • issue/toc_api-3504850
  • issue/toc_api-3509064
  • issue/toc_api-3511338
  • issue/toc_api-3511348
  • issue/toc_api-3504852
  • issue/toc_api-3520197
  • issue/toc_api-3521083
  • issue/toc_api-2986763
  • issue/toc_api-3416816
  • issue/toc_api-3273211
  • issue/toc_api-2909551
30 results
Select Git revision
  • 2905420-fix-empty-render
  • 8.x-1.x
  • 8.x-1.0
  • 8.x-1.0-alpha1
  • 8.x-1.0-alpha2
  • 8.x-1.0-alpha3
  • 8.x-1.0-alpha4
  • 8.x-1.0-beta1
  • 8.x-1.1
  • 8.x-1.2
  • 8.x-1.3
  • 8.x-1.4
  • 8.x-1.5
13 results
Show changes
Commits on Source (9)
Showing
with 335 additions and 122 deletions
contenu
rockowitz
selectmenu
spécial
strto
tocs
mymodule
include:
- project: $_GITLAB_TEMPLATES_REPO
ref: $_GITLAB_TEMPLATES_REF
file:
- '/includes/include.drupalci.main.yml'
- '/includes/include.drupalci.variables.yml'
- '/includes/include.drupalci.workflows.yml'
variables:
OPT_IN_TEST_PREVIOUS_MAJOR: 1
OPT_IN_TEST_NEXT_MINOR: 1
OPT_IN_TEST_NEXT_MAJOR: 0
RUN_JOB_UPGRADE_STATUS: 0
cspell:
allow_failure: false
phpcs:
allow_failure: false
phpstan:
allow_failure: false
phpstan (next minor):
allow_failure: true
phpstan (next major):
allow_failure: false
phpunit (previous major):
allow_failure: false
phpunit (next minor):
allow_failure: false
phpunit (next major):
allow_failure: false
upgrade status:
allow_failure: false
...@@ -11,32 +11,32 @@ Table of Contents ...@@ -11,32 +11,32 @@ Table of Contents
Introduction Introduction
------------ ------------
This module provides a framework for creating table of contents (TOC) from This module provides a framework for creating table of contents (TOC) from
an HTML fragment's header tags. an HTML fragment's header tags.
The TOC API consists of a several classes, services, and plugin: The TOC API consists of a several classes, services, and plugin:
- **Toc**: A class that parses the header tags from an HTML fragment and - **Toc**: A class that parses the header tags from an HTML fragment and
stores a index of these headers that represent a hierarchical table stores a index of these headers that represent a hierarchical table
of contents. of contents.
- **TocType**: A configuration entity that contains options for customizing a - **TocType**: A configuration entity that contains options for customizing a
table of contents. table of contents.
- **TocFormatter**: A service for formatting a table of content's headers, - **TocFormatter**: A service for formatting a table of content's headers,
numbering, and ids. numbering, and ids.
- **TocManager**: A service that creates and manages table of contents instances. - **TocManager**: A service that creates and manages table of contents instances.
- **TocBuilder**: A service that builds and renders a table of contents and - **TocBuilder**: A service that builds and renders a table of contents and
update an HTML document's headers. update an HTML document's headers.
- **TocBlockBase**: A base block which displays the current TOC module's - **TocBlockBase**: A base block which displays the current TOC module's
TOC in a block. TOC in a block.
The TOC API includes templates for formatting a table of contents as a The TOC API includes templates for formatting a table of contents as a
**hierarchical tree** or **jump menu**. All TOC types included in the TOC API **hierarchical tree** or **jump menu**. All TOC types included in the TOC API
are responsive with a hierarchical tree being displayed for desktop users are responsive with a hierarchical tree being displayed for desktop users
and a jump menu displayed for mobile users (browser width less than 768px). and a jump menu displayed for mobile users (browser width less than 768px).
TOC Type features TOC Type features
...@@ -53,26 +53,27 @@ TOC Type features ...@@ -53,26 +53,27 @@ TOC Type features
Implementations Implementations
--------------- ---------------
Note that this module does NOT directly expose any mechanisms for displaying a Note that this module does NOT directly expose any mechanisms for displaying a
table of contents, you must create a custom module and/or install one of the table of contents, you must create a custom module and/or install one of the
below modules to implement a table of contents. below modules to implement a table of contents.
### Custom Module Implementation ### Custom Module Implementation
Below is very simple example of a hook from the TOC API example module Below is very simple example of a hook from the TOC API example module
(toc_api_example.module) that adds a TOC to all page and article nodes on a (toc_api_example.module) that adds a TOC to all page and article nodes on a
website. website.
```php
<?php <?php
/** /**
* @file * @file
* Example of a custom implementation of the TOC API that adds a table of contents to specified content types. * Example of a custom implementation of the TOC API that adds a table of contents to specified content types.
*/ */
use Drupal\node\NodeInterface; use Drupal\node\NodeInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\toc_api\Entity\TocType; use Drupal\toc_api\Entity\TocType;
/** /**
* Implements hook_node_view(). * Implements hook_node_view().
*/ */
...@@ -81,18 +82,18 @@ website. ...@@ -81,18 +82,18 @@ website.
if (in_array($node->getType(), ['page', 'article']) && $view_mode == 'full' && isset($build['body'][0])) { if (in_array($node->getType(), ['page', 'article']) && $view_mode == 'full' && isset($build['body'][0])) {
// Get the completely render (and filtered) body value. // Get the completely render (and filtered) body value.
$body = (string) \Drupal::service('renderer')->render($build['body'][0]); $body = (string) \Drupal::service('renderer')->render($build['body'][0]);
// Get 'default' TOC type options. // Get 'default' TOC type options.
/** @var Drupal\toc_api\TocTypeInterface $toc_type */ /** @var Drupal\toc_api\TocTypeInterface $toc_type */
$toc_type = TocType::load('default'); $toc_type = TocType::load('default');
$options = ($toc_type) ? $toc_type->getOptions() : []; $options = ($toc_type) ? $toc_type->getOptions() : [];
// Create a TOC instance using the TOC manager. // Create a TOC instance using the TOC manager.
/** @var \Drupal\toc_api\TocManagerInterface $toc_manager */ /** @var \Drupal\toc_api\TocManagerInterface $toc_manager */
$toc_manager = \Drupal::service('toc_api.manager'); $toc_manager = \Drupal::service('toc_api.manager');
/** @var \Drupal\toc_api\TocInterface $toc */ /** @var \Drupal\toc_api\TocInterface $toc */
$toc = $toc_manager->create('toc_filter', $body, $options); $toc = $toc_manager->create('toc_filter', $body, $options);
// If the TOC is visible (ie has more than X headers), replace the body // If the TOC is visible (ie has more than X headers), replace the body
// render array with the TOC and update body content using the TOC builder. // render array with the TOC and update body content using the TOC builder.
if ($toc->isVisible()) { if ($toc->isVisible()) {
...@@ -105,31 +106,160 @@ website. ...@@ -105,31 +106,160 @@ website.
} }
} }
} }
```
### Custom Block Implementation
Block code to allow placing a TOC block. It also uses JQ to go through the
page headings and add IDs to them so that the anchor links work.
```php
<?php
namespace Drupal\mymodule\Plugin\Block;
use Drupal\Core\Block\Annotation\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\Node;
use Drupal\toc_api\Entity\TocType;
/**
* Provides a TOC block using the TOC API.
*
* @Block(
* id = "toc_block",
* admin_label = @Translation("MyModule TOC"),
* )
*/
class TOCBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
// Get list of available TOC Types.
$storage = \Drupal::entityTypeManager()->getStorage('toc_type');
$ids = \Drupal::entityQuery('toc_type')->execute();
$form = parent::blockForm($form, $form_state);
$form['toc_type'] = [
'#type' => 'select',
'#title' => $this->t('TOC Type'),
'#options' => $ids,
'#description' => $this->t('Select the type of TOC to be displayed in this block.'),
'#default_value' => $this->configuration['toc_type'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
parent::blockSubmit($form, $form_state);
$this->configuration['toc_type'] = $form_state->getValue('toc_type');
}
/**
* {@inheritdoc}
*/
public function build() {
// Required to break block - render page - insert block - render page, etc, etc.
$full_content = &drupal_static('toc_block_full');
if (!empty($full_content)) return [];
// Get TOC Type configured for this Block.
$toc_type_id = $this->configuration['toc_type'];
$node = \Drupal::routeMatch()->getParameter('node');
if (!($node instanceof \Drupal\node\NodeInterface)) {
return [
'#markup' => $this->t('TOC (%type): Not a NODE page', [
'%type' => $toc_type_id,
]),
];
}
$view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
$full_content = $view_builder->view($node, 'full');
$content = (string) \Drupal::service('renderer')->render($full_content);
// Get TOC options for this Type.
/** @var \Drupal\toc_api\TocTypeInterface $toc_type */
$toc_type = TocType::load($toc_type_id);
$options = ($toc_type) ? $toc_type->getOptions() : [];
// Create a TOC instance using the TOC manager.
/** @var \Drupal\toc_api\TocManagerInterface $toc_manager */
$toc_manager = \Drupal::service('toc_api.manager');
/** @var \Drupal\toc_api\TocInterface $toc */
$toc = $toc_manager->create('toc_filter', $content, $options);
// If the TOC is visible (ie has more than X headers), replace the body
// render array with the TOC and update body content using the TOC builder.
if ($toc->isVisible()) {
/** @var \Drupal\toc_api\TocBuilderInterface $toc_builder */
$toc_builder = \Drupal::service('toc_api.builder');
$build = [
'toc' => $toc_builder->buildToc($toc),
//'content' => $toc_builder->buildContent($toc),
];
}
return $build;
}
}
```
Javascript
```javascript
// Add IDs for TOC H2/H3
var level1 = 0;
var level2 = 0;
$('.toc-section h2, .toc-section h3').each(function() {
if ($(this).prop('tagName') === 'H2') {
level1++;
level2 = 0;
$(this).attr('id', 'section-' + level1 + '-' + level2);
}
else {
level2++;
$(this).attr('id', 'section-' + level1 + '-' + level2);
}
});
```
### Contrib Modules using TOC API ### Contrib Modules using TOC API
- **[TOC filter](https://www.drupal.org/project/toc_filter)** - **[TOC filter](https://www.drupal.org/project/toc_filter)**
Converts header tags into a hierarchical table of contents using Drupal's input Converts header tags into a hierarchical table of contents using Drupal's input
filter system. filter system.
- **[TOC Twig Filter](https://www.drupal.org/project/toc_twig_filter)**
Twig filter rendering tables of contents.
- **[Footnotes](https://www.drupal.org/project/footnotes)**
Easily create automatically numbered footnote citations to references in any
formatted text with full CK Editor 5 support.
Notes Notes
----- -----
- The TOC API is based on Drupal's OO architecture and patterns while the - The TOC API is based on Drupal's OO architecture and patterns while the
supported feature set is based on the supported feature set is based on the
[Table of contents](https://www.drupal.org/project/tableofcontents) module. [Table of contents](https://www.drupal.org/project/tableofcontents) module.
- Only TOC parsing and TOC building are included in the TOC API. - Only TOC parsing and TOC building are included in the TOC API.
JavaScript enhancements for hide/show logic and smooth scrolling are not being JavaScript enhancements for hide/show logic and smooth scrolling are not being
included and should be handled via custom code and/or a contrib module. included and should be handled via custom code and/or a contrib module.
- Even though, the TOC API's module's namespace is 'toc_api', all classes, services, - Even though, the TOC API's module's namespace is 'toc_api', all classes, services,
and templates are using the 'toc' or 'Toc' namespace, because there is minimal and templates are using the 'toc' or 'Toc' namespace, because there is minimal
risk of namespace conflicts with the [TOC module](https://www.drupal.org/project/toc) risk of namespace conflicts with the [TOC module](https://www.drupal.org/project/toc)
because the TOC module does not implement any APIs. The TOC module just because the TOC module does not implement any APIs. The TOC module just
generates a table of contents for specific page content using JavaScript. generates a table of contents for specific page content using JavaScript.
......
...@@ -14,6 +14,8 @@ options: ...@@ -14,6 +14,8 @@ options:
header_id: title header_id: title
header_id_prefix: section header_id_prefix: section
header_exclude_xpath: '' header_exclude_xpath: ''
wrapper: div
title_wrapper: h3
top_label: 'Back to top' top_label: 'Back to top'
top_min: 2 top_min: 2
top_max: 2 top_max: 2
......
...@@ -14,6 +14,8 @@ options: ...@@ -14,6 +14,8 @@ options:
header_id: title header_id: title
header_id_prefix: section header_id_prefix: section
header_exclude_xpath: '' header_exclude_xpath: ''
wrapper: div
title_wrapper: h3
top_label: 'Back to top' top_label: 'Back to top'
top_min: 2 top_min: 2
top_max: 2 top_max: 2
......
...@@ -14,6 +14,8 @@ options: ...@@ -14,6 +14,8 @@ options:
header_id: title header_id: title
header_id_prefix: section header_id_prefix: section
header_exclude_xpath: '' header_exclude_xpath: ''
wrapper: div
title_wrapper: h3
top_label: 'Back to top' top_label: 'Back to top'
top_min: 2 top_min: 2
top_max: 2 top_max: 2
......
...@@ -14,6 +14,8 @@ options: ...@@ -14,6 +14,8 @@ options:
header_id: title header_id: title
header_id_prefix: section header_id_prefix: section
header_exclude_xpath: '' header_exclude_xpath: ''
wrapper: div
title_wrapper: h3
top_label: 'Back to top' top_label: 'Back to top'
top_min: 2 top_min: 2
top_max: 2 top_max: 2
......
...@@ -14,6 +14,8 @@ options: ...@@ -14,6 +14,8 @@ options:
header_id: title header_id: title
header_id_prefix: section header_id_prefix: section
header_exclude_xpath: '' header_exclude_xpath: ''
wrapper: div
title_wrapper: h3
top_label: 'Back to top' top_label: 'Back to top'
top_min: 2 top_min: 2
top_max: 2 top_max: 2
......
...@@ -92,3 +92,9 @@ toc_api.toc_type.*: ...@@ -92,3 +92,9 @@ toc_api.toc_type.*:
type: ignore type: ignore
h6: h6:
type: ignore type: ignore
wrapper:
type: string
label: 'Wrapper element'
title_wrapper:
type: string
label: 'Title wrapper'
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Add border to tree when displayed within a node * Add border to tree when displayed within a node
*/ */
.node .toc-tree { .node .toc-tree {
border: 1px solid #ccc;
padding: 0 1em;
margin: 1em 0; margin: 1em 0;
padding: 0 1em;
border: 1px solid #ccc;
} }
...@@ -4,19 +4,15 @@ ...@@ -4,19 +4,15 @@
*/ */
(function ($, Drupal) { (function ($, Drupal) {
"use strict";
Drupal.behaviors.tocMenu = { Drupal.behaviors.tocMenu = {
attach: function (context) { attach(context) {
$('form.toc-menu > select', context).change(function () { $('form.toc-menu > select', context).change(function () {
var value = $(this).val(); const value = this.value;
if (value) { if (value) {
location.hash = value; window.location.hash = value;
} }
this.selectedIndex = 0; this.selectedIndex = 0;
}); });
} },
}; };
})(jQuery, Drupal); })(jQuery, Drupal);
...@@ -4,25 +4,37 @@ ...@@ -4,25 +4,37 @@
*/ */
(function ($, Drupal, once) { (function ($, Drupal, once) {
"use strict";
Drupal.behaviors.tocTypeOptions = {
attach: function (context) {
$(once('.js-toc-type-options-header-min, .js-toc-type-options-header-max', context)).change(toggleHeaders);
}
};
toggleHeaders();
function toggleHeaders() { function toggleHeaders() {
var min = $('.js-toc-type-options-header-min').val(); const min = parseInt(
var max = $('.js-toc-type-options-header-max').val(); document.querySelector('.js-toc-type-options-header-min')?.value || 1,
10,
);
const max = parseInt(
document.querySelector('.js-toc-type-options-header-max')?.value || 6,
10,
);
for (var i = 1; i <= 6; i++) { for (let i = 1; i <= 6; i++) {
// Having to use the id instead of $('.js-toc-type-options-header-h' + i). const $header = $(`details[id$="-headers-h${i}"]`);
var $header = $('details[id$="-headers-h' + i + '"]'); if (i >= min && i <= max) {
(i >= min && i <= max) ? $header.show() : $header.hide(); $header.show();
} else {
$header.hide();
}
} }
} }
Drupal.behaviors.tocTypeOptions = {
attach(context) {
once(
'toc-type-header-change',
'.js-toc-type-options-header-min, .js-toc-type-options-header-max',
context,
).forEach((el) => {
el.addEventListener('change', toggleHeaders);
});
toggleHeaders();
},
};
})(jQuery, Drupal, once); })(jQuery, Drupal, once);
...@@ -2,6 +2,6 @@ name: 'TOC API example' ...@@ -2,6 +2,6 @@ name: 'TOC API example'
type: module type: module
description: 'Example of a custom implementation of the TOC API that adds a table of contents to specified content types.' description: 'Example of a custom implementation of the TOC API that adds a table of contents to specified content types.'
package: 'Example modules' package: 'Example modules'
core_version_requirement: ^9.4 || ^10.0 core_version_requirement: ^9.5 || ^10.2 || ^11.0
dependencies: dependencies:
- toc_api:toc_api - toc_api:toc_api
<?php <?php
/** /**
* @file * @file
* Example of a custom implementation of the TOC API that adds a table of contents to specified content types. * Example of a custom implementation of the TOC API.
*/ */
use Drupal\node\NodeInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\node\NodeInterface;
use Drupal\toc_api\Entity\TocType; use Drupal\toc_api\Entity\TocType;
/** /**
* Implements hook_node_view(). * Implements hook_node_view().
*/ */
function toc_api_example_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) { function toc_api_example_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, string $view_mode): void {
// Add TOC to 'page' and 'article' content types that are being viewed as a full (page) with a body field. // Add TOC to 'page' and 'article' content types that are being viewed as a
// full (page) with a body field.
if (in_array($node->getType(), ['page', 'article']) && $view_mode == 'full' && isset($build['body'][0])) { if (in_array($node->getType(), ['page', 'article']) && $view_mode == 'full' && isset($build['body'][0])) {
// Get the completely render (and filtered) body value. // Get the completely render (and filtered) body value.
$body = (string) \Drupal::service('renderer')->render($build['body'][0]); $body = (string) \Drupal::service('renderer')->render($build['body'][0]);
// Get 'default' TOC type options. // Get 'default' TOC type options.
/** @var \Drupal\toc_api\TocTypeInterface $toc_type */ /** @var \Drupal\toc_api\TocTypeInterface|null $toc_type */
$toc_type = TocType::load('default'); $toc_type = TocType::load('default');
$options = ($toc_type) ? $toc_type->getOptions() : []; $options = ($toc_type) ? $toc_type->getOptions() : [];
......
parameters:
level: 6
ignoreErrors:
- identifier: missingType.iterableValue
...@@ -64,9 +64,9 @@ class TocType extends ConfigEntityBase implements TocTypeInterface { ...@@ -64,9 +64,9 @@ class TocType extends ConfigEntityBase implements TocTypeInterface {
/** /**
* The TOC type options. * The TOC type options.
* *
* @var array * @var array|null
*/ */
protected $options; protected $options = NULL;
/** /**
* {@inheritdoc} * {@inheritdoc}
......
...@@ -3,15 +3,19 @@ ...@@ -3,15 +3,19 @@
namespace Drupal\toc_api\Plugin\Block; namespace Drupal\toc_api\Plugin\Block;
use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Block\BlockBase; use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Drupal\toc_api\TocInterface;
use Drupal\toc_api\TocManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
* Provides a base TOC block which displays the current TOC module's TOC in a * Provides a base block which displays the current TOC module's TOC in a block.
* block.
*/ */
abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginInterface { abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginInterface {
...@@ -20,7 +24,21 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI ...@@ -20,7 +24,21 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI
* *
* @var \Drupal\Core\Routing\RouteMatchInterface * @var \Drupal\Core\Routing\RouteMatchInterface
*/ */
protected $routeMatch; protected RouteMatchInterface $routeMatch;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The TOC manager.
*
* @var \Drupal\toc_api\TocManagerInterface
*/
protected TocManagerInterface $tocManager;
/** /**
* Creates a LocalActionsBlock instance. * Creates a LocalActionsBlock instance.
...@@ -33,34 +51,38 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI ...@@ -33,34 +51,38 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI
* The plugin implementation definition. * The plugin implementation definition.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match. * The route match.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\toc_api\TocManagerInterface $toc_manager
* The TOC manager.
*/ */
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match) { final public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match, EntityTypeManagerInterface $entity_type_manager, TocManagerInterface $toc_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition); parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->routeMatch = $route_match; $this->routeMatch = $route_match;
$this->entityTypeManager = $entity_type_manager;
$this->tocManager = $toc_manager;
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static( return new static(
$configuration, $configuration,
$plugin_id, $plugin_id,
$plugin_definition, $plugin_definition,
$container->get('current_route_match') $container->get('current_route_match'),
$container->get('entity_type.manager'),
$container->get('toc_api.manager')
); );
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function build() { public function build(): array {
$toc = $this->getCurrentToc(); $toc = $this->getCurrentToc();
if (empty($toc)) {
return [];
}
// Build the TOC. // Build the TOC.
$options = $toc->getOptions(); $options = $toc->getOptions();
$build = [ $build = [
...@@ -82,12 +104,9 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI ...@@ -82,12 +104,9 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI
* @return \Drupal\toc_api\TocInterface * @return \Drupal\toc_api\TocInterface
* A TOC object. * A TOC object.
*/ */
protected function getCurrentToc() { protected function getCurrentToc(): TocInterface {
/** @var \Drupal\toc_api\TocManagerInterface $toc_manager */
$toc_manager = \Drupal::service('toc_api.manager');
// Get the new TOC instance using the module name. // Get the new TOC instance using the module name.
return $toc_manager->getToc($this->getCurrentTocId()); return $this->tocManager->getToc($this->getCurrentTocId());
} }
/** /**
...@@ -99,21 +118,21 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI ...@@ -99,21 +118,21 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI
* @return string * @return string
* The current TOC block's plugin ID. * The current TOC block's plugin ID.
*/ */
protected function getCurrentTocId() { protected function getCurrentTocId(): string {
return $this->pluginId; return $this->pluginId;
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getCacheContexts() { public function getCacheContexts(): array {
return ['route']; return ['route'];
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getCacheTags() { public function getCacheTags(): array {
$node = $this->getCurrentNode(); $node = $this->getCurrentNode();
return ($node) ? ['node:' . $node->id()] : []; return ($node) ? ['node:' . $node->id()] : [];
} }
...@@ -122,14 +141,15 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI ...@@ -122,14 +141,15 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI
* Load the node associated with the current request. * Load the node associated with the current request.
* *
* @return \Drupal\node\NodeInterface|null * @return \Drupal\node\NodeInterface|null
* A node entity, or NULL if no node is not found. * A node entity, or NULL if no node is found.
*/ */
protected function getCurrentNode() { protected function getCurrentNode(): ?NodeInterface {
switch ($this->routeMatch->getRouteName()) { switch ($this->routeMatch->getRouteName()) {
// Look at the request's node revision. // Look at the request's node revision.
case 'node.revision_show': case 'node.revision_show':
return node_revision_load($this->routeMatch $node_storage = $this->entityTypeManager->getStorage('node');
->getParameter('node_revision')); $node = $node_storage->loadRevision($this->routeMatch->getParameter('node_revision'));
return ($node instanceof NodeInterface) ? $node : NULL;
// Look at the request's node preview. // Look at the request's node preview.
case 'entity.node.preview': case 'entity.node.preview':
...@@ -146,14 +166,10 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI ...@@ -146,14 +166,10 @@ abstract class TocBlockBase extends BlockBase implements ContainerFactoryPluginI
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function blockAccess(AccountInterface $account) { protected function blockAccess(AccountInterface $account): AccessResultInterface {
$this->getCurrentTocId();
/** @var \Drupal\toc_api\TocManagerInterface $toc_manager */
$toc_manager = \Drupal::service('toc_api.manager');
// Get the new TOC instance and see if it is visible and should be // Get the new TOC instance and see if it is visible and should be
// displayed in a block. // displayed in a block.
$toc = $toc_manager->getToc($this->getCurrentTocId()); $toc = $this->tocManager->getToc($this->getCurrentTocId());
if (!$toc || !$toc->isVisible() || !$toc->isBlock()) { if (!$toc || !$toc->isVisible() || !$toc->isBlock()) {
return AccessResult::forbidden(); return AccessResult::forbidden();
......
...@@ -72,9 +72,9 @@ class Toc implements TocInterface { ...@@ -72,9 +72,9 @@ class Toc implements TocInterface {
/** /**
* The number of root headers found in the content. * The number of root headers found in the content.
* *
* @var int * @var int|null
*/ */
protected $headerCount; protected $headerCount = NULL;
/** /**
* The source content with unique header ids. * The source content with unique header ids.
...@@ -100,9 +100,9 @@ class Toc implements TocInterface { ...@@ -100,9 +100,9 @@ class Toc implements TocInterface {
/** /**
* The toc represent as a tree. * The toc represent as a tree.
* *
* @var array * @var array|null
*/ */
protected $tree; protected $tree = NULL;
/** /**
* Existing in the source content IDs. * Existing in the source content IDs.
...@@ -192,16 +192,12 @@ class Toc implements TocInterface { ...@@ -192,16 +192,12 @@ class Toc implements TocInterface {
$this->allowedTags = $this->formatter()->convertAllowedTagsToArray($this->options['header_allowed_tags']); $this->allowedTags = $this->formatter()->convertAllowedTagsToArray($this->options['header_allowed_tags']);
$this->initialize(); $this->initialize();
// DEBUG:
// dsm($this->getIndex());
// dsm($this->getTree());
} }
/** /**
* Initializes the table of content index and ensure unique header ids. * Initializes the table of content index and ensure unique header ids.
*/ */
protected function initialize() { protected function initialize(): void {
$this->index = []; $this->index = [];
// Setup an empty array of keys to track the index's keys. // Setup an empty array of keys to track the index's keys.
...@@ -232,7 +228,7 @@ class Toc implements TocInterface { ...@@ -232,7 +228,7 @@ class Toc implements TocInterface {
}, array_keys($this->options['headers'])); }, array_keys($this->options['headers']));
$query = '//*[' . implode(' or ', $predicates) . ']'; $query = '//*[' . implode(' or ', $predicates) . ']';
// According to specification, https://www.w3.org/TR/xpath-31/#id-steps, // According to specification, https://www.w3.org/TR/xpath-31/#id-steps,
// "The resulting node sequence is returned in document order." // "The resulting node sequence is returned in document order".
foreach ($xpath->query($query) as $dom_node) { foreach ($xpath->query($query) as $dom_node) {
/** @var \DOMElement $dom_node */ /** @var \DOMElement $dom_node */
if (in_array($dom_node, $exclude_nodes, TRUE)) { if (in_array($dom_node, $exclude_nodes, TRUE)) {
...@@ -340,10 +336,9 @@ class Toc implements TocInterface { ...@@ -340,10 +336,9 @@ class Toc implements TocInterface {
'#markup' => $header_html, '#markup' => $header_html,
'#allowed_tags' => $this->getAllowedTags(), '#allowed_tags' => $this->getAllowedTags(),
], ],
'url' => Url::fromRoute('<none>', NULL, [ 'url' => Url::fromRoute('<none>', [], [
'fragment' => $header_id, 'fragment' => $header_id,
] ]),
),
]; ];
} }
...@@ -353,10 +348,11 @@ class Toc implements TocInterface { ...@@ -353,10 +348,11 @@ class Toc implements TocInterface {
/** /**
* Gets the TOC formatter. * Gets the TOC formatter.
* *
* @return \Drupal\toc_api\TocFormatter. * @return \Drupal\toc_api\TocFormatter
* The TOC formatter * The TOC formatter
*/ */
protected function formatter() { protected function formatter() {
// @phpstan-ignore-next-line
return \Drupal::service('toc_api.formatter'); return \Drupal::service('toc_api.formatter');
} }
...@@ -399,7 +395,8 @@ class Toc implements TocInterface { ...@@ -399,7 +395,8 @@ class Toc implements TocInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getHeaderCount() { public function getHeaderCount() {
if (!isset($this->headerCount)) { if ($this->headerCount === NULL) {
$this->headerCount = 0;
foreach ($this->index as $item) { foreach ($this->index as $item) {
if (empty($item['parent'])) { if (empty($item['parent'])) {
$this->headerCount++; $this->headerCount++;
...@@ -420,7 +417,7 @@ class Toc implements TocInterface { ...@@ -420,7 +417,7 @@ class Toc implements TocInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getTree() { public function getTree() {
if (!isset($this->tree)) { if ($this->tree === NULL) {
$this->tree = [ $this->tree = [
'title' => $this->options['title'], 'title' => $this->options['title'],
]; ];
...@@ -446,7 +443,7 @@ class Toc implements TocInterface { ...@@ -446,7 +443,7 @@ class Toc implements TocInterface {
* @param array $children * @param array $children
* An array of keys to be associative to the parent header item. * An array of keys to be associative to the parent header item.
*/ */
protected function buildTree(array &$item, array $children) { protected function buildTree(array &$item, array $children): void {
$item['below_type'] = ''; $item['below_type'] = '';
$item['below'] = []; $item['below'] = [];
foreach ($children as $key) { foreach ($children as $key) {
...@@ -499,8 +496,6 @@ class Toc implements TocInterface { ...@@ -499,8 +496,6 @@ class Toc implements TocInterface {
* *
* @param \DOMDocument $dom * @param \DOMDocument $dom
* DOM object. * DOM object.
*
* @return void
*/ */
protected function collectExistingIds(\DOMDocument $dom): void { protected function collectExistingIds(\DOMDocument $dom): void {
$xpath = new \DOMXPath($dom); $xpath = new \DOMXPath($dom);
...@@ -519,7 +514,6 @@ class Toc implements TocInterface { ...@@ -519,7 +514,6 @@ class Toc implements TocInterface {
* *
* @param \DOMDocument $dom * @param \DOMDocument $dom
* DOM object. * DOM object.
* @return void
*/ */
protected function useIdsOnly(\DOMDocument $dom): void { protected function useIdsOnly(\DOMDocument $dom): void {
$xpath = new \DOMXPath($dom); $xpath = new \DOMXPath($dom);
......
...@@ -4,11 +4,14 @@ namespace Drupal\toc_api; ...@@ -4,11 +4,14 @@ namespace Drupal\toc_api;
use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss; use Drupal\Component\Utility\Xss;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\RendererInterface;
/** /**
* Defines a service that builds and renders a table of contents and update an * Defines a service to build TOC.
* HTML document's headers. *
* This service builds and renders a table of contents and update an HTML
* document's headers.
*/ */
class TocBuilder implements TocBuilderInterface { class TocBuilder implements TocBuilderInterface {
...@@ -95,14 +98,14 @@ class TocBuilder implements TocBuilderInterface { ...@@ -95,14 +98,14 @@ class TocBuilder implements TocBuilderInterface {
$fragment_body = $fragment_dom->getElementsByTagName('body')->item(0); $fragment_body = $fragment_dom->getElementsByTagName('body')->item(0);
// Import the new (header) nodes onto the original DOMDocument. // Import the new (header) nodes onto the original DOMDocument.
foreach ($fragment_body->childNodes as $fragment_child_node) { foreach ($fragment_body->childNodes as $fragment_child_node) {
$fragment_child_node = $dom->importNode($fragment_child_node, true); $fragment_child_node = $dom->importNode($fragment_child_node, TRUE);
$fragment_node->appendChild($fragment_child_node); $fragment_node->appendChild($fragment_child_node);
} }
// Replace the header node. // Replace the header node.
$replace = TRUE; $replace = TRUE;
if (!empty($h_nodes)) { if (!empty($h_nodes)) {
// Make sure we are not going // Make sure we are not going.
foreach ($h_nodes as $h) { foreach ($h_nodes as $h) {
if ($dom_node->parentNode->isSameNode($h)) { if ($dom_node->parentNode->isSameNode($h)) {
$replace = FALSE; $replace = FALSE;
...@@ -133,7 +136,7 @@ class TocBuilder implements TocBuilderInterface { ...@@ -133,7 +136,7 @@ class TocBuilder implements TocBuilderInterface {
$fragment_body = $fragment_dom->getElementsByTagName('body')->item(0); $fragment_body = $fragment_dom->getElementsByTagName('body')->item(0);
// Import the new (header) nodes onto the original DOMDocument. // Import the new (header) nodes onto the original DOMDocument.
foreach ($fragment_body->childNodes as $fragment_child_node) { foreach ($fragment_body->childNodes as $fragment_child_node) {
$fragment_child_node = $dom->importNode($fragment_child_node, true); $fragment_child_node = $dom->importNode($fragment_child_node, TRUE);
$fragment_node->appendChild($fragment_child_node); $fragment_node->appendChild($fragment_child_node);
} }
...@@ -150,10 +153,13 @@ class TocBuilder implements TocBuilderInterface { ...@@ -150,10 +153,13 @@ class TocBuilder implements TocBuilderInterface {
*/ */
public function renderToc(TocInterface $toc) { public function renderToc(TocInterface $toc) {
if (!$toc->isVisible()) { if (!$toc->isVisible()) {
return ''; return Markup::create('');
} }
$build = $this->buildToc($toc); $build = $this->buildToc($toc);
if (empty($build)) {
return Markup::create('');
}
return $this->renderer->render($build); return $this->renderer->render($build);
} }
......
...@@ -26,8 +26,8 @@ interface TocBuilderInterface { ...@@ -26,8 +26,8 @@ interface TocBuilderInterface {
* A TOC object. * A TOC object.
* *
* @return array * @return array
* A render array containing the table of content's body content with bookmarked, typed, and custom * A render array containing the table of content's body content with
* headers with back to top links. * bookmarked, typed, and custom headers with back to top links.
*/ */
public function buildContent(TocInterface $toc); public function buildContent(TocInterface $toc);
......