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:
@@ -4,6 +4,7 @@ import { Link } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import Flex from "~/components/Flex";
|
||||
import BreadcrumbMenu from "~/menus/BreadcrumbMenu";
|
||||
import { ellipsis } from "~/styles";
|
||||
import { MenuInternalLink } from "~/types";
|
||||
|
||||
type Props = {
|
||||
@@ -64,6 +65,7 @@ const Slash = styled(GoToIcon)`
|
||||
`;
|
||||
|
||||
const Item = styled(Link)<{ $highlight: boolean; $withIcon: boolean }>`
|
||||
${ellipsis()}
|
||||
display: flex;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
@@ -71,9 +73,6 @@ const Item = styled(Link)<{ $highlight: boolean; $withIcon: boolean }>`
|
||||
color: ${(props) => props.theme.text};
|
||||
font-size: 15px;
|
||||
height: 24px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
font-weight: ${(props) => (props.$highlight ? "500" : "inherit")};
|
||||
margin-left: ${(props) => (props.$withIcon ? "4px" : "0")};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react";
|
||||
import styled, { css, useTheme } from "styled-components";
|
||||
import Flex from "~/components/Flex";
|
||||
import Key from "~/components/Key";
|
||||
import { ellipsis } from "~/styles";
|
||||
|
||||
type Props = {
|
||||
action: ActionImpl;
|
||||
@@ -85,8 +86,7 @@ const Ancestor = styled.span`
|
||||
`;
|
||||
|
||||
const Content = styled(Flex)`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
${ellipsis()}
|
||||
flex-shrink: 1;
|
||||
`;
|
||||
|
||||
@@ -102,9 +102,7 @@ const Item = styled.div<{ active?: boolean }>`
|
||||
justify-content: space-between;
|
||||
cursor: var(--pointer);
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
${ellipsis()}
|
||||
user-select: none;
|
||||
min-width: 0;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import Flex from "~/components/Flex";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import Time from "~/components/Time";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { ellipsis } from "~/styles";
|
||||
import CollectionIcon from "./Icons/CollectionIcon";
|
||||
import EmojiIcon from "./Icons/EmojiIcon";
|
||||
import Squircle from "./Squircle";
|
||||
@@ -217,14 +218,12 @@ const Content = styled(Flex)`
|
||||
`;
|
||||
|
||||
const DocumentMeta = styled(Text)`
|
||||
${ellipsis()}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
color: ${(props) => props.theme.textTertiary};
|
||||
margin: 0 0 0 -2px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const DocumentLink = styled(Link)<{
|
||||
|
||||
@@ -6,6 +6,7 @@ import breakpoint from "styled-components-breakpoint";
|
||||
import Flex from "~/components/Flex";
|
||||
import Disclosure from "~/components/Sidebar/components/Disclosure";
|
||||
import Text from "~/components/Text";
|
||||
import { ellipsis } from "~/styles";
|
||||
|
||||
type Props = {
|
||||
selected: boolean;
|
||||
@@ -70,9 +71,7 @@ function DocumentExplorerNode(
|
||||
}
|
||||
|
||||
const Title = styled(Text)`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
${ellipsis()}
|
||||
margin: 0 4px 0 4px;
|
||||
color: inherit;
|
||||
`;
|
||||
|
||||
@@ -6,6 +6,7 @@ import styled from "styled-components";
|
||||
import { Node as SearchResult } from "~/components/DocumentExplorerNode";
|
||||
import Flex from "~/components/Flex";
|
||||
import Text from "~/components/Text";
|
||||
import { ellipsis } from "~/styles";
|
||||
|
||||
type Props = {
|
||||
selected: boolean;
|
||||
@@ -73,10 +74,8 @@ const Title = styled(Text)`
|
||||
`;
|
||||
|
||||
const Path = styled(Text)<{ $selected: boolean }>`
|
||||
${ellipsis()}
|
||||
padding-top: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0 4px 0 8px;
|
||||
color: ${(props) =>
|
||||
props.$selected ? props.theme.white50 : props.theme.textTertiary};
|
||||
|
||||
@@ -11,6 +11,7 @@ import Flex from "~/components/Flex";
|
||||
import Time from "~/components/Time";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { ellipsis } from "~/styles";
|
||||
|
||||
type Props = {
|
||||
showCollection?: boolean;
|
||||
@@ -192,8 +193,7 @@ const Container = styled(Flex)<{ rtl?: boolean }>`
|
||||
`;
|
||||
|
||||
const Viewed = styled.span`
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
${ellipsis()}
|
||||
`;
|
||||
|
||||
const Modified = styled.span<{ highlight?: boolean }>`
|
||||
|
||||
@@ -4,7 +4,7 @@ import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Flex from "~/components/Flex";
|
||||
import Text from "~/components/Text";
|
||||
import { undraggableOnDesktop } from "~/styles";
|
||||
import { ellipsis, undraggableOnDesktop } from "~/styles";
|
||||
|
||||
const RealTextarea = styled.textarea<{ hasIcon?: boolean }>`
|
||||
border: 0;
|
||||
@@ -29,9 +29,7 @@ const RealInput = styled.input<{ hasIcon?: boolean }>`
|
||||
color: ${(props) => props.theme.text};
|
||||
height: 30px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
${ellipsis()}
|
||||
${undraggableOnDesktop()}
|
||||
|
||||
&:disabled,
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as React from "react";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import Flex from "~/components/Flex";
|
||||
import NavLink from "~/components/NavLink";
|
||||
import { ellipsis } from "~/styles";
|
||||
|
||||
export type Props = Omit<React.HTMLAttributes<HTMLAnchorElement>, "title"> & {
|
||||
image?: React.ReactNode;
|
||||
@@ -103,9 +104,7 @@ const Image = styled(Flex)`
|
||||
const Heading = styled.p<{ $small?: boolean }>`
|
||||
font-size: ${(props) => (props.$small ? 14 : 16)}px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
${ellipsis()}
|
||||
line-height: ${(props) => (props.$small ? 1.3 : 1.2)};
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
@@ -19,7 +19,17 @@ type Props = {
|
||||
* Automatically animates the height of a container based on it's contents.
|
||||
*/
|
||||
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 { height } = useComponentSize(ref);
|
||||
|
||||
@@ -6,7 +6,7 @@ import styled, { css } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Document from "~/models/Document";
|
||||
import Highlight, { Mark } from "~/components/Highlight";
|
||||
import { hover } from "~/styles";
|
||||
import { ellipsis, hover } from "~/styles";
|
||||
import { sharedDocumentPath } from "~/utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
@@ -125,8 +125,7 @@ const Heading = styled.h4<{ rtl?: boolean }>`
|
||||
|
||||
const Title = styled(Highlight)`
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
${ellipsis()}
|
||||
|
||||
${Mark} {
|
||||
padding: 0;
|
||||
@@ -139,10 +138,7 @@ const ResultContext = styled(Highlight)`
|
||||
font-size: 14px;
|
||||
margin-top: -0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
${ellipsis()}
|
||||
|
||||
${Mark} {
|
||||
padding: 0;
|
||||
|
||||
@@ -13,13 +13,15 @@ import { useEditor } from "./EditorContext";
|
||||
type Props = {
|
||||
active?: boolean;
|
||||
children: React.ReactNode;
|
||||
width?: number;
|
||||
forwardedRef?: React.RefObject<HTMLDivElement> | null;
|
||||
};
|
||||
|
||||
const defaultPosition = {
|
||||
left: -1000,
|
||||
left: -10000,
|
||||
top: 0,
|
||||
offset: 0,
|
||||
maxWidth: 1000,
|
||||
visible: false,
|
||||
};
|
||||
|
||||
@@ -48,6 +50,7 @@ function usePosition({
|
||||
right: 0,
|
||||
top: viewportHeight - menuHeight,
|
||||
offset: 0,
|
||||
maxWidth: 1000,
|
||||
visible: true,
|
||||
};
|
||||
}
|
||||
@@ -134,7 +137,7 @@ function usePosition({
|
||||
const margin = 12;
|
||||
const left = Math.min(
|
||||
Math.min(
|
||||
offsetParent.x + offsetParent.width - menuWidth,
|
||||
offsetParent.x + offsetParent.width - menuWidth - margin,
|
||||
window.innerWidth - margin
|
||||
),
|
||||
Math.max(
|
||||
@@ -155,6 +158,7 @@ function usePosition({
|
||||
left: Math.round(left - offsetParent.left),
|
||||
top: Math.round(top - offsetParent.top),
|
||||
offset: Math.round(offset),
|
||||
maxWidth: offsetParent.width,
|
||||
visible: true,
|
||||
};
|
||||
}
|
||||
@@ -189,8 +193,10 @@ const FloatingToolbar = React.forwardRef(
|
||||
<Wrapper
|
||||
active={props.active && position.visible}
|
||||
ref={menuRef}
|
||||
offset={position.offset}
|
||||
$offset={position.offset}
|
||||
style={{
|
||||
width: props.width,
|
||||
maxWidth: `${position.maxWidth}px`,
|
||||
top: `${position.top}px`,
|
||||
left: `${position.left}px`,
|
||||
}}
|
||||
@@ -204,7 +210,7 @@ const FloatingToolbar = React.forwardRef(
|
||||
|
||||
const Wrapper = styled.div<{
|
||||
active?: boolean;
|
||||
offset: number;
|
||||
$offset: number;
|
||||
}>`
|
||||
will-change: opacity, transform;
|
||||
padding: 8px 16px;
|
||||
@@ -234,7 +240,7 @@ const Wrapper = styled.div<{
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: calc(50% - ${(props) => props.offset || 0}px);
|
||||
left: calc(50% - ${(props) => props.$offset || 0}px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const Input = styled.input`
|
||||
margin: 0;
|
||||
outline: none;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
font-size: 16px;
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
DocumentIcon,
|
||||
CloseIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
OpenIcon,
|
||||
} from "outline-icons";
|
||||
import { Mark } from "prosemirror-model";
|
||||
@@ -13,6 +12,8 @@ import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { isInternalUrl, sanitizeUrl } from "@shared/utils/urls";
|
||||
import Flex from "~/components/Flex";
|
||||
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
|
||||
import Scrollable from "~/components/Scrollable";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
import { ToastOptions } from "~/types";
|
||||
import Input from "./Input";
|
||||
@@ -61,6 +62,7 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
discardInputValue = false;
|
||||
initialValue = this.href;
|
||||
initialSelectionLength = this.props.to - this.props.from;
|
||||
resultsRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
state: State = {
|
||||
selectedIndex: -1,
|
||||
@@ -122,11 +124,12 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
handleKeyDown = (event: React.KeyboardEvent): void => {
|
||||
const results = this.results;
|
||||
|
||||
switch (event.key) {
|
||||
case "Enter": {
|
||||
event.preventDefault();
|
||||
const { selectedIndex, value } = this.state;
|
||||
const results = this.state.results[value] || [];
|
||||
const { onCreateLink } = this.props;
|
||||
|
||||
if (selectedIndex >= 0) {
|
||||
@@ -181,8 +184,7 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const { selectedIndex, value } = this.state;
|
||||
const results = this.state.results[value] || [];
|
||||
const { selectedIndex } = this.state;
|
||||
const total = results.length;
|
||||
const nextIndex = selectedIndex + 1;
|
||||
|
||||
@@ -264,10 +266,7 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
dispatch(state.tr.removeMark(from, to, mark));
|
||||
}
|
||||
|
||||
if (onRemoveLink) {
|
||||
onRemoveLink();
|
||||
}
|
||||
|
||||
onRemoveLink?.();
|
||||
view.focus();
|
||||
};
|
||||
|
||||
@@ -289,14 +288,19 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
view.focus();
|
||||
};
|
||||
|
||||
get results() {
|
||||
const { value } = this.state;
|
||||
return (
|
||||
this.state.results[value.trim()] ||
|
||||
this.state.results[this.state.previousValue] ||
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dictionary } = this.props;
|
||||
const { value, selectedIndex } = this.state;
|
||||
const results =
|
||||
this.state.results[value.trim()] ||
|
||||
this.state.results[this.state.previousValue] ||
|
||||
[];
|
||||
|
||||
const results = this.results;
|
||||
const looksLikeUrl = value.match(/^https?:\/\//i);
|
||||
const suggestedLinkTitle = this.suggestedLinkTitle;
|
||||
const isInternal = isInternalUrl(value);
|
||||
@@ -307,7 +311,7 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
suggestedLinkTitle.length > 0 &&
|
||||
!looksLikeUrl;
|
||||
|
||||
const showResults =
|
||||
const hasResults =
|
||||
!!suggestedLinkTitle && (showCreateLink || results.length > 0);
|
||||
|
||||
return (
|
||||
@@ -339,47 +343,53 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
</Tooltip>
|
||||
<Tooltip tooltip={dictionary.removeLink}>
|
||||
<ToolbarButton onClick={this.handleRemoveLink}>
|
||||
{this.initialValue ? (
|
||||
<TrashIcon color="currentColor" />
|
||||
) : (
|
||||
<CloseIcon color="currentColor" />
|
||||
)}
|
||||
<CloseIcon color="currentColor" />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
|
||||
{showResults && (
|
||||
<SearchResults id="link-search-results">
|
||||
{results.map((result, index) => (
|
||||
<LinkSearchResult
|
||||
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}
|
||||
/>
|
||||
))}
|
||||
<SearchResults
|
||||
ref={this.resultsRef}
|
||||
$hasResults={hasResults}
|
||||
role="menu"
|
||||
>
|
||||
<ResizingHeightContainer>
|
||||
{hasResults && (
|
||||
<>
|
||||
{results.map((result, index) => (
|
||||
<LinkSearchResult
|
||||
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 && (
|
||||
<LinkSearchResult
|
||||
key="create"
|
||||
title={suggestedLinkTitle}
|
||||
subtitle={dictionary.createNewDoc}
|
||||
icon={<PlusIcon color="currentColor" />}
|
||||
onPointerMove={() => this.handleFocusLink(results.length)}
|
||||
onClick={() => {
|
||||
this.handleCreateLink(suggestedLinkTitle);
|
||||
{showCreateLink && (
|
||||
<LinkSearchResult
|
||||
key="create"
|
||||
containerRef={this.resultsRef}
|
||||
title={suggestedLinkTitle}
|
||||
subtitle={dictionary.createNewDoc}
|
||||
icon={<PlusIcon color="currentColor" />}
|
||||
onPointerMove={() => this.handleFocusLink(results.length)}
|
||||
onClick={() => {
|
||||
this.handleCreateLink(suggestedLinkTitle);
|
||||
|
||||
if (this.initialSelectionLength) {
|
||||
this.moveSelectionToEnd();
|
||||
}
|
||||
}}
|
||||
selected={results.length === selectedIndex}
|
||||
/>
|
||||
if (this.initialSelectionLength) {
|
||||
this.moveSelectionToEnd();
|
||||
}
|
||||
}}
|
||||
selected={results.length === selectedIndex}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</SearchResults>
|
||||
)}
|
||||
</ResizingHeightContainer>
|
||||
</SearchResults>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
@@ -388,25 +398,20 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
const Wrapper = styled(Flex)`
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
min-width: 336px;
|
||||
pointer-events: all;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const SearchResults = styled.ol`
|
||||
const SearchResults = styled(Scrollable)<{ $hasResults: boolean }>`
|
||||
background: ${(props) => props.theme.toolbarBackground};
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: -3px;
|
||||
margin-bottom: 0;
|
||||
margin: -8px 0 0;
|
||||
border-radius: 0 0 4px 4px;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: none;
|
||||
padding: ${(props) => (props.$hasResults ? "8px 0" : "0")};
|
||||
max-height: 260px;
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import * as React from "react";
|
||||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
import styled from "styled-components";
|
||||
import { ellipsis } from "~/styles";
|
||||
|
||||
type Props = React.HTMLAttributes<HTMLLIElement> & {
|
||||
type Props = React.HTMLAttributes<HTMLDivElement> & {
|
||||
icon: React.ReactNode;
|
||||
selected: boolean;
|
||||
title: 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(
|
||||
(node: HTMLElement | null) => {
|
||||
if (selected && node) {
|
||||
@@ -17,36 +26,46 @@ function LinkSearchResult({ title, subtitle, selected, icon, ...rest }: Props) {
|
||||
scrollMode: "if-needed",
|
||||
block: "center",
|
||||
boundary: (parent) => {
|
||||
// All the parent elements of your target are checked until they
|
||||
// reach the #link-search-results. Prevents body and other parent
|
||||
// elements from being scrolled
|
||||
return parent.id !== "link-search-results";
|
||||
// Prevents body and other parent elements from being scrolled
|
||||
return parent !== containerRef.current;
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[selected]
|
||||
[containerRef, selected]
|
||||
);
|
||||
|
||||
return (
|
||||
<ListItem ref={ref} compact={!subtitle} selected={selected} {...rest}>
|
||||
<IconWrapper>{icon}</IconWrapper>
|
||||
<div>
|
||||
<ListItem
|
||||
ref={ref}
|
||||
compact={!subtitle}
|
||||
selected={selected}
|
||||
role="menuitem"
|
||||
{...rest}
|
||||
>
|
||||
<IconWrapper selected={selected}>{icon}</IconWrapper>
|
||||
<Content>
|
||||
<Title>{title}</Title>
|
||||
{subtitle ? <Subtitle selected={selected}>{subtitle}</Subtitle> : null}
|
||||
</div>
|
||||
</Content>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
const IconWrapper = styled.span`
|
||||
flex-shrink: 0;
|
||||
margin-right: 4px;
|
||||
opacity: 0.8;
|
||||
color: ${(props) => props.theme.toolbarItem};
|
||||
const Content = styled.div`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
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;
|
||||
compact: boolean;
|
||||
}>`
|
||||
@@ -54,9 +73,11 @@ const ListItem = styled.li<{
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
color: ${(props) => props.theme.toolbarItem};
|
||||
margin: 0 8px;
|
||||
color: ${(props) =>
|
||||
props.selected ? props.theme.accentText : props.theme.toolbarItem};
|
||||
background: ${(props) =>
|
||||
props.selected ? props.theme.toolbarHoverBackground : "transparent"};
|
||||
props.selected ? props.theme.accent : "transparent"};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
@@ -68,6 +89,7 @@ const ListItem = styled.li<{
|
||||
`;
|
||||
|
||||
const Title = styled.div`
|
||||
${ellipsis()}
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
@@ -75,6 +97,7 @@ const Title = styled.div`
|
||||
const Subtitle = styled.div<{
|
||||
selected: boolean;
|
||||
}>`
|
||||
${ellipsis()}
|
||||
font-size: 13px;
|
||||
opacity: ${(props) => (props.selected ? 0.75 : 0.5)};
|
||||
`;
|
||||
|
||||
@@ -128,7 +128,7 @@ export default function LinkToolbar({
|
||||
const active = isActive(view, rest.isActive);
|
||||
|
||||
return (
|
||||
<FloatingToolbar ref={menuRef} active={active}>
|
||||
<FloatingToolbar ref={menuRef} active={active} width={336}>
|
||||
{active && (
|
||||
<LinkEditor
|
||||
key={`${selection.from}-${selection.to}`}
|
||||
|
||||
@@ -222,9 +222,15 @@ export default function SelectionToolbar(props: Props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showLinkToolbar = link && range;
|
||||
|
||||
return (
|
||||
<FloatingToolbar active={isActive} ref={menuRef}>
|
||||
{link && range ? (
|
||||
<FloatingToolbar
|
||||
active={isActive}
|
||||
ref={menuRef}
|
||||
width={showLinkToolbar ? 336 : undefined}
|
||||
>
|
||||
{showLinkToolbar ? (
|
||||
<LinkEditor
|
||||
key={`${range.from}-${range.to}`}
|
||||
dictionary={dictionary}
|
||||
|
||||
@@ -770,6 +770,31 @@ export class Editor extends React.PureComponent<
|
||||
/>
|
||||
{!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
|
||||
rtl={isRTL}
|
||||
isTemplate={this.props.template === true}
|
||||
@@ -779,25 +804,6 @@ export class Editor extends React.PureComponent<
|
||||
onClickLink={this.props.onClickLink}
|
||||
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
|
||||
rtl={isRTL}
|
||||
isActive={this.state.blockMenuOpen}
|
||||
|
||||
@@ -45,6 +45,7 @@ import usePolicy from "~/hooks/usePolicy";
|
||||
import useRequest from "~/hooks/useRequest";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import { ellipsis } from "~/styles";
|
||||
import { MenuItem } from "~/types";
|
||||
import { editDocumentUrl, newDocumentPath } from "~/utils/routeHelpers";
|
||||
|
||||
@@ -351,9 +352,7 @@ const Style = styled.div`
|
||||
`;
|
||||
|
||||
const CollectionName = styled.div`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
${ellipsis()}
|
||||
`;
|
||||
|
||||
export default observer(DocumentMenu);
|
||||
|
||||
@@ -12,6 +12,7 @@ import CollectionIcon from "~/components/Icons/CollectionIcon";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { ellipsis } from "~/styles";
|
||||
import { MenuItem } from "~/types";
|
||||
import { newDocumentPath } from "~/utils/routeHelpers";
|
||||
|
||||
@@ -67,9 +68,7 @@ function NewTemplateMenu() {
|
||||
}
|
||||
|
||||
const CollectionName = styled.div`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
${ellipsis()}
|
||||
`;
|
||||
|
||||
export default observer(NewTemplateMenu);
|
||||
|
||||
@@ -11,6 +11,7 @@ import MenuItem from "~/components/ContextMenu/MenuItem";
|
||||
import Separator from "~/components/ContextMenu/Separator";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { ellipsis } from "~/styles";
|
||||
import { replaceTitleVariables } from "~/utils/date";
|
||||
|
||||
type Props = {
|
||||
@@ -81,9 +82,7 @@ function TemplatesMenu({ onSelectTemplate, document }: Props) {
|
||||
|
||||
const TemplateItem = styled.div`
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
${ellipsis()}
|
||||
`;
|
||||
|
||||
const Author = styled.div`
|
||||
|
||||
@@ -153,7 +153,8 @@ function CommentForm({
|
||||
const handleChange = (
|
||||
value: (asString: boolean, trim: boolean) => Record<string, any>
|
||||
) => {
|
||||
setData(value(false, true));
|
||||
const text = value(true, true);
|
||||
setData(text ? value(false, true) : undefined);
|
||||
onTyping?.();
|
||||
};
|
||||
|
||||
@@ -251,7 +252,7 @@ function CommentForm({
|
||||
: `${t("Add a reply")}…`)
|
||||
}
|
||||
/>
|
||||
{inputFocused && (
|
||||
{(inputFocused || data) && (
|
||||
<Flex justify={dir === "rtl" ? "flex-end" : "flex-start"} gap={8}>
|
||||
<ButtonSmall type="submit" borderOnHover>
|
||||
{thread && !thread.isNew ? t("Reply") : t("Post")}
|
||||
|
||||
@@ -173,15 +173,7 @@ function CommentThread({
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
<ResizingHeightContainer
|
||||
hideOverflow={false}
|
||||
config={{
|
||||
transition: {
|
||||
duration: 0.1,
|
||||
ease: "easeInOut",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ResizingHeightContainer hideOverflow={false}>
|
||||
{(focused || commentsInThread.length === 0) && (
|
||||
<Fade timing={100}>
|
||||
<CommentForm
|
||||
|
||||
@@ -8,7 +8,7 @@ import parseTitle from "@shared/utils/parseTitle";
|
||||
import Document from "~/models/Document";
|
||||
import Flex from "~/components/Flex";
|
||||
import EmojiIcon from "~/components/Icons/EmojiIcon";
|
||||
import { hover } from "~/styles";
|
||||
import { ellipsis, hover } from "~/styles";
|
||||
import { sharedDocumentPath } from "~/utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
@@ -42,13 +42,11 @@ const Content = styled(Flex)`
|
||||
`;
|
||||
|
||||
const Title = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
${ellipsis()}
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
padding-top: 3px;
|
||||
white-space: nowrap;
|
||||
color: ${(props) => props.theme.text};
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
|
||||
@@ -10,6 +10,7 @@ import Flex from "~/components/Flex";
|
||||
import Scrollable from "~/components/Scrollable";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import { ellipsis } from "~/styles";
|
||||
import { fadeIn } from "~/styles/animations";
|
||||
|
||||
type Props = React.HTMLAttributes<HTMLDivElement> & {
|
||||
@@ -75,15 +76,13 @@ const ForwardIcon = styled(BackIcon)`
|
||||
`;
|
||||
|
||||
const Title = styled(Flex)`
|
||||
${ellipsis()}
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
@@ -12,6 +12,7 @@ import Text from "~/components/Text";
|
||||
import useCollectionTrees from "~/hooks/useCollectionTrees";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import { ellipsis } from "~/styles";
|
||||
import { flattenTree } from "~/utils/tree";
|
||||
|
||||
type Props = {
|
||||
@@ -123,9 +124,7 @@ const Footer = styled(Flex)`
|
||||
`;
|
||||
|
||||
const StyledText = styled(Text)`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
${ellipsis()}
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import Text from "~/components/Text";
|
||||
import useCollectionTrees from "~/hooks/useCollectionTrees";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import { ellipsis } from "~/styles";
|
||||
import { flattenTree } from "~/utils/tree";
|
||||
|
||||
type Props = {
|
||||
@@ -111,9 +112,7 @@ const Footer = styled(Flex)`
|
||||
`;
|
||||
|
||||
const StyledText = styled(Text)`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
${ellipsis()}
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
|
||||
@@ -52,3 +52,14 @@ export const hideScrollbars = () => `
|
||||
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;
|
||||
`;
|
||||
|
||||
@@ -116,4 +116,4 @@ export const richExtensions: 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];
|
||||
|
||||
Reference in New Issue
Block a user