Skip to content

feat(texteditor): add text alignment toggling buttons#5677

Merged
nucleogenesis merged 16 commits intolearningequality:unstablefrom
habibayman:feat/RTE-text-align
Mar 11, 2026
Merged

feat(texteditor): add text alignment toggling buttons#5677
nucleogenesis merged 16 commits intolearningequality:unstablefrom
habibayman:feat/RTE-text-align

Conversation

@habibayman
Copy link
Member

@habibayman habibayman commented Jan 30, 2026

Summary

This PR includes the addition of a toggle alignment (Left/Right) button.
From the motives of making the RTL editor experience better and give users the ability to seamlessly mix RTL & LTR text in the same editing block.

It includes support for all nodes & Marks we have:

  • paragraphs
  • headers
  • links
  • formulas
  • inline formulas & links
  • images
  • small text (was a bit problematic since it's a custom node)

For this to work with our backward compatible dual markdown conversion pipeline; I have altered the markdown serializer to wrap non-default direction nodes with HTML tags containing the align attribute set.

And of course, updated the unit tests accordingly.


References


Reviewer guidance

  • Try different combinations of nodes & marks and see if their alignment gets aligned correctly

Tip

You can use the checklist I wrote in the description section

  • Check that the alignment button's icon reflects direction correctly
  • Does everything persists on save?

Note

Check the native behavior of the extension from the official TipTap documentation to see where its limitations -if any- exist.
https://tiptap.dev/docs/editor/extensions/functionality/textalign

@learning-equality-bot
Copy link

👋 Thanks for contributing!

We will assign a reviewer within the next two weeks. In the meantime, please ensure that:

  • You ran pre-commit locally
  • All issue requirements are satisfied
  • The contribution is aligned with our Contributing guidelines. Pay extra attention to Using generative AI. Pull requests that don't follow the guidelines will be closed.

We'll be in touch! 😊

@nucleogenesis nucleogenesis self-requested a review February 6, 2026 20:59
@nucleogenesis nucleogenesis self-assigned this Feb 6, 2026
@nucleogenesis nucleogenesis removed their request for review February 6, 2026 20:59
@habibayman habibayman force-pushed the feat/RTE-text-align branch from 00187e5 to f2aa6e6 Compare March 4, 2026 23:05
@habibayman habibayman marked this pull request as ready for review March 5, 2026 02:16
Copy link
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Good feature addition for RTL/LTR text alignment toggling. The serializer refactoring from mutation-based to return-based is a nice improvement. One bug in the overflow dropdown disabled logic.

CI passing.

Blocking:

  • Inverted :disabled logic on overflow align button (see inline)

Suggestions:

  • SVG icon dimension mismatch between align-left and align-right
  • Peer dependency version mismatch for @tiptap/extension-text-align

Nitpick:

  • Double space in image markdown output when no dimensions present

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Reviewed the pull request diff checking for:

  • Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
  • Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
  • Architecture: duplicated concerns, minimal interfaces, composition over inheritance
  • Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
  • Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
  • Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
  • Checked CI status and linked issue acceptance criteria
  • For UI changes: inspected screenshots for layout, visual completeness, and consistency

}
}
break;
return parts.join('\n\n');
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: Nice refactoring of the serializer from mutation-based (result +=) to a return-based approach. This makes each case self-contained and much easier to reason about, especially now that alignment wrapping needs to inspect the serialized children before deciding the output format.

type: 'paragraph',
content: [{ type: 'text', text: 'n', marks: [{ type: 'subscript' }] }],
},
];
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: Thorough test coverage for text alignment serialization — covers right/left/center/no-attr cases, headings, inline marks inside aligned paragraphs, and mixed alignment documents. Good edge case thinking.

Copy link
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Solid iteration — the prior blocking issue (inverted disabled logic) and the SVG dimension mismatch are both fixed. 3 prior finding(s) resolved.

CI passing. New strings target the default branch (unstable) — appropriate.

Suggestions:

  • serializeNode guard clause returns undefined, which the return-based refactor now concatenates (see inline)
  • Double space in no-dimensions image markdown path (prior nitpick, still present — see inline)

Note: the reviewer thread from @nucleogenesis / @radinamatic / @marcellamaki about the alignment icon not reflecting the browser's automatic bidi direction (e.g., Arabic auto-aligning right while the icon still shows left) is a real UX concern worth addressing, though it may be out of scope for this PR.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

Copy link
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Good iteration — the bidi alignment detection via getEffectiveAlignment is a nice solution to the RTL concern raised by reviewers.

CI passing. New strings target unstable (default branch) — appropriate.

3 prior finding(s) resolved.

Suggestions:

  • serializeNode guard clause still returns undefined, which concatenates as the literal string "undefined" (UNADDRESSED — see inline)
  • Double space in no-dimensions image markdown path when alignment is present (UNADDRESSED — see inline)

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

const dom = editorInstance.view.domAtPos(from).node;
const el = dom.nodeType === 1 ? dom : dom.parentElement;

return el ? window.getComputedStyle(el).textAlign : 'left';
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: Smart fallback to getComputedStyle for detecting browser-resolved bidi alignment — this handles the RTL auto-alignment case that pure attribute checking would miss.

Copy link
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Good iteration — all prior findings are resolved. The serializer refactoring to return-based is clean and the bidi alignment detection via getEffectiveAlignment is a solid approach.

CI passing. New strings target unstable (default branch) — appropriate.

5 prior finding(s) resolved.

Suggestions:

  • getEffectiveAlignment missing null guard when called from alignAction computed (see inline)

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria


serializeNode(doc, null, 0);
return result.trim();
return serializeNode(doc, null, 0).trim();
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: The refactor from mutation-based (result +=) to return-based serialization is a meaningful improvement — each node handler is now a pure function returning its output, which makes the control flow easier to reason about and eliminates the shared mutable state.

Copy link
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Good iteration — all prior findings are resolved. The bidi alignment detection via getEffectiveAlignment, the serializer alignment support, and the thorough test coverage make this a solid feature addition.

CI passing. PR targets unstable (default branch) — appropriate for new strings.

6 prior finding(s) resolved.

Suggestions:

  • MobileFormattingBar align button missing :is-available binding (see inline)

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

<ToolbarButton
:title="alignAction.title"
:icon="alignAction.icon"
:is-active="alignAction.isActive"
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: The align button here doesn't bind :is-available="alignAction.isAvailable", unlike the desktop EditorToolbar.vue (line 77). This means the align button won't be disabled inside code blocks on mobile. Consider adding :is-available="alignAction.isAvailable" for parity.

const editor = inject('editor', null);

// helper
const getEffectiveAlignment = editorInstance => {
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: getEffectiveAlignment is a clean solution to the bidi/RTL concern — checking isActive first for explicit alignment, then falling back to getComputedStyle for browser-resolved direction. The null guard at line 10 keeps it consistent with the rest of the composable's defensive patterns.

Copy link
Member

@nucleogenesis nucleogenesis left a comment

Choose a reason for hiding this comment

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

The latest changes look great in manual testing! Thanks @habibayman !

@nucleogenesis nucleogenesis dismissed rtibblesbot’s stale review March 11, 2026 17:25

rtibblesbot's suggestions were addressed

@nucleogenesis nucleogenesis merged commit 6344d05 into learningequality:unstable Mar 11, 2026
15 checks passed
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.

[Rich Text Editor]: add a toggle text alignment (R/L) button

5 participants