fix: Emoji in title positioning (#2927)
* wip * fix measure on first render * wip * refactor * tsc * remove fragment * refactor (again) * cleanup
This commit is contained in:
@@ -96,6 +96,7 @@ export default class Document extends BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
get emoji() {
|
get emoji() {
|
||||||
const { emoji } = parseTitle(this.title);
|
const { emoji } = parseTitle(this.title);
|
||||||
return emoji;
|
return emoji;
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ const Sticky = styled.div`
|
|||||||
|
|
||||||
box-shadow: 1px 0 0 ${(props) => props.theme.divider};
|
box-shadow: 1px 0 0 ${(props) => props.theme.divider};
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
margin-right: 32px;
|
margin-right: 52px;
|
||||||
width: 224px;
|
width: 204px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import styled from "styled-components";
|
|||||||
import breakpoint from "styled-components-breakpoint";
|
import breakpoint from "styled-components-breakpoint";
|
||||||
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||||
import { light } from "@shared/theme";
|
import { light } from "@shared/theme";
|
||||||
import parseTitle from "@shared/utils/parseTitle";
|
|
||||||
import Document from "~/models/Document";
|
import Document from "~/models/Document";
|
||||||
import ContentEditable from "~/components/ContentEditable";
|
import ContentEditable from "~/components/ContentEditable";
|
||||||
import Star, { AnimatedStar } from "~/components/Star";
|
import Star, { AnimatedStar } from "~/components/Star";
|
||||||
@@ -27,6 +26,9 @@ type Props = {
|
|||||||
onSave?: (options: { publish?: boolean; done?: boolean }) => void;
|
onSave?: (options: { publish?: boolean; done?: boolean }) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const lineHeight = "1.25";
|
||||||
|
const fontSize = "2.25em";
|
||||||
|
|
||||||
const EditableTitle = React.forwardRef(
|
const EditableTitle = React.forwardRef(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -43,8 +45,6 @@ const EditableTitle = React.forwardRef(
|
|||||||
const { policies } = useStores();
|
const { policies } = useStores();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const can = policies.abilities(document.id);
|
const can = policies.abilities(document.id);
|
||||||
const { emoji } = parseTitle(value);
|
|
||||||
const startsWithEmojiAndSpace = !!(emoji && value.startsWith(`${emoji} `));
|
|
||||||
const normalizedTitle =
|
const normalizedTitle =
|
||||||
!value && readOnly ? document.titleWithDefault : value;
|
!value && readOnly ? document.titleWithDefault : value;
|
||||||
|
|
||||||
@@ -88,6 +88,28 @@ const EditableTitle = React.forwardRef(
|
|||||||
[onGoToNextInput, onSave]
|
[onGoToNextInput, onSave]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Measures the width of the document's emoji in the title
|
||||||
|
*/
|
||||||
|
const emojiWidth = React.useMemo(() => {
|
||||||
|
const element = window.document.createElement("span");
|
||||||
|
if (!document.emoji) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerText = `${document.emoji}\u00A0`;
|
||||||
|
element.style.visibility = "hidden";
|
||||||
|
element.style.position = "absolute";
|
||||||
|
element.style.left = "-9999px";
|
||||||
|
element.style.lineHeight = lineHeight;
|
||||||
|
element.style.fontSize = fontSize;
|
||||||
|
element.style.width = "max-content";
|
||||||
|
window.document.body?.appendChild(element);
|
||||||
|
const width = window.getComputedStyle(element).width;
|
||||||
|
window.document.body?.removeChild(element);
|
||||||
|
return parseInt(width, 10);
|
||||||
|
}, [document.emoji]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Title
|
<Title
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -98,7 +120,7 @@ const EditableTitle = React.forwardRef(
|
|||||||
: t("Start with a title…")
|
: t("Start with a title…")
|
||||||
}
|
}
|
||||||
value={normalizedTitle}
|
value={normalizedTitle}
|
||||||
$startsWithEmojiAndSpace={startsWithEmojiAndSpace}
|
$emojiWidth={emojiWidth}
|
||||||
$isStarred={document.isStarred}
|
$isStarred={document.isStarred}
|
||||||
autoFocus={!value}
|
autoFocus={!value}
|
||||||
maxLength={MAX_TITLE_LENGTH}
|
maxLength={MAX_TITLE_LENGTH}
|
||||||
@@ -121,19 +143,19 @@ const StarButton = styled(Star)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type TitleProps = {
|
type TitleProps = {
|
||||||
$startsWithEmojiAndSpace: boolean;
|
|
||||||
$isStarred: boolean;
|
$isStarred: boolean;
|
||||||
|
$emojiWidth: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Title = styled(ContentEditable)<TitleProps>`
|
const Title = styled(ContentEditable)<TitleProps>`
|
||||||
line-height: 1.25;
|
line-height: ${lineHeight};
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
background: ${(props) => props.theme.background};
|
background: ${(props) => props.theme.background};
|
||||||
transition: ${(props) => props.theme.backgroundTransition};
|
transition: ${(props) => props.theme.backgroundTransition};
|
||||||
color: ${(props) => props.theme.text};
|
color: ${(props) => props.theme.text};
|
||||||
-webkit-text-fill-color: ${(props) => props.theme.text};
|
-webkit-text-fill-color: ${(props) => props.theme.text};
|
||||||
font-size: 2.25em;
|
font-size: ${fontSize};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -150,8 +172,7 @@ const Title = styled(ContentEditable)<TitleProps>`
|
|||||||
}
|
}
|
||||||
|
|
||||||
${breakpoint("tablet")`
|
${breakpoint("tablet")`
|
||||||
margin-left: ${(props: TitleProps) =>
|
margin-left: ${(props: TitleProps) => -props.$emojiWidth}px;
|
||||||
props.$startsWithEmojiAndSpace ? "-1.2em" : 0};
|
|
||||||
`};
|
`};
|
||||||
|
|
||||||
${AnimatedStar} {
|
${AnimatedStar} {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
"date-fns": "^2.25.0",
|
"date-fns": "^2.25.0",
|
||||||
"dd-trace": "^0.32.2",
|
"dd-trace": "^0.32.2",
|
||||||
"dotenv": "^4.0.0",
|
"dotenv": "^4.0.0",
|
||||||
"emoji-regex": "^6.5.1",
|
"emoji-regex": "^10.0.0",
|
||||||
"es6-error": "^4.1.1",
|
"es6-error": "^4.1.1",
|
||||||
"exports-loader": "^0.6.4",
|
"exports-loader": "^0.6.4",
|
||||||
"fetch-retry": "^4.1.1",
|
"fetch-retry": "^4.1.1",
|
||||||
@@ -140,11 +140,9 @@
|
|||||||
"react-dropzone": "^11.3.2",
|
"react-dropzone": "^11.3.2",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-i18next": "^11.13.0",
|
"react-i18next": "^11.13.0",
|
||||||
"react-is": "^17.0.2",
|
|
||||||
"react-portal": "^4.2.0",
|
"react-portal": "^4.2.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"react-virtual": "^2.8.2",
|
|
||||||
"react-virtualized-auto-sizer": "^1.0.5",
|
"react-virtualized-auto-sizer": "^1.0.5",
|
||||||
"react-waypoint": "^10.1.0",
|
"react-waypoint": "^10.1.0",
|
||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ export default async function present(
|
|||||||
urlId: document.urlId,
|
urlId: document.urlId,
|
||||||
title: document.title,
|
title: document.title,
|
||||||
text,
|
text,
|
||||||
emoji: document.emoji,
|
|
||||||
tasks: document.tasks,
|
tasks: document.tasks,
|
||||||
createdAt: document.createdAt,
|
createdAt: document.createdAt,
|
||||||
createdBy: undefined,
|
createdBy: undefined,
|
||||||
|
|||||||
4
shared/typings/index.d.ts
vendored
4
shared/typings/index.d.ts
vendored
@@ -1,4 +0,0 @@
|
|||||||
declare module "emoji-regex" {
|
|
||||||
const RegExpFactory: () => RegExp;
|
|
||||||
export = RegExpFactory;
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@ export default function parseTitle(text = "") {
|
|||||||
// find and extract first emoji
|
// find and extract first emoji
|
||||||
const matches = regex.exec(title);
|
const matches = regex.exec(title);
|
||||||
const firstEmoji = matches ? matches[0] : null;
|
const firstEmoji = matches ? matches[0] : null;
|
||||||
const startsWithEmoji = firstEmoji && title.startsWith(firstEmoji);
|
const startsWithEmoji = firstEmoji && title.startsWith(`${firstEmoji} `);
|
||||||
const emoji = startsWithEmoji ? firstEmoji : undefined;
|
const emoji = startsWithEmoji ? firstEmoji : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
15
yarn.lock
15
yarn.lock
@@ -5714,9 +5714,9 @@ cssstyle@^2.3.0:
|
|||||||
cssom "~0.3.6"
|
cssom "~0.3.6"
|
||||||
|
|
||||||
csstype@^3.0.2, csstype@^3.0.4:
|
csstype@^3.0.2, csstype@^3.0.4:
|
||||||
version "3.0.9"
|
version "3.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
|
||||||
integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
|
integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
|
||||||
|
|
||||||
cyclist@^1.0.1:
|
cyclist@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
@@ -6262,16 +6262,11 @@ emittery@^0.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
|
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
|
||||||
integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==
|
integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==
|
||||||
|
|
||||||
emoji-regex@*:
|
emoji-regex@*, emoji-regex@^10.0.0:
|
||||||
version "10.0.0"
|
version "10.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.0.0.tgz#96559e19f82231b436403e059571241d627c42b8"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.0.0.tgz#96559e19f82231b436403e059571241d627c42b8"
|
||||||
integrity sha512-KmJa8l6uHi1HrBI34udwlzZY1jOEuID/ft4d8BSSEdRyap7PwBEt910453PJa5MuGvxkLqlt4Uvhu7tttFHViw==
|
integrity sha512-KmJa8l6uHi1HrBI34udwlzZY1jOEuID/ft4d8BSSEdRyap7PwBEt910453PJa5MuGvxkLqlt4Uvhu7tttFHViw==
|
||||||
|
|
||||||
emoji-regex@^6.5.1:
|
|
||||||
version "6.5.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
|
|
||||||
integrity sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==
|
|
||||||
|
|
||||||
emoji-regex@^7.0.1:
|
emoji-regex@^7.0.1:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
||||||
@@ -12186,7 +12181,7 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-i
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
react-is@^17.0.1, react-is@^17.0.2:
|
react-is@^17.0.1:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||||
|
|||||||
Reference in New Issue
Block a user