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 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")};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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)<{
|
||||||
|
|||||||
@@ -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;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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 }>`
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)};
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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}`}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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")}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
Reference in New Issue
Block a user