From a1d5ac0907c2f6de8c9c1524c99693ea46f0c228 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 3 Jul 2021 07:00:10 -0700 Subject: [PATCH] RTL document support (#2263) * Basic RTL support in documents * fix: DocumentListItem and ReferenceListItem for RTL content --- app/components/ContextMenu/index.js | 2 +- app/components/DocumentListItem.js | 10 ++++++++-- app/components/DocumentMeta.js | 3 ++- app/components/DocumentMetaWithViews.js | 2 ++ .../Sidebar/components/SidebarLink.js | 2 +- app/models/Document.js | 20 +++++++++++++++++++ app/scenes/Document/components/Editor.js | 12 +++++++++++ .../Document/components/ReferenceListItem.js | 3 +-- package.json | 4 ++-- yarn.lock | 8 ++++---- 10 files changed, 53 insertions(+), 13 deletions(-) diff --git a/app/components/ContextMenu/index.js b/app/components/ContextMenu/index.js index 0f4c373d..70c31c55 100644 --- a/app/components/ContextMenu/index.js +++ b/app/components/ContextMenu/index.js @@ -46,7 +46,7 @@ export default function ContextMenu({ {(props) => ( - + {rest.visible || rest.animating ? children : null} diff --git a/app/components/DocumentListItem.js b/app/components/DocumentListItem.js index f8de76bf..5e0b8a6f 100644 --- a/app/components/DocumentListItem.js +++ b/app/components/DocumentListItem.js @@ -69,6 +69,7 @@ function DocumentListItem(props: Props, ref) { return ( - - + <Heading dir={document.dir}> + <Title + text={document.titleWithDefault} + highlight={highlight} + dir={document.dir} + /> {document.isNew && document.createdBy.id !== currentUser.id && ( <Badge yellow>{t("New")}</Badge> )} @@ -222,6 +227,7 @@ const DocumentLink = styled(Link)` const Heading = styled.h3` display: flex; + justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")}; align-items: center; height: 24px; margin-top: 0; diff --git a/app/components/DocumentMeta.js b/app/components/DocumentMeta.js index 1ac34582..b5af26a5 100644 --- a/app/components/DocumentMeta.js +++ b/app/components/DocumentMeta.js @@ -11,6 +11,7 @@ import Time from "components/Time"; import useStores from "hooks/useStores"; const Container = styled(Flex)` + justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")}; color: ${(props) => props.theme.textTertiary}; font-size: 13px; white-space: nowrap; @@ -135,7 +136,7 @@ function DocumentMeta({ : 0; return ( - <Container align="center" {...rest}> + <Container align="center" rtl={document.dir === "rtl"} {...rest} dir="ltr"> {updatedByMe ? t("You") : updatedBy.name}  {to ? <Link to={to}>{content}</Link> : content} {showCollection && collection && ( diff --git a/app/components/DocumentMetaWithViews.js b/app/components/DocumentMetaWithViews.js index cd0b7976..6964b391 100644 --- a/app/components/DocumentMetaWithViews.js +++ b/app/components/DocumentMetaWithViews.js @@ -14,6 +14,7 @@ type Props = {| document: Document, isDraft: boolean, to?: string, + rtl?: boolean, |}; function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) { @@ -62,6 +63,7 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) { } const Meta = styled(DocumentMeta)` + justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")}; margin: -12px 0 2em 0; font-size: 14px; position: relative; diff --git a/app/components/Sidebar/components/SidebarLink.js b/app/components/Sidebar/components/SidebarLink.js index 9e248e8b..43a39878 100644 --- a/app/components/Sidebar/components/SidebarLink.js +++ b/app/components/Sidebar/components/SidebarLink.js @@ -84,7 +84,7 @@ function SidebarLink( ref={ref} > {icon && <IconWrapper>{icon}</IconWrapper>} - <Label>{label}</Label> + <Label dir="auto">{label}</Label> </Link> {menu && <Actions showActions={showActions}>{menu}</Actions>} </> diff --git a/app/models/Document.js b/app/models/Document.js index 553106cd..601df02f 100644 --- a/app/models/Document.js +++ b/app/models/Document.js @@ -58,6 +58,26 @@ export default class Document extends BaseModel { return emoji; } + /** + * Best-guess the text direction of the document based on the language the + * title is written in. Note: wrapping as a computed getter means that it will + * only be called directly when the title changes. + */ + @computed + get dir(): "rtl" | "ltr" { + const element = document.createElement("p"); + element.innerHTML = this.title; + element.style.visibility = "hidden"; + element.dir = "auto"; + + // element must appear in body for direction to be computed + document.body?.appendChild(element); + + const direction = window.getComputedStyle(element).direction; + document.body?.removeChild(element); + return direction; + } + @computed get noun(): string { return this.template ? "template" : "document"; diff --git a/app/scenes/Document/components/Editor.js b/app/scenes/Document/components/Editor.js index 375fe439..48a44ded 100644 --- a/app/scenes/Document/components/Editor.js +++ b/app/scenes/Document/components/Editor.js @@ -33,6 +33,7 @@ type Props = {| @observer class DocumentEditor extends React.Component<Props> { @observable activeLinkEvent: ?MouseEvent; + @observable ref = React.createRef<HTMLDivElement | HTMLInputElement>(); focusAtStart = () => { if (this.props.innerRef.current) { @@ -109,13 +110,17 @@ class DocumentEditor extends React.Component<Props> { const normalizedTitle = !title && readOnly ? document.titleWithDefault : title; + console.log(this.ref.current); + return ( <Flex auto column> {readOnly ? ( <Title as="div" + ref={this.ref} $startsWithEmojiAndSpace={startsWithEmojiAndSpace} $isStarred={document.isStarred} + dir="auto" > <span>{normalizedTitle}</span>{" "} {!shareId && <StarButton document={document} size={32} />} @@ -123,6 +128,7 @@ class DocumentEditor extends React.Component<Props> { ) : ( <Title type="text" + ref={this.ref} onChange={onChangeTitle} onKeyDown={this.handleTitleKeyDown} placeholder={document.placeholder} @@ -130,6 +136,7 @@ class DocumentEditor extends React.Component<Props> { $startsWithEmojiAndSpace={startsWithEmojiAndSpace} autoFocus={!title} maxLength={MAX_TITLE_LENGTH} + dir="auto" /> )} {!shareId && ( @@ -137,6 +144,11 @@ class DocumentEditor extends React.Component<Props> { isDraft={isDraft} document={document} to={documentHistoryUrl(document)} + rtl={ + this.ref.current + ? window.getComputedStyle(this.ref.current).direction === "rtl" + : false + } /> )} <Editor diff --git a/app/scenes/Document/components/ReferenceListItem.js b/app/scenes/Document/components/ReferenceListItem.js index c7c91a6a..c305fe6f 100644 --- a/app/scenes/Document/components/ReferenceListItem.js +++ b/app/scenes/Document/components/ReferenceListItem.js @@ -35,7 +35,6 @@ const DocumentLink = styled(Link)` const Title = styled.h3` display: flex; align-items: center; - max-width: 90%; overflow: hidden; text-overflow: ellipsis; font-size: 14px; @@ -78,7 +77,7 @@ function ReferenceListItem({ }} {...rest} > - <Title> + <Title dir="auto"> {document.emoji ? ( <Emoji>{document.emoji}</Emoji> ) : ( diff --git a/package.json b/package.json index d0dd25ea..bf87b607 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "randomstring": "1.1.5", "raw-loader": "^0.5.1", "react": "^17.0.2", - "react-autosize-textarea": "^6.0.0", + "react-autosize-textarea": "^7.1.0", "react-avatar-editor": "^11.1.0", "react-color": "^2.17.3", "react-dnd": "^14.0.1", @@ -142,7 +142,7 @@ "react-window": "^1.8.6", "reakit": "^1.3.6", "regenerator-runtime": "^0.13.7", - "rich-markdown-editor": "^11.12.0", + "rich-markdown-editor": "^11.13.0", "semver": "^7.3.2", "sequelize": "^6.3.4", "sequelize-cli": "^6.2.0", diff --git a/yarn.lock b/yarn.lock index 61cbc4d1..caff421e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11606,10 +11606,10 @@ retry-as-promised@^3.2.0: dependencies: any-promise "^1.3.0" -rich-markdown-editor@^11.12.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.12.0.tgz#7fd9bd80b0afcb06d2bbcb8a2d38ac2279bc3110" - integrity sha512-mf5i/JJZktNwQnEjjQIYGzIoBvh3LAmCZoRC8EcbsACO3ISgOcNdccmdd0utZMs4QHASUgE+eBjtM+awvhIPqw== +rich-markdown-editor@^11.13.0: + version "11.13.0" + resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.13.0.tgz#7009bddbd667c2fdcf6a7d4210913a1c040acdde" + integrity sha512-WZLL7z0sE7kogYjpPWPbLnbWjYwCn5RLoju6Vg6WfVDVWPSRRfWo+IHSKpBAK8kP+h9pKxZItwHrHN4ShXapjA== dependencies: copy-to-clipboard "^3.0.8" lodash "^4.17.11"