-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
preferences: Render markdown descriptions #10431
Conversation
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.
The changes look good to me overall, believe it fixes #7742 if you'd like to link it 👍
I have a couple of suggestions for improvements, but they can be tackled in separate pull-requests:
- ensuring that links (ex:
read more
) open in an external window and do not replace the app. - ensuring that preferences are anchors, so we can reference them when clicking the following
#search.searchOnType#
.
Ah, good to know 👍
I see, I will address them in this PR 👍 |
ff923f3
to
8eea0a0
Compare
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.
Thank you for taking care of the updates, I can confirm that links open in new windows, and we can reference other preference ids through the markdown as anchors.
There are some instances where preferences are not anchorable:
Is this an issue with localization
in general? We might reference invalid preference ids because we want to use strings present in nls.metadata.json
for translation. For an end-user it might be confusing when we use a preference description and refer to an invalid id.
packages/preferences/src/browser/views/components/preference-node-renderer.ts
Outdated
Show resolved
Hide resolved
packages/preferences/src/browser/views/components/preference-node-renderer.ts
Outdated
Show resolved
Hide resolved
8eea0a0
to
d8169bb
Compare
@vince-fugnitto Thanks for the feedback, I removed the references to non-existing preferences and used custom localization keys 👍 |
for (const callback of calls) { | ||
const elements: Element[] = Array.from(div.getElementsByTagName(tag)); | ||
for (const element of elements) { | ||
const result = callback(element); | ||
if (result) { | ||
div.replaceChild(result, element); |
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.
It's still pretty easy to get this to fail. .getElementsByTagName
retrieves all descendants including remote descendants, but (in Chrome, at least) .replaceChild
only works if the element to be replaced is an immediate child of the element on which .replaceChild
is called, so if the original rendering or a previous callback creates a nested structure that includes the tag of interest, the next callback targeting that tag will fail. Since markdown-it
already supports plugins, I wouldn't make this hard on yourself trying to invent a new plugin system for it: just apply the modifications you want to apply to the HTML.
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 would like to use the plugin system of markdown-it
, but it's just not powerful enough. The main issue is that it always renders to text, eliminating every event listener I place on the html elements in the process. The main purpose of the MarkdownRenderer
class is to operate directly on the html elements. (I've tried implementing it using purely markdown-it
at the start, but couldn't get around this limitation)
Anyway, changed this to only select direct children of the div
.
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.
Thanks for adding the tests. I don't see an obvious way to break the current implementation (though a triply nested for-loop is a little scary...) 👍. I'm still not sure whether it's worth adding that machinery (the MarkdownRenderer
class) when the only current use case is a fairly simple one (running one callback on each of two different tags). Adding it puts us on the hook for its correctness in the general case when that level of complexity isn't (yet) needed. But it is working, and the tests suggest it should handle the general case, so I'll leave it up to you.
97f0414
to
1bdafc4
Compare
@colin-grant-work Updated the markdown rendering to behave better when selecting non-direct descendants. Also added some tests. Like I said here, I couldn't find a way by doing it purely through |
@@ -64,6 +64,7 @@ export namespace Preference { | |||
export interface LeafNode extends BaseTreeNode { | |||
depth: number; | |||
preference: { data: PreferenceDataProperty }; | |||
preferenceId: string; |
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'm Ok with adding this, but there is also a utility in the Preference.TreeNode
namespace (getGroupAndIdFromNodeId
) to get the group
and id
(i.e. preferenceId) from the ID of the node.
for (const callback of calls) { | ||
const elements: Element[] = Array.from(div.getElementsByTagName(tag)); | ||
for (const element of elements) { | ||
const result = callback(element); | ||
if (result) { | ||
div.replaceChild(result, element); |
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.
Thanks for adding the tests. I don't see an obvious way to break the current implementation (though a triply nested for-loop is a little scary...) 👍. I'm still not sure whether it's worth adding that machinery (the MarkdownRenderer
class) when the only current use case is a fairly simple one (running one callback on each of two different tags). Adding it puts us on the hook for its correctness in the general case when that level of complexity isn't (yet) needed. But it is working, and the tests suggest it should handle the general case, so I'll leave it up to you.
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 is behaving correctly and the implementation looks sound. The one outstanding issue that I'd like to discuss - it may or may not need to be addressed - is that in Electron, clicking an external link opens a new Electron window rather than external browser window as VSCode does. That can make Electron behave oddly, because events from that window are treated the same as events from 'real' Theia windows, which can be undesirable (I think it would mess with the code to close windows in #10379, for example, since the new window wouldn't have the code to confirm exit). FWIW, I think it would be better for it to open an external window, but the fact that it's so easy to open a non-IDE Electron window probably means that #10379 needs to be more careful about which windows it tries to ask for close confirmation.
Update
I tested this with #10379 and it didn't cause any trouble (to my relief...), but I still think it would be better to open in an external browser if possible. I notice that the links on the Getting Started
page already do this, so they should be able to point the way.
1bdafc4
to
dac076f
Compare
dac076f
to
0f042f3
Compare
@colin-grant-work Thanks, I only tested this in the browser environment. I wasn't aware of the whole |
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.
The changes work well for me 👍
I confirmed that:
- markdown descriptions of preferences are correctly styled
- clicking the anchor links of preferences correctly navigates to the corresponding preference
- opening external links works correctly for the browser and electron
- no visible regressions
- searching works
- scrolling works
- layout restoration works
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.
Looking good and working well for me. 👍
What it does
Closes #7742
Renders the
markdownDescription
property into html instead of just setting thetextContent
to themarkdownDescription
value. As there are some special semantics related to the rendered markdown like links that have to be opened in a new window or linked preferences that have to be rendered as links instead ofcode
, I added a newMarkdownRenderer
that allows easy post-processing on rendered markdown. Perhaps this is a bit overkill, but I couldn't find any other method to overwrite thecode
rule formarkdown-it
.Also updates some preferences that were using
description
instead ofmarkdownDescription
.Previously:
Now:
Compared to vscode:
How to test
Preferences
widget.markdownDescription
property are rendered correctly to html.markdownDescription
instead ofdescription
. (i.e. no markdown syntax exists indescription
properties)Review checklist
Reminder for reviewers