From d0e7f2de65b63f94bdccb99e0d5234af3426799f Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 16 Jan 2022 17:02:33 -0800 Subject: [PATCH] fix: Emoji in title positioning (#2927) * wip * fix measure on first render * wip * refactor * tsc * remove fragment * refactor (again) * cleanup --- app/models/Document.ts | 1 + app/scenes/Document/components/Contents.tsx | 4 +- .../Document/components/EditableTitle.tsx | 39 ++++++++++++++----- package.json | 4 +- server/presenters/document.ts | 1 - shared/typings/index.d.ts | 4 -- shared/utils/parseTitle.ts | 2 +- yarn.lock | 15 +++---- 8 files changed, 40 insertions(+), 30 deletions(-) delete mode 100644 shared/typings/index.d.ts diff --git a/app/models/Document.ts b/app/models/Document.ts index c57836723..5a9e03f79 100644 --- a/app/models/Document.ts +++ b/app/models/Document.ts @@ -96,6 +96,7 @@ export default class Document extends BaseModel { } } + @computed get emoji() { const { emoji } = parseTitle(this.title); return emoji; diff --git a/app/scenes/Document/components/Contents.tsx b/app/scenes/Document/components/Contents.tsx index ca18242db..299ee862a 100644 --- a/app/scenes/Document/components/Contents.tsx +++ b/app/scenes/Document/components/Contents.tsx @@ -96,8 +96,8 @@ const Sticky = styled.div` box-shadow: 1px 0 0 ${(props) => props.theme.divider}; margin-top: 40px; - margin-right: 32px; - width: 224px; + margin-right: 52px; + width: 204px; min-height: 40px; overflow-y: auto; `; diff --git a/app/scenes/Document/components/EditableTitle.tsx b/app/scenes/Document/components/EditableTitle.tsx index a511032ac..5ca621924 100644 --- a/app/scenes/Document/components/EditableTitle.tsx +++ b/app/scenes/Document/components/EditableTitle.tsx @@ -5,7 +5,6 @@ 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"; @@ -27,6 +26,9 @@ type Props = { onSave?: (options: { publish?: boolean; done?: boolean }) => void; }; +const lineHeight = "1.25"; +const fontSize = "2.25em"; + const EditableTitle = React.forwardRef( ( { @@ -43,8 +45,6 @@ const EditableTitle = React.forwardRef( 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; @@ -88,6 +88,28 @@ const EditableTitle = React.forwardRef( [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 ( ` - line-height: 1.25; + line-height: ${lineHeight}; 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-size: ${fontSize}; font-weight: 500; outline: none; border: 0; @@ -150,8 +172,7 @@ const Title = styled(ContentEditable)<TitleProps>` } ${breakpoint("tablet")` - margin-left: ${(props: TitleProps) => - props.$startsWithEmojiAndSpace ? "-1.2em" : 0}; + margin-left: ${(props: TitleProps) => -props.$emojiWidth}px; `}; ${AnimatedStar} { diff --git a/package.json b/package.json index 3bb7bd9d0..4f1921c4c 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "date-fns": "^2.25.0", "dd-trace": "^0.32.2", "dotenv": "^4.0.0", - "emoji-regex": "^6.5.1", + "emoji-regex": "^10.0.0", "es6-error": "^4.1.1", "exports-loader": "^0.6.4", "fetch-retry": "^4.1.1", @@ -140,11 +140,9 @@ "react-dropzone": "^11.3.2", "react-helmet": "^6.1.0", "react-i18next": "^11.13.0", - "react-is": "^17.0.2", "react-portal": "^4.2.0", "react-router-dom": "^5.2.0", "react-table": "^7.7.0", - "react-virtual": "^2.8.2", "react-virtualized-auto-sizer": "^1.0.5", "react-waypoint": "^10.1.0", "react-window": "^1.8.6", diff --git a/server/presenters/document.ts b/server/presenters/document.ts index 40f26f3e7..86a8e6bc8 100644 --- a/server/presenters/document.ts +++ b/server/presenters/document.ts @@ -44,7 +44,6 @@ export default async function present( urlId: document.urlId, title: document.title, text, - emoji: document.emoji, tasks: document.tasks, createdAt: document.createdAt, createdBy: undefined, diff --git a/shared/typings/index.d.ts b/shared/typings/index.d.ts deleted file mode 100644 index 80af8845f..000000000 --- a/shared/typings/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "emoji-regex" { - const RegExpFactory: () => RegExp; - export = RegExpFactory; -} diff --git a/shared/utils/parseTitle.ts b/shared/utils/parseTitle.ts index 67d168fe5..27e4a5e29 100644 --- a/shared/utils/parseTitle.ts +++ b/shared/utils/parseTitle.ts @@ -14,7 +14,7 @@ export default function parseTitle(text = "") { // find and extract first emoji const matches = regex.exec(title); const firstEmoji = matches ? matches[0] : null; - const startsWithEmoji = firstEmoji && title.startsWith(firstEmoji); + const startsWithEmoji = firstEmoji && title.startsWith(`${firstEmoji} `); const emoji = startsWithEmoji ? firstEmoji : undefined; return { diff --git a/yarn.lock b/yarn.lock index 99b218a75..37ce3c2d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5714,9 +5714,9 @@ cssstyle@^2.3.0: cssom "~0.3.6" csstype@^3.0.2, csstype@^3.0.4: - version "3.0.9" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b" - integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw== + version "3.0.10" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" + integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== cyclist@^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" integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== -emoji-regex@*: +emoji-regex@*, emoji-regex@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.0.0.tgz#96559e19f82231b436403e059571241d627c42b8" 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: version "7.0.3" 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" 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" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==