-
Notifications
You must be signed in to change notification settings - Fork 8.6k
FEATURE: rich editor link ui for editing it #32583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
bc77e39
to
3c033a8
Compare
3c033a8
to
7a4687d
Compare
Can we avoid destroying/re-creating when my click ends up not changing the caret position? Kapture.2025-05-13.at.08.12.22.mp4 |
@chapoi It's not due to this work, but it's very annoying that showing a modal is removing the browsers scrollbars, it makes the page all jumpy, I know there are techniques to avoid this, could designers try to give it a go please? Kapture.2025-05-13.at.08.14.01.mp4 |
When I had focus through tab on one of the items menu and I press escape, I don't get back my caret position in the composer. |
Im not sure we should have cursor: pointer on the link given we now treat is as text. It's debatable, given this is still an action. But my reasoning is that seeing the cursor makes me think it's going to open the link right away. What do you think @chapoi ? |
When I edit a link by opening the modal, when I close it I don't get back my caret position in the composer. |
We have no spacing between the back button on edit mode: While in normal mode we have a lot of spacing: Doesnt feel right @chapoi |
Tricky one… I'd say keep the pointer, indicating something can be done feels most important. We could consider using
Yes, I know, but that left side spacing is very hard/impossible to get right due to the use of grid. I'll see if I can tweak it a bit.
Shouldn't that be "unlink"? |
FWIW slack is doing the regular text cursor, and some styling on hover of the link. |
Yes it should, but never got it showing |
@jjaffeux I’ll respond to the other things today, but getting this out first: Unlink is only available for non-autolinks, aren’t they working with I get it may be confusing, but I think supporting removing this autolink will still be confusing, with the caveat of being more work. Making a URL not autolink requires escaping it in the markdown, and I didn't think of a good way to make the escaped text linkable again/autolink on the rich editor - I think it's just simpler to omit the option in this case. |
OK I see, we can for sure start like this and see the feedback we get 👍 |
@jjaffeux I don't think we have an easy alternative in this case, what's destroying the menu is the EDIT: We can skip the menu's default |
Starts defining a more generic API, so a different toolbar instance can be used as a replacement on the main toolbar as well as a foundation for rendering the same toolbar as a floating element. This toolbar reuse started here for the link toolbar: #32583, then got extracted to this PR. --------- Co-authored-by: Sérgio Saquetim <1108771+megothss@users.noreply.github.com>
# Conflicts: # app/assets/javascripts/discourse/app/components/d-editor.gjs # app/assets/javascripts/discourse/tests/integration/components/d-editor-test.gjs # app/assets/stylesheets/common/d-editor.scss
Starts defining a more generic API, so a different toolbar instance can be used as a replacement on the main toolbar as well as a foundation for rendering the same toolbar as a floating element. This toolbar reuse started here for the link toolbar: #32583, then got extracted to this PR. --------- Co-authored-by: Sérgio Saquetim <1108771+megothss@users.noreply.github.com>
…iting modal behavior, floating positioning, etc
app/assets/javascripts/discourse/app/static/prosemirror/extensions/link-toolbar.js
Outdated
Show resolved
Hide resolved
if (event.key !== "Tab" || event.shiftKey) { | ||
return false; | ||
} | ||
|
||
const range = utils.getMarkRange( | ||
view.state.selection.$head, | ||
view.state.schema.marks.link | ||
); | ||
if (!range) { | ||
return false; | ||
} | ||
|
||
const activeMenu = document.querySelector( | ||
'[data-identifier="composer-link-toolbar"]' | ||
); | ||
if (!activeMenu) { | ||
return false; | ||
} | ||
|
||
event.preventDefault(); | ||
|
||
const focusable = activeMenu.querySelector( | ||
'button, a, [tabindex]:not([tabindex="-1"]), .select-kit' | ||
); | ||
|
||
if (focusable) { | ||
focusable.focus(); | ||
return true; | ||
} | ||
|
||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a better solution, but this part is unfortunate. Having to querySelector an element, always feels bad
updatePosition( | ||
menuInstance.trigger, | ||
menuInstance.content, | ||
{} | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we shouldn't haven't this function on the menu instance itself, so we don't have to import float-kit internals
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im pre-approving even if there are few thing to fix/improve, it's already working very well and in a good state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
target="_blank" | ||
rel="noopener noreferrer" | ||
class={{concatClass "btn no-text btn-icon" button.className}} | ||
title={{button.title}} | ||
tabindex={{this.tabIndex button}} | ||
{{on "keydown" (or @rovingButtonBar @data.rovingButtonBar)}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could extract this to a getter?
get rovingButtonBar() {
return this.args.rovingButtonBar || this.args.data.rovingButtonBar;
}
view.focus(); | ||
return false; | ||
} | ||
return rovingButtonBar(event); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we pass a containerClass here to be more specific, or would it be unnecessary?
@@ -18,7 +18,7 @@ import { i18n } from "discourse-i18n"; | |||
|
|||
export default class InsertHyperlink extends Component { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is only used in core, I would advise renaming this to UpsertHyperlink
or something for clarity to indicate it can be used for both insert + edit. If it's too much trouble + plugins are using it probably not necessary for now.
let linkState; | ||
|
||
return { | ||
update(view) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is massive...on the one hand it's kind of good because it's sequential but on the other hand it can be hard to figure out what is going on at various stages because of the large conditionals and the definition of the handlers
in the middle. Two things I would personally split out:
- A separate function for what we are doing on desktop (showing dmenu) vs mobile (replacing the toolbar)
- Potentially moving
handlers
out of the stream of text, it would also make it a little easier to read because they are just passed as an argument toLinkToolbar
, and you need to kind of skip over them at first to get to the meat and potatoes of whatupdate
is doing
Object.assign(linkState, attrs); | ||
} | ||
|
||
if (getContext().capabilities.viewport.sm) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I am reading this right, it's saying anything > small viewport (so anything not mobile) will do the DMenu stuff? I find this API a little tricky, because you can't reverse it without being verbose. For example the false condition which is a lot smaller means we are on mobile, but you can't do if (!getContext().capabilities.viewport.sm) { // replace toolbar }
because that will apply to XL and so on.
Would be cool if we had a viewport.lessThanSm
(with better naming) that uses max-width media queries for these scenarios.
@@ -37,10 +38,13 @@ export default class DMenuInstance extends FloatKitInstance { | |||
|
|||
setOwner(this, owner); | |||
this.options = { ...MENU.options, ...options }; | |||
this.portalOutletOverride = options.portalOutletElement; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but IMO this should be portalOutletOverrideElement
@@ -2795,6 +2795,9 @@ en: | |||
link_title: "Hyperlink" | |||
link_description: "enter link description here" | |||
link_dialog_title: "Insert Hyperlink" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could take the opportunity here to change these to Insert hyperlink
and Edit hyperlink
to follow https://meta.discourse.org/t/formatting-text-in-discourse-documentation-and-uis/324637
expect(find(".d-modal__body input.link-url").value).to eq("https://example.com") | ||
|
||
find(".d-modal__body input.link-text").set("Updated Example") | ||
find(".d-modal__body input.link-url").set("https://updated-example.com") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for these fill_in(with: "blah")
is generally better and what we use more
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will apply to all the other specs here too
|
||
find(".d-modal__body input.link-text").set("Updated Example") | ||
find(".d-modal__body input.link-url").set("https://updated-example.com") | ||
find(".d-modal__footer .btn-primary").click |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use the PageObjects::Modals::Base
page object you can use click_primary_button
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will apply to all the other specs here too
expect(find(".d-modal__body input.link-text").value).to eq("Example") | ||
expect(find(".d-modal__body input.link-url").value).to eq("https://example.com") | ||
|
||
find(".d-modal__body input.link-text").set("Updated Example") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use the PageObjects::Modals::Base
page object you can do modal.body.find("input.link-text")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will apply to all the other specs here too
@renato another one from the call today with Sam and Joffrey, if you are hovering on text and choose "Insert hyperlink" button, we don't prefill the text in the modal with the text under the cursor Also on the open external link button, we should change the tooltip to "Open link in external tab" or something rather than "Visit link" |
I responded on dev, I don't agree with this – and it's kind of a scope creep, let's discuss and address in a follow-up PR if needed. Having a single word under the cursor but NOT selected be auto-added while opening the Insert hyperlink modal seems like a stretch to me. |
Displays a floating toolbar when the selection position is in a link, on desktop. On mobile, replaces the existing toolbar with the link toolbar temporarily.
Desktop
Kapture.2025-04-26.at.15.49.04.1.mp4
Mobile