Skip to content

Added selection-aware word count plugin for koenig-lexical#29431

Open
kevinfengcs88 wants to merge 1 commit into
TryGhost:mainfrom
kevinfengcs88:selection-word-count
Open

Added selection-aware word count plugin for koenig-lexical#29431
kevinfengcs88 wants to merge 1 commit into
TryGhost:mainfrom
kevinfengcs88:selection-word-count

Conversation

@kevinfengcs88

Copy link
Copy Markdown

Reopens TryGhost/Koenig#1976 against this repo, as suggested in its closing comment after the Koenig monorepo was merged in.

Adds a headless SelectionWordCountPlugin that shows the word count of the current selection.

  • counting reuses the same countWords util as WordCountPlugin, so selection counts should stay consistent with the document total
  • selections inside nested editors (like image captions) work through the same pattern WordCountPlugin uses in KoenigNestedComposer
  • the editor renders "X of Y words" while text is selected (e.g., "2 of 9 words", and "0 of 9 words" for when the user only selects whitespace)
  • the only change from the old PR is renaming the new files to .tsx and .ts to match the TypeScript conversion here

Video demo (this is the same video from the old PR that was opened against the archived Koenig repo):

ghost_koenig_word_counter.mp4

ref TryGhost/Koenig#1976

- reopens the Koenig PR above against this repo, as suggested when the Koenig monorepo was merged in and archived
- reports the word count of the current selection so the editor UI can show "X of Y words" while text is selected
- reuses the same countWords util as WordCountPlugin so selection counts stay consistent with the document total
- selections in nested editors (image captions, callouts) work through the same context-ref pattern WordCountPlugin uses in KoenigNestedComposer
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds selection-based word counting for main and nested Lexical editors. The new plugin tracks non-collapsed selections, combines nested editor counts, throttles updates, and cleans up stale entries. Composer context and nested composer wiring expose the functionality, while the demo displays selection and total word counts. The plugin is publicly exported, and Playwright tests cover selection states, whitespace, partial words, nested editors, and select-all behavior.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a selection-aware word count plugin to koenig-lexical.
Description check ✅ Passed The description is clearly related to the changeset and matches the new selection word count plugin, nested editor support, and TypeScript renaming.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
koenig/koenig-lexical/src/components/KoenigComposer.tsx (1)

115-130: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider memoizing the context value.

The provider value object is a new literal on every render, so every consumer (including all nested composers/plugins) re-renders whenever KoenigComposer re-renders. This diff adds 3 more fields on top of an existing unmemoized object; wrapping it in useMemo would limit unnecessary re-renders across the composer tree.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@koenig/koenig-lexical/src/components/KoenigComposer.tsx` around lines 115 -
130, Memoize the context value object passed to KoenigComposerContext.Provider
using useMemo, including all listed fields and their dependencies, so consumers
only re-render when a context value actually changes.
koenig/koenig-lexical/src/plugins/SelectionWordCountPlugin.tsx (1)

14-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Relies on Lexical's internal _parentEditor field.

editor._parentEditor is explicitly annotated @internal in Lexical's own type definitions (confirmed via Lexical's LexicalEditor.d.ts), so this isn't a documented public API and could change across Lexical upgrades without a semver-relevant note. Worth confirming there isn't a supported way to distinguish the "top-level" editor from nested ones (e.g. an explicit isNested/composer-provided flag threaded from KoenigComposer/KoenigNestedComposer), and if not, at least leaving a comment noting the dependency on Lexical internals for future maintainers.

Also applies to: 85-87

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@koenig/koenig-lexical/src/plugins/SelectionWordCountPlugin.tsx` around lines
14 - 25, Replace the direct _parentEditor check in the SelectionWordCountPlugin
effect with a supported top-level-versus-nested composer signal, threaded from
KoenigComposer/KoenigNestedComposer if available. If no public signal exists,
retain the check but add a concise comment documenting its reliance on Lexical’s
internal field and upgrade risk.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@koenig/koenig-lexical/src/components/KoenigComposer.tsx`:
- Around line 115-130: Memoize the context value object passed to
KoenigComposerContext.Provider using useMemo, including all listed fields and
their dependencies, so consumers only re-render when a context value actually
changes.

In `@koenig/koenig-lexical/src/plugins/SelectionWordCountPlugin.tsx`:
- Around line 14-25: Replace the direct _parentEditor check in the
SelectionWordCountPlugin effect with a supported top-level-versus-nested
composer signal, threaded from KoenigComposer/KoenigNestedComposer if available.
If no public signal exists, retain the check but add a concise comment
documenting its reliance on Lexical’s internal field and upgrade risk.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 937456f5-ceb1-4c38-a8fb-71caebed2c5e

📥 Commits

Reviewing files that changed from the base of the PR and between c52f279 and 68ddd2c.

📒 Files selected for processing (7)
  • koenig/koenig-lexical/demo/DemoApp.tsx
  • koenig/koenig-lexical/demo/components/WordCount.tsx
  • koenig/koenig-lexical/src/components/KoenigComposer.tsx
  • koenig/koenig-lexical/src/components/KoenigNestedComposer.tsx
  • koenig/koenig-lexical/src/index.ts
  • koenig/koenig-lexical/src/plugins/SelectionWordCountPlugin.tsx
  • koenig/koenig-lexical/test/e2e/plugins/SelectionWordCountPlugin.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant