chore: Move to Typescript (#2783)
This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously. closes #1282
This commit is contained in:
161
app/scenes/Document/components/EditableTitle.tsx
Normal file
161
app/scenes/Document/components/EditableTitle.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||
import { light } from "@shared/theme";
|
||||
import parseTitle from "@shared/utils/parseTitle";
|
||||
import Document from "~/models/Document";
|
||||
import ContentEditable from "~/components/ContentEditable";
|
||||
import Star, { AnimatedStar } from "~/components/Star";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { isModKey } from "~/utils/keyboard";
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
document: Document;
|
||||
readOnly?: boolean;
|
||||
onChange: (text: string) => void;
|
||||
onGoToNextInput: (insertParagraph?: boolean) => void;
|
||||
onSave?: (options: { publish?: boolean; done?: boolean }) => void;
|
||||
};
|
||||
|
||||
function EditableTitle({
|
||||
value,
|
||||
document,
|
||||
readOnly,
|
||||
onChange,
|
||||
onSave,
|
||||
onGoToNextInput,
|
||||
}: 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 handleKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) && <StarButton document={document} size={32} />}
|
||||
</Title>
|
||||
);
|
||||
}
|
||||
|
||||
const StarButton = styled(Star)`
|
||||
position: relative;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
`;
|
||||
|
||||
const Title = styled(ContentEditable)<{
|
||||
$startsWithEmojiAndSpace: boolean;
|
||||
$isStarred: boolean;
|
||||
}>`
|
||||
line-height: 1.25;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
background: ${(props) => props.theme.background};
|
||||
transition: ${(props) => props.theme.backgroundTransition};
|
||||
color: ${(props) => props.theme.text};
|
||||
-webkit-text-fill-color: ${(props) => props.theme.text};
|
||||
font-size: 2.25em;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
resize: none;
|
||||
|
||||
> span {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: ${(props) => props.theme.placeholder};
|
||||
-webkit-text-fill-color: ${(props) => props.theme.placeholder};
|
||||
}
|
||||
|
||||
${breakpoint("tablet")`
|
||||
margin-left: ${(props: any) =>
|
||||
props.$startsWithEmojiAndSpace ? "-1.2em" : 0};
|
||||
`};
|
||||
|
||||
${AnimatedStar} {
|
||||
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
${AnimatedStar} {
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
color: ${(props) => light.text};
|
||||
-webkit-text-fill-color: ${(props) => light.text};
|
||||
background: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export default observer(EditableTitle);
|
||||
Reference in New Issue
Block a user