Skip to content

feat(client): add markdown toolbar, list continuation, and @mention s…#25

Open
fzlzjerry wants to merge 4 commits into
ohmygit-hub:mainfrom
fzlzjerry:markdown-composer-toolbar-mentions
Open

feat(client): add markdown toolbar, list continuation, and @mention s…#25
fzlzjerry wants to merge 4 commits into
ohmygit-hub:mainfrom
fzlzjerry:markdown-composer-toolbar-mentions

Conversation

@fzlzjerry

Copy link
Copy Markdown

This pull request introduces a new shared markdown composer shell component with a formatting toolbar, and refactors both the conversation comment composer and markdown editor to use this new shell. The changes also add robust markdown formatting actions, including list continuation logic, and comprehensive tests for these behaviors.

New shared markdown composer shell:

  • Added the ConversationMarkdownComposerShell component, which provides a markdown editor with a formatting toolbar, live preview, and mention suggestions, encapsulating common logic for both comment and editor use cases.
  • Updated conversation-comment-composer.vue and conversation-markdown-editor.vue to use the new ConversationMarkdownComposerShell component, removing duplicated editor and preview code. [1] [2] [3] [4]

Markdown formatting actions:

  • Added markdown-format-actions.ts with pure functions to build markdown formatting edits (bold, italic, heading, quote, code, code block, link, lists) and logic for continuing/exiting markdown lists.
  • Comprehensive tests for all formatting actions and list continuation logic in markdown-format-actions.test.ts.

Testing and verification:

  • Added tests to verify that both the comment composer and markdown editor host the shared composer shell and formatting toolbar. [1] [2]
  • Added tests to verify the formatting toolbar renders and interacts as expected in the new shell.

Screenshot:

CleanShot 2026-07-09 at 18 09 08@2x

…uggestions

Make issue/PR comment composers easier to use with formatting actions, Enter-to-continue lists, fenced code blocks, and GitHub-style @user autocomplete.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 9, 2026 10:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a shared markdown composer β€œshell” to the Electron renderer, consolidating duplicated composer/editor UI into a single component that provides a formatting toolbar, live preview, and @mention suggestions. It also extends the underlying Monaco editor wrapper with markdown list continuation behavior and exposes editor helper methods needed by the new shell.

Changes:

  • Introduces ConversationMarkdownComposerShell (toolbar + editor + preview + mention suggestions) and refactors both the comment composer and markdown editor to use it.
  • Adds pure markdown formatting actions (bold/italic/heading/quote/code/link/lists) plus markdown list continuation logic, with unit tests.
  • Extends MonacoCodeEditor with key interception, cursor listeners, and formatting helper APIs to support the shell.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/client/src/renderer/i18n/locales/en.json Adds English strings for the markdown toolbar and mention suggestion UI.
packages/client/src/renderer/i18n/locales/zh.json Adds Chinese strings for the markdown toolbar and mention suggestion UI.
packages/client/src/renderer/components/editor/monaco-code-editor.vue Adds markdown list continuation on Enter, key interception hooks, and exposes editor helper APIs.
packages/client/src/renderer/components/conversation/mention-suggestion-menu.vue New mention suggestions popover UI with local+remote candidate merging and loading/empty states.
packages/client/src/renderer/components/conversation/mention-query.ts New mention detection + candidate merging helpers.
packages/client/src/renderer/components/conversation/mention-query.test.ts Unit tests for mention detection and candidate merging.
packages/client/src/renderer/components/conversation/markdown-format-toolbar.vue New formatting toolbar component emitting formatting actions.
packages/client/src/renderer/components/conversation/markdown-format-actions.ts New pure formatting edit builders and list continuation decision logic.
packages/client/src/renderer/components/conversation/markdown-format-actions.test.ts Unit tests for formatting edit builders and list continuation logic.
packages/client/src/renderer/components/conversation/conversation-markdown-editor.vue Refactors markdown editor to use the shared composer shell.
packages/client/src/renderer/components/conversation/conversation-markdown-editor.test.ts Updates test to assert the shared shell is hosted.
packages/client/src/renderer/components/conversation/conversation-markdown-composer-shell.vue New shared shell component (toolbar + Monaco + preview + mention integration).
packages/client/src/renderer/components/conversation/conversation-markdown-composer-shell.test.ts Adds test asserting toolbar integration in the shell source.
packages/client/src/renderer/components/conversation/conversation-comment-composer.vue Refactors comment composer to use the shared composer shell.
packages/client/src/renderer/components/conversation/conversation-comment-composer.test.ts Updates test to assert the shared shell is hosted.

πŸ’‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/client/src/renderer/components/conversation/mention-suggestion-menu.vue Outdated
Comment thread packages/client/src/renderer/components/conversation/mention-suggestion-menu.vue Outdated
@fzlzjerry

Copy link
Copy Markdown
Author

@Dustella @sheepbox8646

@Dustella Dustella left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed in depth β€” the core logic here is genuinely good. markdown-format-actions.ts and mention-query.ts are pure, well-tested functions (email exclusion, mid-word rejection, nested-indent renumbering, empty-item exit all covered), conversation-markdown-composer-shell.vue is a clean shared extraction that de-duplicates both composers (-43 lines each), and the toolbar is exemplary: all @oh-my-github/ui primitives, lucide icons, semantic tokens, select-none, matched en/zh i18n. Preview reuses the existing markdown renderer (no new injection surface), and remote user search is debounced with a monotonic sequence guard against out-of-order responses.

One clarification for anyone eyeing the +234 on monaco-code-editor.vue: that is not a new file or scope creep β€” it already exists on main and is used by both composers; this PR extends it with the imperative methods the feature needs (wrapSelection, getCursorContext, key interception). Correct place for that logic.

Two things to address before merge:

major β€” the @mention menu is clipped / unusable in the lower half of the composer.
mention-suggestion-menu.vue is position: absolute, and its nearest positioned ancestor is the relative h-48 editor column β€” but the composer root is overflow-hidden. The menu opens downward (top = cursorTop + lineHeight + 4, max-h-64 = 256px) inside a 192px (h-48) editor, so once the cursor is past ~the middle it overflows the overflow-hidden root and gets cut off (bottom edge, and the right edge in the md:grid-cols-2 layout). There's also no upward flip. Fix: teleport the menu to body and position in viewport coordinates (or render it outside the overflow-hidden root), and flip upward when there's no room below.

major β€” the mention dropdown is hand-rolled <button> rows.
packages/client/CLAUDE.md explicitly and repeatedly prohibits hand-built menus ("never hand-written <button> rows … use DropdownMenu / ContextMenu"; the component picker maps a searchable pick β†’ Combobox). Credit where due β€” it uses correct tokens (bg-popover, --ui-selected/--ui-hover, --shadow-dropdown), Avatar/Spinner, select-none and i18n throughout, so it's not a token violation, only a hand-rolled-control one. Fair caveat: a cursor-anchored typeahead is genuinely awkward to force into the stock DropdownMenu/Combobox (they own their own trigger + positioning), so this likely warrants a small shared "mention/typeahead popover" component β€” worth clearing the shape with the maintainer rather than either forcing the stock component or shipping bespoke per-page markup.

Minors / nits:

  • minor β€” monaco-code-editor.vue: Enter is bound unconditionally with no when context. It correctly falls through to a plain newline for non-markdown/readonly (so other surfaces are unchanged), but a when/context guard would avoid shadowing Monaco's accept-suggestion-on-Enter if the suggest widget is ever open in a markdown model.
  • minor β€” mention-query.ts MENTION_TAIL accepts invalid logins (leading/trailing hyphen, @-foo). Harmless (UI gate only; the merge step filters), but could match GitHub's real login rules.
  • nit β€” mentionActiveIndex isn't reset to 0 when the candidate list reorders on query change; the highlight can momentarily point at a different user. No crash β€” a stale out-of-range index falls through to a newline.

Everything else checks out β€” i18n keys match, filenames are kebab-case, the useAssignableUsersQuery / searchWorkspace({ mode: 'users' }) signatures and GitHubWorkspaceSearchItem shape are correct, and the pure-function tests are meaningful. Really nice feature; just needs the menu clipping fixed and a decision on the dropdown component.

fzlzjerry added a commit to fzlzjerry/ohmygithub that referenced this pull request Jul 12, 2026
… aria

- Let ArrowUp/ArrowDown fall through to the editor when the mention menu
  is open but has no candidates, so cursor navigation is never trapped;
  the cursor sync closes the menu once the caret leaves the query.
- Scope role="listbox" to the options container so it only exists while
  it holds role="option" children; the searching/empty messages are now
  role="status" text instead of invalid listbox content.

Addresses the Copilot review comments on ohmygit-hub#25.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… aria

- Let ArrowUp/ArrowDown fall through to the editor when the mention menu
  is open but has no candidates, so cursor navigation is never trapped;
  the cursor sync closes the menu once the caret leaves the query.
- Scope role="listbox" to the options container so it only exists while
  it holds role="option" children; the searching/empty messages are now
  role="status" text instead of invalid listbox content.

Addresses the Copilot review comments on ohmygit-hub#25.
…n mention edges

Addresses Dustella's review on ohmygit-hub#25:

- Teleport the mention menu to body and position it in viewport
  coordinates so the composer's overflow-hidden shell and the h-48
  editor column can no longer clip it; flip it above the caret when
  the space below runs out, clamp it horizontally, and re-anchor it
  when the editor scrolls under a stationary caret.
- Guard the markdown list-continuation Enter command with
  !suggestWidgetVisible so it no longer shadows Monaco's
  accept-suggestion-on-Enter.
- Tighten mention detection to logins GitHub can accept (no leading
  hyphen, no consecutive hyphens) while still tolerating a trailing
  hyphen mid-typing.
- Reset the mention highlight to the top when the query changes so it
  cannot point at a reordered candidate.
@fzlzjerry fzlzjerry force-pushed the markdown-composer-toolbar-mentions branch from 627ef80 to 8300ff5 Compare July 12, 2026 04:25
@fzlzjerry

Copy link
Copy Markdown
Author

@Dustella

@fzlzjerry

Copy link
Copy Markdown
Author

@Dustella Thanks for the deep review β€” everything is addressed in 8300ff5 except the component-shape question, which I'd like your (and the maintainers') call on.

major β€” menu clipped in the lower half of the composer: fixed. The menu now <Teleport to="body">s and positions with fixed viewport coordinates (getCursorScreenPosition returns viewport-relative caret coords + line height), so the overflow-hidden composer root and the h-48 editor column can no longer clip it β€” including the right edge in the md:grid-cols-2 layout, which is now clamped to the viewport. It flips above the caret when the space below can't fit the menu (bottom-anchored so it grows upward), and re-anchors on editor scroll via onDidScrollChange. The placement logic is a pure function (computeMentionMenuPlacement) with unit tests for below/flip/clamp.

major β€” hand-rolled <button> rows: partially agree, and per your suggestion I'd like to clear the shape rather than force a stock component. The stock DropdownMenu/Combobox own their trigger, focus, and positioning β€” a caret-anchored typeahead must keep focus in Monaco and anchor to a text position, which fights both. What this PR ships is effectively the "small shared mention/typeahead popover" you describe: mention-suggestion-menu.vue is a single shared component used by both composers through the shell, on menu tokens (bg-popover, --ui-selected/--ui-hover, --shadow-dropdown, menu-shell radius). If maintainers prefer it promoted into @oh-my-github/ui as a reusable TypeaheadPopover primitive (with the option rows moved onto menuItemClass), I'm happy to do that in a follow-up β€” just say the word on where it should live.

minor β€” unconditional Enter binding: fixed β€” the list-continuation command is now registered with the '!suggestWidgetVisible' context, so Monaco's accept-suggestion-on-Enter wins while the suggest widget is open.

minor β€” MENTION_TAIL accepts invalid logins: fixed β€” detection now requires the login to start alphanumeric and rejects consecutive hyphens (@-foo, @a--b), while still tolerating a trailing hyphen so the menu doesn't close mid-typing of @ab-c.

nit β€” stale mentionActiveIndex on query change: fixed β€” the highlight resets to the top whenever the detected query changes, before the candidate list reorders.

All covered by new unit/source tests; pnpm typecheck and the client build pass.

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.

3 participants