diff --git a/core/modules/contextual/js/contextual.es6.js b/core/modules/contextual/js/contextual.es6.js index 2b26ee2a83..ebe731db0a 100644 --- a/core/modules/contextual/js/contextual.es6.js +++ b/core/modules/contextual/js/contextual.es6.js @@ -186,7 +186,7 @@ // Drupal.contextual.collection. window.setTimeout(() => { initContextual( - $context.find(`[data-contextual-id="${contextualID.id}"]`), + $context.find(`[data-contextual-id="${contextualID.id}"]:empty`).eq(0), html, ); }); diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js index 6264b74c20..396bc6b27d 100644 --- a/core/modules/contextual/js/contextual.js +++ b/core/modules/contextual/js/contextual.js @@ -107,7 +107,7 @@ var html = storage.getItem('Drupal.contextual.' + contextualID.id); if (html && html.length) { window.setTimeout(function () { - initContextual($context.find('[data-contextual-id="' + contextualID.id + '"]'), html); + initContextual($context.find('[data-contextual-id="' + contextualID.id + '"]:empty').eq(0), html); }); return; } @@ -156,4 +156,4 @@ $(document).on('drupalContextualLinkAdded', function (event, data) { Drupal.ajax.bindAjaxLinks(data.$el[0]); }); -})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage); \ No newline at end of file +})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage); diff --git a/core/modules/contextual/tests/modules/contextual_test/config/optional/views.view.contextual_recent.yml b/core/modules/contextual/tests/modules/contextual_test/config/optional/views.view.contextual_recent.yml new file mode 100644 index 0000000000..b3c1ea4c84 --- /dev/null +++ b/core/modules/contextual/tests/modules/contextual_test/config/optional/views.view.contextual_recent.yml @@ -0,0 +1,327 @@ +langcode: en +status: true +dependencies: + module: + - node + - user +id: contextual_recent +label: 'Recent content' +module: node +description: 'Recent content.' +tag: default +base_table: node_field_data +base_field: nid +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: some + options: + items_per_page: 10 + offset: 0 + style: + type: html_list + options: + grouping: { } + row_class: '' + default_row_class: true + type: ul + wrapper_class: item-list + class: '' + row: + type: fields + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + exclude: false + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + relationship: none + group_type: group + admin_label: '' + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + type: string + settings: + link_to_entity: true + plugin_id: field + changed: + id: changed + table: node_field_data + field: changed + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp_ago + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: changed + plugin_id: field + filters: + status_extra: + id: status_extra + table: node_field_data + field: status_extra + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: false + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + operator_limit_selection: false + operator_list: { } + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: node + plugin_id: node_status + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: + '***LANGUAGE_language_content***': '***LANGUAGE_language_content***' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + reduce: false + operator_limit_selection: false + operator_list: { } + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: node + entity_field: langcode + plugin_id: language + sorts: + changed: + id: changed + table: node_field_data + field: changed + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + granularity: second + entity_type: node + entity_field: changed + plugin_id: date + title: 'Recent content' + header: { } + footer: { } + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: 'No content available.' + plugin_id: text_custom + relationships: + uid: + id: uid + table: node_field_data + field: uid + relationship: none + group_type: group + admin_label: author + required: true + entity_type: node + entity_field: uid + plugin_id: standard + arguments: { } + display_extenders: { } + use_more: false + use_more_always: false + use_more_text: More + link_url: '' + link_display: '0' + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } + block_1: + display_plugin: block + id: block_1 + display_title: Block + position: 2 + display_options: + display_extenders: { } + defaults: + style: false + row: false + row: + type: 'entity:node' + options: + relationship: none + view_mode: teaser + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } diff --git a/core/modules/contextual/tests/src/FunctionalJavascript/DuplicateContextualLinksTest.php b/core/modules/contextual/tests/src/FunctionalJavascript/DuplicateContextualLinksTest.php new file mode 100644 index 0000000000..d5c6274228 --- /dev/null +++ b/core/modules/contextual/tests/src/FunctionalJavascript/DuplicateContextualLinksTest.php @@ -0,0 +1,58 @@ +drupalPlaceBlock('views_block:contextual_recent-block_1', ['id' => 'first']); + $this->drupalPlaceBlock('views_block:contextual_recent-block_1', ['id' => 'second']); + $this->drupalCreateContentType(['type' => 'page']); + $this->drupalCreateNode(); + $this->drupalLogin($this->drupalCreateUser([ + 'access content', + 'access contextual links', + 'administer nodes', + 'administer blocks', + 'administer views', + 'edit any page content', + ])); + // Ensure same contextual links work correct with fresh and cached page. + foreach (['fresh', 'cached'] as $state) { + $this->drupalGet('user'); + $contextual_id = '[data-contextual-id^="node:node=1"]'; + $this->assertJsCondition("(typeof jQuery !== 'undefined' && jQuery('[data-contextual-id]:empty').length === 0)"); + $this->getSession()->executeScript("jQuery('#block-first $contextual_id .trigger').trigger('click');"); + $contextual_links = $this->assertSession()->waitForElementVisible('css', "#block-first $contextual_id .contextual-links"); + $this->assertTrue($contextual_links->isVisible(), "Contextual links are visible with $state page."); + } + } + +}