Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions src/features/editor/ui/HighlightButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,70 @@
import { useCallback } from 'react'

/**
* Yellow highlight color value used for the highlight style.
* Unique identifier for the highlighter style.
* Uses a dedicated value ("highlight") instead of BlockNote's built-in
* colour names so that the highlighter and the color-picker's yellow
* are treated as separate styles.
*/
const HIGHLIGHT_COLOR = 'yellow'
const HIGHLIGHT_STYLE = 'highlight'

/**
* Toggle button that applies or removes a yellow highlight on the
* selected text using BlockNote's built-in `backgroundColor` style.
* Toggle button that applies or removes a fluorescent-yellow highlight
* on the selected text using a custom `backgroundColor` style value.
*
* The button is hidden when the editor is read-only or no inline
* content is selected. It appears active (pressed) when the
* selection already has the yellow highlight applied.
*/
export const HighlightButton = () => {
const Components = useComponentsContext()!

Check warning on line 26 in src/features/editor/ui/HighlightButton.tsx

View workflow job for this annotation

GitHub Actions / Frontend Lint & Format

lint/style/noNonNullAssertion

Forbidden non-null assertion.
const editor = useBlockNoteEditor()

const state = useEditorState({
editor,
selector: ({ editor }) => {
// Hide the button when the editor is in read-only mode.
if (!editor.isEditable) {
return undefined
}

// Collect the blocks covered by the current selection (or the block at cursor).
const selectedBlocks = editor.getSelection()?.blocks || [
editor.getTextCursorPosition().block,
]

// Hide the button when none of the selected blocks contain inline content.
const hasContent = selectedBlocks.some(
(block) => block.content !== undefined
)
if (!hasContent) {
return undefined
}

// Determine whether the highlight style is already applied to the selection.
const activeBg = editor.getActiveStyles().backgroundColor
return { active: activeBg === HIGHLIGHT_COLOR }
return { active: activeBg === HIGHLIGHT_STYLE }
},
})

/**
* Toggles the fluorescent highlight on the current editor selection.
*
* Focuses the editor first, then adds or removes the custom
* `backgroundColor: "highlight"` style depending on whether the
* highlight is already active on the selection.
*/
const toggleHighlight = useCallback(() => {
editor.focus()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const style = { backgroundColor: HIGHLIGHT_COLOR } as any
const isHighlighted =
editor.getActiveStyles().backgroundColor === HIGHLIGHT_COLOR
const style = { backgroundColor: HIGHLIGHT_STYLE } as any

if (isHighlighted) {
if (state?.active) {
editor.removeStyles(style)
} else {
editor.addStyles(style)
}
}, [editor])
}, [editor, state])

if (state === undefined) {
return null
Expand Down
85 changes: 62 additions & 23 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,19 @@ body {
color: oklch(0.828 0.111 54);
}

/*
* Highlighter markers – shared shape.
*
* Both "yellow" (colour-picker) and "highlight" (dedicated highlighter) use
* the same padding / margin / border-radius to emulate a highlighter stroke.
*/
span[data-style-type="backgroundColor"][data-value="yellow"],
span[data-style-type="backgroundColor"][data-value="highlight"] {
padding: 0.1em 0.15em;
margin-right: 0.1em;
border-radius: 0.2em;
}

/*
* Highlighter marker (yellow) - light mode.
*
Expand All @@ -249,9 +262,6 @@ body {
*/
span[data-style-type="backgroundColor"][data-value="yellow"] {
background-color: oklch(0.92 0.17 90);
padding: 0.1em 0.15em;
margin-right: 0.1em;
border-radius: 0.2em;
}

/*
Expand All @@ -265,6 +275,24 @@ span[data-style-type="backgroundColor"][data-value="yellow"] {
color: oklch(0.25 0.05 85);
}

/*
* Highlighter marker (fluorescent) - light mode.
*
* Dedicated highlighter style that is independent from the colour
* picker's "yellow" so the two can use different shades.
*/
span[data-style-type="backgroundColor"][data-value="highlight"] {
background-color: oklch(0.96 0.11 95);
}

/*
* Highlighter marker (fluorescent) - dark mode.
*/
.dark span[data-style-type="backgroundColor"][data-value="highlight"] {
background-color: oklch(0.75 0.10 95);
color: oklch(0.25 0.05 85);
}

/*
* Override BlockNote link colour in dark mode.
*
Expand Down Expand Up @@ -496,28 +524,39 @@ html.cursor-autohide-hidden * {
cursor: none !important;
}

html.cursor-autohide-hidden [role="dialog"],
html.cursor-autohide-hidden [role="menu"],
html.cursor-autohide-hidden [role="listbox"],
html.cursor-autohide-hidden .bn-suggestion-menu,
html.cursor-autohide-hidden .bn-link-toolbar,
html.cursor-autohide-hidden .bn-color-picker-dropdown,
html.cursor-autohide-hidden .bn-formatting-toolbar,
html.cursor-autohide-hidden .bn-table-handle-menu,
html.cursor-autohide-hidden [data-slot="select-content"],
html.cursor-autohide-hidden [data-slot="dropdown-menu-content"] {
/*
* Interactive overlays that keep the cursor visible during auto-hide.
*
* The `:is()` pseudo-class avoids repeating the `html.cursor-autohide-hidden`
* ancestor for every selector. The first rule targets the overlay containers
* themselves; the second targets their descendants.
*/
html.cursor-autohide-hidden :is(
[role="dialog"],
[role="menu"],
[role="listbox"],
.bn-suggestion-menu,
.bn-link-toolbar,
.bn-color-picker-dropdown,
.bn-formatting-toolbar,
.bn-table-handle-menu,
[data-slot="select-content"],
[data-slot="dropdown-menu-content"]
) {
cursor: auto !important;
}

html.cursor-autohide-hidden [role="dialog"] *,
html.cursor-autohide-hidden [role="menu"] *,
html.cursor-autohide-hidden [role="listbox"] *,
html.cursor-autohide-hidden .bn-suggestion-menu *,
html.cursor-autohide-hidden .bn-link-toolbar *,
html.cursor-autohide-hidden .bn-color-picker-dropdown *,
html.cursor-autohide-hidden .bn-formatting-toolbar *,
html.cursor-autohide-hidden .bn-table-handle-menu *,
html.cursor-autohide-hidden [data-slot="select-content"] *,
html.cursor-autohide-hidden [data-slot="dropdown-menu-content"] * {
html.cursor-autohide-hidden :is(
[role="dialog"],
[role="menu"],
[role="listbox"],
.bn-suggestion-menu,
.bn-link-toolbar,
.bn-color-picker-dropdown,
.bn-formatting-toolbar,
.bn-table-handle-menu,
[data-slot="select-content"],
[data-slot="dropdown-menu-content"]
) * {
cursor: auto !important;
}
Loading