RTL document support (#2263)

* Basic RTL support in documents

* fix: DocumentListItem and ReferenceListItem for RTL content
This commit is contained in:
Tom Moor
2021-07-03 07:00:10 -07:00
committed by GitHub
parent 04eabe68a7
commit a1d5ac0907
10 changed files with 53 additions and 13 deletions

View File

@ -46,7 +46,7 @@ export default function ContextMenu({
<Menu hideOnClickOutside preventBodyScroll {...rest}> <Menu hideOnClickOutside preventBodyScroll {...rest}>
{(props) => ( {(props) => (
<Position {...props}> <Position {...props}>
<Background> <Background dir="auto">
{rest.visible || rest.animating ? children : null} {rest.visible || rest.animating ? children : null}
</Background> </Background>
</Position> </Position>

View File

@ -69,6 +69,7 @@ function DocumentListItem(props: Props, ref) {
return ( return (
<DocumentLink <DocumentLink
ref={ref} ref={ref}
dir={document.dir}
$isStarred={document.isStarred} $isStarred={document.isStarred}
$menuOpen={menuOpen} $menuOpen={menuOpen}
to={{ to={{
@ -77,8 +78,12 @@ function DocumentListItem(props: Props, ref) {
}} }}
> >
<Content> <Content>
<Heading> <Heading dir={document.dir}>
<Title text={document.titleWithDefault} highlight={highlight} /> <Title
text={document.titleWithDefault}
highlight={highlight}
dir={document.dir}
/>
{document.isNew && document.createdBy.id !== currentUser.id && ( {document.isNew && document.createdBy.id !== currentUser.id && (
<Badge yellow>{t("New")}</Badge> <Badge yellow>{t("New")}</Badge>
)} )}
@ -222,6 +227,7 @@ const DocumentLink = styled(Link)`
const Heading = styled.h3` const Heading = styled.h3`
display: flex; display: flex;
justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")};
align-items: center; align-items: center;
height: 24px; height: 24px;
margin-top: 0; margin-top: 0;

View File

@ -11,6 +11,7 @@ import Time from "components/Time";
import useStores from "hooks/useStores"; import useStores from "hooks/useStores";
const Container = styled(Flex)` const Container = styled(Flex)`
justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")};
color: ${(props) => props.theme.textTertiary}; color: ${(props) => props.theme.textTertiary};
font-size: 13px; font-size: 13px;
white-space: nowrap; white-space: nowrap;
@ -135,7 +136,7 @@ function DocumentMeta({
: 0; : 0;
return ( return (
<Container align="center" {...rest}> <Container align="center" rtl={document.dir === "rtl"} {...rest} dir="ltr">
{updatedByMe ? t("You") : updatedBy.name}&nbsp; {updatedByMe ? t("You") : updatedBy.name}&nbsp;
{to ? <Link to={to}>{content}</Link> : content} {to ? <Link to={to}>{content}</Link> : content}
{showCollection && collection && ( {showCollection && collection && (

View File

@ -14,6 +14,7 @@ type Props = {|
document: Document, document: Document,
isDraft: boolean, isDraft: boolean,
to?: string, to?: string,
rtl?: boolean,
|}; |};
function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) { function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
@ -62,6 +63,7 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
} }
const Meta = styled(DocumentMeta)` const Meta = styled(DocumentMeta)`
justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")};
margin: -12px 0 2em 0; margin: -12px 0 2em 0;
font-size: 14px; font-size: 14px;
position: relative; position: relative;

View File

@ -84,7 +84,7 @@ function SidebarLink(
ref={ref} ref={ref}
> >
{icon && <IconWrapper>{icon}</IconWrapper>} {icon && <IconWrapper>{icon}</IconWrapper>}
<Label>{label}</Label> <Label dir="auto">{label}</Label>
</Link> </Link>
{menu && <Actions showActions={showActions}>{menu}</Actions>} {menu && <Actions showActions={showActions}>{menu}</Actions>}
</> </>

View File

@ -58,6 +58,26 @@ export default class Document extends BaseModel {
return emoji; 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 @computed
get noun(): string { get noun(): string {
return this.template ? "template" : "document"; return this.template ? "template" : "document";

View File

@ -33,6 +33,7 @@ type Props = {|
@observer @observer
class DocumentEditor extends React.Component<Props> { class DocumentEditor extends React.Component<Props> {
@observable activeLinkEvent: ?MouseEvent; @observable activeLinkEvent: ?MouseEvent;
@observable ref = React.createRef<HTMLDivElement | HTMLInputElement>();
focusAtStart = () => { focusAtStart = () => {
if (this.props.innerRef.current) { if (this.props.innerRef.current) {
@ -109,13 +110,17 @@ class DocumentEditor extends React.Component<Props> {
const normalizedTitle = const normalizedTitle =
!title && readOnly ? document.titleWithDefault : title; !title && readOnly ? document.titleWithDefault : title;
console.log(this.ref.current);
return ( return (
<Flex auto column> <Flex auto column>
{readOnly ? ( {readOnly ? (
<Title <Title
as="div" as="div"
ref={this.ref}
$startsWithEmojiAndSpace={startsWithEmojiAndSpace} $startsWithEmojiAndSpace={startsWithEmojiAndSpace}
$isStarred={document.isStarred} $isStarred={document.isStarred}
dir="auto"
> >
<span>{normalizedTitle}</span>{" "} <span>{normalizedTitle}</span>{" "}
{!shareId && <StarButton document={document} size={32} />} {!shareId && <StarButton document={document} size={32} />}
@ -123,6 +128,7 @@ class DocumentEditor extends React.Component<Props> {
) : ( ) : (
<Title <Title
type="text" type="text"
ref={this.ref}
onChange={onChangeTitle} onChange={onChangeTitle}
onKeyDown={this.handleTitleKeyDown} onKeyDown={this.handleTitleKeyDown}
placeholder={document.placeholder} placeholder={document.placeholder}
@ -130,6 +136,7 @@ class DocumentEditor extends React.Component<Props> {
$startsWithEmojiAndSpace={startsWithEmojiAndSpace} $startsWithEmojiAndSpace={startsWithEmojiAndSpace}
autoFocus={!title} autoFocus={!title}
maxLength={MAX_TITLE_LENGTH} maxLength={MAX_TITLE_LENGTH}
dir="auto"
/> />
)} )}
{!shareId && ( {!shareId && (
@ -137,6 +144,11 @@ class DocumentEditor extends React.Component<Props> {
isDraft={isDraft} isDraft={isDraft}
document={document} document={document}
to={documentHistoryUrl(document)} to={documentHistoryUrl(document)}
rtl={
this.ref.current
? window.getComputedStyle(this.ref.current).direction === "rtl"
: false
}
/> />
)} )}
<Editor <Editor

View File

@ -35,7 +35,6 @@ const DocumentLink = styled(Link)`
const Title = styled.h3` const Title = styled.h3`
display: flex; display: flex;
align-items: center; align-items: center;
max-width: 90%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 14px; font-size: 14px;
@ -78,7 +77,7 @@ function ReferenceListItem({
}} }}
{...rest} {...rest}
> >
<Title> <Title dir="auto">
{document.emoji ? ( {document.emoji ? (
<Emoji>{document.emoji}</Emoji> <Emoji>{document.emoji}</Emoji>
) : ( ) : (

View File

@ -123,7 +123,7 @@
"randomstring": "1.1.5", "randomstring": "1.1.5",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-autosize-textarea": "^6.0.0", "react-autosize-textarea": "^7.1.0",
"react-avatar-editor": "^11.1.0", "react-avatar-editor": "^11.1.0",
"react-color": "^2.17.3", "react-color": "^2.17.3",
"react-dnd": "^14.0.1", "react-dnd": "^14.0.1",
@ -142,7 +142,7 @@
"react-window": "^1.8.6", "react-window": "^1.8.6",
"reakit": "^1.3.6", "reakit": "^1.3.6",
"regenerator-runtime": "^0.13.7", "regenerator-runtime": "^0.13.7",
"rich-markdown-editor": "^11.12.0", "rich-markdown-editor": "^11.13.0",
"semver": "^7.3.2", "semver": "^7.3.2",
"sequelize": "^6.3.4", "sequelize": "^6.3.4",
"sequelize-cli": "^6.2.0", "sequelize-cli": "^6.2.0",

View File

@ -11606,10 +11606,10 @@ retry-as-promised@^3.2.0:
dependencies: dependencies:
any-promise "^1.3.0" any-promise "^1.3.0"
rich-markdown-editor@^11.12.0: rich-markdown-editor@^11.13.0:
version "11.12.0" version "11.13.0"
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.12.0.tgz#7fd9bd80b0afcb06d2bbcb8a2d38ac2279bc3110" resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.13.0.tgz#7009bddbd667c2fdcf6a7d4210913a1c040acdde"
integrity sha512-mf5i/JJZktNwQnEjjQIYGzIoBvh3LAmCZoRC8EcbsACO3ISgOcNdccmdd0utZMs4QHASUgE+eBjtM+awvhIPqw== integrity sha512-WZLL7z0sE7kogYjpPWPbLnbWjYwCn5RLoju6Vg6WfVDVWPSRRfWo+IHSKpBAK8kP+h9pKxZItwHrHN4ShXapjA==
dependencies: dependencies:
copy-to-clipboard "^3.0.8" copy-to-clipboard "^3.0.8"
lodash "^4.17.11" lodash "^4.17.11"