fix: Wide selection of comment toolbar fixes (#5160

* fix: Margin on floating toolbar
fix: Flash of toolbar on wide screens

* fix: Nesting of comment marks

* fix: Post button not visible when there is a draft comment, makes it look like the comment is saved
fix: Styling of link editor results now matches other menus
fix: Allow small link editor in comments sidebar

* fix: Cannot use arrow keys to navigate suggested links
Added animation to link suggestions
Added mixin for text ellipsis

* fix: Link input appears non-rounded when no creation option

* Accidental removal
This commit is contained in:
Tom Moor
2023-04-07 18:52:57 -04:00
committed by GitHub
parent a5c44ee961
commit c202198d61
28 changed files with 211 additions and 171 deletions

View File

@@ -4,6 +4,7 @@ import { Link } from "react-router-dom";
import styled from "styled-components"; import styled from "styled-components";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import BreadcrumbMenu from "~/menus/BreadcrumbMenu"; import BreadcrumbMenu from "~/menus/BreadcrumbMenu";
import { ellipsis } from "~/styles";
import { MenuInternalLink } from "~/types"; import { MenuInternalLink } from "~/types";
type Props = { type Props = {
@@ -64,6 +65,7 @@ const Slash = styled(GoToIcon)`
`; `;
const Item = styled(Link)<{ $highlight: boolean; $withIcon: boolean }>` const Item = styled(Link)<{ $highlight: boolean; $withIcon: boolean }>`
${ellipsis()}
display: flex; display: flex;
flex-shrink: 1; flex-shrink: 1;
min-width: 0; min-width: 0;
@@ -71,9 +73,6 @@ const Item = styled(Link)<{ $highlight: boolean; $withIcon: boolean }>`
color: ${(props) => props.theme.text}; color: ${(props) => props.theme.text};
font-size: 15px; font-size: 15px;
height: 24px; height: 24px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-weight: ${(props) => (props.$highlight ? "500" : "inherit")}; font-weight: ${(props) => (props.$highlight ? "500" : "inherit")};
margin-left: ${(props) => (props.$withIcon ? "4px" : "0")}; margin-left: ${(props) => (props.$withIcon ? "4px" : "0")};

View File

@@ -4,6 +4,7 @@ import * as React from "react";
import styled, { css, useTheme } from "styled-components"; import styled, { css, useTheme } from "styled-components";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import Key from "~/components/Key"; import Key from "~/components/Key";
import { ellipsis } from "~/styles";
type Props = { type Props = {
action: ActionImpl; action: ActionImpl;
@@ -85,8 +86,7 @@ const Ancestor = styled.span`
`; `;
const Content = styled(Flex)` const Content = styled(Flex)`
overflow: hidden; ${ellipsis()}
text-overflow: ellipsis;
flex-shrink: 1; flex-shrink: 1;
`; `;
@@ -102,9 +102,7 @@ const Item = styled.div<{ active?: boolean }>`
justify-content: space-between; justify-content: space-between;
cursor: var(--pointer); cursor: var(--pointer);
text-overflow: ellipsis; ${ellipsis()}
white-space: nowrap;
overflow: hidden;
user-select: none; user-select: none;
min-width: 0; min-width: 0;

View File

@@ -13,6 +13,7 @@ import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton"; import NudeButton from "~/components/NudeButton";
import Time from "~/components/Time"; import Time from "~/components/Time";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import { ellipsis } from "~/styles";
import CollectionIcon from "./Icons/CollectionIcon"; import CollectionIcon from "./Icons/CollectionIcon";
import EmojiIcon from "./Icons/EmojiIcon"; import EmojiIcon from "./Icons/EmojiIcon";
import Squircle from "./Squircle"; import Squircle from "./Squircle";
@@ -217,14 +218,12 @@ const Content = styled(Flex)`
`; `;
const DocumentMeta = styled(Text)` const DocumentMeta = styled(Text)`
${ellipsis()}
display: flex; display: flex;
align-items: center; align-items: center;
gap: 2px; gap: 2px;
color: ${(props) => props.theme.textTertiary}; color: ${(props) => props.theme.textTertiary};
margin: 0 0 0 -2px; margin: 0 0 0 -2px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`; `;
const DocumentLink = styled(Link)<{ const DocumentLink = styled(Link)<{

View File

@@ -6,6 +6,7 @@ import breakpoint from "styled-components-breakpoint";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import Disclosure from "~/components/Sidebar/components/Disclosure"; import Disclosure from "~/components/Sidebar/components/Disclosure";
import Text from "~/components/Text"; import Text from "~/components/Text";
import { ellipsis } from "~/styles";
type Props = { type Props = {
selected: boolean; selected: boolean;
@@ -70,9 +71,7 @@ function DocumentExplorerNode(
} }
const Title = styled(Text)` const Title = styled(Text)`
white-space: nowrap; ${ellipsis()}
overflow: hidden;
text-overflow: ellipsis;
margin: 0 4px 0 4px; margin: 0 4px 0 4px;
color: inherit; color: inherit;
`; `;

View File

@@ -6,6 +6,7 @@ import styled from "styled-components";
import { Node as SearchResult } from "~/components/DocumentExplorerNode"; import { Node as SearchResult } from "~/components/DocumentExplorerNode";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import Text from "~/components/Text"; import Text from "~/components/Text";
import { ellipsis } from "~/styles";
type Props = { type Props = {
selected: boolean; selected: boolean;
@@ -73,10 +74,8 @@ const Title = styled(Text)`
`; `;
const Path = styled(Text)<{ $selected: boolean }>` const Path = styled(Text)<{ $selected: boolean }>`
${ellipsis()}
padding-top: 2px; padding-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 4px 0 8px; margin: 0 4px 0 8px;
color: ${(props) => color: ${(props) =>
props.$selected ? props.theme.white50 : props.theme.textTertiary}; props.$selected ? props.theme.white50 : props.theme.textTertiary};

View File

@@ -11,6 +11,7 @@ import Flex from "~/components/Flex";
import Time from "~/components/Time"; import Time from "~/components/Time";
import useCurrentUser from "~/hooks/useCurrentUser"; import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import { ellipsis } from "~/styles";
type Props = { type Props = {
showCollection?: boolean; showCollection?: boolean;
@@ -192,8 +193,7 @@ const Container = styled(Flex)<{ rtl?: boolean }>`
`; `;
const Viewed = styled.span` const Viewed = styled.span`
text-overflow: ellipsis; ${ellipsis()}
overflow: hidden;
`; `;
const Modified = styled.span<{ highlight?: boolean }>` const Modified = styled.span<{ highlight?: boolean }>`

View File

@@ -4,7 +4,7 @@ import styled from "styled-components";
import breakpoint from "styled-components-breakpoint"; import breakpoint from "styled-components-breakpoint";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import Text from "~/components/Text"; import Text from "~/components/Text";
import { undraggableOnDesktop } from "~/styles"; import { ellipsis, undraggableOnDesktop } from "~/styles";
const RealTextarea = styled.textarea<{ hasIcon?: boolean }>` const RealTextarea = styled.textarea<{ hasIcon?: boolean }>`
border: 0; border: 0;
@@ -29,9 +29,7 @@ const RealInput = styled.input<{ hasIcon?: boolean }>`
color: ${(props) => props.theme.text}; color: ${(props) => props.theme.text};
height: 30px; height: 30px;
min-width: 0; min-width: 0;
overflow: hidden; ${ellipsis()}
text-overflow: ellipsis;
white-space: nowrap;
${undraggableOnDesktop()} ${undraggableOnDesktop()}
&:disabled, &:disabled,

View File

@@ -3,6 +3,7 @@ import * as React from "react";
import styled, { useTheme } from "styled-components"; import styled, { useTheme } from "styled-components";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import NavLink from "~/components/NavLink"; import NavLink from "~/components/NavLink";
import { ellipsis } from "~/styles";
export type Props = Omit<React.HTMLAttributes<HTMLAnchorElement>, "title"> & { export type Props = Omit<React.HTMLAttributes<HTMLAnchorElement>, "title"> & {
image?: React.ReactNode; image?: React.ReactNode;
@@ -103,9 +104,7 @@ const Image = styled(Flex)`
const Heading = styled.p<{ $small?: boolean }>` const Heading = styled.p<{ $small?: boolean }>`
font-size: ${(props) => (props.$small ? 14 : 16)}px; font-size: ${(props) => (props.$small ? 14 : 16)}px;
font-weight: 500; font-weight: 500;
white-space: nowrap; ${ellipsis()}
text-overflow: ellipsis;
overflow: hidden;
line-height: ${(props) => (props.$small ? 1.3 : 1.2)}; line-height: ${(props) => (props.$small ? 1.3 : 1.2)};
margin: 0; margin: 0;
`; `;

View File

@@ -19,7 +19,17 @@ type Props = {
* Automatically animates the height of a container based on it's contents. * Automatically animates the height of a container based on it's contents.
*/ */
export function ResizingHeightContainer(props: Props) { export function ResizingHeightContainer(props: Props) {
const { hideOverflow, children, config, style } = props; const {
hideOverflow,
children,
config = {
transition: {
duration: 0.1,
ease: "easeInOut",
},
},
style,
} = props;
const ref = React.useRef<HTMLDivElement>(null); const ref = React.useRef<HTMLDivElement>(null);
const { height } = useComponentSize(ref); const { height } = useComponentSize(ref);

View File

@@ -6,7 +6,7 @@ import styled, { css } from "styled-components";
import breakpoint from "styled-components-breakpoint"; import breakpoint from "styled-components-breakpoint";
import Document from "~/models/Document"; import Document from "~/models/Document";
import Highlight, { Mark } from "~/components/Highlight"; import Highlight, { Mark } from "~/components/Highlight";
import { hover } from "~/styles"; import { ellipsis, hover } from "~/styles";
import { sharedDocumentPath } from "~/utils/routeHelpers"; import { sharedDocumentPath } from "~/utils/routeHelpers";
type Props = { type Props = {
@@ -125,8 +125,7 @@ const Heading = styled.h4<{ rtl?: boolean }>`
const Title = styled(Highlight)` const Title = styled(Highlight)`
max-width: 90%; max-width: 90%;
overflow: hidden; ${ellipsis()}
text-overflow: ellipsis;
${Mark} { ${Mark} {
padding: 0; padding: 0;
@@ -139,10 +138,7 @@ const ResultContext = styled(Highlight)`
font-size: 14px; font-size: 14px;
margin-top: -0.25em; margin-top: -0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
${ellipsis()}
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
${Mark} { ${Mark} {
padding: 0; padding: 0;

View File

@@ -13,13 +13,15 @@ import { useEditor } from "./EditorContext";
type Props = { type Props = {
active?: boolean; active?: boolean;
children: React.ReactNode; children: React.ReactNode;
width?: number;
forwardedRef?: React.RefObject<HTMLDivElement> | null; forwardedRef?: React.RefObject<HTMLDivElement> | null;
}; };
const defaultPosition = { const defaultPosition = {
left: -1000, left: -10000,
top: 0, top: 0,
offset: 0, offset: 0,
maxWidth: 1000,
visible: false, visible: false,
}; };
@@ -48,6 +50,7 @@ function usePosition({
right: 0, right: 0,
top: viewportHeight - menuHeight, top: viewportHeight - menuHeight,
offset: 0, offset: 0,
maxWidth: 1000,
visible: true, visible: true,
}; };
} }
@@ -134,7 +137,7 @@ function usePosition({
const margin = 12; const margin = 12;
const left = Math.min( const left = Math.min(
Math.min( Math.min(
offsetParent.x + offsetParent.width - menuWidth, offsetParent.x + offsetParent.width - menuWidth - margin,
window.innerWidth - margin window.innerWidth - margin
), ),
Math.max( Math.max(
@@ -155,6 +158,7 @@ function usePosition({
left: Math.round(left - offsetParent.left), left: Math.round(left - offsetParent.left),
top: Math.round(top - offsetParent.top), top: Math.round(top - offsetParent.top),
offset: Math.round(offset), offset: Math.round(offset),
maxWidth: offsetParent.width,
visible: true, visible: true,
}; };
} }
@@ -189,8 +193,10 @@ const FloatingToolbar = React.forwardRef(
<Wrapper <Wrapper
active={props.active && position.visible} active={props.active && position.visible}
ref={menuRef} ref={menuRef}
offset={position.offset} $offset={position.offset}
style={{ style={{
width: props.width,
maxWidth: `${position.maxWidth}px`,
top: `${position.top}px`, top: `${position.top}px`,
left: `${position.left}px`, left: `${position.left}px`,
}} }}
@@ -204,7 +210,7 @@ const FloatingToolbar = React.forwardRef(
const Wrapper = styled.div<{ const Wrapper = styled.div<{
active?: boolean; active?: boolean;
offset: number; $offset: number;
}>` }>`
will-change: opacity, transform; will-change: opacity, transform;
padding: 8px 16px; padding: 8px 16px;
@@ -234,7 +240,7 @@ const Wrapper = styled.div<{
z-index: -1; z-index: -1;
position: absolute; position: absolute;
bottom: -2px; bottom: -2px;
left: calc(50% - ${(props) => props.offset || 0}px); left: calc(50% - ${(props) => props.$offset || 0}px);
pointer-events: none; pointer-events: none;
} }

View File

@@ -10,6 +10,7 @@ const Input = styled.input`
margin: 0; margin: 0;
outline: none; outline: none;
flex-grow: 1; flex-grow: 1;
min-width: 0;
@media (hover: none) and (pointer: coarse) { @media (hover: none) and (pointer: coarse) {
font-size: 16px; font-size: 16px;

View File

@@ -3,7 +3,6 @@ import {
DocumentIcon, DocumentIcon,
CloseIcon, CloseIcon,
PlusIcon, PlusIcon,
TrashIcon,
OpenIcon, OpenIcon,
} from "outline-icons"; } from "outline-icons";
import { Mark } from "prosemirror-model"; import { Mark } from "prosemirror-model";
@@ -13,6 +12,8 @@ import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { isInternalUrl, sanitizeUrl } from "@shared/utils/urls"; import { isInternalUrl, sanitizeUrl } from "@shared/utils/urls";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
import Scrollable from "~/components/Scrollable";
import { Dictionary } from "~/hooks/useDictionary"; import { Dictionary } from "~/hooks/useDictionary";
import { ToastOptions } from "~/types"; import { ToastOptions } from "~/types";
import Input from "./Input"; import Input from "./Input";
@@ -61,6 +62,7 @@ class LinkEditor extends React.Component<Props, State> {
discardInputValue = false; discardInputValue = false;
initialValue = this.href; initialValue = this.href;
initialSelectionLength = this.props.to - this.props.from; initialSelectionLength = this.props.to - this.props.from;
resultsRef = React.createRef<HTMLDivElement>();
state: State = { state: State = {
selectedIndex: -1, selectedIndex: -1,
@@ -122,11 +124,12 @@ class LinkEditor extends React.Component<Props, State> {
}; };
handleKeyDown = (event: React.KeyboardEvent): void => { handleKeyDown = (event: React.KeyboardEvent): void => {
const results = this.results;
switch (event.key) { switch (event.key) {
case "Enter": { case "Enter": {
event.preventDefault(); event.preventDefault();
const { selectedIndex, value } = this.state; const { selectedIndex, value } = this.state;
const results = this.state.results[value] || [];
const { onCreateLink } = this.props; const { onCreateLink } = this.props;
if (selectedIndex >= 0) { if (selectedIndex >= 0) {
@@ -181,8 +184,7 @@ class LinkEditor extends React.Component<Props, State> {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const { selectedIndex, value } = this.state; const { selectedIndex } = this.state;
const results = this.state.results[value] || [];
const total = results.length; const total = results.length;
const nextIndex = selectedIndex + 1; const nextIndex = selectedIndex + 1;
@@ -264,10 +266,7 @@ class LinkEditor extends React.Component<Props, State> {
dispatch(state.tr.removeMark(from, to, mark)); dispatch(state.tr.removeMark(from, to, mark));
} }
if (onRemoveLink) { onRemoveLink?.();
onRemoveLink();
}
view.focus(); view.focus();
}; };
@@ -289,14 +288,19 @@ class LinkEditor extends React.Component<Props, State> {
view.focus(); view.focus();
}; };
get results() {
const { value } = this.state;
return (
this.state.results[value.trim()] ||
this.state.results[this.state.previousValue] ||
[]
);
}
render() { render() {
const { dictionary } = this.props; const { dictionary } = this.props;
const { value, selectedIndex } = this.state; const { value, selectedIndex } = this.state;
const results = const results = this.results;
this.state.results[value.trim()] ||
this.state.results[this.state.previousValue] ||
[];
const looksLikeUrl = value.match(/^https?:\/\//i); const looksLikeUrl = value.match(/^https?:\/\//i);
const suggestedLinkTitle = this.suggestedLinkTitle; const suggestedLinkTitle = this.suggestedLinkTitle;
const isInternal = isInternalUrl(value); const isInternal = isInternalUrl(value);
@@ -307,7 +311,7 @@ class LinkEditor extends React.Component<Props, State> {
suggestedLinkTitle.length > 0 && suggestedLinkTitle.length > 0 &&
!looksLikeUrl; !looksLikeUrl;
const showResults = const hasResults =
!!suggestedLinkTitle && (showCreateLink || results.length > 0); !!suggestedLinkTitle && (showCreateLink || results.length > 0);
return ( return (
@@ -339,47 +343,53 @@ class LinkEditor extends React.Component<Props, State> {
</Tooltip> </Tooltip>
<Tooltip tooltip={dictionary.removeLink}> <Tooltip tooltip={dictionary.removeLink}>
<ToolbarButton onClick={this.handleRemoveLink}> <ToolbarButton onClick={this.handleRemoveLink}>
{this.initialValue ? ( <CloseIcon color="currentColor" />
<TrashIcon color="currentColor" />
) : (
<CloseIcon color="currentColor" />
)}
</ToolbarButton> </ToolbarButton>
</Tooltip> </Tooltip>
{showResults && ( <SearchResults
<SearchResults id="link-search-results"> ref={this.resultsRef}
{results.map((result, index) => ( $hasResults={hasResults}
<LinkSearchResult role="menu"
key={result.url} >
title={result.title} <ResizingHeightContainer>
subtitle={result.subtitle} {hasResults && (
icon={<DocumentIcon color="currentColor" />} <>
onPointerMove={() => this.handleFocusLink(index)} {results.map((result, index) => (
onClick={this.handleSelectLink(result.url, result.title)} <LinkSearchResult
selected={index === selectedIndex} key={result.url}
/> title={result.title}
))} subtitle={result.subtitle}
icon={<DocumentIcon color="currentColor" />}
onPointerMove={() => this.handleFocusLink(index)}
onClick={this.handleSelectLink(result.url, result.title)}
selected={index === selectedIndex}
containerRef={this.resultsRef}
/>
))}
{showCreateLink && ( {showCreateLink && (
<LinkSearchResult <LinkSearchResult
key="create" key="create"
title={suggestedLinkTitle} containerRef={this.resultsRef}
subtitle={dictionary.createNewDoc} title={suggestedLinkTitle}
icon={<PlusIcon color="currentColor" />} subtitle={dictionary.createNewDoc}
onPointerMove={() => this.handleFocusLink(results.length)} icon={<PlusIcon color="currentColor" />}
onClick={() => { onPointerMove={() => this.handleFocusLink(results.length)}
this.handleCreateLink(suggestedLinkTitle); onClick={() => {
this.handleCreateLink(suggestedLinkTitle);
if (this.initialSelectionLength) { if (this.initialSelectionLength) {
this.moveSelectionToEnd(); this.moveSelectionToEnd();
} }
}} }}
selected={results.length === selectedIndex} selected={results.length === selectedIndex}
/> />
)}
</>
)} )}
</SearchResults> </ResizingHeightContainer>
)} </SearchResults>
</Wrapper> </Wrapper>
); );
} }
@@ -388,25 +398,20 @@ class LinkEditor extends React.Component<Props, State> {
const Wrapper = styled(Flex)` const Wrapper = styled(Flex)`
margin-left: -8px; margin-left: -8px;
margin-right: -8px; margin-right: -8px;
min-width: 336px;
pointer-events: all; pointer-events: all;
gap: 8px; gap: 8px;
`; `;
const SearchResults = styled.ol` const SearchResults = styled(Scrollable)<{ $hasResults: boolean }>`
background: ${(props) => props.theme.toolbarBackground}; background: ${(props) => props.theme.toolbarBackground};
position: absolute; position: absolute;
top: 100%; top: 100%;
width: 100%; width: 100%;
height: auto; height: auto;
left: 0; left: 0;
padding: 0; margin: -8px 0 0;
margin: 0;
margin-top: -3px;
margin-bottom: 0;
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
overflow-y: auto; padding: ${(props) => (props.$hasResults ? "8px 0" : "0")};
overscroll-behavior: none;
max-height: 260px; max-height: 260px;
@media (hover: none) and (pointer: coarse) { @media (hover: none) and (pointer: coarse) {

View File

@@ -1,15 +1,24 @@
import * as React from "react"; import * as React from "react";
import scrollIntoView from "smooth-scroll-into-view-if-needed"; import scrollIntoView from "smooth-scroll-into-view-if-needed";
import styled from "styled-components"; import styled from "styled-components";
import { ellipsis } from "~/styles";
type Props = React.HTMLAttributes<HTMLLIElement> & { type Props = React.HTMLAttributes<HTMLDivElement> & {
icon: React.ReactNode; icon: React.ReactNode;
selected: boolean; selected: boolean;
title: React.ReactNode; title: React.ReactNode;
subtitle?: React.ReactNode; subtitle?: React.ReactNode;
containerRef: React.RefObject<HTMLDivElement>;
}; };
function LinkSearchResult({ title, subtitle, selected, icon, ...rest }: Props) { function LinkSearchResult({
title,
subtitle,
containerRef,
selected,
icon,
...rest
}: Props) {
const ref = React.useCallback( const ref = React.useCallback(
(node: HTMLElement | null) => { (node: HTMLElement | null) => {
if (selected && node) { if (selected && node) {
@@ -17,36 +26,46 @@ function LinkSearchResult({ title, subtitle, selected, icon, ...rest }: Props) {
scrollMode: "if-needed", scrollMode: "if-needed",
block: "center", block: "center",
boundary: (parent) => { boundary: (parent) => {
// All the parent elements of your target are checked until they // Prevents body and other parent elements from being scrolled
// reach the #link-search-results. Prevents body and other parent return parent !== containerRef.current;
// elements from being scrolled
return parent.id !== "link-search-results";
}, },
}); });
} }
}, },
[selected] [containerRef, selected]
); );
return ( return (
<ListItem ref={ref} compact={!subtitle} selected={selected} {...rest}> <ListItem
<IconWrapper>{icon}</IconWrapper> ref={ref}
<div> compact={!subtitle}
selected={selected}
role="menuitem"
{...rest}
>
<IconWrapper selected={selected}>{icon}</IconWrapper>
<Content>
<Title>{title}</Title> <Title>{title}</Title>
{subtitle ? <Subtitle selected={selected}>{subtitle}</Subtitle> : null} {subtitle ? <Subtitle selected={selected}>{subtitle}</Subtitle> : null}
</div> </Content>
</ListItem> </ListItem>
); );
} }
const IconWrapper = styled.span` const Content = styled.div`
flex-shrink: 0; overflow: hidden;
margin-right: 4px;
opacity: 0.8;
color: ${(props) => props.theme.toolbarItem};
`; `;
const ListItem = styled.li<{ const IconWrapper = styled.span<{ selected: boolean }>`
flex-shrink: 0;
margin-right: 4px;
height: 24px;
opacity: 0.8;
color: ${(props) =>
props.selected ? props.theme.accentText : props.theme.toolbarItem};
`;
const ListItem = styled.div<{
selected: boolean; selected: boolean;
compact: boolean; compact: boolean;
}>` }>`
@@ -54,9 +73,11 @@ const ListItem = styled.li<{
align-items: center; align-items: center;
padding: 8px; padding: 8px;
border-radius: 4px; border-radius: 4px;
color: ${(props) => props.theme.toolbarItem}; margin: 0 8px;
color: ${(props) =>
props.selected ? props.theme.accentText : props.theme.toolbarItem};
background: ${(props) => background: ${(props) =>
props.selected ? props.theme.toolbarHoverBackground : "transparent"}; props.selected ? props.theme.accent : "transparent"};
font-family: ${(props) => props.theme.fontFamily}; font-family: ${(props) => props.theme.fontFamily};
text-decoration: none; text-decoration: none;
overflow: hidden; overflow: hidden;
@@ -68,6 +89,7 @@ const ListItem = styled.li<{
`; `;
const Title = styled.div` const Title = styled.div`
${ellipsis()}
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
`; `;
@@ -75,6 +97,7 @@ const Title = styled.div`
const Subtitle = styled.div<{ const Subtitle = styled.div<{
selected: boolean; selected: boolean;
}>` }>`
${ellipsis()}
font-size: 13px; font-size: 13px;
opacity: ${(props) => (props.selected ? 0.75 : 0.5)}; opacity: ${(props) => (props.selected ? 0.75 : 0.5)};
`; `;

View File

@@ -128,7 +128,7 @@ export default function LinkToolbar({
const active = isActive(view, rest.isActive); const active = isActive(view, rest.isActive);
return ( return (
<FloatingToolbar ref={menuRef} active={active}> <FloatingToolbar ref={menuRef} active={active} width={336}>
{active && ( {active && (
<LinkEditor <LinkEditor
key={`${selection.from}-${selection.to}`} key={`${selection.from}-${selection.to}`}

View File

@@ -222,9 +222,15 @@ export default function SelectionToolbar(props: Props) {
return null; return null;
} }
const showLinkToolbar = link && range;
return ( return (
<FloatingToolbar active={isActive} ref={menuRef}> <FloatingToolbar
{link && range ? ( active={isActive}
ref={menuRef}
width={showLinkToolbar ? 336 : undefined}
>
{showLinkToolbar ? (
<LinkEditor <LinkEditor
key={`${range.from}-${range.to}`} key={`${range.from}-${range.to}`}
dictionary={dictionary} dictionary={dictionary}

View File

@@ -770,6 +770,31 @@ export class Editor extends React.PureComponent<
/> />
{!readOnly && this.view && ( {!readOnly && this.view && (
<> <>
{this.marks.link && (
<LinkToolbar
isActive={this.state.linkMenuOpen}
onCreateLink={this.props.onCreateLink}
onSearchLink={this.props.onSearchLink}
onClickLink={this.props.onClickLink}
onClose={this.handleCloseLinkMenu}
/>
)}
{this.nodes.emoji && (
<EmojiMenu
rtl={isRTL}
isActive={this.state.emojiMenuOpen}
search={this.state.blockMenuSearch}
onClose={this.handleCloseEmojiMenu}
/>
)}
{this.nodes.mention && (
<MentionMenu
rtl={isRTL}
isActive={this.state.mentionMenuOpen}
search={this.state.blockMenuSearch}
onClose={this.handleCloseMentionMenu}
/>
)}
<SelectionToolbar <SelectionToolbar
rtl={isRTL} rtl={isRTL}
isTemplate={this.props.template === true} isTemplate={this.props.template === true}
@@ -779,25 +804,6 @@ export class Editor extends React.PureComponent<
onClickLink={this.props.onClickLink} onClickLink={this.props.onClickLink}
onCreateLink={this.props.onCreateLink} onCreateLink={this.props.onCreateLink}
/> />
<LinkToolbar
isActive={this.state.linkMenuOpen}
onCreateLink={this.props.onCreateLink}
onSearchLink={this.props.onSearchLink}
onClickLink={this.props.onClickLink}
onClose={this.handleCloseLinkMenu}
/>
<EmojiMenu
rtl={isRTL}
isActive={this.state.emojiMenuOpen}
search={this.state.blockMenuSearch}
onClose={this.handleCloseEmojiMenu}
/>
<MentionMenu
rtl={isRTL}
isActive={this.state.mentionMenuOpen}
search={this.state.blockMenuSearch}
onClose={this.handleCloseMentionMenu}
/>
<BlockMenu <BlockMenu
rtl={isRTL} rtl={isRTL}
isActive={this.state.blockMenuOpen} isActive={this.state.blockMenuOpen}

View File

@@ -45,6 +45,7 @@ import usePolicy from "~/hooks/usePolicy";
import useRequest from "~/hooks/useRequest"; import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts"; import useToasts from "~/hooks/useToasts";
import { ellipsis } from "~/styles";
import { MenuItem } from "~/types"; import { MenuItem } from "~/types";
import { editDocumentUrl, newDocumentPath } from "~/utils/routeHelpers"; import { editDocumentUrl, newDocumentPath } from "~/utils/routeHelpers";
@@ -351,9 +352,7 @@ const Style = styled.div`
`; `;
const CollectionName = styled.div` const CollectionName = styled.div`
overflow: hidden; ${ellipsis()}
white-space: nowrap;
text-overflow: ellipsis;
`; `;
export default observer(DocumentMenu); export default observer(DocumentMenu);

View File

@@ -12,6 +12,7 @@ import CollectionIcon from "~/components/Icons/CollectionIcon";
import useCurrentTeam from "~/hooks/useCurrentTeam"; import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePolicy from "~/hooks/usePolicy"; import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import { ellipsis } from "~/styles";
import { MenuItem } from "~/types"; import { MenuItem } from "~/types";
import { newDocumentPath } from "~/utils/routeHelpers"; import { newDocumentPath } from "~/utils/routeHelpers";
@@ -67,9 +68,7 @@ function NewTemplateMenu() {
} }
const CollectionName = styled.div` const CollectionName = styled.div`
overflow: hidden; ${ellipsis()}
white-space: nowrap;
text-overflow: ellipsis;
`; `;
export default observer(NewTemplateMenu); export default observer(NewTemplateMenu);

View File

@@ -11,6 +11,7 @@ import MenuItem from "~/components/ContextMenu/MenuItem";
import Separator from "~/components/ContextMenu/Separator"; import Separator from "~/components/ContextMenu/Separator";
import useCurrentUser from "~/hooks/useCurrentUser"; import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import { ellipsis } from "~/styles";
import { replaceTitleVariables } from "~/utils/date"; import { replaceTitleVariables } from "~/utils/date";
type Props = { type Props = {
@@ -81,9 +82,7 @@ function TemplatesMenu({ onSelectTemplate, document }: Props) {
const TemplateItem = styled.div` const TemplateItem = styled.div`
text-align: left; text-align: left;
overflow: hidden; ${ellipsis()}
text-overflow: ellipsis;
white-space: nowrap;
`; `;
const Author = styled.div` const Author = styled.div`

View File

@@ -153,7 +153,8 @@ function CommentForm({
const handleChange = ( const handleChange = (
value: (asString: boolean, trim: boolean) => Record<string, any> value: (asString: boolean, trim: boolean) => Record<string, any>
) => { ) => {
setData(value(false, true)); const text = value(true, true);
setData(text ? value(false, true) : undefined);
onTyping?.(); onTyping?.();
}; };
@@ -251,7 +252,7 @@ function CommentForm({
: `${t("Add a reply")}`) : `${t("Add a reply")}`)
} }
/> />
{inputFocused && ( {(inputFocused || data) && (
<Flex justify={dir === "rtl" ? "flex-end" : "flex-start"} gap={8}> <Flex justify={dir === "rtl" ? "flex-end" : "flex-start"} gap={8}>
<ButtonSmall type="submit" borderOnHover> <ButtonSmall type="submit" borderOnHover>
{thread && !thread.isNew ? t("Reply") : t("Post")} {thread && !thread.isNew ? t("Reply") : t("Post")}

View File

@@ -173,15 +173,7 @@ function CommentThread({
</Flex> </Flex>
))} ))}
<ResizingHeightContainer <ResizingHeightContainer hideOverflow={false}>
hideOverflow={false}
config={{
transition: {
duration: 0.1,
ease: "easeInOut",
},
}}
>
{(focused || commentsInThread.length === 0) && ( {(focused || commentsInThread.length === 0) && (
<Fade timing={100}> <Fade timing={100}>
<CommentForm <CommentForm

View File

@@ -8,7 +8,7 @@ import parseTitle from "@shared/utils/parseTitle";
import Document from "~/models/Document"; import Document from "~/models/Document";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import EmojiIcon from "~/components/Icons/EmojiIcon"; import EmojiIcon from "~/components/Icons/EmojiIcon";
import { hover } from "~/styles"; import { ellipsis, hover } from "~/styles";
import { sharedDocumentPath } from "~/utils/routeHelpers"; import { sharedDocumentPath } from "~/utils/routeHelpers";
type Props = { type Props = {
@@ -42,13 +42,11 @@ const Content = styled(Flex)`
`; `;
const Title = styled.div` const Title = styled.div`
overflow: hidden; ${ellipsis()}
text-overflow: ellipsis;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: 1.25; line-height: 1.25;
padding-top: 3px; padding-top: 3px;
white-space: nowrap;
color: ${(props) => props.theme.text}; color: ${(props) => props.theme.text};
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;

View File

@@ -10,6 +10,7 @@ import Flex from "~/components/Flex";
import Scrollable from "~/components/Scrollable"; import Scrollable from "~/components/Scrollable";
import Tooltip from "~/components/Tooltip"; import Tooltip from "~/components/Tooltip";
import useMobile from "~/hooks/useMobile"; import useMobile from "~/hooks/useMobile";
import { ellipsis } from "~/styles";
import { fadeIn } from "~/styles/animations"; import { fadeIn } from "~/styles/animations";
type Props = React.HTMLAttributes<HTMLDivElement> & { type Props = React.HTMLAttributes<HTMLDivElement> & {
@@ -75,15 +76,13 @@ const ForwardIcon = styled(BackIcon)`
`; `;
const Title = styled(Flex)` const Title = styled(Flex)`
${ellipsis()}
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
text-overflow: ellipsis;
white-space: nowrap;
user-select: none; user-select: none;
overflow: hidden;
width: 0; width: 0;
flex-grow: 1; flex-grow: 1;
`; `;

View File

@@ -12,6 +12,7 @@ import Text from "~/components/Text";
import useCollectionTrees from "~/hooks/useCollectionTrees"; import useCollectionTrees from "~/hooks/useCollectionTrees";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts"; import useToasts from "~/hooks/useToasts";
import { ellipsis } from "~/styles";
import { flattenTree } from "~/utils/tree"; import { flattenTree } from "~/utils/tree";
type Props = { type Props = {
@@ -123,9 +124,7 @@ const Footer = styled(Flex)`
`; `;
const StyledText = styled(Text)` const StyledText = styled(Text)`
white-space: nowrap; ${ellipsis()}
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 0; margin-bottom: 0;
`; `;

View File

@@ -12,6 +12,7 @@ import Text from "~/components/Text";
import useCollectionTrees from "~/hooks/useCollectionTrees"; import useCollectionTrees from "~/hooks/useCollectionTrees";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts"; import useToasts from "~/hooks/useToasts";
import { ellipsis } from "~/styles";
import { flattenTree } from "~/utils/tree"; import { flattenTree } from "~/utils/tree";
type Props = { type Props = {
@@ -111,9 +112,7 @@ const Footer = styled(Flex)`
`; `;
const StyledText = styled(Text)` const StyledText = styled(Text)`
white-space: nowrap; ${ellipsis()}
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 0; margin-bottom: 0;
`; `;

View File

@@ -52,3 +52,14 @@ export const hideScrollbars = () => `
display: none; display: none;
} }
`; `;
/**
* Mixin to make text ellipse when it overflows.
*
* @returns string of CSS
*/
export const ellipsis = () => `
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
`;

View File

@@ -116,4 +116,4 @@ export const richExtensions: Nodes = [
/** /**
* Add commenting and mentions to a set of nodes * Add commenting and mentions to a set of nodes
*/ */
export const withComments = (nodes: Nodes) => [...nodes, Mention, Comment]; export const withComments = (nodes: Nodes) => [Mention, Comment, ...nodes];