Added click handling and HTML attribute link options to the editor#2656
Added click handling and HTML attribute link options to the editor#2656matthewlipski wants to merge 1 commit intomainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR introduces a new Changes
Sequence DiagramsequenceDiagram
actor User
participant Editor as BlockNote Editor
participant Plugin as Link Click Plugin
participant Handler as Custom onClick/<br/>window.open
User->>Editor: Click on link element
Editor->>Plugin: ProseMirror plugin intercepts click
Plugin->>Plugin: Verify left-click & editability
Plugin->>Plugin: Find nearest <a> element<br/>(within editor root)
alt Custom onClick provided
Plugin->>Handler: Invoke options.links.onClick(event)
Handler->>Handler: Execute custom logic
else No custom onClick
Plugin->>Handler: window.open(href, target)
Handler->>Handler: Open link in target window
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
packages/core/src/editor/managers/ExtensionManager/extensions.ts (2)
145-152: Remove commented-outenableClickSelectionblock.Dead commented-out code referring to an option that is "always disabled" is noise — if it's not coming back in this PR, prefer deleting it (it'll still be in git history). If it's a planned follow-up, a
TODOreferencing an issue is clearer than a paragraph of commented code.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/editor/managers/ExtensionManager/extensions.ts` around lines 145 - 152, Remove the dead commented-out block that references enableClickSelection and the extendMarkRange call (the commented lines containing "if (options.enableClickSelection) {", "tiptapEditor.commands.extendMarkRange", and "markType.name"); delete those commented lines rather than leaving them in place, or if you want to keep a note add a single-line TODO referencing the issue number, but do not keep the multi-line commented code in ExtensionManager/extensions.ts.
96-174: Missing test coverage for the new click handler.The PR checklist claims "Unit tests covering the new feature have been added", but this handler has non-trivial branching (left-button check, editable check, ancestor walk bounded to editor root,
onClickoverride vs.window.openfallback) and several of the bugs above would be caught by straightforward tests. Consider adding unit tests that at minimum cover:
- Non-left-button click (should be ignored).
- Click on non-anchor descendant inside an anchor (ancestor lookup).
- Anchor outside editor root (should not be handled).
onClickprovided →onClickinvoked andwindow.opennot called.onClicknot provided →window.opencalled with correcthref/target, including when those come from mark attrs rather than DOM attributes.Happy to sketch out a test file for this handler if you'd like.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/editor/managers/ExtensionManager/extensions.ts` around lines 96 - 174, Add unit tests for the click handler produced by ExtensionManager.addProseMirrorPlugins: exercise the Plugin.props.handleClick branching (check non-left-button returns false; clicking a non-anchor descendant inside an anchor resolves via closest; clicks on anchors outside the tiptapEditor.view.dom root are ignored), verify options.links.onClick is invoked when present (and window.open is not called), and verify window.open is called with correct href/target when onClick is absent, including when href/target are provided via getAttributes(view.state, markType.name) rather than the DOM anchor; target the handleClick logic by constructing a mock view (tiptapEditor.view), stub getAttributes, and spy on window.open and options.links.onClick to assert behavior.docs/content/docs/features/blocks/inline-content.mdx (1)
115-127: ClarifyonClicksemantics in the docs.The description states that providing
onClickdisables the default open-in-new-window behavior, but it does not mention a few subtleties that users are likely to hit:
- The handler receives only the raw
MouseEvent; to gethref/target, the consumer has to walkevent.targetup to the nearest<a>themselves. Exposing at least the anchor element (orhref/target) in the callback signature would make this much more usable.- The default (no
onClick) currently opens viawindow.open(href, target)with nonoopener/noreferrer— worth documenting so consumers know when to supply their own handler for security-sensitive contexts.- It would help to mention that the callback is only invoked on primary-button (left) clicks inside an editable view.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/content/docs/features/blocks/inline-content.mdx` around lines 115 - 127, Update the onClick docs for BlockNoteEditor.links to clarify behavior: state that links.onClick receives only the raw MouseEvent (not the anchor or href/target) so consumers must walk event.target up to the nearest <a> to read href/target, note the default behavior when onClick is undefined is window.open(href, target) without noopener/noreferrer (so recommend supplying a custom handler for security-sensitive contexts), and mention the callback is only invoked for primary-button (left) clicks inside an editable view; reference the onClick option and BlockNoteEditor.links in the text so readers can locate the setting.packages/core/src/editor/BlockNoteEditor.ts (1)
143-161: Consider lettingonClicksignal whether it handled the event.The current signature
(event: MouseEvent) => voidforces the editor to treat everyonClickinvocation as fully handling the click (seeextensions.tslines 154–165), which is fine for the documented "custom routing" use case but makes it impossible for a consumer to fall through to the default open-in-new-window behavior conditionally (e.g. ignore modifier-clicks, or only intercept same-origin links). Returningvoid | boolean— wheretruemeans "handled, skip default" and falsy means "fall through to default" — would be a more forward-compatible API. Not blocking, just easier to evolve than widening the return type later.- onClick?: (event: MouseEvent) => void; + onClick?: (event: MouseEvent) => void | boolean;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/editor/BlockNoteEditor.ts` around lines 143 - 161, Update the links.onClick handler to return a boolean-ish value so consumers can signal whether they handled the event: change the documented signature from (event: MouseEvent) => void to (event: MouseEvent) => boolean | void (or boolean | undefined), update the implementation that invokes onClick (the caller in extensions.ts that currently treats any invocation as handled) to only suppress the default open-on-click behavior when onClick returns a truthy value, and update the JSDoc comment for links.HTMLAttributes/onClick to explain that returning true prevents the default open-in-new-window behavior while falsy allows fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/content/docs/features/blocks/inline-content.mdx`:
- Around line 86-98: The docs show using the protected constructor (new
BlockNoteEditor(...)) which is not public; update the three examples to call the
public factory instead by replacing new BlockNoteEditor({...}) with
BlockNoteEditor.create({...}) so they match the editor's public API (constructor
is protected in BlockNoteEditor.ts) and will type-check for users.
In `@packages/core/src/editor/managers/ExtensionManager/extensions.ts`:
- Around line 143-168: The handleClick branch that calls options.links?.onClick
currently leaves handled as false so the DOM click may still trigger navigation;
update the onClick branch in the handleClick implementation to prevent default
browser navigation and mark the click as handled — e.g., call
event.preventDefault() (and optionally event.stopPropagation()) and set handled
= true after invoking options.links.onClick(event) so the function returns true
and the click is consumed; keep the existing fallback behavior (using
getAttributes/href/window.open) unchanged.
- Around line 130-131: Fix the typo and complete the truncated comment in
extensions.ts: change "Tntentionally" to "Intentionally" and finish the sentence
so it reads clearly (e.g., "Intentionally limit the lookup to the editor root.
Using tag names like DIV as boundaries breaks with custom NodeViews, so we must
scope to the editor root to avoid incorrect boundary detection."). Update the
comment near the ExtensionManager or extensions.ts lookup logic (the block that
mentions editor root and NodeViews) so it is grammatically correct and conveys
the full rationale.
- Around line 161-163: The current call window.open(href, target) is vulnerable
to tabnabbing; update the logic where href/target are used (the branch that sets
handled = true) to call window.open with the feature string
"noopener,noreferrer" (e.g., window.open(href, target, "noopener,noreferrer"))
so the opened page cannot access window.opener; ensure this is applied whenever
opening external links from the editor (the code path using href, target,
handled) and keep handled = true unchanged.
- Around line 158-164: Replace uses of the DOM IDL properties so fallback values
work: read href and target via link.getAttribute('href') and
link.getAttribute('target') (e.g., const hrefAttr = link.getAttribute('href') ??
attrs.href; const linkTarget = link.getAttribute('target') ?? attrs.target)
instead of link.href / link.target; rename the local target variable to avoid
shadowing the DOM property (e.g., linkTarget) and pass that to window.open;
ensure that when options.links.onClick is called you set handled = true (or call
event.preventDefault()) so ProseMirror doesn't allow default navigation; also
correct the typo "Tntentionally" to "Intentionally" in the surrounding
comment/strings.
---
Nitpick comments:
In `@docs/content/docs/features/blocks/inline-content.mdx`:
- Around line 115-127: Update the onClick docs for BlockNoteEditor.links to
clarify behavior: state that links.onClick receives only the raw MouseEvent (not
the anchor or href/target) so consumers must walk event.target up to the nearest
<a> to read href/target, note the default behavior when onClick is undefined is
window.open(href, target) without noopener/noreferrer (so recommend supplying a
custom handler for security-sensitive contexts), and mention the callback is
only invoked for primary-button (left) clicks inside an editable view; reference
the onClick option and BlockNoteEditor.links in the text so readers can locate
the setting.
In `@packages/core/src/editor/BlockNoteEditor.ts`:
- Around line 143-161: Update the links.onClick handler to return a boolean-ish
value so consumers can signal whether they handled the event: change the
documented signature from (event: MouseEvent) => void to (event: MouseEvent) =>
boolean | void (or boolean | undefined), update the implementation that invokes
onClick (the caller in extensions.ts that currently treats any invocation as
handled) to only suppress the default open-on-click behavior when onClick
returns a truthy value, and update the JSDoc comment for
links.HTMLAttributes/onClick to explain that returning true prevents the default
open-in-new-window behavior while falsy allows fallback.
In `@packages/core/src/editor/managers/ExtensionManager/extensions.ts`:
- Around line 145-152: Remove the dead commented-out block that references
enableClickSelection and the extendMarkRange call (the commented lines
containing "if (options.enableClickSelection) {",
"tiptapEditor.commands.extendMarkRange", and "markType.name"); delete those
commented lines rather than leaving them in place, or if you want to keep a note
add a single-line TODO referencing the issue number, but do not keep the
multi-line commented code in ExtensionManager/extensions.ts.
- Around line 96-174: Add unit tests for the click handler produced by
ExtensionManager.addProseMirrorPlugins: exercise the Plugin.props.handleClick
branching (check non-left-button returns false; clicking a non-anchor descendant
inside an anchor resolves via closest; clicks on anchors outside the
tiptapEditor.view.dom root are ignored), verify options.links.onClick is invoked
when present (and window.open is not called), and verify window.open is called
with correct href/target when onClick is absent, including when href/target are
provided via getAttributes(view.state, markType.name) rather than the DOM
anchor; target the handleClick logic by constructing a mock view
(tiptapEditor.view), stub getAttributes, and spy on window.open and
options.links.onClick to assert behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c07edf04-54b9-4e24-985b-228535edc5ce
📒 Files selected for processing (3)
docs/content/docs/features/blocks/inline-content.mdxpackages/core/src/editor/BlockNoteEditor.tspackages/core/src/editor/managers/ExtensionManager/extensions.ts
| ```ts | ||
| const editor = new BlockNoteEditor({ | ||
| links: { | ||
| HTMLAttributes: { | ||
| class: "my-link-class", | ||
| target: "_blank", | ||
| }, | ||
| onClick: (event) => { | ||
| // Custom click logic, e.g. routing without a page reload. | ||
| }, | ||
| }, | ||
| }); | ||
| ``` |
There was a problem hiding this comment.
Docs examples use new BlockNoteEditor(...), but the constructor is protected.
In packages/core/src/editor/BlockNoteEditor.ts, the constructor is declared protected and the public entry point is the static BlockNoteEditor.create(...). All three snippets in this section (lines 86–98, 104–113, 119–127) invoke new BlockNoteEditor(...), which is not a valid public API and will fail type-checking for users copy-pasting these examples. Consider switching to BlockNoteEditor.create({ ... }) to match the rest of the documentation.
📝 Proposed fix (apply to all three examples)
-const editor = new BlockNoteEditor({
+const editor = BlockNoteEditor.create({
links: {
...
},
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ```ts | |
| const editor = new BlockNoteEditor({ | |
| links: { | |
| HTMLAttributes: { | |
| class: "my-link-class", | |
| target: "_blank", | |
| }, | |
| onClick: (event) => { | |
| // Custom click logic, e.g. routing without a page reload. | |
| }, | |
| }, | |
| }); | |
| ``` |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/content/docs/features/blocks/inline-content.mdx` around lines 86 - 98,
The docs show using the protected constructor (new BlockNoteEditor(...)) which
is not public; update the three examples to call the public factory instead by
replacing new BlockNoteEditor({...}) with BlockNoteEditor.create({...}) so they
match the editor's public API (constructor is protected in BlockNoteEditor.ts)
and will type-check for users.
| // Tntentionally limit the lookup to the editor root. | ||
| // Using tag names like DIV as boundaries breaks with custom NodeViews, |
There was a problem hiding this comment.
Typo: "Tntentionally" → "Intentionally", and the comment sentence is truncated ("Using tag names like DIV as boundaries breaks with custom NodeViews,").
- // Tntentionally limit the lookup to the editor root.
- // Using tag names like DIV as boundaries breaks with custom NodeViews,
+ // Intentionally limit the lookup to the editor root.
+ // Using tag names like DIV as boundaries breaks with custom NodeViews.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Tntentionally limit the lookup to the editor root. | |
| // Using tag names like DIV as boundaries breaks with custom NodeViews, | |
| // Intentionally limit the lookup to the editor root. | |
| // Using tag names like DIV as boundaries breaks with custom NodeViews. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/editor/managers/ExtensionManager/extensions.ts` around
lines 130 - 131, Fix the typo and complete the truncated comment in
extensions.ts: change "Tntentionally" to "Intentionally" and finish the sentence
so it reads clearly (e.g., "Intentionally limit the lookup to the editor root.
Using tag names like DIV as boundaries breaks with custom NodeViews, so we must
scope to the editor root to avoid incorrect boundary detection."). Update the
comment near the ExtensionManager or extensions.ts lookup logic (the block that
mentions editor root and NodeViews) so it is grammatically correct and conveys
the full rationale.
| let handled = false; | ||
|
|
||
| // `enableClickSelection` is always disabled. | ||
| // if (options.enableClickSelection) { | ||
| // const commandResult = | ||
| // tiptapEditor.commands.extendMarkRange( | ||
| // markType.name, | ||
| // ); | ||
| // handled = commandResult; | ||
| // } | ||
|
|
||
| if (options.links?.onClick) { | ||
| options.links.onClick(event); | ||
| } else { | ||
| const attrs = getAttributes(view.state, markType.name); | ||
| const href = link.href ?? attrs.href; | ||
| const target = link.target ?? attrs.target; | ||
|
|
||
| if (href) { | ||
| window.open(href, target); | ||
| handled = true; | ||
| } | ||
| } | ||
|
|
||
| return handled; | ||
| }, |
There was a problem hiding this comment.
onClick branch returns handled = false, so the browser may still navigate.
When options.links?.onClick is provided, handled stays false, and the plugin returns false from handleClick. ProseMirror then does not treat the click as consumed, and the underlying DOM click event continues to propagate — for a real <a href="…">, the browser's default navigation will fire after your custom handler, which typically defeats the whole point of overriding onClick (e.g. SPA routing) and can cause a full page reload.
Either return true (and/or call event.preventDefault()) when onClick is invoked, or document that consumers must call preventDefault() themselves. The former matches the documented behavior ("default behavior is disabled and this function is called instead"):
🐛 Proposed fix
- if (options.links?.onClick) {
- options.links.onClick(event);
+ if (options.links?.onClick) {
+ options.links.onClick(event);
+ handled = true;
} else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/editor/managers/ExtensionManager/extensions.ts` around
lines 143 - 168, The handleClick branch that calls options.links?.onClick
currently leaves handled as false so the DOM click may still trigger navigation;
update the onClick branch in the handleClick implementation to prevent default
browser navigation and mark the click as handled — e.g., call
event.preventDefault() (and optionally event.stopPropagation()) and set handled
= true after invoking options.links.onClick(event) so the function returns true
and the click is consumed; keep the existing fallback behavior (using
getAttributes/href/window.open) unchanged.
| const href = link.href ?? attrs.href; | ||
| const target = link.target ?? attrs.target; | ||
|
|
||
| if (href) { | ||
| window.open(href, target); | ||
| handled = true; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
HTMLAnchorElement href and target IDL attribute return type when attribute absent
💡 Result:
When the href attribute is absent on an HTMLAnchorElement, the href IDL attribute returns the empty string (""), as explicitly stated in MDN documentation with examples showing a newly created <a> element returns "". This differs from setting href to an empty string, which resolves to the document's base URL. When the target attribute is absent, the target IDL attribute returns the empty string (""), as both are reflected DOMString attributes per the HTML specification's reflection rules: the getter returns the attribute's value or null/empty string if absent, and MDN confirms it "reflects the target attribute" with no default value specified.
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/href
- 2: https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement/href
- 3: https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement/target
- 4: https://html.spec.whatwg.org/multipage/common-dom-interfaces.html
- 5: https://whatwg.org/html/common-dom-interfaces.html
🏁 Script executed:
find . -type f -name "extensions.ts" | grep -i extensionmanagerRepository: TypeCellOS/BlockNote
Length of output: 130
🏁 Script executed:
cat -n ./packages/core/src/editor/managers/ExtensionManager/extensions.ts | sed -n '110,180p'Repository: TypeCellOS/BlockNote
Length of output: 2993
🏁 Script executed:
cat -n ./packages/core/src/editor/managers/ExtensionManager/extensions.ts | sed -n '1,50p'Repository: TypeCellOS/BlockNote
Length of output: 2019
🏁 Script executed:
cat -n ./packages/core/src/editor/managers/ExtensionManager/extensions.ts | sed -n '100,175p'Repository: TypeCellOS/BlockNote
Length of output: 3229
link.href ?? attrs.href and link.target ?? attrs.target never fall back.
HTMLAnchorElement.href and HTMLAnchorElement.target are IDL attributes that always return a string — they are never null/undefined. For an anchor with no href attribute, link.href is "", and for no target, link.target is "". Because nullish coalescing only falls back on null/undefined, the attrs.href / attrs.target paths are effectively dead code, and when the rendered anchor has an empty target, you'll pass "" to window.open instead of the mark's configured target.
Additionally, when options.links.onClick is provided (line 154), handled is never set to true, so the handler returns false and ProseMirror doesn't claim the click, allowing default browser navigation to proceed even though the callback was fired. The callback should either set handled = true or call event.preventDefault().
Also fix the typo at line 130: "Tntentionally" → "Intentionally".
Use getAttribute(...) (which returns null when missing and round-trips through ??), rename target to avoid confusion with the DOM element property, and set handled = true when onClick is invoked:
🐛 Proposed fix
if (options.links?.onClick) {
options.links.onClick(event);
+ handled = true;
} else {
const attrs = getAttributes(view.state, markType.name);
- const href = link.href ?? attrs.href;
- const target = link.target ?? attrs.target;
+ const href = link.getAttribute("href") || attrs.href;
+ const linkTarget =
+ link.getAttribute("target") || attrs.target;
if (href) {
- window.open(href, target);
+ window.open(href, linkTarget);
handled = true;
}
}
- // Tntentionally limit the lookup to the editor root.
+ // Intentionally limit the lookup to the editor root.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const href = link.href ?? attrs.href; | |
| const target = link.target ?? attrs.target; | |
| if (href) { | |
| window.open(href, target); | |
| handled = true; | |
| } | |
| const href = link.getAttribute("href") || attrs.href; | |
| const linkTarget = | |
| link.getAttribute("target") || attrs.target; | |
| if (href) { | |
| window.open(href, linkTarget); | |
| handled = true; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/editor/managers/ExtensionManager/extensions.ts` around
lines 158 - 164, Replace uses of the DOM IDL properties so fallback values work:
read href and target via link.getAttribute('href') and
link.getAttribute('target') (e.g., const hrefAttr = link.getAttribute('href') ??
attrs.href; const linkTarget = link.getAttribute('target') ?? attrs.target)
instead of link.href / link.target; rename the local target variable to avoid
shadowing the DOM property (e.g., linkTarget) and pass that to window.open;
ensure that when options.links.onClick is called you set handled = true (or call
event.preventDefault()) so ProseMirror doesn't allow default navigation; also
correct the typo "Tntentionally" to "Intentionally" in the surrounding
comment/strings.
| if (href) { | ||
| window.open(href, target); | ||
| handled = true; |
There was a problem hiding this comment.
window.open(href, target) without noopener,noreferrer is a tabnabbing risk.
Opening a user-controlled URL in a new browsing context without noopener leaves window.opener accessible to the destination page, which can then navigate the original editor tab via window.opener.location. Since link href/target come from editor content (often user-authored), this should use "noopener,noreferrer" features by default:
- window.open(href, target);
+ window.open(href, linkTarget || "_blank", "noopener,noreferrer");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (href) { | |
| window.open(href, target); | |
| handled = true; | |
| if (href) { | |
| window.open(href, target, "noopener,noreferrer"); | |
| handled = true; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/editor/managers/ExtensionManager/extensions.ts` around
lines 161 - 163, The current call window.open(href, target) is vulnerable to
tabnabbing; update the logic where href/target are used (the branch that sets
handled = true) to call window.open with the feature string
"noopener,noreferrer" (e.g., window.open(href, target, "noopener,noreferrer"))
so the opened page cannot access window.opener; ensure this is applied whenever
opening external links from the editor (the code path using href, target,
handled) and keep handled = true unchanged.
@blocknote/ariakit
@blocknote/code-block
@blocknote/core
@blocknote/mantine
@blocknote/react
@blocknote/server-util
@blocknote/shadcn
@blocknote/xl-ai
@blocknote/xl-docx-exporter
@blocknote/xl-email-exporter
@blocknote/xl-multi-column
@blocknote/xl-odt-exporter
@blocknote/xl-pdf-exporter
commit: |
Summary
This PR adds a new editor option:
These do basically what they say -
HTMLAttributesadds HTML attributes to rendered link elements andonClickreplaces the default click behaviour (which opens the link in a new tab).Closes #1539
Rationale
Some users are finding it annoying that links open a new tab on click when they're just trying to move the selection.
HTML attributes allow for slight customization for link rendering. It's the best we can do atm, but really more of a stopgap solution as consumers should ideally be able to override the default link rendering with whatever they want.
Changes
See above.
Impact
N/A
Testing
N/A (example needed?)
Screenshots/Video
N/A
Checklist
Additional Notes
Summary by CodeRabbit
New Features
Documentation