Skip to content

JS-9778: Refactor Search & Replace popup layout and styling#2224

Open
barissalihbabacan wants to merge 6 commits into
anyproto:developfrom
barissalihbabacan:feat/search-and-replace-v2
Open

JS-9778: Refactor Search & Replace popup layout and styling#2224
barissalihbabacan wants to merge 6 commits into
anyproto:developfrom
barissalihbabacan:feat/search-and-replace-v2

Conversation

@barissalihbabacan

Copy link
Copy Markdown
Contributor

  • I understand that contributing to this repository will require me to agree with the CLA

Description

This PR refactors the Search & Replace popup component layout and styling to align with the updated design specifications and resolve visual bugs:

  1. Layout & Alignment Updates:

    • Removed the legacy toggle chevron from the left side of the search bar.
    • Added the new menu/action/replace icon (using the requested Replace icon: Replace Icon) inside the right-aligned arrowWrapper to toggle Replace mode. The icon changes color to --color-control-accent (blue) when Replace mode is active.
    • Updated the styling of up/down arrow icons to match active/inactive states properly using --color-control-inactive.
    • Restructured the .replaceWrapper block: it is now placed as a direct child of .wrap so its width spans the entire width of the popup (aligning its right edge with the arrows pill).
  2. Matte/Opaque Background:

    • Changed the background-color of .replaceWrapper from semi-transparent var(--color-shape-highlight-medium) to solid var(--color-bg-primary) to prevent editor content/text from bleeding through and rendering behind it.
  3. Internal Structure & UX:

    • Redesigned .replaceWrapper interior layout: the "Replace with..." input field is placed at the top, and the buttons ("Replace all" as a text link, and "Replace" as a blue pill button) are aligned at the bottom-right.
    • Added a useEffect hook to automatically recalculate the menu's position via position() whenever replaceMode toggles, preventing any alignment/popover shifting issues.
    • Improved read-only detection by passing the isReadonly flag directly from keyboard.ts (evaluating space participant permissions, locked status, and deleted/archived states).

What type of PR is this? (check all applicable)

  • 🍕 Feature
  • 🐛 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • 🧑‍💻 Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🤖 Build
  • 🔁 CI

Related Tickets & Documents

Mobile & Desktop Screenshots/Recordings

image

Added tests?

  • 👍 yes
  • 🙅 no, because they aren't needed
  • 🙋 no, because I need help

Added to documentation?

  • 📜 README.md
  • 📓 tech-docs
  • 🙅 no documentation needed

[optional] Are there any post-deployment tasks we need to perform?

@ra3orblade

Copy link
Copy Markdown
Contributor

Please verify your changes and include only changes that your PR introduces, you include changes that are irrelevant to the PR.

@yasyuk

yasyuk commented May 20, 2026

Copy link
Copy Markdown
Contributor

Thanks for the update!

  1. Please use --color-shape-tertiary as a background for the search and replace input. Remove outlines from the search input and the search and replace window.
Screenshot 2026-05-20 at 11 23 52
  1. Replace buttons doesn't works for me. Only "replace all" works well.

@barissalihbabacan
barissalihbabacan force-pushed the feat/search-and-replace-v2 branch from 4bbbf04 to 55f6c94 Compare May 23, 2026 17:32
@barissalihbabacan

Copy link
Copy Markdown
Contributor Author

Changes summary

Addressing feedback from @ra3orblade and @yasyuk.


Bug fix: Replace (single) button not working (@yasyuk)

Root cause: The Editable component applies both editable and focusable classes to the same DOM element (cne = ['editable', classNameEditor]). This meant U.Dom.select('.editable', focusable) was querying for a .editable descendant inside itself — always returning null — which caused activeMatchRef.position to be null and silently aborted every Replace click.

Fix (src/ts/component/menu/search/text.tsx):

const containerEl = U.Dom.hasClass(focusable, 'editable')
    ? focusable
    : U.Dom.select('.editable', focusable);

Styling (@yasyuk)

  • Background: .filterContainer, .arrowWrapper, and .replaceWrapper backgrounds changed to --color-shape-tertiary
  • Borders removed: Removed 1px solid var(--color-shape-secondary) outlines from .filterContainer, .arrowWrapper, and .replaceWrapper

Irrelevant changes cleaned up (@ra3orblade)

Rebased onto the latest anyproto:develop. The PR now contains only the 7 files directly related to this feature:

File Purpose
src/ts/component/menu/search/text.tsx Main component — replace mode UI, Replace/Replace All logic
src/scss/menu/search/text.scss Styles for the refactored layout
src/ts/lib/keyboard.ts Extended onSearchText with replaceMode param; added isReadonly detection (participant write permission, archived/deleted/locked state)
src/ts/component/editor/page.tsx Wires Ctrl+H in the editor to open the menu directly in replace mode
src/json/shortcut.ts Registers the replaceText (Ctrl+H) shortcut in the shortcuts popup
src/json/text.json Adds translation keys: commonReplace, commonReplaceAll, commonReplaceWith, menuObjectSearchAndReplace
.husky/pre-commit npxbunx to fix a pre-commit hook crash caused by npm 11.x misinterpreting min-release-age=7d as before=null (RangeError: Invalid time value)

@ra3orblade

Copy link
Copy Markdown
Contributor

@barissalihbabacan please check your changes, there are changes to the text.json which are removing strings that are used in the code.

@barissalihbabacan

Copy link
Copy Markdown
Contributor Author

Hi @ra3orblade,

I've restored the missing translation keys in text.json that were accidentally deleted, while keeping the newly introduced translation keys for the Search & Replace features.

The changes have been pushed to the branch. Let me know if everything looks good now!

barissalihbabacan and others added 3 commits May 25, 2026 20:19
- Fix Replace (single) button: .editable and .focusable are the same
  element in the Anytype Editable component; querying .editable inside
  .focusable always returned null, making position null and silently
  aborting the replace operation
- Use --color-shape-tertiary background for filterContainer, arrowWrapper
  and replaceWrapper (replaces color-shape-highlight-medium / bg-primary)
- Remove 1px borders from filterContainer, arrowWrapper and replaceWrapper
  per reviewer feedback
- Switch pre-commit hook from npx to bunx to fix npm min-release-age
  incompatibility with npm 11.12.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@barissalihbabacan
barissalihbabacan force-pushed the feat/search-and-replace-v2 branch from 8fe8816 to 02500ca Compare May 25, 2026 17:20
@ra3orblade

Copy link
Copy Markdown
Contributor

Hi @ra3orblade,

I've restored the missing translation keys in text.json that were accidentally deleted, while keeping the newly introduced translation keys for the Search & Replace features.

The changes have been pushed to the branch. Let me know if everything looks good now!

thanks, from my side looks good, if @yasyuk does not have any design changes I will merge.

@yasyuk

yasyuk commented May 26, 2026

Copy link
Copy Markdown
Contributor

Thanks! The general implementation looks good from my side. I'll tweak the icons myself in the develop branch.

However, there are a few bugs left in the general functionality:

  1. When replacing words one-by-one, it gets stuck on the 3rd one:
    https://github.com/user-attachments/assets/af0dcad3-9356-4649-a0b0-05ac332faf1a

  2. When undoing the replacements, the counter of words found isn't updated:
    https://github.com/user-attachments/assets/258c409c-0170-4b6c-a0d2-f16f60fa05f6

  3. Sometimes it replaces words in the wrong places:
    https://github.com/user-attachments/assets/b5828602-c926-4ee2-bd21-d90b1208af21

@barissalihbabacan

Copy link
Copy Markdown
Contributor Author

@yasyuk @ra3orblade Thank you both for your time and the detailed feedback! It is currently the Kurban Bayramı (Eid al-Adha) holiday, so I am briefly away from the keyboard, but I will dive into these remaining bugs and get them resolved very soon. Thanks for your patience!
Is there anything else you'd like to add to the message before you post it?

…ace replacements

- Verify cached position against store text before replacing to prevent
  wrong-offset replacements when DOM is stale
- Add MutationObserver to detect undo/redo/external edits and re-run
  search so match counter stays in sync
- Replace unreliable setTimeout(50) with requestAnimationFrame chaining
  for post-replace DOM synchronization
- Add observer lifecycle management (disconnect during own DOM mutations,
  reconnect after, cleanup on unmount)
@barissalihbabacan

Copy link
Copy Markdown
Contributor Author

Sorry for the late response — I've been in the middle of a product launch at work for the past 3 weeks.

Bug fixes for the 3 issues reported by @yasyuk:

Root cause: Block text components are not wrapped in MobX observer, so after blockInsertText mutates the store, the contentEditable DOM is not re-rendered. findAndReplaceDOMText then runs against stale DOM, producing incorrect match positions.

Fixes:

  1. Replace getting stuck on 3rd instance — Before replacing, verify the cached position against S.Block.getLeaf().content.text. If stale, re-compute the correct offset by searching the store text.
  2. Undo (Ctrl+Z) doesn't update match counter — Added a MutationObserver on the editor container to detect external content changes (undo/redo) and re-run the search with 200ms debounce.
  3. Replacements in wrong places — Same position verification as fix Feature/Navigation #1 prevents writing to incorrect offsets.
  4. Replaced unreliable setTimeout(50) with requestAnimationFrame chaining for post-replace DOM sync.

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