diff --git a/composer.json b/composer.json index 03faf09e..a53ced6c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,6 @@ "drupal/autosave_form": "^1.7", "drupal/block_class": "^4.0.1", "drupal/blog": "^3.1", - "drupal/book": "^2.0", "drupal/bootstrap_layouts": "^5.4", "drupal/button_link": "2.0.x-dev@dev", "drupal/ckeditor_abbreviation": "^5.0", @@ -51,6 +50,7 @@ "drupal/layout_builder_styles": "^2.1", "drupal/layout_library": "^1.0@beta", "drupal/linkit": "^7.0", + "drupal/livre": "^1.0", "drupal/media_entity_instagram": "^4.0@beta", "drupal/media_entity_twitter": "^2.10", "drupal/media_entity_slideshow": "^2.6", @@ -219,6 +219,10 @@ "3500238 - Rename tests to fix core phpunit kernel tests": "https://www.drupal.org/files/issues/2025-08-20/3500238-core-11.1.8--16.patch" }, + "drupal/ckeditor_details": { + "3244311 - Allow
element to be placed without
": + "https://www.drupal.org/files/issues/2024-11-15/3244311-9.patch" + }, "drupal/ctools": { "Enter drupal/ctools patch #2667652 description here": "https://www.drupal.org/files/issues/ctools-option_to_expose-2667652-3.patch", @@ -270,10 +274,6 @@ "drupal/webform": { "Enter drupal/webform patch #3205860 description here": "https://www.drupal.org/files/issues/2021-03-26/webform_clientside_validation-3205860-2.patch" - }, - "drupal/ckeditor_details": { - "3244311 - Allow
element to be placed without
": - "https://www.drupal.org/files/issues/2024-11-15/3244311-9.patch" } } }, diff --git a/modules/custom/wxt_ext/wxt_ext_book/config/install/wxt_ext_book.settings.yml b/modules/custom/wxt_ext/wxt_ext_book/config/install/wxt_ext_book.settings.yml index 59c77fba..f8fdf924 100644 --- a/modules/custom/wxt_ext/wxt_ext_book/config/install/wxt_ext_book.settings.yml +++ b/modules/custom/wxt_ext/wxt_ext_book/config/install/wxt_ext_book.settings.yml @@ -1,4 +1,4 @@ navigation: persistent_nav: 1 - page_title_nav_labels: 1 + page_title_nav_labels: 0 page_nav_home_link: 0 diff --git a/modules/custom/wxt_ext/wxt_ext_book/config/optional/core.entity_form_display.node.book.default.yml b/modules/custom/wxt_ext/wxt_ext_book/config/optional/core.entity_form_display.node.book.default.yml new file mode 100644 index 00000000..7b0a0980 --- /dev/null +++ b/modules/custom/wxt_ext/wxt_ext_book/config/optional/core.entity_form_display.node.book.default.yml @@ -0,0 +1,96 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.book.body + - node.type.book + - workflows.workflow.editorial + module: + - content_moderation + - path + - text +id: node.book.default +targetEntityType: node +bundle: book +mode: default +content: + body: + type: text_textarea_with_summary + weight: 1 + region: content + settings: + rows: 5 + summary_rows: 2 + placeholder: '' + show_summary: false + third_party_settings: { } + created: + type: datetime_timestamp + weight: 10 + region: content + settings: { } + third_party_settings: { } + langcode: + type: language_select + weight: 2 + region: content + settings: + include_locked: true + third_party_settings: { } + moderation_state: + type: moderation_state_default + weight: 100 + region: content + settings: { } + third_party_settings: { } + path: + type: path + weight: 30 + region: content + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + weight: 15 + region: content + settings: + display_label: true + third_party_settings: { } + status: + type: boolean_checkbox + weight: 120 + region: content + settings: + display_label: true + third_party_settings: { } + sticky: + type: boolean_checkbox + weight: 16 + region: content + settings: + display_label: true + third_party_settings: { } + title: + type: string_textfield + weight: -5 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + translation: + weight: 10 + region: content + settings: { } + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } +hidden: { } diff --git a/modules/custom/wxt_ext/wxt_ext_book/config/optional/core.entity_view_display.node.book.full.yml b/modules/custom/wxt_ext/wxt_ext_book/config/optional/core.entity_view_display.node.book.full.yml new file mode 100644 index 00000000..9a47d8ec --- /dev/null +++ b/modules/custom/wxt_ext/wxt_ext_book/config/optional/core.entity_view_display.node.book.full.yml @@ -0,0 +1,124 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.full + - field.field.node.book.body + - node.type.book + module: + - bootstrap_layouts + - layout_builder + - layout_discovery + - layout_library + - text + - user +third_party_settings: + layout_builder: + enabled: true + allow_custom: false + sections: + - + layout_id: layout_onecol + layout_settings: + label: '' + components: + a6a77cb9-5ce3-46ba-8c0d-bddafc87fce5: + uuid: a6a77cb9-5ce3-46ba-8c0d-bddafc87fce5 + region: content + configuration: + id: 'extra_field_block:node:book:content_moderation_control' + label_display: '0' + context_mapping: + entity: layout_builder.entity + weight: 0 + additional: { } + f1d6ab5d-4531-4061-b90d-766c806e9ceb: + uuid: f1d6ab5d-4531-4061-b90d-766c806e9ceb + region: content + configuration: + id: 'extra_field_block:node:book:links' + label_display: '0' + context_mapping: + entity: layout_builder.entity + weight: 1 + additional: { } + 00f097fe-a6e5-4467-9d3c-2a1dacd2a827: + uuid: 00f097fe-a6e5-4467-9d3c-2a1dacd2a827 + region: content + configuration: + id: 'field_block:node:book:body' + label_display: '0' + context_mapping: + entity: layout_builder.entity + formatter: + type: text_default + label: hidden + settings: { } + third_party_settings: { } + weight: 2 + additional: { } + third_party_settings: { } + - + layout_id: bs_1col + layout_settings: + label: '' + context_mapping: { } + layout: + wrapper: div + classes: + row: row + add_layout_class: 1 + attributes: '' + regions: + main: + wrapper: div + classes: + col-sm-12: col-sm-12 + add_region_classes: 1 + attributes: '' + components: + 0eade8e1-27b0-4519-9287-610ab4abd46a: + uuid: 0eade8e1-27b0-4519-9287-610ab4abd46a + region: main + configuration: + id: 'extra_field_block:node:book:book_navigation' + label: 'Book navigation' + label_display: '0' + provider: layout_builder + context_mapping: + entity: layout_builder.entity + formatter: + settings: { } + third_party_settings: { } + weight: 0 + additional: { } + third_party_settings: { } + layout_library: + enable: false +id: node.book.full +targetEntityType: node +bundle: book +mode: full +content: + body: + type: text_default + label: hidden + settings: { } + third_party_settings: { } + weight: 101 + region: content + content_moderation_control: + settings: { } + third_party_settings: { } + weight: -20 + region: content + links: + settings: { } + third_party_settings: { } + weight: 100 + region: content +hidden: + book_navigation: true + footnotes_group: true + langcode: true + search_api_excerpt: true diff --git a/modules/custom/wxt_ext/wxt_ext_book/config/optional/field.field.node.book.body.yml b/modules/custom/wxt_ext/wxt_ext_book/config/optional/field.field.node.book.body.yml new file mode 100644 index 00000000..d80c5a4d --- /dev/null +++ b/modules/custom/wxt_ext/wxt_ext_book/config/optional/field.field.node.book.body.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - filter.format.footnote + - filter.format.full_html + - filter.format.rich_text + - node.type.book + module: + - text +id: node.book.body +field_name: body +entity_type: node +bundle: book +label: Body +description: 'A description for this book page.' +required: true +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: false + required_summary: false + allowed_formats: + - rich_text + - full_html + - footnote +field_type: text_with_summary diff --git a/modules/custom/wxt_ext/wxt_ext_book/config/rewrite/node.type.book.yml b/modules/custom/wxt_ext/wxt_ext_book/config/optional/node.type.book.yml similarity index 80% rename from modules/custom/wxt_ext/wxt_ext_book/config/rewrite/node.type.book.yml rename to modules/custom/wxt_ext/wxt_ext_book/config/optional/node.type.book.yml index 0bc003bd..feab13a1 100644 --- a/modules/custom/wxt_ext/wxt_ext_book/config/rewrite/node.type.book.yml +++ b/modules/custom/wxt_ext/wxt_ext_book/config/optional/node.type.book.yml @@ -1,21 +1,20 @@ -config_rewrite: replace langcode: en status: true dependencies: - enforced: - module: - - book module: - menu_ui + - wxt_ext_workflow third_party_settings: menu_ui: available_menus: - main parent: 'main:' + wxt_ext_workflow: + workflow: editorial name: 'Book page' type: book description: 'Books have a built-in hierarchical navigation (GC Subway).' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: false diff --git a/modules/custom/wxt_ext/wxt_ext_book/config/optional/pathauto.pattern.book.yml b/modules/custom/wxt_ext/wxt_ext_book/config/optional/pathauto.pattern.book.yml new file mode 100644 index 00000000..6066062b --- /dev/null +++ b/modules/custom/wxt_ext/wxt_ext_book/config/optional/pathauto.pattern.book.yml @@ -0,0 +1,23 @@ +langcode: en +status: true +dependencies: + module: + - node +id: book +label: Book +type: 'canonical_entities:node' +pattern: '[node:book:parents:join-path]/[node:title]' +selection_criteria: + de676eda-f1c9-4740-8f70-f4b1dc2943c1: + id: 'entity_bundle:node' + negate: false + uuid: de676eda-f1c9-4740-8f70-f4b1dc2943c1 + context_mapping: + node: node + bundles: + book: book +selection_logic: and +weight: -10 +relationships: + 'node:langcode:language': + label: Language diff --git a/modules/custom/wxt_ext/wxt_ext_book/config/rewrite/book.settings.yml b/modules/custom/wxt_ext/wxt_ext_book/config/rewrite/book.settings.yml index 457feceb..4a58f57d 100644 --- a/modules/custom/wxt_ext/wxt_ext_book/config/rewrite/book.settings.yml +++ b/modules/custom/wxt_ext/wxt_ext_book/config/rewrite/book.settings.yml @@ -1,5 +1,10 @@ -allowed_type_book: 1 -block: - navigation: - mode: 'all pages' -child_type_book: book +allowed_types: + - + content_type: book + child_type: book + - + content_type: page + child_type: page +book_sort: weight +truncate_label: true +use_parent_selector: true diff --git a/modules/custom/wxt_ext/wxt_ext_book/wxt_ext_book.install b/modules/custom/wxt_ext/wxt_ext_book/wxt_ext_book.install new file mode 100644 index 00000000..1a9af73c --- /dev/null +++ b/modules/custom/wxt_ext/wxt_ext_book/wxt_ext_book.install @@ -0,0 +1,260 @@ +getStorage('entity_view_display'); + $display = $storage->load('node.page.full'); + + if (!$display || !$display instanceof LayoutEntityDisplayInterface) { + return; + } + + $lb = $display->getThirdPartySettings('layout_builder'); + if (empty($lb['enabled'])) { + return; + } + + // Idempotent: if any section already contains BOTH components, do nothing. + foreach ($display->getSections() as $section) { + if (_wxt_ext_book_section_has_component_id($section, 'extra_field_block:node:page:links') && + _wxt_ext_book_section_has_component_id($section, 'extra_field_block:node:page:book_navigation')) { + return; + } + } + + $new_section = _wxt_ext_book_build_bs_1col_container_section(); + $new_section->appendComponent(_wxt_ext_book_build_extra_field_component( + 'extra_field_block:node:page:links', + 'Links', + 0 + )); + $new_section->appendComponent(_wxt_ext_book_build_extra_field_component( + 'extra_field_block:node:page:book_navigation', + 'Book navigation', + 1 + )); + + // Insert after the original section (index 0). If no sections exist, insert at 0. + $insert_index = count($display->getSections()) > 0 ? 1 : 0; + + // Your class supports insertSection() (you verified). + $display->insertSection($insert_index, $new_section); + $display->save(); +} + +/** + * Insert a new bs_1col section at index 1 on layout library template. + */ +function _wxt_ext_book_patch_layout_library(EntityTypeManagerInterface $etm): void { + $config_name = 'layout_library.layout.node_page_full_default'; + $config = \Drupal::configFactory()->getEditable($config_name); + + // If it doesn't exist yet, bail. + if ($config->isNew()) { + return; + } + + $layout_array = $config->get('layout') ?? []; + if (!is_array($layout_array)) { + $layout_array = []; + } + + // Idempotent check. + $already = FALSE; + foreach ($layout_array as $section_array) { + $components = $section_array['components'] ?? []; + $has_links = FALSE; + $has_book_nav = FALSE; + foreach ($components as $c) { + $id = $c['configuration']['id'] ?? ''; + $has_links = $has_links || ($id === 'extra_field_block:node:page:links'); + $has_book_nav = $has_book_nav || ($id === 'extra_field_block:node:page:book_navigation'); + } + if ($has_links && $has_book_nav) { + $already = TRUE; + break; + } + } + + if ($already) { + return; + } + + $new_section = _wxt_ext_book_build_bs_1col_container_section_array_with_components(); + $insert_index = count($layout_array) > 0 ? 1 : 0; + array_splice($layout_array, $insert_index, 0, [$new_section]); + + $config->set('layout', $layout_array); + $config->save(); + +} + +/** + * Build a bs_1col Layout Builder Section matching your YAML. + */ +function _wxt_ext_book_build_bs_1col_container_section(): Section { + return new Section('bs_1col', [ + 'label' => '', + 'context_mapping' => [], + 'layout' => [ + 'wrapper' => 'div', + 'classes' => [ + 'row' => 'row', + ], + 'add_layout_class' => 1, + 'attributes' => '', + ], + 'regions' => [ + 'main' => [ + 'wrapper' => 'div', + 'classes' => [ + 'col-sm-12' => 'col-sm-12', + ], + 'add_region_classes' => 1, + 'attributes' => '', + ], + ], + ]); +} + +/** + * Build a Layout Builder component for an extra field block. + */ +function _wxt_ext_book_build_extra_field_component(string $plugin_id, string $label, int $weight): SectionComponent { + $uuid = \Drupal::service('uuid')->generate(); + + $component = new SectionComponent($uuid, 'main', [ + 'id' => $plugin_id, + 'label' => $label, + 'label_display' => '0', + 'provider' => 'layout_builder', + 'context_mapping' => [ + 'entity' => 'layout_builder.entity', + ], + 'formatter' => [ + 'settings' => [], + 'third_party_settings' => [], + ], + ]); + $component->setWeight($weight); + + return $component; +} + +/** + * Helper: check whether a Section already contains a component id. + */ +function _wxt_ext_book_section_has_component_id(Section $section, string $plugin_id): bool { + foreach ($section->getComponents() as $component) { + $conf = $component->get('configuration'); + if (($conf['id'] ?? '') === $plugin_id) { + return TRUE; + } + } + return FALSE; +} + +/** + * Build the layout library section as an array with both components. + */ +function _wxt_ext_book_build_bs_1col_container_section_array_with_components(): array { + $uuid_links = \Drupal::service('uuid')->generate(); + $uuid_book = \Drupal::service('uuid')->generate(); + + return [ + 'layout_id' => 'bs_1col', + 'layout_settings' => [ + 'label' => '', + 'context_mapping' => [], + 'layout' => [ + 'wrapper' => 'div', + 'classes' => [ + 'row' => 'row', + ], + 'add_layout_class' => 1, + 'attributes' => '', + ], + 'regions' => [ + 'main' => [ + 'wrapper' => 'div', + 'classes' => [ + 'col-sm-12' => 'col-sm-12', + ], + 'add_region_classes' => 1, + 'attributes' => '', + ], + ], + ], + 'components' => [ + $uuid_links => [ + 'uuid' => $uuid_links, + 'region' => 'main', + 'configuration' => [ + 'id' => 'extra_field_block:node:page:links', + 'label' => 'Links', + 'label_display' => '0', + 'provider' => 'layout_builder', + 'context_mapping' => [ + 'entity' => 'layout_builder.entity', + ], + 'formatter' => [ + 'settings' => [], + 'third_party_settings' => [], + ], + ], + 'weight' => 0, + 'additional' => [], + ], + $uuid_book => [ + 'uuid' => $uuid_book, + 'region' => 'main', + 'configuration' => [ + 'id' => 'extra_field_block:node:page:book_navigation', + 'label' => 'Book navigation', + 'label_display' => '0', + 'provider' => 'layout_builder', + 'context_mapping' => [ + 'entity' => 'layout_builder.entity', + ], + 'formatter' => [ + 'settings' => [], + 'third_party_settings' => [], + ], + ], + 'weight' => 1, + 'additional' => [], + ], + ], + 'third_party_settings' => [], + ]; +} diff --git a/modules/custom/wxt_ext/wxt_ext_book/wxt_ext_book.module b/modules/custom/wxt_ext/wxt_ext_book/wxt_ext_book.module index fe253f0b..5681a02a 100644 --- a/modules/custom/wxt_ext/wxt_ext_book/wxt_ext_book.module +++ b/modules/custom/wxt_ext/wxt_ext_book/wxt_ext_book.module @@ -14,8 +14,8 @@ function wxt_ext_book_preprocess_page(&$variables) { $node = \Drupal::routeMatch()->getParameter('node'); if ($node instanceof NodeInterface) { - if (!empty($node->book)) { - + $book_array = $node->getBook(); + if (!empty($book_array)) { $language = \Drupal::languageManager()->getCurrentLanguage()->getId(); if ($node->hasTranslation($language)) { @@ -23,7 +23,7 @@ function wxt_ext_book_preprocess_page(&$variables) { } // If book child page. - if ($node->book['bid'] != $node->id()) { + if ($book_array['bid'] != $node->id()) { $config = [ 'label_display' => '0', 'block_mode' => 'book pages', @@ -32,14 +32,14 @@ function wxt_ext_book_preprocess_page(&$variables) { $block = \Drupal::service('plugin.manager.block')->createInstance('book_navigation', $config); $variables['book_navigation'] = $block->build(); - $book = \Drupal::entityTypeManager()->getStorage('node')->load($node->book['bid']); + $book = \Drupal::entityTypeManager()->getStorage('node')->load($book_array['bid']); if ($book instanceof NodeInterface) { if ($book->hasTranslation($language)) { $book = $book->getTranslation($language); } - $book_title = $book->getTitle(); + $book_title = $book->label(); $variables['book_title'] = $book_title; } } @@ -54,10 +54,11 @@ function wxt_ext_book_preprocess_book_navigation(&$variables) { $node = \Drupal::routeMatch()->getParameter('node'); if ($node instanceof NodeInterface) { - if (!empty($node->book)) { + $book_array = $node->getBook(); + if (!empty($book_array)) { $config = \Drupal::config('wxt_ext_book.settings'); - if ($node->book['bid'] == $node->id()) { + if ($book_array['bid'] == $node->id()) { // Current page is the book index. $variables['is_book_index'] = TRUE; @@ -131,8 +132,11 @@ function _wxt_ext_book_set_variables_recursive(&$items, $active_nid) { function wxt_ext_book_preprocess_page_title(&$variables) { $node = \Drupal::routeMatch()->getParameter('node'); - if ($node instanceof NodeInterface && !empty($node->book)) { - $variables['is_book'] = TRUE; + if ($node instanceof NodeInterface) { + $book_array = $node->getBook(); + if (!empty($book_array)) { + $variables['is_book'] = TRUE; + } } }