- First commit to issue fork.
- @jds1 opened merge request.
- Status changed to Needs review
over 2 years ago 2:28pm 21 March 2023 - 🇺🇸United States jds1 Hudson Valley, NY
Rerolled #41 against 6.0.x https://git.drupalcode.org/project/linkit/-/merge_requests/16. Everything passes. Patch applies cleanly locally against both 6.0.x-dev and 6.0.0-beta4. Tested locally and now I'm getting aliases instead of node URLs! Marking as "Needs Review" – thank you!
- last update
over 2 years ago 92 pass - last update
over 2 years ago 83 pass - last update
over 2 years ago 38 pass, 20 fail - last update
over 2 years ago Patch Failed to Apply - last update
over 2 years ago Patch Failed to Apply - last update
over 2 years ago 73 pass, 12 fail - last update
over 2 years ago 46 pass, 2 fail - First commit to issue fork.
- last update
over 2 years ago 83 pass - last update
over 2 years ago 73 pass, 12 fail - last update
over 2 years ago 73 pass, 12 fail - last update
over 2 years ago Composer require failure - last update
over 2 years ago 83 pass - last update
over 2 years ago Composer require failure - last update
over 2 years ago 83 pass - 🇦🇺Australia lxpcfly Canberra
The linkit-n2877535-50.patch is successfully changed the URL Link field from "node/xxx" to URL Alias, but it still needs to work, as the saved URL is still "node/xxx" rather than saved as its path alias, 'canonical' is not active.
- 🇺🇸United States damienmckenna NH, USA
It's ok that the saved value is "node/xxx" because it's converted to the correct URL when the content is rendered; it's a feature.
- 🇦🇺Australia lxpcfly Canberra
Thank you Damien, but I think once it has the path alias also it appears in the Link field, it should save as path alias with canonical active.
- 🇺🇸United States damienmckenna NH, USA
That would need to be a separate discussion.
- 🇨🇦Canada vladt
I'm encountering an issue after applying patch #50 to Linkit 6.0.0-rc1 (as well as Linkit 5.0-beta 13), the data-entity-substitution, data-entity-type, data-entity-uuid attributes are no longer being added to the link, so the LinkitFilter is no longer working. I can't see why this would be the case after applying this patch, removing the patch resolves this and the attributes are added but I need the functionality provided by this patch. Could anyone suggest why this may be the case?
- Status changed to Needs work
about 2 years ago 6:28pm 9 August 2023 - 🇺🇸United States moshe weitzman Boston, MA
Sounds like this is right status.
- Status changed to RTBC
about 2 years ago 5:18pm 28 August 2023 - 🇺🇸United States moshe weitzman Boston, MA
In my instance, that check is preventing the values from being unset, not unsetting them. Changing status after my review.
- 🇺🇸United States R_H-L
Testing this in 9.5, #50 breaks the a tag extra attributes. The tag gets put in as just the bare '/node/x' without any data attributes.
- 🇺🇸United States mark_fullmer Tucson
Addressing the comments in #51, #53, and #59, all of which are suggesting that the URL alias is what should be saved to the database, rather than the internal route, I agree with Damien McKenna's statement in #52:
It's ok that the saved value is "node/xxx" because it's converted to the correct URL when the content is rendered; it's a feature.
- Status changed to Needs work
almost 2 years ago 11:51am 21 September 2023 - 🇺🇸United States mark_fullmer Tucson
It's ok that the saved value is "node/xxx" because it's converted to the correct URL when the content is rendered
After testing to confirm, I take back my comment. Using the latest patch, the comments in #51, #52, and #59 are effectively pointing out a problem -- not that the internal URL is what is saved (that's fine), but that when the resulting page is rendering, the internal URL is what is rendered to the end-user, rather than the URL alias.
Changing status to "Needs work" to address this. I'm also surprised that there is apparently no test coverage to catch this. I'd like to add test coverage for this going forward: the URL alias should be rendered on the page after save.
- 🇺🇸United States moshe weitzman Boston, MA
This patch no longer applies to the most recent release (Sep 30) :(
- 🇺🇸United States mark_fullmer Tucson
This patch no longer applies to the most recent release (Sep 30)
Here's a revised patch that replicates verbatim what was in the patch in #50, and which will apply to the current development release for 6.0.x and 6.1.x.
Noting that the comments from #51, #52, and #59 still need to be addressed: when the resulting page is rendered, the internal URL should not be rendered to the end-user. The URL alias should. Test coverage needs to be added for this, too. Leaving status as "Needs work."
- 🇦🇲Armenia arthur.baghdasar
Ive removed this part from the patch everything seems to be working fine for me.
In the code below $input variable comes with an Alias and I don't understand why we should get the $path from it.
In My case the $path variable is converted back to the node/[nid] which is then being set to be the new input.+ if (!empty($input) && \Drupal::moduleHandler()->moduleExists('path_alias')) { + /** @var \Drupal\path_alias\AliasManagerInterface $aliasManager */ + $aliasManager = \Drupal::service('path_alias.manager'); + $path = $aliasManager->getPathByAlias($input); + if ($path !== $input) { + $input = $path; + } + }
- Status changed to Needs review
almost 2 years ago 1:25pm 7 October 2023 - last update
almost 2 years ago Composer require failure - last update
almost 2 years ago Composer require failure - 🇺🇸United States kthull Fort Wayne, Indiana
Patch from #64 applied to 6.1.2 for me and solved the generic node path on D10.1.5
- Status changed to Needs work
almost 2 years ago 7:44pm 7 November 2023 - 🇳🇱Netherlands spadxiii
I tried to get something working, but did not succeed.
My goal was to use the alias during editing and store the node-url in the database. The link would then show the node alias in the editor, but in the database the node/ was stored. By adding another data-property (data-entity-path) I tried adding a editorDowncast and dataDowncast in the plugin, but that didn't work correctly: it would create a new a-tag wrapping the one that was being made by the base link plugin.
By adding a general upcast conversion, the href is changed to the alias:
editor.conversion.for('upcast') .elementToAttribute( { view: { name: 'a', attributes: { href: true, ['data-entity-alias']: true } }, model: { key: 'linkHref', value: viewElement => viewElement.getAttribute('data-entity-alias'), }, converterPriority: 'high' } );
But I failed trying to move the other way around. I tried adding a dataDowncast (which would set the path back to the href), and an editorDowncast (which would use the alias as href). This didn't work, because when creating an element with the same attribute, would add another element to the html instead of merging them (effectively overwriting the href-attribute)
I also tried using a data downcastDispatcher, but that doesn't seem to be able to get the correct viewElement (or rather, it couldn't find any element at all).ps. there are some other code changes required as well to not only pass but also use the alias and path in the links.
- 🇺🇸United States mrweiner
For anybody who needs a quick and dirty fix for this, I'm handling it in hook_preprocess_field() with:
function cc_gin_preprocess_field__node__body__article(&$variables) { // Assuming $text is the HTML content you're preprocessing $text = $variables["items"][0]["content"]["#text"]; // Create a new DOMDocument and load the HTML content $dom = new DOMDocument(); @$dom->loadHTML(mb_convert_encoding($text, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $links = $dom->getElementsByTagName('a'); foreach ($links as $link) { // Check if the link is an internal node link if ($link->hasAttribute('href') && str_starts_with($link->getAttribute('href'), '/node/')) { // Extract node ID $path = $link->getAttribute('href'); // Fetch the alias for the node $alias = \Drupal::service('path_alias.manager')->getAliasByPath($path); // Replace the href attribute with the alias $link->setAttribute('href', $alias); } } // Save the updated HTML $variables["items"][0]["content"]["#text"] = $dom->saveHTML(); }
Note that this was generated by chatgpt, so I'm not sure that all of the args in $dom->loadHTML() are needed, but it seems to do the trick for me.
- 🇳🇿New Zealand klidifia
I updated this issue with some code that will use the entity title (already obtained via Linkit) as the default link text for a brand new inserted link without a selection range: ✨ Display node title (a text) in by default when creating link in ckeditor5 Closed: duplicate
- 🇫🇷France federiko_
Patch from #64 applied to 7.0.0-alpha1 seems to be working well for me ; here is the generated link code :
<a class="test-css" href="/fr/mentions-legales" target="_blank" aria-label="test-aria" id="test-idd" rel="test-rel" data-entity-type="node" data-entity-uuid="7a012594-8e2f-446f-8559-ac377b1ec0d5" data-entity-substitution="canonical" tabindex="-1" data-title="Test title">Test link</a>
- First commit to issue fork.
- @casey opened merge request.
- @casey opened merge request.
- 🇳🇱Netherlands casey
Sorry for the noise;
First I rebased the existing MR branch on the latest 6.1.x branch but apparently you can't change the destination of MRs. I then reverted that existing MR.
I then applied the patch from #64 to both the 6.1.x and the 7.x branch; apparently the 6.1.x branch has more (recent) changes than the 7.x branch. I've created MRs for both branches.
- 🇳🇱Netherlands casey
Snapshots of latest state of MRs for both 6.1.x and 7.x for safe usage with composer-patches
- 🇺🇸United States drpldrp San Francisco, CA
Only attaching a patch for 6.1.x because that's what I'm using.
It should return alias on selection, downcast to internal path for the source, and upcast to alias when editing.
I did see some errors thrown related to tooltip or something but it seem to affect functionality so I didn't bother fixing it.
- 🇺🇸United States drpldrp San Francisco, CA
Moved some things around so there's fewer changes and easier to understand.
The buildPath() signature change can break extending classes (eg. media_link_enhancements) ... but it seems silly to repeat that code as a new function with just the path processing change.
I'm also aware that the synchronous call is maybe not ideal and throws a warning.
If someone were inclined to do this without the sync call:
The problem is on the editor initial load with existing links.
Link upcast/downcast occurs and there's no alias info in the source data where upcast/downcast usually get its values from.
And the source data shouldn't have alias info because we don't want to save alias data into markup.
And, at least for me, I want to use LinkIt matchers but not the LinkIt url converter because it's not compatible with links on drupal-media images; extra data attributes like those required for url conversion are consumed off the link.Possible solution is to find where to generate alias info from the source data and make it available to the upcast/downcast and before the editor is available for users to start clicking around. Maybe it can be done in the field widget initialization.
- 🇺🇸United States drpldrp San Francisco, CA
Okay, last one: this version doesn't use synch xhr, adds data-entity-alias attributes on form load, and removes them on downcast.
There's a minor issue with switching text formats between no-ckeditor5 and ckeditor5.
The alias addition on form load is set to happen for ckeditor5.
If switching from ckeditor5 to no ckeditor5, the data-entity-alias will show up.
Conversely, if switching from no ckeditor5 to ckeditor5, the aliases will not be populated in the link balloon.One method to address this is the same way the editor js calls xss filtering on text format changes.
In fact, the data-entity-alias form preload could probably all be handled in js anyways like that. - 🇺🇸United States drpldrp San Francisco, CA
Loading alias info into global drupalSettings allows it to be available to the ckeditor5 linkit plugin on init so the alias can be upcast into the model. Storing in drupalSettings also allows it to be available to all text formats and persistent between switching text formats.
- 🇺🇸United States Kasey_MK
I think this no longer applies on 7.0.5. The patch from #77 applies, but appears to be causing the error reported on Can't Insert or Update Link after Upgrading to Drupal Core 10.5 🐛 Can't Insert or Update Link after Upgrading to Drupal Core 10.5 Active
- 🇺🇸United States Kasey_MK
Ah! My patented "wait a while and see if things 'magically solve themselves'" technique seems to have worked!
Thanks to all the wizards out there!
- 🇦🇲Armenia vah67007@gmail.com
Patch #64 worked fine for me. Running 10.5.x version of core.
- 🇪🇨Ecuador dcasanova
I tested the last patch, but it didn't work for Drupal 10.5.x and Linkit 7.0.8. If anyone has any idea how to solve this, I would greatly appreciate it.
- 🇺🇸United States mark_fullmer Tucson
Weighing in as a maintainer of the Linkit module, I would point out that there are others in the community who want the entity title to show: ✨ Show entity title after autocomplete selection instead of internal route (e.g., /node/123) Needs work . This, plus the fact that it is possible to configure the Linkit metadata to render the entity title in the matcher information, makes me disinclined to plan to include this in a release of Linkit. Community members are free to use the patch here, of course, but I'd really recommend going the route of adjusting the autocomplete metadata in the Linkit settings.
- First commit to issue fork.
- @realityloop opened merge request.
- 🇦🇺Australia realityloop
MR 138: for 7.x Continue to use internal route so links don’t get broken, but display url alias in linkit widget and CKeditor when present.
- 🇦🇺Australia realityloop
Apologies for the commit noise.. Now that all tests are passing for my MR138....
Drupal Link Handling Changes
1. Main Change: Internal Routes for Storage, Aliases for Display
- Before: Links could be entered using alias (e.g.,
/my-page
), which could break if the alias changed (e.g., to/updated-page
). - After: Internal links are stored using Drupal's stable internal route (e.g.,
entity:node/1
), but the UI shows the current alias (e.g.,/my-page
). This prevents broken links. - Why?: Aliases can change (e.g., for SEO), but internal routes stay the same. The change ensures links remain working even if aliases are updated.
- Fragments: Fragments (e.g.,
#section ?query=1
) are preserved in storage and display, but the changes ensure they're appended to the alias in the UI when present.
2. Examples of Creating Links
Internal Link (e.g., to a Node):
- Without Fragment:
- Before: User types
/my-page
in the link field. Stored asinternal:/my-page
. If alias changes to/new-page
, link breaks. - After: User types
/my-page
or selects from autocomplete. Stored asentity:node/1
(internal route). Displayed as/my-page
(alias). If alias changes, link still works.
- Before: User types
- With Fragment:
- Before: User types
/my-page#section
in the link field. Stored asinternal:/my-page#section
. If alias changes to/new-page
, link breaks. - After: User types
/my-page#section
or selects from autocomplete. Stored asentity:node/1#section
(internal route). Displayed as/my-page#section
(alias with fragment). If alias changes, link still works.
- Before: User types
- Autocomplete: Suggestions show the alias (with or without fragment) in the display, but use internal routes internally.
External Link (e.g., to Another Site):
- Without Fragment:
- Before: User types
https://example.com
. Stored ashttps://example.com
. No change in behavior. - After: Same as before—stored and displayed as
https://example.com
. The change doesn't affect external links.
- Before: User types
- With Fragment:
- Before: User types
https://example.com#anchor
. Stored ashttps://example.com#anchor
. - After: Same as before—stored and displayed as
https://example.com#anchor
.
- Before: User types
- Autocomplete: External matcher suggests the URL (with or without fragment) for bare domains.
Relative Link (e.g., to a Custom Path):
- Without Fragment:
- Before: User types
/custom/path
. Stored asinternal:/custom/path
. Works as a relative link. - After: Same as before—stored as
internal:/custom/path
. If it matches an alias, it might convert to an entity link; otherwise, stays relative.
- Before: User types
- With Fragment:
- Before: User types
/custom/path#part
. Stored asinternal:/custom/path#part
. - After: Same as before—stored as
internal:/custom/path#part
. If it matches an alias, it will convert to an entity link; otherwise, stays relative.
- Before: User types
- Autocomplete: If no entity matches, it suggests the path (with or without fragment) as-is.
3. Examples of Editing Links
Internal Link:
- Without Fragment:
- Before: Link stored as
internal:/my-page
. User edits to/updated-page
. Stored asinternal:/updated-page
. If/my-page
was an alias, editing might not update the entity reference. - After: Link stored as
entity:node/1
. User sees/my-page
in the field. If they edit to/updated-page
(new alias for the same node), it resolves back toentity:node/1
. If edited to a non-entity path, it becomesinternal:/updated-page
.
- Before: Link stored as
- With Fragment:
- Before: Link stored as
internal:/my-page#section
. User edits to/updated-page#newsection
. Stored asinternal:/updated-page#newsection
. - After: Link stored as
entity:node/1#section
. User sees/my-page#section
in the field. If they edit to/updated-page#newsection
, it resolves back toentity:node/1#newsection
. If edited to a non-entity path, it becomesinternal:/updated-page#newsection
.
- Before: Link stored as
External Link:
- Without Fragment:
- Before: Link stored as
https://example.com
. User edits tohttps://newexample.com
. Stored ashttps://newexample.com
. - After: Same as before.
- Before: Link stored as
- With Fragment:
- Before: Link stored as
https://example.com#anchor
. User edits tohttps://newexample.com#newanchor
. Stored ashttps://newexample.com#newanchor
. - After: Same as before. Fragments are preserved.
- Before: Link stored as
Relative Link:
- Without Fragment:
- Before: Link stored as
internal:/custom/path
. User edits to/new/path
. Stored asinternal:/new/path
. - After: Same. If the new path resolves to an entity, it converts to
entity:...
; otherwise, stays relative.
- Before: Link stored as
- With Fragment:
- Before: Link stored as
internal:/custom/path#part
. User edits to/new/path#newpart
. Stored asinternal:/new/path#newpart
. - After: Same. If the new path resolves to an entity, it converts to
entity:...
#newpart; otherwise, stays relative with the fragment.
- Before: Link stored as
4. Other Changes in the Commit
- UI Improvements: Updated JS for autocomplete behavior (e.g., better display in CKEditor, including fragments).
- Autocomplete Controller: Added logic to fetch display paths for entities, ensuring aliases (with or without fragments) are shown in suggestions.
- Widget Updates: Enhanced the link field to display aliases (with or without fragments) while storing internal routes.
- Matcher and Substitution: Tweaks to how entity paths are built (e.g., using
path_processing: false
for internal routes,true
for aliases), with fragment handling. - No Breaking Changes: Existing links continue to work; the commit improves reliability without disrupting current setups.
5. Overall Impact
- Pros: Links are more robust (don't break on alias changes). Users see friendly aliases (with or without fragments) in the UI.
- Cons: Slightly more complex internally, but transparent to users.
- Testing Note: The commit includes changes to ensure autocomplete and editing work seamlessly with or without fragments.
- Before: Links could be entered using alias (e.g.,
- 🇦🇺Australia realityloop
Apologies for the commit noise.. Now that all tests are passing for my MR138....
Drupal Link Handling Changes
1. Main Change: Internal Routes for Storage, Aliases for Display
- Before: Links could be entered using alias (e.g.,
/my-page
), which could break if the alias changed (e.g., to/updated-page
). - After: Internal links are stored using Drupal's stable internal route (e.g.,
entity:node/1
), but the UI shows the current alias (e.g.,/my-page
). This prevents broken links. - Why?: Aliases can change (e.g., for SEO), but internal routes stay the same. The change ensures links remain working even if aliases are updated.
- Fragments: Fragments (e.g.,
#section ?query=1
) are preserved in storage and display, but the changes ensure they're appended to the alias in the UI when present.
2. Examples of Creating Links
Internal Link (e.g., to a Node):
- Without Fragment:
- Before: User types
/my-page
in the link field. Stored asinternal:/my-page
. If alias changes to/new-page
, link breaks. - After: User types
/my-page
or selects from autocomplete. Stored asentity:node/1
(internal route). Displayed as/my-page
(alias). If alias changes, link still works.
- Before: User types
- With Fragment:
- Before: User types
/my-page#section
in the link field. Stored asinternal:/my-page#section
. If alias changes to/new-page
, link breaks. - After: User types
/my-page#section
or selects from autocomplete. Stored asentity:node/1#section
(internal route). Displayed as/my-page#section
(alias with fragment). If alias changes, link still works.
- Before: User types
- Autocomplete: Suggestions show the alias (with or without fragment) in the display, but use internal routes internally.
External Link (e.g., to Another Site):
- Without Fragment:
- Before: User types
https://example.com
. Stored ashttps://example.com
. No change in behavior. - After: Same as before—stored and displayed as
https://example.com
. The change doesn't affect external links.
- Before: User types
- With Fragment:
- Before: User types
https://example.com#anchor
. Stored ashttps://example.com#anchor
. - After: Same as before—stored and displayed as
https://example.com#anchor
.
- Before: User types
- Autocomplete: External matcher suggests the URL (with or without fragment) for bare domains.
Relative Link (e.g., to a Custom Path):
- Without Fragment:
- Before: User types
/custom/path
. Stored asinternal:/custom/path
. Works as a relative link. - After: Same as before—stored as
internal:/custom/path
. If it matches an alias, it might convert to an entity link; otherwise, stays relative.
- Before: User types
- With Fragment:
- Before: User types
/custom/path#part
. Stored asinternal:/custom/path#part
. - After: Same as before—stored as
internal:/custom/path#part
. If it matches an alias, it will convert to an entity link; otherwise, stays relative.
- Before: User types
- Autocomplete: If no entity matches, it suggests the path (with or without fragment) as-is.
3. Examples of Editing Links
Internal Link:
- Without Fragment:
- Before: Link stored as
internal:/my-page
. User edits to/updated-page
. Stored asinternal:/updated-page
. If/my-page
was an alias, editing might not update the entity reference. - After: Link stored as
entity:node/1
. User sees/my-page
in the field. If they edit to/updated-page
(new alias for the same node), it resolves back toentity:node/1
. If edited to a non-entity path, it becomesinternal:/updated-page
.
- Before: Link stored as
- With Fragment:
- Before: Link stored as
internal:/my-page#section
. User edits to/updated-page#newsection
. Stored asinternal:/updated-page#newsection
. - After: Link stored as
entity:node/1#section
. User sees/my-page#section
in the field. If they edit to/updated-page#newsection
, it resolves back toentity:node/1#newsection
. If edited to a non-entity path, it becomesinternal:/updated-page#newsection
.
- Before: Link stored as
External Link:
- Without Fragment:
- Before: Link stored as
https://example.com
. User edits tohttps://newexample.com
. Stored ashttps://newexample.com
. - After: Same as before.
- Before: Link stored as
- With Fragment:
- Before: Link stored as
https://example.com#anchor
. User edits tohttps://newexample.com#newanchor
. Stored ashttps://newexample.com#newanchor
. - After: Same as before. Fragments are preserved.
- Before: Link stored as
Relative Link:
- Without Fragment:
- Before: Link stored as
internal:/custom/path
. User edits to/new/path
. Stored asinternal:/new/path
. - After: Same. If the new path resolves to an entity, it converts to
entity:...
; otherwise, stays relative.
- Before: Link stored as
- With Fragment:
- Before: Link stored as
internal:/custom/path#part
. User edits to/new/path#newpart
. Stored asinternal:/new/path#newpart
. - After: Same. If the new path resolves to an entity, it converts to
entity:...
#newpart; otherwise, stays relative with the fragment.
- Before: Link stored as
4. Other Changes in the Commit
- UI Improvements: Updated JS for autocomplete behavior (e.g., better display in CKEditor, including fragments).
- Autocomplete Controller: Added logic to fetch display paths for entities, ensuring aliases (with or without fragments) are shown in suggestions.
- Widget Updates: Enhanced the link field to display aliases (with or without fragments) while storing internal routes.
- Matcher and Substitution: Tweaks to how entity paths are built (e.g., using
path_processing: false
for internal routes,true
for aliases), with fragment handling. - No Breaking Changes: Existing links continue to work; the commit improves reliability without disrupting current setups.
5. Overall Impact
- Pros: Links are more robust (don't break on alias changes). Users see friendly aliases (with or without fragments) in the UI.
- Cons: Slightly more complex internally, but transparent to users.
- Testing Note: The commit includes changes to ensure autocomplete and editing work seamlessly with or without fragments.
Some screengrabs:
- Before: Links could be entered using alias (e.g.,