fix: Document titles in RTL script not correctly aligned
This commit is contained in:
@@ -20,78 +20,85 @@ type Props = Omit<React.HTMLAttributes<HTMLSpanElement>, "ref" | "onChange"> & {
|
||||
* Defines a content editable component with the same interface as a native
|
||||
* HTMLInputElement (or, as close as we can get).
|
||||
*/
|
||||
function ContentEditable({
|
||||
disabled,
|
||||
onChange,
|
||||
onInput,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
value,
|
||||
children,
|
||||
className,
|
||||
maxLength,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
readOnly,
|
||||
...rest
|
||||
}: Props) {
|
||||
const ref = React.useRef<HTMLSpanElement>(null);
|
||||
const [innerHTML, setInnerHTML] = React.useState<string>(value);
|
||||
const lastValue = React.useRef("");
|
||||
const ContentEditable = React.forwardRef(
|
||||
(
|
||||
{
|
||||
disabled,
|
||||
onChange,
|
||||
onInput,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
value,
|
||||
children,
|
||||
className,
|
||||
maxLength,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
readOnly,
|
||||
dir,
|
||||
...rest
|
||||
}: Props,
|
||||
forwardedRef: React.RefObject<HTMLSpanElement>
|
||||
) => {
|
||||
const innerRef = React.useRef<HTMLSpanElement>(null);
|
||||
const ref = forwardedRef || innerRef;
|
||||
const [innerHTML, setInnerHTML] = React.useState<string>(value);
|
||||
const lastValue = React.useRef("");
|
||||
|
||||
const wrappedEvent = (
|
||||
callback:
|
||||
| React.FocusEventHandler<HTMLSpanElement>
|
||||
| React.FormEventHandler<HTMLSpanElement>
|
||||
| React.KeyboardEventHandler<HTMLSpanElement>
|
||||
| undefined
|
||||
) => (event: any) => {
|
||||
const text = ref.current?.innerText || "";
|
||||
const wrappedEvent = (
|
||||
callback:
|
||||
| React.FocusEventHandler<HTMLSpanElement>
|
||||
| React.FormEventHandler<HTMLSpanElement>
|
||||
| React.KeyboardEventHandler<HTMLSpanElement>
|
||||
| undefined
|
||||
) => (event: any) => {
|
||||
const text = ref.current?.innerText || "";
|
||||
|
||||
if (maxLength && isPrintableKeyEvent(event) && text.length >= maxLength) {
|
||||
event?.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (maxLength && isPrintableKeyEvent(event) && text.length >= maxLength) {
|
||||
event?.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (text !== lastValue.current) {
|
||||
lastValue.current = text;
|
||||
onChange && onChange(text);
|
||||
}
|
||||
if (text !== lastValue.current) {
|
||||
lastValue.current = text;
|
||||
onChange && onChange(text);
|
||||
}
|
||||
|
||||
callback?.(event);
|
||||
};
|
||||
callback?.(event);
|
||||
};
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (autoFocus) {
|
||||
ref.current?.focus();
|
||||
}
|
||||
});
|
||||
React.useLayoutEffect(() => {
|
||||
if (autoFocus) {
|
||||
ref.current?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (value !== ref.current?.innerText) {
|
||||
setInnerHTML(value);
|
||||
}
|
||||
}, [value]);
|
||||
React.useEffect(() => {
|
||||
if (value !== ref.current?.innerText) {
|
||||
setInnerHTML(value);
|
||||
}
|
||||
}, [value, ref]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Content
|
||||
ref={ref}
|
||||
contentEditable={!disabled && !readOnly}
|
||||
onInput={wrappedEvent(onInput)}
|
||||
onBlur={wrappedEvent(onBlur)}
|
||||
onKeyDown={wrappedEvent(onKeyDown)}
|
||||
data-placeholder={placeholder}
|
||||
role="textbox"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: innerHTML,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={className} dir={dir}>
|
||||
<Content
|
||||
ref={ref}
|
||||
contentEditable={!disabled && !readOnly}
|
||||
onInput={wrappedEvent(onInput)}
|
||||
onBlur={wrappedEvent(onBlur)}
|
||||
onKeyDown={wrappedEvent(onKeyDown)}
|
||||
data-placeholder={placeholder}
|
||||
role="textbox"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: innerHTML,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const Content = styled.span`
|
||||
&:empty {
|
||||
@@ -108,4 +115,4 @@ const Content = styled.span`
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo<Props>(ContentEditable);
|
||||
export default ContentEditable;
|
||||
|
||||
@@ -27,86 +27,92 @@ type Props = {
|
||||
onSave?: (options: { publish?: boolean; done?: boolean }) => void;
|
||||
};
|
||||
|
||||
function EditableTitle({
|
||||
value,
|
||||
document,
|
||||
readOnly,
|
||||
onChange,
|
||||
onSave,
|
||||
onGoToNextInput,
|
||||
starrable,
|
||||
}: Props) {
|
||||
const { policies } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const can = policies.abilities(document.id);
|
||||
const { emoji } = parseTitle(value);
|
||||
const startsWithEmojiAndSpace = !!(emoji && value.startsWith(`${emoji} `));
|
||||
const normalizedTitle =
|
||||
!value && readOnly ? document.titleWithDefault : value;
|
||||
const EditableTitle = React.forwardRef(
|
||||
(
|
||||
{
|
||||
value,
|
||||
document,
|
||||
readOnly,
|
||||
onChange,
|
||||
onSave,
|
||||
onGoToNextInput,
|
||||
starrable,
|
||||
}: Props,
|
||||
ref: React.RefObject<HTMLSpanElement>
|
||||
) => {
|
||||
const { policies } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const can = policies.abilities(document.id);
|
||||
const { emoji } = parseTitle(value);
|
||||
const startsWithEmojiAndSpace = !!(emoji && value.startsWith(`${emoji} `));
|
||||
const normalizedTitle =
|
||||
!value && readOnly ? document.titleWithDefault : value;
|
||||
|
||||
const handleKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
const handleKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
|
||||
if (isModKey(event)) {
|
||||
if (isModKey(event)) {
|
||||
onSave?.({
|
||||
done: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onGoToNextInput(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "Tab" || event.key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
onGoToNextInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "p" && isModKey(event) && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
onSave?.({
|
||||
publish: true,
|
||||
done: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onGoToNextInput(true);
|
||||
return;
|
||||
}
|
||||
if (event.key === "s" && isModKey(event)) {
|
||||
event.preventDefault();
|
||||
onSave?.({});
|
||||
return;
|
||||
}
|
||||
},
|
||||
[onGoToNextInput, onSave]
|
||||
);
|
||||
|
||||
if (event.key === "Tab" || event.key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
onGoToNextInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "p" && isModKey(event) && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
onSave?.({
|
||||
publish: true,
|
||||
done: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "s" && isModKey(event)) {
|
||||
event.preventDefault();
|
||||
onSave?.({});
|
||||
return;
|
||||
}
|
||||
},
|
||||
[onGoToNextInput, onSave]
|
||||
);
|
||||
|
||||
return (
|
||||
<Title
|
||||
onChange={onChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={
|
||||
document.isTemplate
|
||||
? t("Start your template…")
|
||||
: t("Start with a title…")
|
||||
}
|
||||
value={normalizedTitle}
|
||||
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
|
||||
$isStarred={document.isStarred}
|
||||
autoFocus={!value}
|
||||
maxLength={MAX_TITLE_LENGTH}
|
||||
readOnly={readOnly}
|
||||
dir="auto"
|
||||
>
|
||||
{(can.star || can.unstar) && starrable !== false && (
|
||||
<StarButton document={document} size={32} />
|
||||
)}
|
||||
</Title>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Title
|
||||
onChange={onChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={
|
||||
document.isTemplate
|
||||
? t("Start your template…")
|
||||
: t("Start with a title…")
|
||||
}
|
||||
value={normalizedTitle}
|
||||
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
|
||||
$isStarred={document.isStarred}
|
||||
autoFocus={!value}
|
||||
maxLength={MAX_TITLE_LENGTH}
|
||||
readOnly={readOnly}
|
||||
dir="auto"
|
||||
ref={ref}
|
||||
>
|
||||
{(can.star || can.unstar) && starrable !== false && (
|
||||
<StarButton document={document} size={32} />
|
||||
)}
|
||||
</Title>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const StarButton = styled(Star)`
|
||||
position: relative;
|
||||
|
||||
@@ -37,6 +37,7 @@ class DocumentEditor extends React.Component<Props> {
|
||||
activeLinkEvent: MouseEvent | null | undefined;
|
||||
|
||||
ref = React.createRef<HTMLDivElement | HTMLInputElement>();
|
||||
titleRef = React.createRef<HTMLSpanElement>();
|
||||
|
||||
focusAtStart = () => {
|
||||
if (this.props.innerRef.current) {
|
||||
@@ -94,6 +95,7 @@ class DocumentEditor extends React.Component<Props> {
|
||||
return (
|
||||
<Flex auto column>
|
||||
<EditableTitle
|
||||
ref={this.titleRef}
|
||||
value={title}
|
||||
readOnly={readOnly}
|
||||
document={document}
|
||||
@@ -107,8 +109,9 @@ class DocumentEditor extends React.Component<Props> {
|
||||
document={document}
|
||||
to={documentHistoryUrl(document)}
|
||||
rtl={
|
||||
this.ref.current
|
||||
? window.getComputedStyle(this.ref.current).direction === "rtl"
|
||||
this.titleRef.current
|
||||
? window.getComputedStyle(this.titleRef.current).direction ===
|
||||
"rtl"
|
||||
: false
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user