diff --git a/css/search_api.admin.css b/css/search_api.admin.css index 4c8f52aa..26a130ae 100644 --- a/css/search_api.admin.css +++ b/css/search_api.admin.css @@ -91,3 +91,30 @@ details .messages { max-height: 30em; overflow: auto; } + +.button.button--search-api-link { + display: inline; + cursor: pointer; + padding: 0; + border: 0; + border-radius: 0; + box-shadow: none; + color: #0074BD; + background: none; + -webkit-appearance: none; + -moz-appearance: none; + font-weight: 400; + text-decoration: none; +} + +.button.button--search-api-link:hover, +.button.button--search-api-link:focus, +.button.button--search-api-link:active { + text-decoration: underline; + text-shadow: none; + padding: 0; + border: 0; + box-shadow: none; + color: #0074BD; + background: none; +} diff --git a/search_api.links.action.yml b/search_api.links.action.yml index 7d85b506..5b99f87f 100644 --- a/search_api.links.action.yml +++ b/search_api.links.action.yml @@ -13,8 +13,3 @@ search_api.execute_tasks: title: 'Execute pending tasks' appears_on: - search_api.overview -entity.search_api_index.add_fields: - route_name: entity.search_api_index.add_fields - title: 'Add fields' - appears_on: - - entity.search_api_index.fields diff --git a/search_api.routing.yml b/search_api.routing.yml index 34c34480..4c8c4e35 100644 --- a/search_api.routing.yml +++ b/search_api.routing.yml @@ -124,7 +124,19 @@ entity.search_api_index.fields: _entity_access: 'search_api_index.fields' entity.search_api_index.add_fields: - path: '/admin/config/search/search-api/index/{search_api_index}/fields/add' + path: '/admin/config/search/search-api/index/{search_api_index}/fields/add/nojs' + options: + parameters: + search_api_index: + tempstore: TRUE + type: 'entity:search_api_index' + defaults: + _entity_form: 'search_api_index.add_fields' + requirements: + _entity_access: 'search_api_index.fields' + +entity.search_api_index.add_fields_ajax: + path: '/admin/config/search/search-api/index/{search_api_index}/fields/add/ajax' options: parameters: search_api_index: @@ -136,7 +148,7 @@ entity.search_api_index.add_fields: _entity_access: 'search_api_index.fields' entity.search_api_index.field_config: - path: '/admin/config/search/search-api/index/{search_api_index}/fields/{field_id}/edit' + path: '/admin/config/search/search-api/index/{search_api_index}/fields/edit/{field_id}' options: parameters: search_api_index: @@ -149,7 +161,7 @@ entity.search_api_index.field_config: _entity_access: 'search_api_index.fields' entity.search_api_index.remove_field: - path: '/admin/config/search/search-api/index/{search_api_index}/fields/{field_id}/remove' + path: '/admin/config/search/search-api/index/{search_api_index}/fields/remove/{field_id}' options: parameters: search_api_index: diff --git a/search_api.theme.inc b/search_api.theme.inc index ebb0bd7e..0212e867 100644 --- a/search_api.theme.inc +++ b/search_api.theme.inc @@ -41,6 +41,7 @@ function theme_search_api_admin_fields_table($variables) { else { $rows[] = array( 'data' => $row, + 'data-field-row-id' => $name, 'title' => strip_tags($form['fields'][$name]['description']['#value']), ); } @@ -160,6 +161,9 @@ function theme_search_api_form_item_list(array $variables) { $build = array( '#theme' => 'item_list', ); + if (!empty($element['#title'])) { + $build['#title'] = $element['#title']; + } foreach (Element::children($element) as $key) { $build['#items'][$key] = $element[$key]; } diff --git a/src/Controller/IndexController.php b/src/Controller/IndexController.php index 07bdebdc..a1d9c487 100644 --- a/src/Controller/IndexController.php +++ b/src/Controller/IndexController.php @@ -3,9 +3,14 @@ namespace Drupal\search_api\Controller; use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\RemoveCommand; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\EventSubscriber\AjaxResponseSubscriber; use Drupal\search_api\IndexInterface; use Drupal\search_api\SearchApiException; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -14,6 +19,57 @@ class IndexController extends ControllerBase { /** + * The request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack|null + */ + protected $requestStack; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + /** @var static $controller */ + $controller = parent::create($container); + + $controller->setRequestStack($container->get('request_stack')); + + return $controller; + } + + /** + * Retrieves the request stack. + * + * @return \Symfony\Component\HttpFoundation\RequestStack + * The request stack. + */ + public function getRequestStack() { + return $this->requestStack ?: \Drupal::service('request_stack'); + } + + /** + * Retrieves the current request. + * + * @return \Symfony\Component\HttpFoundation\Request|null + */ + public function getRequest() { + return $this->getRequestStack()->getCurrentRequest(); + } + + /** + * Sets the request stack. + * + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The new request stack. + * + * @return $this + */ + public function setRequestStack(RequestStack $request_stack) { + $this->requestStack = $request_stack; + return $this; + } + + /** * Displays information about a search index. * * @param \Drupal\search_api\IndexInterface $search_api_index @@ -96,10 +152,12 @@ public function indexBypassEnable(IndexInterface $search_api_index) { */ public function removeField(IndexInterface $search_api_index, $field_id) { $fields = $search_api_index->getFields(); + $success = FALSE; if (isset($fields[$field_id])) { try { $search_api_index->removeField($field_id); $search_api_index->save(); + $success = TRUE; } catch (SearchApiException $e) { $args['%field'] = $fields[$field_id]->getLabel(); @@ -110,7 +168,13 @@ public function removeField(IndexInterface $search_api_index, $field_id) { throw new NotFoundHttpException(); } - // Redirect to the index's "View" page. + // If this is an AJAX request, just remove the row in question. + if ($success && $this->getRequest()->request->get(AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER)) { + $response = new AjaxResponse(); + $response->addCommand(new RemoveCommand("tr[data-field-row-id='$field_id']")); + return $response; + } + // Redirect to the index's "Fields" page. $url = $search_api_index->toUrl('fields'); return $this->redirect($url->getRouteName(), $url->getRouteParameters()); } diff --git a/src/Entity/Index.php b/src/Entity/Index.php index bbe53e78..69fe5192 100644 --- a/src/Entity/Index.php +++ b/src/Entity/Index.php @@ -78,7 +78,8 @@ * "add-form" = "/admin/config/search/search-api/add-index", * "edit-form" = "/admin/config/search/search-api/index/{search_api_index}/edit", * "fields" = "/admin/config/search/search-api/index/{search_api_index}/fields", - * "add-fields" = "/admin/config/search/search-api/index/{search_api_index}/fields/add", + * "add-fields" = "/admin/config/search/search-api/index/{search_api_index}/fields/add/nojs", + * "add-fields-ajax" = "/admin/config/search/search-api/index/{search_api_index}/fields/add/ajax", * "break-lock-form" = "/admin/config/search/search-api/index/{search_api_index}/fields/break-lock", * "processors" = "/admin/config/search/search-api/index/{search_api_index}/processors", * "delete-form" = "/admin/config/search/search-api/index/{search_api_index}/delete", diff --git a/src/Form/FieldConfigurationForm.php b/src/Form/FieldConfigurationForm.php index c81912f5..367bbc9b 100644 --- a/src/Form/FieldConfigurationForm.php +++ b/src/Form/FieldConfigurationForm.php @@ -2,11 +2,15 @@ namespace Drupal\search_api\Form; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Component\Utility\Html; use Drupal\Core\Datetime\DateFormatterInterface; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\EventSubscriber\MainContentViewSubscriber; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Url; use Drupal\search_api\Processor\ConfigurablePropertyInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -33,6 +37,13 @@ class FieldConfigurationForm extends EntityForm { protected $field; /** + * The "id" attribute of the generated form. + * + * @var string + */ + protected $formIdAttribute; + + /** * {@inheritdoc} */ public function getFormId() { @@ -76,9 +87,6 @@ public static function create(ContainerInterface $container) { public function buildForm(array $form, FormStateInterface $form_state) { $field = $this->getField(); - $args['%field'] = $field->getLabel(); - $form['#title'] = $this->t('Edit field %field', $args); - if (!$field) { $args['@id'] = $this->getRequest()->attributes->get('field_id'); $form['message'] = array( @@ -87,6 +95,21 @@ public function buildForm(array $form, FormStateInterface $form_state) { return $form; } + $args['%field'] = $field->getLabel(); + $form['#title'] = $this->t('Edit field %field', $args); + + if ($this->getRequest()->query->get('modal_redirect')) { + $form['title']['#markup'] = new FormattableMarkup('
The data type of a field determines how it can be used for searching and filtering. The boost is used to give additional weight to certain fields, for example titles or tags.
For information about the data types available for indexing, see the data types table at the bottom of the page.
', array('@url' => '#search-api-data-types-table')); if ($fields = $index->getFieldsByDatasource(NULL)) { @@ -284,12 +307,26 @@ protected function buildFieldsTable(array $fields) { // don't break the table structure. (theme_search_api_admin_fields_table() // does not add empty cells.) $build['fields'][$key]['edit']['#markup'] = ''; - if ($field->getDataDefinition() instanceof ConfigurablePropertyInterface) { - $build['fields'][$key]['edit'] = array( - '#type' => 'link', - '#title' => $this->t('Edit'), - '#url' => Url::fromRoute('entity.search_api_index.field_config', $route_parameters), - ); + try { + if ($field->getDataDefinition() instanceof ConfigurablePropertyInterface) { + $build['fields'][$key]['edit'] = array( + '#type' => 'link', + '#title' => $this->t('Edit'), + '#url' => Url::fromRoute('entity.search_api_index.field_config', $route_parameters), + '#attributes' => [ + 'class' => [ + 'use-ajax', + ], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + ]), + ], + ); + } + } + catch (SearchApiException $e) { + // Could not retrieve data definition: ignore. } $build['fields'][$key]['remove']['#markup'] = ''; if (!$field->isIndexedLocked()) { @@ -297,6 +334,9 @@ protected function buildFieldsTable(array $fields) { '#type' => 'link', '#title' => $this->t('Remove'), '#url' => Url::fromRoute('entity.search_api_index.remove_field', $route_parameters), + '#attributes' => array( + 'class' => array('use-ajax'), + ), ); } } diff --git a/tests/src/Functional/IntegrationTest.php b/tests/src/Functional/IntegrationTest.php index 3bfbd64f..32cb1507 100644 --- a/tests/src/Functional/IntegrationTest.php +++ b/tests/src/Functional/IntegrationTest.php @@ -1035,7 +1035,7 @@ protected function checkUnsavedChanges() { $this->assertSession()->responseContains($message_parts[0]); $this->assertSession()->responseContains($message_parts[1]); $this->assertFalse($this->xpath('//input[not(@disabled)]')); - $this->drupalGet($this->getIndexPath('fields/rendered_item/edit')); + $this->drupalGet($this->getIndexPath('fields/edit/rendered_item')); $this->assertSession()->responseContains($message_parts[0]); $this->assertSession()->responseContains($message_parts[1]); $this->assertFalse($this->xpath('//input[not(@disabled)]')); diff --git a/tests/src/Functional/ProcessorIntegrationTest.php b/tests/src/Functional/ProcessorIntegrationTest.php index a8cdb021..1208b73e 100644 --- a/tests/src/Functional/ProcessorIntegrationTest.php +++ b/tests/src/Functional/ProcessorIntegrationTest.php @@ -307,7 +307,7 @@ public function checkAggregatedFieldsIntegration() { $this->submitForm([], 'aggregated_field'); $args['%label'] = 'Aggregated field'; $this->assertSession()->responseContains(new FormattableMarkup('Field %label was added to the index.', $args)); - $this->assertSession()->addressEquals($this->getIndexPath('fields/aggregated_field/edit')); + $this->assertSession()->addressEquals($this->getIndexPath('fields/edit/aggregated_field')); $edit = [ 'type' => 'first', 'fields[entity:node/title]' => 'title', @@ -500,7 +500,7 @@ public function checkRenderedItemIntegration() { $this->submitForm([], 'rendered_item'); $args['%label'] = 'Rendered HTML output'; $this->assertSession()->responseContains(new FormattableMarkup('Field %label was added to the index.', $args)); - $this->assertSession()->addressEquals($this->getIndexPath('fields/rendered_item/edit')); + $this->assertSession()->addressEquals($this->getIndexPath('fields/edit/rendered_item')); $edit = [ 'roles[]' => ['authenticated'], 'view_mode[entity:node][article]' => 'default',