-
Notifications
You must be signed in to change notification settings - Fork 25
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
Text Command Popover Fixes/UI Improvements #7728
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7728 +/- ##
==========================================
- Coverage 72.21% 72.14% -0.08%
==========================================
Files 1264 1267 +3
Lines 39628 39688 +60
Branches 7343 7355 +12
==========================================
+ Hits 28617 28631 +14
- Misses 11011 11057 +46 ☔ View full report in Codecov by Sentry. |
@@ -294,15 +277,6 @@ export const initSelectionTooltip = once(() => { | |||
{ passive: true }, | |||
); | |||
|
|||
// Try to avoid sticky tool-tip on SPA navigation |
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.
Moved to createTooltip so the handler is removed when not showing
@@ -34,71 +32,3 @@ export function getElementText(element: TextEditorElement): string { | |||
|
|||
return $(element).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.
Moved to contentScript/commandPopover
folder because it needs to call the pageScript
* @param element the element to insert text into. Can be a text input, textarea, or contenteditable element. | ||
* @param text the text to insert | ||
*/ | ||
export async function insertAtCursorWithCustomEditorSupport({ |
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.
@fregante I'm assuming you might not upstream editor-specific affordances to textFieldEdit because they require a pageScript?
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.
My idea is that it's up to the user of the library to know and work around these limitations. This means that such a library would already expect to be run in the right context, leaving messaging out of it.
As for text-field-edit, I don’t think I'll upstream it because the advanced methods (wrap, replace) might be pretty complex for each custom editor. textFieldEdit.replace()
already doesn't support the basic content editable.
I could work on a dedicated get/set/delete/insert library and API for advanced editors, but it looks like you got this going in the extension already. Let me know if you'd like me to work on anything specific
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.
As for text-field-edit, I don’t think I'll upstream it because the advanced methods (wrap, replace) might be pretty complex for each custom editor. textFieldEdit.replace() already doesn't support the basic content editable.
👍 Makes sense -- that library has a different surface area/goals
Let me know if you'd like me to work on anything specific
Will do as we come across gaps/quirks
*/ | ||
// Currently using the tooltip terminology to match the filename, but will likely switch to popover in the future | ||
// to match the web popover API terminology. | ||
export function tooltipFactory(): HTMLElement { |
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.
Consolidated command popover and selection popover's properties
src/contentScript/tooltipDom.ts
Outdated
popover.style.setProperty("border-radius", "5px"); | ||
// Can't use colors file because the element is being rendered directly on the host | ||
popover.style.setProperty("background-color", "#ffffff"); // $S0 color | ||
popover.style.setProperty("border", "2px solid #a8a1b4"); // $N200 color |
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've been using this pattern to set the style all at once:
Object.assign(popover.style, {
border: "2px solid #a8a1b4",
etc
})
src/contentScript/textEditorDom.ts
Outdated
|
||
// Ensure the element has focus, so that text is inserted at the cursor position | ||
if (document.activeElement !== element) { | ||
element.focus(); |
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 already taken care of by text-field-edit, so it's not necessary for that reason.
However it will also reset the focus to wherever it was before the insertText call.
I'd just lump the two focus calls together and keep your first comment:
// Let the user continue writing etc
window.focus()
field.focus()
src/contentScript/textEditorDom.ts
Outdated
); | ||
|
||
if (isSelectableTextControlElement(element)) { | ||
textFieldEdit.insert(element, 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.
I don’t think we need a dedicated call now that text-field-edit supports content editable fields. This is also missing the window.focus call seen below
* @param element the element to insert text into. Can be a text input, textarea, or contenteditable element. | ||
* @param text the text to insert | ||
*/ | ||
export async function insertAtCursorWithCustomEditorSupport({ |
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.
My idea is that it's up to the user of the library to know and work around these limitations. This means that such a library would already expect to be run in the right context, leaving messaging out of it.
As for text-field-edit, I don’t think I'll upstream it because the advanced methods (wrap, replace) might be pretty complex for each custom editor. textFieldEdit.replace()
already doesn't support the basic content editable.
I could work on a dedicated get/set/delete/insert library and API for advanced editors, but it looks like you got this going in the extension already. Let me know if you'd like me to work on anything specific
src/contentScript/textEditorDom.ts
Outdated
import { isSelectableTextControlElement } from "@/types/inputTypes"; | ||
|
||
/** | ||
* @file file for contentScript-specific text editor DOM utilities that require Javascript API calls to editors. |
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 you mean pageScript. The content script doesn't have access to CKEditor's properties. This file should probably be moved to the pageScript folder or out of contentScript anyway
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'll clarify tomorrow - this file is actually OK in the content script. It checks for CKEditor using the class name and then calls the pageScript via messenger
The @/contrib/ckeditor
module could split up into pageScript-specific vs. safe methods to clarify the behavior
As you mention here: https://github.com/pixiebrix/pixiebrix-extension/pull/7728/files#r1501980861, we might instead opt to move insertAtCursorWithCustomEditorSupport to the pageScript vs. doing some logic in the contentScript and some in the pageScript.
Historically, I've preferred using contentScript where possible due to reduced likelihood of the host page JS somehow interfering. Also, injecting the pageScript has a slight overhead when it needs to be injected
if (selectionTooltip) { | ||
// Cleanly unmount React component to ensure any listeners are cleaned up. | ||
// https://react.dev/reference/react-dom/unmountComponentAtNode | ||
unmountComponentAtNode(selectionTooltip); |
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.
🙌
We should use this more. I added a few of these calls when working on #4258, but I wasn't consistent.
}); | ||
document.removeEventListener("keydown", handleKeyDown, { | ||
capture: true, | ||
}); |
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 might prefer creating a signal and then just return () => controller.abort()
to cancel all at once without gotchas/comments/forgotten removals.
|
||
element.focus(); | ||
|
||
if (isSelectableTextControlElement(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.
I think this excludes type=number
fields, but then the function goes on to assume we're working on a content editable. There should probably be a "is content editable" check below
? getCaretCoordinates(textControl, selectionStart) | ||
: { | ||
top: 0, | ||
left: 0, | ||
height: Number.parseInt( | ||
window.getComputedStyle(targetElement).lineHeight, |
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 this returns "normal"
by default. I remember calculating line-height being an issue for the longest time.
void updatePosition(); | ||
// For now just destroy the tooltip on document/element scroll to avoid gotchas with floating UI's `position: fixed` | ||
// strategy. See tooltipController.ts for more details. | ||
document.activeElement?.addEventListener( |
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.
Can all these listeners be wrapped in a single call? I saw the same piece of code copy-pasted below I think
element, | ||
onSubmit, | ||
onOffset, | ||
}: { | ||
commandKey?: string; | ||
element: TextEditorElement; | ||
onSubmit: () => void; | ||
onSubmit: (query: string) => void; |
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.
Passing the query in the callback so the handler can replace only the \query
When the PR is merged, the first loom link found on this PR will be posted to |
What does this PR do?
\
to not conflict with common key on Zendesk, Notion, etc.Tab
submission wasn't working for editors that use Tab for indent (e.g., CKEditor)selectionstart
insertAtCursorWithCustomEditorSupport
method called from InsertAtCursorEffect and command popoverRemaining Work
<all_urls>
instead of*://*
. We might consider changing our "All URLs" shortcut to use<all_urls>
Discussion
Demo
Future Work
insertAtCursorWithCustomEditorSupport
as needed to work with other custom editorsChecklist
document.execCommand
src/tsconfig.strictNullChecks.json
(if possible)