fix: Inset icon in collection headers, minor ContentEditable refactor (#3168)

This commit is contained in:
Tom Moor
2022-02-25 20:38:46 -08:00
committed by GitHub
parent 7bb12b3f6d
commit ccacb65d9e
5 changed files with 79 additions and 54 deletions

View File

@@ -81,6 +81,17 @@ const ContentEditable = React.forwardRef(
}
}, [value, ref]);
// Ensure only plain text can be pasted into title when pasting from another
// rich text editor
const handlePaste = React.useCallback(
(event: React.ClipboardEvent<HTMLSpanElement>) => {
event.preventDefault();
const text = event.clipboardData.getData("text/plain");
window.document.execCommand("insertText", false, text);
},
[]
);
return (
<div className={className} dir={dir} onClick={onClick}>
<Content
@@ -89,6 +100,7 @@ const ContentEditable = React.forwardRef(
onInput={wrappedEvent(onInput)}
onBlur={wrappedEvent(onBlur)}
onKeyDown={wrappedEvent(onKeyDown)}
onPaste={handlePaste}
data-placeholder={placeholder}
suppressContentEditableWarning
role="textbox"
@@ -103,6 +115,14 @@ const ContentEditable = React.forwardRef(
);
const Content = styled.span`
background: ${(props) => props.theme.background};
transition: ${(props) => props.theme.backgroundTransition};
color: ${(props) => props.theme.text};
-webkit-text-fill-color: ${(props) => props.theme.text};
outline: none;
resize: none;
cursor: text;
&:empty {
display: inline-block;
}

View File

@@ -5,14 +5,6 @@ const Heading = styled.h1<{ centered?: boolean }>`
align-items: center;
user-select: none;
${(props) => (props.centered ? "text-align: center;" : "")}
svg {
margin-top: 4px;
margin-left: -6px;
margin-right: 2px;
align-self: flex-start;
flex-shrink: 0;
}
`;
export default Heading;

View File

@@ -0,0 +1,33 @@
import * as React from "react";
type Options = {
fontSize?: string;
lineHeight?: string;
};
/**
* Measures the width of an emoji character
*/
export default function useEmojiWidth(
emoji: string | undefined,
{ fontSize = "2.25em", lineHeight = "1.25" }: Options
) {
return React.useMemo(() => {
const element = window.document.createElement("span");
if (!emoji) {
return 0;
}
element.innerText = `${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);
}, [emoji, fontSize, lineHeight]);
}

View File

@@ -9,6 +9,8 @@ import {
useHistory,
useRouteMatch,
} from "react-router-dom";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Collection from "~/models/Collection";
import Search from "~/scenes/Search";
import Badge from "~/components/Badge";
@@ -107,8 +109,7 @@ function CollectionScene() {
title={
<>
<CollectionIcon collection={collection} expanded />
&nbsp;
{collection.name}
&nbsp;{collection.name}
</>
}
actions={<Actions collection={collection} />}
@@ -123,9 +124,9 @@ function CollectionScene() {
<Empty collection={collection} />
) : (
<>
<Heading>
<CollectionIcon collection={collection} size={40} expanded />{" "}
{collection.name}{" "}
<HeadingWithIcon>
<HeadingIcon collection={collection} size={40} expanded />
{collection.name}
{!collection.permission && (
<Tooltip
tooltip={t(
@@ -136,7 +137,7 @@ function CollectionScene() {
<Badge>{t("Private")}</Badge>
</Tooltip>
)}
</Heading>
</HeadingWithIcon>
<CollectionDescription collection={collection} />
<PinnedDocuments
@@ -243,4 +244,18 @@ function CollectionScene() {
);
}
const HeadingWithIcon = styled(Heading)`
display: flex;
align-items: center;
${breakpoint("tablet")`
margin-left: -40px;
`};
`;
const HeadingIcon = styled(CollectionIcon)`
align-self: flex-start;
flex-shrink: 0;
`;
export default observer(CollectionScene);

View File

@@ -7,6 +7,7 @@ import { light } from "@shared/theme";
import Document from "~/models/Document";
import ContentEditable from "~/components/ContentEditable";
import Star, { AnimatedStar } from "~/components/Star";
import useEmojiWidth from "~/hooks/useEmojiWidth";
import usePolicy from "~/hooks/usePolicy";
import { isModKey } from "~/utils/keyboard";
@@ -51,17 +52,6 @@ const EditableTitle = React.forwardRef(
ref.current?.focus();
}, [ref]);
// Ensure only plain text can be pasted into title when pasting from another
// rich text editor
const handlePaste = React.useCallback(
(event: React.ClipboardEvent<HTMLSpanElement>) => {
event.preventDefault();
const text = event.clipboardData.getData("text/plain");
window.document.execCommand("insertText", false, text);
},
[]
);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent) => {
if (event.key === "Enter") {
@@ -102,34 +92,16 @@ 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]);
const emojiWidth = useEmojiWidth(document.emoji, {
fontSize,
lineHeight,
});
return (
<Title
onClick={handleClick}
onChange={onChange}
onKeyDown={handleKeyDown}
onPaste={handlePaste}
placeholder={placeholder}
value={normalizedTitle}
$emojiWidth={emojiWidth}
@@ -170,17 +142,10 @@ const Title = styled(ContentEditable)<TitleProps>`
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: ${fontSize};
font-weight: 500;
outline: none;
border: 0;
padding: 0;
resize: none;
cursor: text;
> span {
outline: none;