From abbbe88e76e20e3d24d760372e5e3d51fc63b4e7 Mon Sep 17 00:00:00 2001
From: Brian Canini <canini.16@osu.edu>
Date: Thu, 23 Jan 2020 11:57:14 -0500
Subject: [PATCH] applying patch to linkit - Linkit for Link field:
 https://www.drupal.org/project/linkit/issues/2712951

---
 composer.json                                 |   3 +
 vendor/composer/installed.json                |   3 +
 web/modules/linkit/PATCHES.txt                |   7 +
 .../linkit/config/schema/linkit.schema.yml    |  18 +
 web/modules/linkit/js/linkit.autocomplete.js  |  53 ++-
 web/modules/linkit/linkit.module              |   2 +-
 web/modules/linkit/src/Entity/Profile.php     |  14 +
 .../Field/FieldFormatter/LinkitFormatter.php  | 188 ++++++++++
 .../Plugin/Field/FieldWidget/LinkitWidget.php | 178 ++++++++++
 .../Plugin/Linkit/Matcher/EmailMatcher.php    |   5 +-
 .../Plugin/Linkit/Matcher/EntityMatcher.php   |   6 +
 .../Linkit/Matcher/FrontPageMatcher.php       |  12 +-
 web/modules/linkit/src/ProfileInterface.php   |  11 +
 .../linkit/src/Utility/LinkitHelper.php       | 192 ++++++++++
 .../FunctionalJavascript/LinkFieldTest.php    | 334 ++++++++++++++++++
 .../FunctionalJavascript/LinkitDialogTest.php |   3 -
 .../src/Kernel/LinkitAutocompleteTest.php     |   3 +-
 .../src/Kernel/LinkitEditorLinkDialogTest.php |   2 +-
 18 files changed, 1014 insertions(+), 20 deletions(-)
 create mode 100644 web/modules/linkit/PATCHES.txt
 create mode 100644 web/modules/linkit/src/Plugin/Field/FieldFormatter/LinkitFormatter.php
 create mode 100644 web/modules/linkit/src/Plugin/Field/FieldWidget/LinkitWidget.php
 create mode 100644 web/modules/linkit/src/Utility/LinkitHelper.php
 create mode 100644 web/modules/linkit/tests/src/FunctionalJavascript/LinkFieldTest.php

diff --git a/composer.json b/composer.json
index 9a0b9757b1..2f545b2350 100644
--- a/composer.json
+++ b/composer.json
@@ -298,6 +298,9 @@
             },
             "drupal/smtp": {
               "2781157": "https://www.drupal.org/files/issues/2018-11-07/2781157-n10.patch"
+            },
+            "drupal/linkit": {
+              "2712951": "https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch"
             }
         }
     },
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 14fc650cb0..7508cad86e 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -5639,6 +5639,9 @@
                     "status": "not-covered",
                     "message": "Beta releases are not covered by Drupal security advisories."
                 }
+            },
+            "patches_applied": {
+                "2712951": "https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch"
             }
         },
         "installation-source": "dist",
diff --git a/web/modules/linkit/PATCHES.txt b/web/modules/linkit/PATCHES.txt
new file mode 100644
index 0000000000..2c2692ba41
--- /dev/null
+++ b/web/modules/linkit/PATCHES.txt
@@ -0,0 +1,7 @@
+This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
+Patches applied to this directory:
+
+2712951
+Source: https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch
+
+
diff --git a/web/modules/linkit/config/schema/linkit.schema.yml b/web/modules/linkit/config/schema/linkit.schema.yml
index c6e3f1d10c..b8d487cbd0 100644
--- a/web/modules/linkit/config/schema/linkit.schema.yml
+++ b/web/modules/linkit/config/schema/linkit.schema.yml
@@ -112,3 +112,21 @@ ckeditor.plugin.drupallink:
     linkit_profile:
       type: string
       label: 'Linkit profile'
+
+# Schema for the Linkit widget.
+field.widget.settings.linkit:
+  type: field.widget.settings.link_default
+  label: 'Linkit widget settings'
+  mapping:
+    linkit_profile:
+      type: string
+      label: 'Linkit profile'
+
+# Schema for the Linkit formatter.
+field.formatter.settings.linkit:
+  type: field.formatter.settings.link
+  label: 'Linkit format settings'
+  mapping:
+    linkit_profile:
+      type: string
+      label: 'Linkit profile'
diff --git a/web/modules/linkit/js/linkit.autocomplete.js b/web/modules/linkit/js/linkit.autocomplete.js
index 558278ddff..b2ccca311d 100644
--- a/web/modules/linkit/js/linkit.autocomplete.js
+++ b/web/modules/linkit/js/linkit.autocomplete.js
@@ -63,21 +63,36 @@
    *   False to prevent further handlers.
    */
   function selectHandler(event, ui) {
-    var $form = $(event.target).closest('form');
+    var $context = $(event.target).closest('form,fieldset,tr');
+
     if (!ui.item.path) {
       throw 'Missing path param.' + JSON.stringify(ui.item);
     }
 
-    $('input[name="href_dirty_check"]', $form).val(ui.item.path);
+    $('input[name="href_dirty_check"]', $context).val(ui.item.path);
 
     if (ui.item.entity_type_id || ui.item.entity_uuid || ui.item.substitution_id) {
       if (!ui.item.entity_type_id || !ui.item.entity_uuid || !ui.item.substitution_id) {
         throw 'Missing path param.' + JSON.stringify(ui.item);
       }
-
-      $('input[name="attributes[data-entity-type]"]', $form).val(ui.item.entity_type_id);
-      $('input[name="attributes[data-entity-uuid]"]', $form).val(ui.item.entity_uuid);
-      $('input[name="attributes[data-entity-substitution]"]', $form).val(ui.item.substitution_id);
+    }
+    $('input[name="attributes[href]"], input[name$="[attributes][href]"]', $context).val(ui.item.path);
+    $('input[name="attributes[data-entity-type]"], input[name$="[attributes][data-entity-type]"]', $context).val(ui.item.entity_type_id);
+    $('input[name="attributes[data-entity-uuid]"], input[name$="[attributes][data-entity-uuid]"]', $context).val(ui.item.entity_uuid);
+    $('input[name="attributes[data-entity-substitution]"], input[name$="[attributes][data-entity-substitution]"]', $context).val(ui.item.substitution_id);
+
+    if (ui.item.label) {
+      // Automatically set the link title.
+      var $linkTitle = $(event.target).closest('.form-item').siblings('.form-type-textfield').find('.linkit-widget-title');
+      if ($linkTitle.length > 0) {
+        if (!$linkTitle.val() || $linkTitle.hasClass('link-widget-title--auto')) {
+          // Set value to the label.
+          $linkTitle.val(ui.item.label);
+
+          // Flag title as being automatically set.
+          $linkTitle.addClass('link-widget-title--auto');
+        }
+      }
     }
 
     event.target.value = ui.item.path;
@@ -158,7 +173,7 @@
       // Act on textfields with the "form-linkit-autocomplete" class.
       var $autocomplete = $(context).find('input.form-linkit-autocomplete').once('linkit-autocomplete');
       if ($autocomplete.length) {
-        $.widget('custom.autocomplete', $.ui.autocomplete, {
+        $.widget('ui.autocomplete', $.ui.autocomplete, {
           _create: function () {
             this._super();
             this.widget().menu('option', 'items', '> :not(.linkit-result-line--group)');
@@ -169,10 +184,30 @@
 
         // Use jQuery UI Autocomplete on the textfield.
         $autocomplete.autocomplete(autocomplete.options);
-        $autocomplete.autocomplete('widget').addClass('linkit-ui-autocomplete');
 
         $autocomplete.click(function () {
-          $autocomplete.autocomplete('search', $autocomplete.val());
+          var $this = $(this);
+          $this.autocomplete('search', $this.val());
+        });
+
+        // Process each item.
+        $autocomplete.each(function () {
+          var $uri = $(this);
+          $uri.autocomplete('widget').addClass('linkit-ui-autocomplete');
+
+          $uri.closest('.form-item').siblings('.form-type-textfield').find('.linkit-widget-title')
+            .each(function() {
+              // Set automatic title flag if title is the same as uri text.
+              var $title  = $(this);
+              var uriValue = $uri.val();
+              if (uriValue && uriValue === $title.val()) {
+                $title.addClass('link-widget-title--auto');
+              }
+            })
+            .change(function () {
+              // Remove automatic title flag.
+              $(this).removeClass('link-widget-title--auto');
+            });
         });
 
         $autocomplete.on('compositionstart.autocomplete', function () {
diff --git a/web/modules/linkit/linkit.module b/web/modules/linkit/linkit.module
index affeaebf4d..14d14258b1 100644
--- a/web/modules/linkit/linkit.module
+++ b/web/modules/linkit/linkit.module
@@ -133,7 +133,6 @@ function linkit_form_editor_link_dialog_submit(array &$form, FormStateInterface
   }
 
   $fields = [
-    'href',
     'data-entity-type',
     'data-entity-uuid',
     'data-entity-substitution',
@@ -150,4 +149,5 @@ function linkit_form_editor_link_dialog_submit(array &$form, FormStateInterface
       }
     }
   }
+
 }
diff --git a/web/modules/linkit/src/Entity/Profile.php b/web/modules/linkit/src/Entity/Profile.php
index 651242f830..070e98472a 100644
--- a/web/modules/linkit/src/Entity/Profile.php
+++ b/web/modules/linkit/src/Entity/Profile.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 use Drupal\linkit\MatcherCollection;
 use Drupal\linkit\MatcherInterface;
+use Drupal\linkit\Plugin\Linkit\Matcher\EntityMatcher;
 use Drupal\linkit\ProfileInterface;
 
 /**
@@ -108,6 +109,19 @@ public function getMatcher($instance_id) {
     return $this->getMatchers()->get($instance_id);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getMatcherByEntityType($entity_type_id) {
+    foreach ($this->getMatchers() as $matcher) {
+      if ($matcher instanceof EntityMatcher && $matcher->getPluginDefinition()['target_entity'] === $entity_type_id) {
+        return $matcher;
+      }
+    }
+
+    return NULL;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/web/modules/linkit/src/Plugin/Field/FieldFormatter/LinkitFormatter.php b/web/modules/linkit/src/Plugin/Field/FieldFormatter/LinkitFormatter.php
new file mode 100644
index 0000000000..775da51423
--- /dev/null
+++ b/web/modules/linkit/src/Plugin/Field/FieldFormatter/LinkitFormatter.php
@@ -0,0 +1,188 @@
+<?php
+
+namespace Drupal\linkit\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Path\PathValidatorInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\link\LinkItemInterface;
+use Drupal\link\Plugin\Field\FieldFormatter\LinkFormatter;
+use Drupal\linkit\Entity\Profile;
+use Drupal\linkit\SubstitutionManagerInterface;
+use Drupal\linkit\Utility\LinkitHelper;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Plugin implementation of the 'linkit' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "linkit",
+ *   label = @Translation("Linkit"),
+ *   field_types = {
+ *     "link"
+ *   }
+ * )
+ */
+class LinkitFormatter extends LinkFormatter implements ContainerFactoryPluginInterface {
+
+  /**
+   * The substitution manager.
+   *
+   * @var \Drupal\linkit\SubstitutionManagerInterface
+   */
+  protected $substitutionManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['label'],
+      $configuration['view_mode'],
+      $configuration['third_party_settings'],
+      $container->get('path.validator'),
+      $container->get('plugin.manager.linkit.substitution'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * Constructs a new LinkitFormatter.
+   *
+   * @param string $plugin_id
+   *   The plugin_id for the formatter.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The definition of the field to which the formatter is associated.
+   * @param array $settings
+   *   The formatter settings.
+   * @param string $label
+   *   The formatter label display setting.
+   * @param string $view_mode
+   *   The view mode.
+   * @param array $third_party_settings
+   *   Third party settings.
+   * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
+   *   The path validator service.
+   * @param \Drupal\linkit\SubstitutionManagerInterface $substitution_manager
+   *   The substitution manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, PathValidatorInterface $path_validator, SubstitutionManagerInterface $substitution_manager, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $path_validator);
+    $this->substitutionManager = $substitution_manager;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'linkit_profile' => 'default',
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $elements = parent::settingsForm($form, $form_state);
+
+    $linkit_profiles = $this->entityTypeManager->getStorage('linkit_profile')->loadMultiple();
+
+    $options = [];
+    foreach ($linkit_profiles as $linkit_profile) {
+      $options[$linkit_profile->id()] = $linkit_profile->label();
+    }
+
+    $elements['linkit_profile'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Linkit profile'),
+      '#description' => $this->t('Must be the same as the profile selected on the form display for this field.'),
+      '#options' => $options,
+      '#default_value' => $this->getSetting('linkit_profile'),
+    ];
+
+    return $elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = parent::settingsSummary();
+
+    $linkit_profile_id = $this->getSetting('linkit_profile');
+    $linkit_profile = $this->entityTypeManager->getStorage('linkit_profile')->load($linkit_profile_id);
+
+    if ($linkit_profile) {
+      $summary[] = $this->t('Linkit profile: @linkit_profile', ['@linkit_profile' => $linkit_profile->label()]);
+    }
+
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items, $langcode) {
+    $elements = parent::viewElements($items, $langcode);
+
+    // Loop over the elements and substitute the URL.
+    foreach ($elements as $delta => &$item) {
+      /** @var \Drupal\link\LinkItemInterface $link_item */
+      $link_item = $items->get($delta);
+      $substituted_url = $this->getSubstitutedUrl($link_item);
+      // Convert generated URL into a URL object.
+      if ($substituted_url && ($url = \Drupal::pathValidator()->getUrlIfValid($substituted_url->getGeneratedUrl()))) {
+        $item['#url'] = $url;
+      }
+    }
+
+    return $elements;
+  }
+
+  /**
+   * Returns a substitution URL for the given linked item.
+   *
+   * In case the items links to an entity use a substituted/generated URL.
+   *
+   * @param \Drupal\link\LinkItemInterface $item
+   *   The link item.
+   *
+   * @return \Drupal\Core\GeneratedUrl|null
+   *   The substitution URL, or NULL if not able to retrieve it from the item.
+   */
+  protected function getSubstitutedUrl(LinkItemInterface $item) {
+    if (parse_url($item->uri, PHP_URL_SCHEME) == 'entity') {
+      if ($entity = LinkitHelper::getEntityFromUri($item->uri)) {
+        $profile = Profile::load($this->getSettings()['linkit_profile']);
+
+        /** @var \\Drupal\linkit\Plugin\Linkit\Matcher\EntityMatcher $matcher */
+        $matcher = $profile->getMatcherByEntityType($entity->getEntityTypeId());
+        $substitution_type = $matcher ? $matcher->getConfiguration()['settings']['substitution_type'] : SubstitutionManagerInterface::DEFAULT_SUBSTITUTION;
+        return $this->substitutionManager->createInstance($substitution_type)->getUrl($entity);
+      }
+    }
+
+    return NULL;
+  }
+
+}
diff --git a/web/modules/linkit/src/Plugin/Field/FieldWidget/LinkitWidget.php b/web/modules/linkit/src/Plugin/Field/FieldWidget/LinkitWidget.php
new file mode 100644
index 0000000000..d2f7823bfb
--- /dev/null
+++ b/web/modules/linkit/src/Plugin/Field/FieldWidget/LinkitWidget.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace Drupal\linkit\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Url;
+use Drupal\link\Plugin\Field\FieldWidget\LinkWidget;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\linkit\Utility\LinkitHelper;
+
+/**
+ * Plugin implementation of the 'linkit' widget.
+ *
+ * @FieldWidget(
+ *   id = "linkit",
+ *   label = @Translation("Linkit"),
+ *   field_types = {
+ *     "link"
+ *   }
+ * )
+ */
+class LinkitWidget extends LinkWidget {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'linkit_profile' => 'default',
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    $item = $items[$delta];
+    $uri = $item->uri;
+    $uri_scheme = parse_url($uri, PHP_URL_SCHEME);
+    if (!empty($uri) && empty($uri_scheme)) {
+      $uri = LinkitHelper::uriFromUserInput($uri);
+      $uri_scheme = parse_url($uri, PHP_URL_SCHEME);
+    }
+    $uri_as_url = !empty($uri) ? Url::fromUri($uri)->toString() : '';
+    $linkit_profile_id = $this->getSetting('linkit_profile');
+
+    // The current field value could have been entered by a different user.
+    // However, if it is inaccessible to the current user, do not display it
+    // to them.
+    $default_allowed = !$item->isEmpty() && (\Drupal::currentUser()->hasPermission('link to any page') || $item->getUrl()->access());
+
+    if ($default_allowed && $uri_scheme == 'entity') {
+      $entity = LinkitHelper::getEntityFromUri($uri);
+    }
+
+    $element['uri'] = [
+      '#type' => 'linkit',
+      '#title' => $this->t('URL'),
+      '#placeholder' => $this->getSetting('placeholder_url'),
+      '#default_value' => $default_allowed ? $uri_as_url : NULL,
+      '#maxlength' => 2048,
+      '#required' => $element['#required'],
+      '#description' => $this->t('Start typing to find content or paste a URL and click on the suggestion below.'),
+      '#autocomplete_route_name' => 'linkit.autocomplete',
+      '#autocomplete_route_parameters' => [
+        'linkit_profile_id' => $linkit_profile_id,
+      ],
+      '#error_no_message' => TRUE,
+    ];
+
+    $element['attributes']['href'] = [
+      '#type' => 'hidden',
+      '#default_value' => $default_allowed ? $uri : '',
+    ];
+
+    $element['attributes']['data-entity-type'] = [
+      '#type' => 'hidden',
+      '#default_value' => $default_allowed && isset($entity) ? $entity->getEntityTypeId() : '',
+    ];
+
+    $element['attributes']['data-entity-uuid'] = [
+      '#type' => 'hidden',
+      '#default_value' => $default_allowed && isset($entity) ? $entity->uuid() : '',
+    ];
+
+    $element['attributes']['data-entity-substitution'] = [
+      '#type' => 'hidden',
+      '#default_value' => $default_allowed && isset($entity) ? $entity->getEntityTypeId() == 'file' ? 'file' : 'canonical' : '',
+    ];
+
+    $element['title'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Link text'),
+      '#placeholder' => $this->getSetting('placeholder_title'),
+      '#default_value' => isset($items[$delta]->title) ? $items[$delta]->title : NULL,
+      '#maxlength' => 255,
+      '#access' => $this->getFieldSetting('title') != DRUPAL_DISABLED,
+      '#attributes' => [
+        'class' => ['linkit-widget-title'],
+      ],
+      '#error_no_message' => TRUE,
+    ];
+    // Post-process the title field to make it conditionally required if URL is
+    // non-empty. Omit the validation on the field edit form, since the field
+    // settings cannot be saved otherwise.
+    if (!$this->isDefaultValueWidget($form_state) && $this->getFieldSetting('title') == DRUPAL_REQUIRED) {
+      $element['#element_validate'][] = [get_called_class(), 'validateTitleElement'];
+    }
+
+    // If cardinality is 1, ensure a proper label is output for the field.
+    if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() == 1) {
+      // If the link title is disabled, use the field definition label as the
+      // title of the 'uri' element.
+      if ($this->getFieldSetting('title') == DRUPAL_DISABLED) {
+        $element['uri']['#title'] = $element['#title'];
+      }
+      // Otherwise wrap everything in a details element.
+      else {
+        $element += [
+          '#type' => 'fieldset',
+        ];
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $elements = parent::settingsForm($form, $form_state);
+
+    $linkit_profiles = \Drupal::entityTypeManager()->getStorage('linkit_profile')->loadMultiple();
+
+    $options = [];
+    foreach ($linkit_profiles as $linkit_profile) {
+      $options[$linkit_profile->id()] = $linkit_profile->label();
+    }
+
+    $elements['linkit_profile'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Linkit profile'),
+      '#options' => $options,
+      '#default_value' => $this->getSetting('linkit_profile'),
+    ];
+
+    return $elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = parent::settingsSummary();
+
+    $linkit_profile_id = $this->getSetting('linkit_profile');
+    $linkit_profile = \Drupal::entityTypeManager()->getStorage('linkit_profile')->load($linkit_profile_id);
+
+    if ($linkit_profile) {
+      $summary[] = $this->t('Linkit profile: @linkit_profile', ['@linkit_profile' => $linkit_profile->label()]);
+    }
+
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
+    foreach ($values as &$value) {
+      $value['uri'] = LinkitHelper::uriFromUserInput($value['uri']);
+      $value += ['options' => []];
+    }
+    return $values;
+  }
+
+}
diff --git a/web/modules/linkit/src/Plugin/Linkit/Matcher/EmailMatcher.php b/web/modules/linkit/src/Plugin/Linkit/Matcher/EmailMatcher.php
index c9513d94d0..d6ea361306 100644
--- a/web/modules/linkit/src/Plugin/Linkit/Matcher/EmailMatcher.php
+++ b/web/modules/linkit/src/Plugin/Linkit/Matcher/EmailMatcher.php
@@ -23,11 +23,14 @@ class EmailMatcher extends MatcherBase {
   public function execute($string) {
     $suggestions = new SuggestionCollection();
 
+    // Strip the mailto: prefix to match only the e-mail part of the string.
+    $string = str_replace('mailto:', '', $string);
+
     // Check for an e-mail address then return an e-mail match and create a
     // mail-to link if appropriate.
     if (filter_var($string, FILTER_VALIDATE_EMAIL)) {
       $suggestion = new DescriptionSuggestion();
-      $suggestion->setLabel($this->t('E-mail @email', ['@email' => $string]))
+      $suggestion->setLabel($string)
         ->setPath('mailto:' . Html::escape($string))
         ->setGroup($this->t('E-mail'))
         ->setDescription($this->t('Opens your mail client ready to e-mail @email', ['@email' => $string]));
diff --git a/web/modules/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php b/web/modules/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php
index 9d56a479b6..84c9a44bba 100644
--- a/web/modules/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php
+++ b/web/modules/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php
@@ -344,6 +344,12 @@ public function execute($string) {
 
       $entity = $this->entityRepository->getTranslationFromContext($entity);
       $suggestion = $this->createSuggestion($entity);
+      if ($query = parse_url($string, PHP_URL_QUERY)) {
+        $suggestion->setPath($suggestion->getPath() . '?' . $query);
+      }
+      if ($fragment = parse_url($string, PHP_URL_FRAGMENT)) {
+        $suggestion->setPath($suggestion->getPath() . '#' . $fragment);
+      }
       $suggestions->addSuggestion($suggestion);
     }
 
diff --git a/web/modules/linkit/src/Plugin/Linkit/Matcher/FrontPageMatcher.php b/web/modules/linkit/src/Plugin/Linkit/Matcher/FrontPageMatcher.php
index dd3ca15eaa..90347b6ed6 100644
--- a/web/modules/linkit/src/Plugin/Linkit/Matcher/FrontPageMatcher.php
+++ b/web/modules/linkit/src/Plugin/Linkit/Matcher/FrontPageMatcher.php
@@ -2,10 +2,10 @@
 
 namespace Drupal\linkit\Plugin\Linkit\Matcher;
 
-use Drupal\Core\Url;
 use Drupal\linkit\MatcherBase;
 use Drupal\linkit\Suggestion\DescriptionSuggestion;
 use Drupal\linkit\Suggestion\SuggestionCollection;
+use Drupal\linkit\Utility\LinkitHelper;
 
 /**
  * Provides specific linkit matchers for the front page.
@@ -22,12 +22,18 @@ class FrontPageMatcher extends MatcherBase {
    */
   public function execute($string) {
     $suggestions = new SuggestionCollection();
+    $front_path = '/';
+    $query_and_fragment = LinkitHelper::getQueryAndFragment($string);
+
+    if (!empty($query_and_fragment)) {
+      $string = substr($string, 0, strpos($string, $query_and_fragment));
+    }
 
     // Special for link to front page.
-    if (strpos($string, 'front') !== FALSE) {
+    if (strpos($string, 'front') !== FALSE || $string == $front_path) {
       $suggestion = new DescriptionSuggestion();
       $suggestion->setLabel($this->t('Front page'))
-        ->setPath(Url::fromRoute('<front>')->toString())
+        ->setPath($front_path . $query_and_fragment)
         ->setGroup($this->t('System'))
         ->setDescription($this->t('The front page for this site.'));
 
diff --git a/web/modules/linkit/src/ProfileInterface.php b/web/modules/linkit/src/ProfileInterface.php
index de8c5da84d..3d2e8cd014 100644
--- a/web/modules/linkit/src/ProfileInterface.php
+++ b/web/modules/linkit/src/ProfileInterface.php
@@ -38,6 +38,17 @@ public function setDescription($description);
    */
   public function getMatcher($instance_id);
 
+  /**
+   * Returns the first enabled matcher for the given entity type ID.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return \Drupal\linkit\Plugin\Linkit\Matcher\EntityMatcher|null
+   *   An entity matcher instance or null if not found.
+   */
+  public function getMatcherByEntityType($entity_type_id);
+
   /**
    * Returns the matchers for this profile.
    *
diff --git a/web/modules/linkit/src/Utility/LinkitHelper.php b/web/modules/linkit/src/Utility/LinkitHelper.php
new file mode 100644
index 0000000000..56e9fab5bc
--- /dev/null
+++ b/web/modules/linkit/src/Utility/LinkitHelper.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Drupal\linkit\Utility;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Url;
+
+/**
+ * Provides helper to operate on URIs.
+ */
+class LinkitHelper {
+
+  /**
+   * Load the entity referenced by an entity scheme uri.
+   *
+   * @param string $uri
+   *   An internal uri string representing an entity path, such as
+   *   "entity:node/23".
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|null
+   *   The most appropriate translation of the entity that matches the given
+   *   uri, or NULL if could not match any entity.
+   */
+  public static function getEntityFromUri($uri) {
+    // Stripe out potential query and fragment from the uri.
+    $uri = strtok(strtok($uri, "?"), "#");
+    list($entity_type, $entity_id) = explode('/', substr($uri, 7), 2);
+    $entity_manager = \Drupal::entityTypeManager();
+    if ($entity_manager->getDefinition($entity_type, FALSE)) {
+      if ($entity = $entity_manager->getStorage($entity_type)->load($entity_id)) {
+        return \Drupal::service('entity.repository')->getTranslationFromContext($entity);
+      }
+    }
+
+    return NULL;
+  }
+
+  /**
+   * Returns a processed uri with a proper scheme (if applicable).
+   *
+   * Turns the internal links into uri strings.
+   *
+   * @param string $input
+   *   The raw (or processed) input.
+   *
+   * @return string|null
+   *   The uri string or null if the input is empty.
+   */
+  public static function uriFromUserInput($input) {
+    if (empty($input)) {
+      return NULL;
+    }
+
+    $host = parse_url($input, PHP_URL_HOST);
+    $scheme = parse_url($input, PHP_URL_SCHEME);
+
+    if ($scheme == 'mailto') {
+      return $input;
+    }
+
+    if (UrlHelper::isExternal($input)) {
+      if (UrlHelper::externalIsLocal($input, \Drupal::request()->getSchemeAndHttpHost())) {
+        // The link points to this domain. Make it relative to perform an entity
+        // lookup.
+        $host_end = strpos($input, $host) + strlen($host);
+        $input = substr($input, $host_end);
+      }
+      else {
+        // This link is really external.
+        return $input;
+      }
+    }
+
+    // Make sure the URI starts with a slash, otherwise the Url's factory
+    // methods will throw exceptions.
+    $starts_with_a_slash = strpos($input, '/') === 0;
+    $is_front = substr($input, 0, 7) === '<front>';
+    if (!$scheme && !$is_front && !$starts_with_a_slash) {
+      $input = "/$input";
+    }
+
+    $entity = self::getEntityFromUserInput($input);
+    if ($entity) {
+      return 'entity:' . $entity->getEntityTypeId() . '/' . $entity->id() . static::getQueryAndFragment($input);
+    }
+
+    // It's a relative link. If it's a file, store it as `base:`. Otherwise it's
+    // most likely internal.
+    $public_files_dir = \Drupal::service('stream_wrapper_manager')
+      ->getViaScheme('public')
+      ->getDirectoryPath();
+    if (strpos($input, "/$public_files_dir") === 0) {
+      return "base:$input";
+    }
+    else {
+      return "internal:$input";
+    }
+  }
+
+  /**
+   * Tries to convert an uri into an entity in multiple ways.
+   *
+   * @param string $input
+   *   A uri or a path.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|null
+   *   The entity if found, null otherwise.
+   */
+  public static function getEntityFromUserInput($input) {
+    $scheme = parse_url($input, PHP_URL_SCHEME);
+
+    // Check if it's an entity URI (e.g. entity:node/1).
+    if ($scheme === 'entity' && ($entity = static::getEntityFromUri($input))) {
+      return $entity;
+    }
+
+    // If not, it can be a path pointing to an entity.
+    if (!$scheme) {
+      // Which can be hidden behind an alias in any of the site's languages.
+      $input = 'internal:' . static::getPathByAlias($input);
+    }
+
+    try {
+      $route_name = Url::fromUri($input)->getRouteName();
+      if ($route_name != "entity.node.edit_form" && $route_name != "node.add") {
+        $params = Url::fromUri($input)->getRouteParameters();
+        $possibly_an_entity_type = key($params);
+        $entity = \Drupal::entityTypeManager()
+          ->getStorage($possibly_an_entity_type)
+          ->load($params[$possibly_an_entity_type]);
+        return \Drupal::service('entity.repository')
+          ->getTranslationFromContext($entity);
+      }
+    }
+    catch (\Exception $e) {
+      // Or not.
+    }
+
+    return NULL;
+  }
+
+  /**
+   * Tries to translate the given raw url path into an internal one.
+   *
+   * @param string $input
+   *   Raw URL string consisting of a path and, optionally, query and fragment.
+   *
+   * @return string
+   *   The internal path if any matched. The input string otherwise.
+   */
+  public static function getPathByAlias($input) {
+    $prefixes = \Drupal::config('language.negotiation')->get('url.prefixes');
+    /** @var \Drupal\Core\Path\AliasManagerInterface $path_alias_manager */
+    $path_alias_manager = \Drupal::service('path.alias_manager');
+    /** @var \Drupal\Core\Language\LanguageManagerInterface $language_manager */
+    $language_manager = \Drupal::service('language_manager');
+
+    foreach ($language_manager->getLanguages() as $language) {
+      $prefix = $prefixes[$language->getId()];
+      // Strip the language prefix.
+      $initial_path = parse_url($input, PHP_URL_PATH);
+      $path_without_prefix = preg_replace("/^\/$prefix\//", '/', $initial_path);
+      $path_resolved = $path_alias_manager->getPathByAlias($path_without_prefix, $language->getId());
+      if ($path_resolved !== $path_without_prefix) {
+        return $path_resolved . static::getQueryAndFragment($input);
+      }
+    }
+
+    return $input;
+  }
+
+  /**
+   * Returns the query and fragment part of a given URL string.
+   *
+   * @param string $input
+   *   An arbitrary URL.
+   *
+   * @return string
+   *   The query and fragment parts or an empty string.
+   */
+  public static function getQueryAndFragment($input) {
+    $result = '';
+    if ($query = parse_url($input, PHP_URL_QUERY)) {
+      $result .= "?$query";
+    }
+    if ($fragment = parse_url($input, PHP_URL_FRAGMENT)) {
+      $result .= "#$fragment";
+    }
+    return $result;
+  }
+
+}
diff --git a/web/modules/linkit/tests/src/FunctionalJavascript/LinkFieldTest.php b/web/modules/linkit/tests/src/FunctionalJavascript/LinkFieldTest.php
new file mode 100644
index 0000000000..de5343892e
--- /dev/null
+++ b/web/modules/linkit/tests/src/FunctionalJavascript/LinkFieldTest.php
@@ -0,0 +1,334 @@
+<?php
+
+namespace Drupal\Tests\linkit\FunctionalJavascript;
+
+use Drupal\entity_test\Entity\EntityTestMul;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\linkit\Tests\ProfileCreationTrait;
+use Drupal\node\Entity\NodeType;
+
+/**
+ * Tests the widget and formatter for Link fields.
+ *
+ * @group linkit
+ */
+class LinkFieldTest extends WebDriverTestBase {
+
+  use ProfileCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'node',
+    'language',
+    'field_ui',
+    'entity_test',
+    'link',
+    'linkit',
+  ];
+
+  /**
+   * A linkit profile.
+   *
+   * @var \Drupal\linkit\ProfileInterface
+   */
+  protected $linkitProfile;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $matcherManager = $this->container->get('plugin.manager.linkit.matcher');
+    /** @var \Drupal\linkit\MatcherInterface $plugin */
+
+    $this->linkitProfile = $this->createProfile();
+    $plugin = $matcherManager->createInstance('entity:entity_test_mul');
+    $this->linkitProfile->addMatcher($plugin->getConfiguration());
+    $plugin = $matcherManager->createInstance('email');
+    $this->linkitProfile->addMatcher($plugin->getConfiguration());
+    $plugin = $matcherManager->createInstance('front_page');
+    $this->linkitProfile->addMatcher($plugin->getConfiguration());
+    $this->linkitProfile->save();
+
+    // Create a node type for testing.
+    NodeType::create(['type' => 'page', 'name' => 'page'])->save();
+
+    // Create a link field.
+    $storage = FieldStorageConfig::create([
+      'field_name' => 'field_test_link',
+      'entity_type' => 'node',
+      'type' => 'link',
+    ]);
+    $storage->save();
+    FieldConfig::create([
+      'bundle' => 'page',
+      'entity_type' => 'node',
+      'field_name' => 'field_test_link',
+    ])->save();
+
+    // Define our widget and formatter for this field.
+    entity_get_form_display('node', 'page', 'default')
+      ->setComponent('field_test_link', [
+        'type' => 'linkit',
+      ])
+      ->save();
+    entity_get_display('node', 'page', 'default')
+      ->setComponent('field_test_link', [
+        'type' => 'linkit',
+      ])
+      ->save();
+
+    $account = $this->drupalCreateUser([
+      'administer node fields',
+      'administer node display',
+      'administer nodes',
+      'bypass node access',
+      'view test entity',
+    ]);
+
+    $this->drupalLogin($account);
+  }
+
+  /**
+   * Test the "linkit" widget and formatter.
+   */
+  public function testLinkFieldWidgetAndFormatter() {
+    $session = $this->getSession();
+    $assert_session = $this->assertSession();
+    $page = $session->getPage();
+
+    // Create a test entity to be used as target.
+    /** @var \Drupal\Core\Entity\EntityInterface $entity */
+    $entity = EntityTestMul::create(['name' => 'Foo']);
+    $entity->save();
+
+    // Test the widget behavior.
+    $this->drupalGet('node/add/page');
+
+    $assert_session->elementContains('css', '#edit-field-test-link-wrapper', 'Start typing to find content or paste a URL and click on the suggestion below.');
+    $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper');
+    $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $uri_input->setValue('f');
+    $session->getDriver()->keyDown($uri_input->getXpath(), ' ');
+    $assert_session->waitOnAutocomplete();
+
+    // With the default profile no results are found.
+    $autocomplete_results_wrapper = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete');
+    $this->assertTrue($autocomplete_results_wrapper->isVisible());
+    $result_description = $assert_session->elementExists('css', 'li.linkit-result-line .linkit-result-line--description', $autocomplete_results_wrapper);
+    $this->assertEquals('Linkit could not find any suggestions. This URL will be used as is.', $result_description->getText());
+
+    // Set the widget to use our profile and try again.
+    entity_get_form_display('node', 'page', 'default')
+      ->setComponent('field_test_link', [
+        'type' => 'linkit',
+        'settings' => [
+          'linkit_profile' => $this->linkitProfile->id(),
+        ],
+      ])
+      ->save();
+    $this->drupalGet('node/add/page');
+    $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper');
+    $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $uri_input->setValue('f');
+    $session->getDriver()->keyDown($uri_input->getXpath(), 'o');
+    $assert_session->waitOnAutocomplete();
+    $first_result = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete li.linkit-result-line span.linkit-result-line--title');
+    $first_result->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // Check that the URL input field value shows the entity path.
+    $url_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $this->assertEquals($entity->toUrl()->toString(), $url_input->getValue());
+    // Check that the title was populated automatically.
+    $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper);
+    $this->assertEquals('Foo', $title_input->getValue());
+
+    // Give the node a title and save the page.
+    $page->fillField('title[0][value]', 'Host test node 1');
+    $page->pressButton('Save');
+    $assert_session->pageTextContains('Host test node 1 has been created');
+
+    // Check that we are viewing the node, and the formatter displays what we
+    // expect.
+    $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link');
+    $link_element = $assert_session->elementExists('css', 'a', $field_wrapper);
+    $this->assertEquals('Foo', $link_element->getText());
+    $href_value = $link_element->getAttribute('href');
+    $this->assertContains("/entity_test_mul/manage/{$entity->id()}", $href_value);
+
+    // Test internal entity targets with anchors.
+    /** @var \Drupal\Core\Entity\EntityInterface $entity */
+    $entity2 = EntityTestMul::create(['name' => 'Anchored Entity']);
+    $entity2->save();
+
+    // Test the widget behavior.
+    $this->drupalGet('node/add/page');
+
+    $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper');
+    $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $uri_input->setValue('Anchored');
+    $session->getDriver()->keyDown($uri_input->getXpath(), ' ');
+    $assert_session->waitOnAutocomplete();
+    $first_result = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete li.linkit-result-line span.linkit-result-line--title');
+    $first_result->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // Check that the URL input field value shows the entity path.
+    $url_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $this->assertEquals($entity2->toUrl()->toString(), $url_input->getValue());
+    // Check that the title was populated automatically.
+    $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper);
+    $this->assertEquals('Anchored Entity', $title_input->getValue());
+
+    // Add an anchor to the URL field.
+    $url_input->setValue($entity2->toUrl()->toString() . '#with-anchor');
+
+    // Give the node a title and save the page.
+    $page->fillField('title[0][value]', 'Host test node 2');
+    $page->pressButton('Save');
+    $assert_session->pageTextContains('Host test node 2 has been created');
+
+    // Check that we are viewing the node, and the formatter displays what we
+    // expect.
+    $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link');
+    $link_element = $assert_session->elementExists('css', 'a', $field_wrapper);
+    $this->assertEquals('Anchored Entity', $link_element->getText());
+    $href_value = $link_element->getAttribute('href');
+    $this->assertContains("/entity_test_mul/manage/{$entity2->id()}#with-anchor", $href_value);
+
+    // Test external URLs.
+    $this->drupalGet('node/add/page');
+
+    $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper');
+    $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $uri_input->setValue('https://google.com#foobar');
+    $session->getDriver()->keyDown($uri_input->getXpath(), ' ');
+    $assert_session->waitOnAutocomplete();
+    $autocomplete_results_wrapper = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete');
+    $this->assertTrue($autocomplete_results_wrapper->isVisible());
+    $result_description = $assert_session->elementExists('css', 'li.linkit-result-line .linkit-result-line--description', $autocomplete_results_wrapper);
+    $this->assertEquals('Linkit could not find any suggestions. This URL will be used as is.', $result_description->getText());
+    $first_result = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete li.linkit-result-line span.linkit-result-line--title');
+    $first_result->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // Set a manual value for the title.
+    $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper);
+    $title_input->setValue('This is google');
+
+    // Give the node a title and save the page.
+    $page->fillField('title[0][value]', 'Host test node 3');
+    $page->pressButton('Save');
+    $assert_session->pageTextContains('Host test node 3 has been created');
+
+    // Check that we are viewing the node, and the formatter displays what we
+    // expect.
+    $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link');
+    $link_element = $assert_session->elementExists('css', 'a', $field_wrapper);
+    $this->assertEquals('This is google', $link_element->getText());
+    $href_value = $link_element->getAttribute('href');
+    $this->assertContains('https://google.com#foobar', $href_value);
+
+    // Test emails.
+    $this->drupalGet('node/add/page');
+
+    $email = 'drupal@example.com';
+
+    $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper');
+    $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $uri_input->setValue($email);
+    $session->getDriver()->keyDown($uri_input->getXpath(), ' ');
+    $assert_session->waitOnAutocomplete();
+    $first_result = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete li.linkit-result-line span.linkit-result-line--title');
+    $first_result->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $url_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $this->assertEquals('mailto:' . $email, $url_input->getValue());
+    // Check that the title was populated automatically.
+    $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper);
+    $this->assertEquals($email, $title_input->getValue());
+
+    // Give the node a title and save the page.
+    $page->fillField('title[0][value]', 'Host test node 4');
+    $page->pressButton('Save');
+    $assert_session->pageTextContains('Host test node 4 has been created');
+
+    // Check that we are viewing the node, and the formatter displays what we
+    // expect.
+    $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link');
+    $link_element = $assert_session->elementExists('css', 'a', $field_wrapper);
+    $this->assertEquals($email, $link_element->getText());
+    $href_value = $link_element->getAttribute('href');
+    $this->assertContains('mailto:' . $email, $href_value);
+
+    // Test internal host.
+    $this->drupalGet('node/add/page');
+
+    $url = \Drupal::request()->getSchemeAndHttpHost() . '/node/1';
+
+    $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper');
+    $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $uri_input->setValue($url);
+    $session->getDriver()->keyDown($uri_input->getXpath(), ' ');
+    $assert_session->waitOnAutocomplete();
+    $first_result = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete li.linkit-result-line span.linkit-result-line--title');
+    $first_result->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $url_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $this->assertEquals($url, $url_input->getValue());
+    // Check that the title was populated automatically.
+    $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper);
+    $this->assertEquals($url, $title_input->getValue());
+    // Give the node a title and save the page.
+    $page->fillField('title[0][value]', 'Host test node 5');
+    $page->pressButton('Save');
+    $assert_session->pageTextContains('Host test node 5 has been created');
+
+    // Check that we are viewing the node, and the formatter displays what we
+    // expect Should display the relative url without the host.
+    $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link');
+    $link_element = $assert_session->elementExists('css', 'a', $field_wrapper);
+    $this->assertEquals($url, $link_element->getText());
+    $href_value = $link_element->getAttribute('href');
+    $this->assertContains('/node/1', $href_value);
+
+    // Test front page.
+    $this->drupalGet('node/add/page');
+
+    $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper');
+    $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+    $uri_input->setValue('<front>');
+    $session->getDriver()->keyDown($uri_input->getXpath(), ' ');
+    $assert_session->waitOnAutocomplete();
+    $first_result = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete li.linkit-result-line span.linkit-result-line--title');
+    $first_result->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $url_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper);
+
+    $this->assertEquals('/', $url_input->getValue());
+    // Check that the title was populated automatically.
+    $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper);
+    $this->assertEquals('Front page', $title_input->getValue());
+
+    // Give the node a title and save the page.
+    $page->fillField('title[0][value]', 'Host test node 6');
+    $page->pressButton('Save');
+    $assert_session->pageTextContains('Host test node 6 has been created');
+
+    $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link');
+    $link_element = $assert_session->elementExists('css', 'a', $field_wrapper);
+    $this->assertEquals('Front page', $link_element->getText());
+    $href_value = $link_element->getAttribute('href');
+    $this->assertContains('/', $href_value);
+  }
+
+}
diff --git a/web/modules/linkit/tests/src/FunctionalJavascript/LinkitDialogTest.php b/web/modules/linkit/tests/src/FunctionalJavascript/LinkitDialogTest.php
index 7d5f8ce322..8d9ad93ff2 100644
--- a/web/modules/linkit/tests/src/FunctionalJavascript/LinkitDialogTest.php
+++ b/web/modules/linkit/tests/src/FunctionalJavascript/LinkitDialogTest.php
@@ -191,9 +191,6 @@ public function testLinkDialog() {
     // Find the first result and click it.
     $page->find('xpath', '//li[contains(@class, "linkit-result-line") and contains(@class, "ui-menu-item")][1]')->click();
 
-    // Make sure the linkit field field is populated with the node url.
-    $this->assertEquals($entity->toUrl()->toString(), $href_field->getValue(), 'The href field is populated with the node url.');
-
     // Make sure all other fields are populated.
     $this->assertEqualsWithJs('attributes[data-entity-type]', $entity->getEntityTypeId());
     $this->assertEqualsWithJs('attributes[data-entity-uuid]', $entity->uuid());
diff --git a/web/modules/linkit/tests/src/Kernel/LinkitAutocompleteTest.php b/web/modules/linkit/tests/src/Kernel/LinkitAutocompleteTest.php
index df6c2abb1b..c42721bb4c 100644
--- a/web/modules/linkit/tests/src/Kernel/LinkitAutocompleteTest.php
+++ b/web/modules/linkit/tests/src/Kernel/LinkitAutocompleteTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\linkit\Kernel;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Html;
 use Drupal\entity_test\Entity\EntityTest;
@@ -110,7 +109,7 @@ public function testAutocompletionEmail() {
 
     $email = 'drupal@example.com';
     $data = $this->getAutocompleteResult($email);
-    $this->assertSame((string) new FormattableMarkup('E-mail @email', ['@email' => $email]), $data[0]['label'], 'Autocomplete returned email suggestion.');
+    $this->assertSame($email, $data[0]['label'], 'Autocomplete returned email suggestion.');
     $this->assertSame('mailto:' . $email, $data[0]['path'], 'Autocomplete returned email suggestion with an mailto href.');
   }
 
diff --git a/web/modules/linkit/tests/src/Kernel/LinkitEditorLinkDialogTest.php b/web/modules/linkit/tests/src/Kernel/LinkitEditorLinkDialogTest.php
index 0a1963b44e..42504fcdb6 100644
--- a/web/modules/linkit/tests/src/Kernel/LinkitEditorLinkDialogTest.php
+++ b/web/modules/linkit/tests/src/Kernel/LinkitEditorLinkDialogTest.php
@@ -128,7 +128,7 @@ public function testAdd() {
 
     $form_state->setValue(['attributes', 'href'], 'https://example.com/');
     $form_state->setValue('href_dirty_check', '');
-    $form_state->setValue(['attributes', 'data-entity-type'], $this->randomString());
+    $form_state->setValue(['attributes', 'data-entity-type'], $entity->getEntityTypeId());
     $form_state->setValue(['attributes', 'data-entity-uuid'], $this->randomString());
     $form_state->setValue(['attributes', 'data-entity-substitution'], $this->randomString());
     $form_builder->submitForm($form_object, $form_state);
-- 
GitLab