-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Add a basic keyboard shortcuts overview dialog #11717
Conversation
Manage this branch in SquashTest this branch here: https://kituuukitukeyboard-shortcuts-fkfnh.squash.io |
wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html
Show resolved
Hide resolved
Hey @lb- , I have tried my best with the design. I used a custom scss file for styling. And also, instead of hard coding everything, I just made a dictionary of all the shortcuts so that in the future, addition and removal of shortcuts could be more accessible. I am not good in naming CSS classes LoL : D Also, the figma design has the tables divided into 2 columns but as we have less width available in our current dialog, ig it's better to keep single column. If everything is fine, please tell me more about writing unit tests for this PR. |
Awesome I will take a look. Just a reminder to review the failing CI items when you get the chance, some may be just formatting (see formatting guidelines https://docs.wagtail.org/en/stable/contributing/python_guidelines.html#python-coding-guidelines - As for unit tests, it looks like we have lots of tests that are globally checking for
|
5133c6b
to
18b3b4e
Compare
I guess some test fails are still there even after running make format and merging those 2 PRs. |
@lb- Can you suggest something for this PR too? So that I can modify some tests to support this PR. : D |
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.
@kituuu this is awesome progress so far, thank you!
I have added a bunch of comments inline, there will probably be a bit to do after that (we may want to tweak styles, review tests to add) but overall this is on the right track.
Items to action from this feedback;
- Update classes to use the
w-prefixed-kebab-convention
and avoid using&___some-selector
and instead re-write the full name in each block. - Use
theme
variables for any sizing, font weight, widths/padding/margins etc, if a variable does not exist, use the closest to your design goal. This ensures consistency through the UI and that we are using dark/light theme variations correctly + rems consistently. - Translations; every single string needs to be translated, except the actual shortcuts, this is in the template HTML and the template tags.
- Remove the duplicate column header for the table
- For the template tag, use a tuple as a key with an identifier (simple kebab-string) and the translated label.
- Use the same variable name as the value passed in the context from the template tag for code readability
- Move the 'common actions' above 'actions' & rename 'actions' to 'Page editing actions'
- Move the position, in the code, of the new
register_keyboard_shortcuts_menu_item
to a more intuitive position in the code
One column for this layout is fine I think we do not want to have to redesign the whole dialogs as part of this initial work.
I have not looked at the tests sorry but encourage you to have a full read of the CI output to try to dig into what the cause could be.
If you can, I would suggest a rebase/squash on main after these updates are done, it will help the next round of reviews.
Thank you again! this is great to see.
@thibaudcolas - please take a look if you get the chance also. Thanks @laymonage for your input.
wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html
Outdated
Show resolved
Hide resolved
wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html
Outdated
Show resolved
Hide resolved
wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html
Outdated
Show resolved
Hide resolved
@lb- It's my pleasure to work with you guys. I will make all the changes as soon as possible. 🙃 |
fc59b7c
to
becc9dd
Compare
Hey, @lb-, I have made all the changes you asked for. I will now try to figure out the failed test cases. |
@kituuu I know that debugging unit tests can be a bit tricky but I will walk you through one. *
def test_empty(self):
ModelLogEntry.objects.all().delete()
response = self.client.get(self.url)
soup = self.get_soup(response.content)
results = soup.select_one("#listing-results")
table = soup.select_one("#listing-results table")
self.assertEqual(response.status_code, 200)
self.assertIsNotNone(results)
self.assertEqual(results.text.strip(), "There are no log entries to display.")
self.assertIsNone(table) Further tests
|
93bcc09
to
5626d4a
Compare
Hey @lb-, only one following test is left. Can you say something regarding this. |
@kituuu you can ignore that current single failing test for now. This is our test against Django's main branch to flag anything that could go wrong in a future Django release. It's not related to your changes so it's ok to ignore. |
wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html
Outdated
Show resolved
Hide resolved
wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html
Outdated
Show resolved
Hide resolved
Here's a starting point for Python unit tests. @kituuu File: class TestKeyboardShortcutsDialog(WagtailTestUtils, TestCase):
def setUp(self):
self.login()
def test_keyboard_shortcuts_trigger_in_sidebar(self):
response = self.client.get(reverse("wagtailadmin_home"))
self.assertEqual(response.status_code, 200)
sidebar_data = (
self.get_soup(response.content)
.select_one("#wagtail-sidebar-props")
.contents[0]
)
self.assertIn(
json.dumps(
{
"role": "button",
"data-a11y-dialog-show": "keyboard-shortcuts-dialog",
"data-action": "w-action#noop:prevent:stop",
"data-controller": "w-action",
}
),
sidebar_data,
)
def test_keyboard_shortcuts_dialog(self):
response = self.client.get(reverse("wagtailadmin_home"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailadmin/shared/keyboard_shortcuts_dialog.html"
)
soup = self.get_soup(response.content)
# Check that the keyboard shortcuts dialog is present
shortcuts_dialog = soup.select_one("#keyboard-shortcuts-dialog")
self.assertIsNotNone(shortcuts_dialog)
# Check that the keyboard shortcuts dialog has basic accessible content
self.assertIn(
"All keyboard shortcuts", shortcuts_dialog.find("caption").prettify()
)
self.assertIn("Keyboard shortcut", shortcuts_dialog.find("thead").prettify()) Note that we have to use This unit test set should be updated to also check a few other parts of the shortcuts, and you will also need to test that the cmd / control key is correctly identified based on user agent. The default test will use See https://docs.djangoproject.com/en/5.0/topics/testing/tools/#making-requests for how to go about this. Something like... # In the test where we want to test the non-default behaviour we will not use `self.client` instead create a new client that has a different user agent.
client = Client(headers={"user-agent": "something-that-will-be-like-macos"})
client.get(reverse("wagtailadmin_home") Good luck, hopefully that helps you with the next step. |
Hey, @lb- So, firstly, I added the two tests you gave me. Apart from this, I am writing some tests to simulate the behaviour of Mac & windows machines to test the shortcuts. My initial plan was to create a new Client object with some header content resembling the Marcos Things I tried to fix this:
But something else is needed. If you have a better way of doing this, could you tell me? If I have to write more unit tests, tell me. EDIT1: Ignore my VScode theme pls |
Ahh I see, yeah, I did not thank of that, great digging. @kituuu One option would be to just change the
As an aside, it's much easier to work with pasted text from your code in comments and not a screenshot, saves me having to re-write what you have done so far. |
13772a7
to
56e5d1b
Compare
@lb- So I tried the way you suggested.
On running this, I got But with this, I just got the idea of trying it in the setUp function itself. So I created a new class.
And this second one is working as expected. EDIT1: One won't send any screenshots from now on 🙃. |
@kituuu great, that's fine to do the second class approach. However, may I suggest you move BOTH test classes to their own file instead. So instead of putting the tests in It's likely we will add to this keyboard shortcuts system over time, including the ability to support a hooks or some other mechanism for registering shortcuts so having its own file makes sense. This feature will be a similar scale to dismissables which also has its own test file Great investigating here, I have learned a lot from you on this also. |
fbe1bb2
to
4e11ac4
Compare
Done. |
Thanks. I'll do a full review and see if there is anything missing. Don't worry about quantity of contributions, this is awesome work and will be a great improvement for all Wagtail users. |
"shortcut": f"{modifier} + shift + v", | ||
}, | ||
{"label": _("Undo"), "shortcut": f"{modifier} + z"}, | ||
{"label": _("Redo"), "shortcut": f"{modifier} + y"}, |
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 default Redo on Mac is SHIFT + ⌘ + Z
, this one opens up history in Chrome. ;)
Mac also does't spell out shift but uses a arrow up icon.
Not sure how close we want to stay to OS style icons/wording.
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 we use words for modifier keys like SHIFT, we need to check if these words need translation.
So maybe it is better to stick with icons?
EN: Press the shift key.
FR: Appuyez sur la touche Maj.
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 already checked that with the translations channel, they advised against this.
{"label": _("Cut"), "shortcut": f"{modifier} + x"}, | ||
{"label": _("Paste"), "shortcut": f"{modifier} + v"}, | ||
{ | ||
"label": _("Paste without formatting"), |
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 mac wording for this label is "Paste and Match Style".
Not sure how close we want to stay to OS style icons/wording.
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.
How about Paste text only
?
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 Windows-speak is "Paste without formatting" and on Mac it is "Paste and Match Style".
The feature seems the same: Whatever snippet is in the clipboard, drop the styling, but match the current style in the text editor.
I assume (don't have Windows here), that if you use "Paste without formatting" into a heading element, the text still gets the heading elements style applied???
My suggestion is, to align shortcuts and labels (wording) with the OS.
Something like:
mac = "mac"
win = "windows"
os = mac if re.search(r"Mac|iPod|iPhone|iPad", user_agent) else win
shortcuts = {
("common-actions", _("Common Actions")): [
...,
{
"label": _("Paste and Match Style") if os == mac else _("Paste without formatting"),
"shortcut": f"{modifier} + shift + v",
},
# You'd need something similar for the other shortcut.
{
"label": _("Redo"),
"shortcut": f"Shift + {modifier} + z" if os == mac else f"{modifier} + y",
},
...,
]
}
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.
yeah, that's fair.
@kituuu are you up for this kind of change, happy if we still keep the is_mac
variable but the approach suggested above is nice.
main thing is to be able to support the adjusted wording for the paste without formatting, paste and match style.
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 ^ can become quite ugly code. So we can also do something like this:
# Windows is the default.
shortcuts = [
# Win/Mac are the same.
{
"label": "Some label",
"shortcut": "Some shortcut",
},
# Mac has other label
{
"label": "Some label",
"label_mac": "Some Mac label",
"shortcut": "Some shortcut",
},
# Mac has other shortcut
{
"label": "Some label",
"shortcut": "Some shortcut",
"shortcut_mac": "Some Mac shortcut",
},
# Mac has other label and shortcut
{
"label": "Some label",
"label_mac": "Some Mac label",
"shortcut": "Some shortcut",
"shortcut_mac": "Some Mac shortcut",
},
]
# Update the labels and shortcuts with the Mac variant if we are on OSX or iOS.
if re.search(r"Mac|iPod|iPhone|iPad", user_agent):
for item in shortcuts:
item["label"] = item.pop("label_mac", item["label"])
item["shortcut"] = item.pop("shortcut_mac", item["shortcut"])
print(shortcuts)
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.
@lb- @allcaps I understand the required changes, but do we have any list or thing for these changes? For example, what should the label name be on Mac and Windows? Right now, I only know that "Paste and Match Style" and "Paste without formatting" need to be changed, but I don't know about other shortcuts.
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 have made changes to this specific "Paste and Match Style". Are there any more labels that need to be changed based on OS?
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.
Also, there should be some changes in unit tests to check this change. Should I update the unit tests in this PR itself? Or can we ignore it for now?
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.
@lb- : D
{"label": _("Redo"), "shortcut": f"{modifier} + y"}, | ||
], | ||
("page-editing-actions", _("Page editing actions")): [ | ||
{"label": _("Save"), "shortcut": f"{modifier} + s"}, |
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 didn't know about ⌘ + s
👏
Models (snippets) are saved, but pages Save draft
. Maybe the label should be Save (draft)
?
This shortcut overrides the default browser behaviour. And in this specific case, that seems okay. Saving HTML is not a common thing to do. Overriding OS/browser shortcuts is something we should consider when adding more shortcuts.
This shortcut doesn't work when the cursor is inside a text-area. It will use the default, trigger the browser to save the HTML. -- Fixing this might be outside the scope of this PR.
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.
Great feedback - thanks!
The behaviour for keyboard shortcuts to not listen inside input fields is intentional. However, I see how this could be confusing for the CMD + S scenario.
I'll look at that for a different PR.
Generally I agree that we should not override keyboard shortcuts in the browser but this has been in the code for a long time. Be sure to have a read of #3949 for the big picture plan for keyboard shortcuts, I've already raised the same concerns there.
{"label": _("Redo"), "shortcut": f"{modifier} + y"}, | ||
], | ||
("page-editing-actions", _("Page editing actions")): [ | ||
{"label": _("Save"), "shortcut": f"{modifier} + s"}, |
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.
{"label": _("Save"), "shortcut": f"{modifier} + s"}, | |
{"label": _("Save changes"), "shortcut": f"{modifier} + s"}, |
How about this @allcaps - keeps it generic across usage.
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 really a nitpick. For the first iteration we can go with "Save".
Just to explain myself: "Save changes" or "Save" do not clarify that pages don't have a "Save" option. Pages have a workflow with "Save draft" and "Publish". Therefore, "Save" on a page will always be result in a "Save draft". I think some users expect saving a already publish page will publish their changes. But that won't happen. They just get a new draft.
The modal with shortcuts is like a cheatsheet. And it is common for cheatsheets to have additional explanation. Maybe help texts? "A page will be saved as draft."
Nice to have. Not for now.
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.
Yep. Let's keep this simple for now, I anticipate we would have a way for the label/shortcut to be registered somehow at a View or Form level.
Or we would drive this from the JS side and pull out the label from the element itself.
As this is static for this first version I think we'll have to compromise with something generic.
But if you have ideas on how we could provide a way for this to be registered - I'm super keen to hear them. Add a comment to #3949
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.
Done
efedef4
to
bd80e7e
Compare
Instead of checking for the word Preview NOT showing, only check for the exact URL. Instead of checking for all `tr` in the templates, only check for those within the correct scope.
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 @kituuu and also thanks for the extra cross-check @allcaps .
I have reviewed the shortcuts and designs and will make some changes when merging to get this finalised.
For the styling, we just needed some extra refinement to get this closer to the design. Font sizes/weights, colours of headings, background on the kbd
. I have also simplified the custom classes accordingly. Your work here is great but just wanted to get that last bit of polish in.
I have a few other recommendations but these are quite small and nitpicky so I will just apply these when merging. Tuples vs dicts, adding Docstrings and comments. I have renamed 'page editing actions' to just 'Actions' again, as this can be used for Snippets and potentially other places also.
Finally, I have opted to include a {% block shortcuts %}
within the template. We do not want to officially document this but at least there is an indirect way to customise these shortcuts (simply) when this goes live.
Here's the final state (comparing to the designs).
It's not exactly what you envisioned @benenright but as close as we can get without redesigning parts of the dialog structure and without introducing more complex HTML for the keyboard values (e.g. you have bold for the letters in the shortcuts).
I have tested in Chrome 124, Safari 17.1 and Firefox 123 - looking good in all.
I will make the changes discussed, force push with a changelog and look to merge this in.
@@ -0,0 +1,28 @@ | |||
{% load wagtailadmin_tags i18n %} | |||
{% dialog icon_name='regex' id='keyboard-shortcuts-dialog' title=_("Keyboard shortcuts") %} |
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 suggest we put the root classname="w-keyboard-shortcuts"
on the dialog here, instead of on the table
. Will make it easier to style the content as a whole.
@@ -0,0 +1,28 @@ | |||
{% load wagtailadmin_tags i18n %} | |||
{% dialog icon_name='regex' id='keyboard-shortcuts-dialog' title=_("Keyboard shortcuts") %} | |||
<table class="w-keyboard-shortcuts"> |
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.
<table class="w-keyboard-shortcuts"> | |
<table class="w-w-full"> |
Probably simpler to have the full width as a tailwind class, with the root class moved to the dialog.
user_agent = context["request"].headers.get("User-Agent", "") | ||
is_mac = re.search(r"Mac|iPod|iPhone|iPad", user_agent) | ||
modifier = "⌘" if is_mac else "Ctrl" | ||
shortcuts = { |
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.
Sorry for the back and forth on structure here but a few changes I will do when merging but wanted to explain.
- Let's use tuples for the shortcuts ->
{"label": _("Copy"), "shortcut": f"{modifier} + c"}
would be(_("Copy"), f"{modifier} + c")
. This is a bit easier to read when looking at a lot of these. - Let's stick to the same casing for
Shift
not a mix ofShift
andshift
. - Let's stick to the same casing for titles and labels, e.g.
Common actions
notCommon Actions
andPaste and match style
notPaste and Match Style
. Leaning towards 'Sentence case' not 'Title Case'. - Might be nice to add a simple docstring on this class for explanation.
"""
Renders the keyboard shortcuts dialog content with the
appropriate shortcuts for the user's platform.
Note: Shortcut keys are intentionally not translated.
"""
@@ -998,6 +998,23 @@ def register_editors_guide_menu_item(): | |||
) | |||
|
|||
|
|||
@hooks.register("register_help_menu_item") | |||
def register_keyboard_shortcuts_menu_item(): |
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.
Let's add a simple docstring here, helps for future context. I can add this when merging.
def register_keyboard_shortcuts_menu_item(): | |
def register_keyboard_shortcuts_menu_item(): | |
""" | |
Triggers the keyboard shortcuts dialog to open when clicked | |
while preventing the default link click action. | |
""" | |
bd80e7e
to
11c79df
Compare
Include the ability to trigger the dialog from the sidebar Include base styling and unit tests Fixes #wagtail#11711 Relates to larger work for keyboard shortcuts in wagtail#3949
11c79df
to
48f5a64
Compare
#11767 created for the next bit to do (add an icon), for anyone who is keen. |
#11711 [REVIEWs & CHANGEs]
The basic structure for this issue is ready.
TODO (For my convenience):
Check this
Do you think the implementation of the design from Figma is necessary? Because right now, the default styling and existing dialogue template do not fit that design. I will have to write separate CSS and a new dialogue box (Maybe we can make this new one default if it is better visually).
[Personally I feel this dialogue box design (figma one) is a bit better in distinguishing different sections : D ]