-
-
Notifications
You must be signed in to change notification settings - Fork 0
[#296] Let an automated list follow the topics of the page it is on. #301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
151ec5a
e796cb9
c7f0914
49a172d
4807205
9b0dfc9
5c6b827
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ dependencies: | |
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_link_below | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_site_sections | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_topics | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_topics_from_page | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Places the new checkbox on the paragraph edit form. Weight
The dependency line is added automatically by config export; it is not a separate decision. |
||
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_type | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_theme | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_title | ||
|
|
@@ -330,6 +331,13 @@ content: | |
| size: 60 | ||
| placeholder: '' | ||
| third_party_settings: { } | ||
| field_c_p_list_topics_from_page: | ||
| type: boolean_checkbox | ||
| weight: 6 | ||
| region: content | ||
| settings: | ||
| display_label: true | ||
| third_party_settings: { } | ||
| field_c_p_list_type: | ||
| type: options_select | ||
| weight: 12 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ dependencies: | |
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_link_below | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_site_sections | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_topics | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_topics_from_page | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hides the new field on the paragraph's view display. This is not a preference - it is required. Every setting field on this paragraph ( The dependency line above is added by config export. |
||
| - field.field.paragraph.civictheme_automated_list.field_c_p_list_type | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_theme | ||
| - field.field.paragraph.civictheme_automated_list.field_c_p_title | ||
|
|
@@ -187,4 +188,5 @@ hidden: | |
| field_c_p_list_feed_description: true | ||
| field_c_p_list_feed_slug: true | ||
| field_c_p_list_feed_title: true | ||
| field_c_p_list_topics_from_page: true | ||
| search_api_excerpt: true | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| uuid: e14c416c-d960-4b6f-997a-28b9d066b806 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The field that carries the whole feature. It is a plain boolean rather than a list of modes (
The description does the real work of preventing confusion: it states that turning this on ignores the Topics field, since nothing in the UI otherwise shows that Topics has gone inert. |
||
| langcode: en | ||
| status: true | ||
| dependencies: | ||
| config: | ||
| - field.storage.paragraph.field_c_p_list_topics_from_page | ||
| - paragraphs.paragraphs_type.civictheme_automated_list | ||
| id: paragraph.civictheme_automated_list.field_c_p_list_topics_from_page | ||
| field_name: field_c_p_list_topics_from_page | ||
| entity_type: paragraph | ||
| bundle: civictheme_automated_list | ||
| label: 'Use topics of the current page' | ||
| description: 'Lists content sharing a topic with the page this list is placed on, and leaves that page out. Turning this on ignores the Topics chosen below.' | ||
| required: false | ||
| translatable: false | ||
| default_value: | ||
| - | ||
| value: 0 | ||
| default_value_callback: '' | ||
| settings: | ||
| on_label: 'On' | ||
| off_label: 'Off' | ||
| field_type: boolean | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| uuid: 23a1da2f-ff37-48b0-8f36-884310059227 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storage for the new boolean. Nothing here was chosen by hand beyond the type and
|
||
| langcode: en | ||
| status: true | ||
| dependencies: | ||
| module: | ||
| - paragraphs | ||
| id: paragraph.field_c_p_list_topics_from_page | ||
| field_name: field_c_p_list_topics_from_page | ||
| entity_type: paragraph | ||
| type: boolean | ||
| settings: { } | ||
| module: core | ||
| locked: false | ||
| cardinality: 1 | ||
| translatable: true | ||
| indexes: { } | ||
| persist_with_no_fields: false | ||
| custom_storage: false | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| uuid: 5d9a1e32-1f80-40d0-8067-054765cf9763 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gives topic terms an alias, without which the "second stable inbound link" this issue asks for would be
Worth knowing: adding this pattern is not enough on its own. Most existing topics carry |
||
| langcode: en | ||
| status: true | ||
| dependencies: | ||
| config: | ||
| - taxonomy.vocabulary.civictheme_topics | ||
| module: | ||
| - taxonomy | ||
| id: civictheme_topics | ||
| label: Topic | ||
| type: 'canonical_entities:taxonomy_term' | ||
| pattern: '/topics/[term:name]' | ||
| selection_criteria: | ||
| 9f1d5c7a-3e4b-4a2d-8c6f-1b7e2a9d4c53: | ||
| id: 'entity_bundle:taxonomy_term' | ||
| negate: false | ||
| context_mapping: | ||
| taxonomy_term: taxonomy_term | ||
| bundles: | ||
| civictheme_topics: civictheme_topics | ||
| uuid: 9f1d5c7a-3e4b-4a2d-8c6f-1b7e2a9d4c53 | ||
| selection_logic: and | ||
| weight: -5 | ||
| relationships: { } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,24 +27,27 @@ display: | |
| display_options: | ||
| fields: { } | ||
| pager: | ||
| type: mini | ||
| type: full | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the widest change in the PR and the one most worth challenging.
I took that blast radius deliberately. The alternative was a dedicated view at The judgment that makes it acceptable is that no other vocabulary's terms are linked from anywhere on the site, so in practice only topic pages are reachable. If that is wrong - if a site section term page is linked somewhere I did not find - this changes a page you did not ask me to change. That is the assumption to check. Specifics:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Superseded by Now changed to exactly that. Worth knowing for anyone touching it: no node bundle has a The blast-radius point in the comment above still stands unchanged: this is core's shared term view, so it governs every vocabulary, and it rests on the assumption that no other vocabulary's term pages are linked from the site. One thing this made visible rather than introduced: the core feed attachment renders an unstyled "Subscribe to " link at the foot of the page. It was always in this view; it is only noticeable now that the page is reachable and styled. Not touched here. |
||
| options: | ||
| offset: 0 | ||
| pagination_heading_level: h4 | ||
| items_per_page: 10 | ||
| total_pages: 0 | ||
| items_per_page: 12 | ||
| total_pages: null | ||
| id: 0 | ||
| tags: | ||
| next: ›› | ||
| previous: ‹‹ | ||
| next: Next | ||
| previous: Previous | ||
| first: First | ||
| last: Last | ||
| expose: | ||
| items_per_page: false | ||
| items_per_page_label: 'Items per page' | ||
| items_per_page_options: '5, 10, 25, 50' | ||
| items_per_page_options: '6, 12, 24, 48' | ||
| items_per_page_options_all: false | ||
| items_per_page_options_all_label: '- All -' | ||
| offset: false | ||
| offset_label: Offset | ||
| quantity: 5 | ||
| exposed_form: | ||
| type: basic | ||
| options: | ||
|
|
@@ -214,16 +217,21 @@ display: | |
| default_group_multiple: { } | ||
| group_items: { } | ||
| style: | ||
| type: default | ||
| type: grid | ||
| options: | ||
| grouping: { } | ||
| row_class: '' | ||
| default_row_class: true | ||
| uses_fields: false | ||
| columns: 1 | ||
| automatic_width: true | ||
| alignment: horizontal | ||
| row_class_custom: '' | ||
| row_class_default: true | ||
| col_class_custom: '' | ||
| col_class_default: true | ||
| row: | ||
| type: 'entity:node' | ||
| options: | ||
| view_mode: teaser | ||
| relationship: none | ||
| view_mode: civictheme_snippet | ||
| query: | ||
| type: views_query | ||
| options: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| status: true | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lists topic pages in the XML sitemap. They were excluded before, correctly - they had no alias and core's teaser rendering, so there was nothing worth submitting.
Scoped to |
||
| priority: 0.5 | ||
| changefreq: 0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Related content and topic pages | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A new doc, because
Indexed in |
||
|
|
||
| Excluding the site navigation, most pages on this site had exactly one inbound internal link, and for the majority of them that link was a pager page. Nothing was unreachable, but a post published a while ago was reachable only by paging the blog listing. Two things address that, and they work together. | ||
|
|
||
| ## An Automated list can follow the page's own topics | ||
|
|
||
| The Automated list paragraph carries a **Use topics of the current page** checkbox (`field_c_p_list_topics_from_page`). With it on, the list ignores the Topics chosen on the paragraph and matches the topics of the node it is placed on instead, which turns the same component into a related-content list. Nothing new was built to do this: it is the Automated list, configured differently. | ||
|
|
||
| `drevops_civictheme_automated_list_view_alter()` in `web/themes/custom/drevops/includes/automated_list.inc` swaps the Topics contextual argument just before the view runs. CivicTheme fires that alter through the theme manager as well as the module handler, which is why this lives in the theme alongside the rest of the component's theming rather than in a module. | ||
|
|
||
| Points worth knowing before changing it: | ||
|
|
||
| - **The page being viewed is already excluded.** The view's fourth contextual filter is `nid` with `not` set, and it takes its value from the route, so a related list never lists the post it sits on. `_civictheme_automated_list__update_view()` passes only three arguments, which is what leaves that one to its route default. | ||
| - **The topics argument is position 1.** The arguments are ordered content type, topics, site sections. Reordering them in the view would silently point the swap at the wrong filter. | ||
| - **A post with no topics gets `none`, not `all`.** Without that, an empty topic set would fall through to the view's `all` default and advertise the whole site as related. | ||
| - **An empty result hides the heading.** `drevops_preprocess_paragraph__civictheme_automated_list()` clears the title when the list found nothing, so a post whose topics nothing else shares does not render a heading introducing empty space. | ||
|
|
||
| `do_base_deploy_add_related_lists()` puts one of these lists on every existing blog post. It is skipped for a post that already has one, so it is safe to re-run. | ||
|
|
||
| ## Topic pages | ||
|
|
||
| Topic terms are the second, stable inbound link: unlike a pager page, a topic page does not change what it points at as content is added. | ||
|
|
||
| - `pathauto.pattern.civictheme_topics` puts them at `/topics/<name>`. | ||
| - `do_base_deploy_alias_topic_terms()` hands every existing topic back to that pattern. Topics created programmatically carry `PathautoState::SKIP`, which is why most of them had no alias at all and a bulk generate would not give them one. | ||
| - The topic tags at the foot of a post link to these pages. `_drevops_node_add_topic_tags()` reads the referenced terms rather than their labels so each tag gets a `url`, which is what the `civictheme:tag` component turns into a link. | ||
| - `views.view.taxonomy_term` renders term pages as a CivicTheme promo-card grid with a full pager, rather than core's teaser list. This is the shared term view, so it applies to every vocabulary - acceptable because no other vocabulary's terms are linked from the site. | ||
| - Topic pages are listed in the XML sitemap. | ||
|
|
||
| ## Related | ||
|
|
||
| - [SEO](seo.md) - meta tags, social share cards and structured data | ||
| - [Development agreements](development.md) - deploy hook conventions these follow | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| @p0 @civictheme @civictheme_automated_list | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Behat rather than PHPUnit because the behaviour only exists once a real request has a node in the route: the alter reads Four scenarios, each isolating one decision rather than one feature:
The background builds a fourth post with no topics that should never appear, so scenario 2 fails if the topics argument is dropped entirely rather than merely mis-scoped. |
||
| Feature: Automated list following the page's own topics | ||
|
|
||
| As a site visitor | ||
| I want a post to link to others on the same topic | ||
| So that I can keep reading without going back to a paginated listing | ||
|
|
||
| Background: | ||
| Given the following "civictheme_topics" terms: | ||
| | name | | ||
| | [TEST] Shared Topic | | ||
| | [TEST] Other Topic | | ||
|
|
||
| And the following "blog" content: | ||
| | title | moderation_state | created | field_c_n_topics | | ||
| | [TEST] Post Being Read | published | [relative:-1 day] | [TEST] Shared Topic | | ||
| | [TEST] Post Same Topic | published | [relative:-2 days] | [TEST] Shared Topic | | ||
| | [TEST] Post Other Topic | published | [relative:-3 days] | [TEST] Other Topic | | ||
| | [TEST] Post Without Topic | published | [relative:-4 days] | | | ||
|
|
||
| And the following fields for the paragraph "civictheme_automated_list" exist in the field "field_c_n_components" within the "blog" "node" identified by the field "title" and the value "[TEST] Post Being Read": | ||
| | field_c_p_title | [TEST] Related posts | | ||
| | field_c_p_list_type | civictheme_automated_list__block1 | | ||
| | field_c_p_list_content_type | blog | | ||
| | field_c_p_list_limit_type | limited | | ||
| | field_c_p_list_limit | 3 | | ||
| | field_c_p_list_topics_from_page | 1 | | ||
|
|
||
| @api | ||
| Scenario: The list shows posts sharing the page's topic | ||
| Given I am an anonymous user | ||
|
|
||
| When I visit the "blog" content page with the title "[TEST] Post Being Read" | ||
| Then I should see the text "[TEST] Related posts" | ||
| And I should see "[TEST] Post Same Topic" | ||
|
|
||
| @api | ||
| Scenario: The list leaves out the page it is on and posts on other topics | ||
| Given I am an anonymous user | ||
|
|
||
| When I visit the "blog" content page with the title "[TEST] Post Being Read" | ||
| Then I should not see "[TEST] Post Other Topic" | ||
| And I should not see "[TEST] Post Without Topic" | ||
| And I should see 1 ".ct-promo-card" elements | ||
|
|
||
| @api | ||
| Scenario: A post whose topics nothing shares shows no heading | ||
| Given the following fields for the paragraph "civictheme_automated_list" exist in the field "field_c_n_components" within the "blog" "node" identified by the field "title" and the value "[TEST] Post Without Topic": | ||
| | field_c_p_title | [TEST] Related posts | | ||
| | field_c_p_list_type | civictheme_automated_list__block1 | | ||
| | field_c_p_list_content_type | blog | | ||
| | field_c_p_list_limit_type | limited | | ||
| | field_c_p_list_limit | 3 | | ||
| | field_c_p_list_topics_from_page | 1 | | ||
|
|
||
| And I am an anonymous user | ||
|
|
||
| When I visit the "blog" content page with the title "[TEST] Post Without Topic" | ||
| Then I should not see the text "[TEST] Related posts" | ||
|
|
||
| @api | ||
| Scenario: A post links to the topic pages it belongs to | ||
| Given I am an anonymous user | ||
|
|
||
| When I visit the "blog" content page with the title "[TEST] Post Being Read" | ||
| Then I should see the link "[TEST] Shared Topic" | ||
|
|
||
| When I click "[TEST] Shared Topic" | ||
| Then the path should be "/topics/test-shared-topic" | ||
| And I should see "[TEST] Post Same Topic" | ||
| And I should see "[TEST] Post Being Read" | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| And I should not see "[TEST] Post Other Topic" | ||
| And I should not see "[TEST] Post Without Topic" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| use Drupal\media\MediaInterface; | ||
| use Drupal\menu_link_content\MenuLinkContentInterface; | ||
| use Drupal\node\NodeInterface; | ||
| use Drupal\paragraphs\Entity\Paragraph; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two deploy hooks, both existing only to bring existing content up to what the new code assumes. Neither is needed for a fresh install.
Two details in that hook that look odd:
Both follow the batching and reporting conventions in |
||
| use Drupal\paragraphs\ParagraphInterface; | ||
| use Drupal\path_alias\PathAliasInterface; | ||
| use Drupal\pathauto\PathautoState; | ||
|
|
@@ -1249,3 +1250,82 @@ function _do_base_blog_reindex(int|string $nid, array $langcodes): void { | |
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Gives every topic term a URL alias so topic pages are reachable. | ||
| */ | ||
| function do_base_deploy_alias_topic_terms(?array &$sandbox = NULL): ?string { | ||
| $query = \Drupal::entityQuery('taxonomy_term') | ||
| ->accessCheck(FALSE) | ||
| ->condition('vid', 'civictheme_topics'); | ||
|
|
||
| return Helper::entity($sandbox, 25)->batchQuery($query, static function (TermInterface $term): void { | ||
| _do_base_alias_topic_term($term); | ||
| }, status: Reporter::UPDATED); | ||
| } | ||
|
|
||
| /** | ||
| * Puts a topic term back under the alias pattern and saves it. | ||
| */ | ||
| function _do_base_alias_topic_term(TermInterface $term): void { | ||
| if (!$term->hasField('path')) { | ||
| return; | ||
| } | ||
|
|
||
| // Terms created programmatically carry SKIP, which is why most topics have | ||
| // no alias at all. Handing them back to the pattern is what puts them on | ||
| // /topics/<name>, and it keeps them there when an editor renames one. | ||
| $term->set('path', ['pathauto' => PathautoState::CREATE]); | ||
| $term->save(); | ||
| } | ||
|
|
||
| /** | ||
| * Adds a related-content list to the foot of every blog post. | ||
| */ | ||
| function do_base_deploy_add_related_lists(?array &$sandbox = NULL): ?string { | ||
| $query = \Drupal::entityQuery('node') | ||
| ->accessCheck(FALSE) | ||
| ->condition('type', 'blog'); | ||
|
|
||
| return Helper::entity($sandbox, 10)->batchQuery($query, static function (NodeInterface $node): void { | ||
| _do_base_add_related_list($node); | ||
| }, status: Reporter::UPDATED); | ||
| } | ||
|
|
||
| /** | ||
| * Appends an Automated list configured to follow the post's own topics. | ||
| */ | ||
| function _do_base_add_related_list(NodeInterface $node): void { | ||
| if (!$node->hasField('field_c_n_components')) { | ||
| return; | ||
| } | ||
|
|
||
| foreach ($node->get('field_c_n_components')->referencedEntities() as $existing) { | ||
| if ($existing instanceof ParagraphInterface && $existing->bundle() === 'civictheme_automated_list' && !$existing->get('field_c_p_list_topics_from_page')->isEmpty() && (bool) $existing->get('field_c_p_list_topics_from_page')->value) { | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| $paragraph = Paragraph::create([ | ||
| 'type' => 'civictheme_automated_list', | ||
| 'field_c_p_title' => 'Related posts', | ||
| 'field_c_p_list_type' => 'civictheme_automated_list__block1', | ||
| 'field_c_p_list_content_type' => 'blog', | ||
| 'field_c_p_list_limit_type' => 'limited', | ||
| 'field_c_p_list_limit' => 3, | ||
| 'field_c_p_list_column_count' => 3, | ||
| 'field_c_p_list_topics_from_page' => 1, | ||
| ]); | ||
| $paragraph->setParentEntity($node, 'field_c_n_components'); | ||
| $paragraph->save(); | ||
|
|
||
| $components = $node->get('field_c_n_components')->getValue(); | ||
| $components[] = [ | ||
| 'target_id' => $paragraph->id(), | ||
| 'target_revision_id' => $paragraph->getRevisionId(), | ||
| ]; | ||
|
|
||
| $node->set('field_c_n_components', $components); | ||
| $node->setNewRevision(FALSE); | ||
| $node->save(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ require_once __DIR__ . '/includes/banner.inc'; | |
| require_once __DIR__ . '/includes/paragraphs.inc'; | ||
| require_once __DIR__ . '/includes/divider.inc'; | ||
| require_once __DIR__ . '/includes/manual_list.inc'; | ||
| require_once __DIR__ . '/includes/automated_list.inc'; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One Placed next to |
||
| require_once __DIR__ . '/includes/steps.inc'; | ||
| require_once __DIR__ . '/includes/page.inc'; | ||
| require_once __DIR__ . '/includes/node.inc'; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One line in the docs index, because
AGENTS.mdenumerates every file indocs/with a one-line description and a new doc that is not listed there is a doc nobody finds. Nothing else in this file changed.