feat: auto text direction (RTL) in composer editor and previews - #1993
giladresisi wants to merge 5 commits into
Conversation
Strix Security ReviewNo security issues found. Updated for Reviewed by Strix |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
| const editor = useEditor({ | ||
| extensions: [ | ||
| Document, | ||
| Paragraph, | ||
| Paragraph.configure({ | ||
| HTMLAttributes: { | ||
| dir: 'auto', | ||
| }, | ||
| }), | ||
| Text, | ||
| Underline, | ||
| Bold, |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Good catch, confirmed: sanitizePostContent stripped the per paragraph dir attribute before storage. The impact was partial (the editor re applies dir=auto from the extension config and the preview wrappers carry their own dir=auto), but stored content lost per paragraph granularity and the public share page rendered without it. Fixed in 179ba4d by adding dir to ALLOWED_ATTR.
| /> | ||
| )} | ||
| <div | ||
| dir="auto" | ||
| className="text-[14px] font-[400] whitespace-pre-line" | ||
| dangerouslySetInnerHTML={{ | ||
| __html: renderContent?.[0]?.text, |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Confirmed with a minimal repro: the Latin username as first strong character forced the whole caption block LTR. Fixed in fd487c8 by wrapping the caption content in its own span with dir=auto, so the username stays inline (like on Instagram itself) while the content resolves its own direction. Verifying this also surfaced that the literal indexOf('
') checks in strip.html.validation.ts and add.edit.modal.tsx missed paragraphs carrying the new dir attribute, which made stripHtmlValidation return raw HTML at publish time; fixed in the same commit by matching '<p' instead.
| existingData.posts.map((post) => ({ | ||
| delay: post.delay, | ||
| content: | ||
| post.content.indexOf('<p>') > -1 | ||
| post.content.indexOf('<p') > -1 | ||
| ? post.content | ||
| : post.content | ||
| .split('\n') |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Fair point, tightened to a tag-shaped check: /<p[\s>]/i in both add.edit.modal.tsx and the strip.html.validation.ts guard. It matches
and
but ignores a stray <p followed by other characters in plain text. Note the same false positive class already existed with the original indexOf('
') check for text containing a literal
, so this is now stricter than the original code as well.
| .replace(/</gi, '<'); | ||
| } | ||
|
|
||
| if (value.indexOf('<p>') === -1 && !none) { | ||
| if (!/<p[\s>]/i.test(value) && !none) { | ||
| return value; | ||
| } | ||
|
|
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Confirmed and reproduced: markdown mode returned the paragraphs concatenated with no newlines, and the headings also lost their # prefixes since h1-h3 now carry dir=auto too. Fixed in the markdown branch by making the p and h1/h2/h3 regexes attribute-tolerant ([^>]*). Verified the output is now identical for content with and without the dir attribute.
What kind of change does this PR introduce?
Feature (frontend, composer + previews). Adds automatic text direction (
dir="auto") to the post composer: the TipTap editor emitsdir="auto"on every paragraph and heading (new-launch/editor.tsx), and the text wrappers of the default preview (general.preview.component.tsx) and the Facebook, LinkedIn, Instagram, TikTok, YouTube, Pinterest and Reddit previews carrydir="auto"as well. Supporting changes the new attribute required:'dir'added to the sanitizer whitelist (sanitize.post.content.ts), the literalindexOf('<p>')checks instrip.html.validation.tsandadd.edit.modal.tsxrelaxed toindexOf('<p')so paragraphs with attributes are still recognized (otherwise stripHtmlValidation returned raw HTML at publish time), and the Instagram preview caption is wrapped in its owndir="auto"span so the prepended Latin username doesn't force the caption LTR.Why was this change needed?
A customer writing posts in Hebrew reported that Postiz has no RTL support: with the UI in English, Hebrew text in the editor and preview renders left-to-right with misplaced punctuation. Direction was only ever inherited from the document (it flips only when the whole UI language is Hebrew/Arabic).
dir="auto"resolves direction from the first strong directional character, which is exactly how X, Facebook and Threads render post text (verified in their live DOM), so the editor and previews now match what gets published. The editor resolves per paragraph; previews resolve whole-post, same as the platforms.Other information:
The first strong character decides: lines starting with neutral characters (numbers, emoji, "@") follow the first strong letter after them, matching platform behavior. The
indexOf('<p')fix matters for correctness beyond previews - without it, content saved with the new attribute would have been published to platforms with literal HTML tags.QA
Checklist: