fix: Bag 'o fixes
Remove menu hover styles on mobile Fixed duplicate hover+active behavior on editor menus Fixed editor menus visibly scroll to the top when reopened Fixed some minor editor spacing issues Renamed shred routeHelpers -> urlHelpers
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
changelogUrl,
|
||||
mailToUrl,
|
||||
githubIssuesUrl,
|
||||
} from "@shared/utils/routeHelpers";
|
||||
} from "@shared/utils/urlHelpers";
|
||||
import stores from "~/stores";
|
||||
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
|
||||
import { createAction } from "~/actions";
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as React from "react";
|
||||
import { MenuItem as BaseMenuItem } from "reakit/Menu";
|
||||
import styled, { css } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { hover } from "~/styles";
|
||||
import MenuIconWrapper from "../MenuIconWrapper";
|
||||
|
||||
type Props = {
|
||||
@@ -123,7 +124,7 @@ export const MenuAnchorCSS = css<{ level?: number; disabled?: boolean }>`
|
||||
? "pointer-events: none;"
|
||||
: `
|
||||
|
||||
&:hover,
|
||||
&:${hover},
|
||||
&:focus,
|
||||
&.focus-visible {
|
||||
color: ${props.theme.white};
|
||||
|
||||
@@ -19,6 +19,7 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import { hover } from "~/styles";
|
||||
import { newDocumentPath } from "~/utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
@@ -200,7 +201,7 @@ const DocumentLink = styled(Link)<{
|
||||
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:${hover},
|
||||
&:active,
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { withTranslation, Trans, WithTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { githubIssuesUrl } from "@shared/utils/routeHelpers";
|
||||
import { githubIssuesUrl } from "@shared/utils/urlHelpers";
|
||||
import Button from "~/components/Button";
|
||||
import CenteredContent from "~/components/CenteredContent";
|
||||
import HelpText from "~/components/HelpText";
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import Document from "~/models/Document";
|
||||
import { hover } from "~/styles";
|
||||
import NudeButton from "./NudeButton";
|
||||
|
||||
type Props = {
|
||||
@@ -56,7 +57,7 @@ export const AnimatedStar = styled(StarredIcon)`
|
||||
flex-shrink: 0;
|
||||
transition: all 100ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
&: ${hover} {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
&:active {
|
||||
|
||||
@@ -10,13 +10,9 @@ type BlockMenuProps = Omit<
|
||||
> &
|
||||
Required<Pick<Props, "onLinkToolbarOpen" | "embeds">>;
|
||||
|
||||
class BlockMenu extends React.Component<BlockMenuProps> {
|
||||
get items() {
|
||||
return getMenuItems(this.props.dictionary);
|
||||
}
|
||||
|
||||
clearSearch = () => {
|
||||
const { state, dispatch } = this.props.view;
|
||||
function BlockMenu(props: BlockMenuProps) {
|
||||
const clearSearch = () => {
|
||||
const { state, dispatch } = props.view;
|
||||
const parent = findParentNode((node) => !!node)(state.selection);
|
||||
|
||||
if (parent) {
|
||||
@@ -24,27 +20,25 @@ class BlockMenu extends React.Component<BlockMenuProps> {
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<CommandMenu
|
||||
{...this.props}
|
||||
filterable={true}
|
||||
onClearSearch={this.clearSearch}
|
||||
renderMenuItem={(item, _index, options) => {
|
||||
return (
|
||||
<BlockMenuItem
|
||||
onClick={options.onClick}
|
||||
selected={options.selected}
|
||||
icon={item.icon}
|
||||
title={item.title}
|
||||
shortcut={item.shortcut}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
items={this.items}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CommandMenu
|
||||
{...props}
|
||||
filterable={true}
|
||||
onClearSearch={clearSearch}
|
||||
renderMenuItem={(item, _index, options) => {
|
||||
return (
|
||||
<BlockMenuItem
|
||||
onClick={options.onClick}
|
||||
selected={options.selected}
|
||||
icon={item.icon}
|
||||
title={item.title}
|
||||
shortcut={item.shortcut}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
items={getMenuItems(props.dictionary)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default BlockMenu;
|
||||
|
||||
@@ -29,7 +29,7 @@ function BlockMenuItem({
|
||||
if (selected && node) {
|
||||
scrollIntoView(node, {
|
||||
scrollMode: "if-needed",
|
||||
block: "center",
|
||||
block: "nearest",
|
||||
boundary: (parent) => {
|
||||
// All the parent elements of your target are checked until they
|
||||
// reach the #block-menu-container. Prevents body and other parent
|
||||
@@ -64,6 +64,12 @@ function BlockMenuItem({
|
||||
);
|
||||
}
|
||||
|
||||
const Shortcut = styled.span`
|
||||
color: ${(props) => props.theme.textTertiary};
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
`;
|
||||
|
||||
const MenuItem = styled.button<{
|
||||
selected: boolean;
|
||||
}>`
|
||||
@@ -90,7 +96,6 @@ const MenuItem = styled.button<{
|
||||
padding: 0 16px;
|
||||
outline: none;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
color: ${(props) => props.theme.blockToolbarTextSelected};
|
||||
background: ${(props) =>
|
||||
@@ -98,13 +103,11 @@ const MenuItem = styled.button<{
|
||||
? props.theme.blockToolbarSelectedBackground ||
|
||||
props.theme.blockToolbarTrigger
|
||||
: props.theme.blockToolbarHoverBackground};
|
||||
|
||||
${Shortcut} {
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Shortcut = styled.span`
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
`;
|
||||
|
||||
export default BlockMenuItem;
|
||||
|
||||
@@ -84,6 +84,11 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (!prevProps.isActive && this.props.isActive) {
|
||||
// reset scroll position to top when opening menu as the contents are
|
||||
// hidden, not unrendered
|
||||
if (this.menuRef.current) {
|
||||
this.menuRef.current.scroll({ top: 0 });
|
||||
}
|
||||
const position = this.calculatePosition(this.props);
|
||||
|
||||
this.setState({
|
||||
@@ -485,16 +490,25 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
const selected = index === this.state.selectedIndex && isActive;
|
||||
|
||||
if (!item.title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handlePointer = () => {
|
||||
if (this.state.selectedIndex !== index) {
|
||||
this.setState({ selectedIndex: index });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ListItem key={index}>
|
||||
<ListItem
|
||||
key={index}
|
||||
onPointerMove={handlePointer}
|
||||
onPointerDown={handlePointer}
|
||||
>
|
||||
{this.props.renderMenuItem(item as any, index, {
|
||||
selected,
|
||||
selected: index === this.state.selectedIndex,
|
||||
onClick: () => this.insertItem(item),
|
||||
})}
|
||||
</ListItem>
|
||||
|
||||
@@ -540,7 +540,8 @@ const EditorStyles = styled.div<{
|
||||
ul.checkbox_list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: ${(props) => (props.rtl ? "0 -24px 0 0" : "0 0 0 -24px")};
|
||||
margin-left: ${(props) => (props.rtl ? "0" : "-24px")};
|
||||
margin-right: ${(props) => (props.rtl ? "-24px" : "0")};
|
||||
}
|
||||
|
||||
ul li,
|
||||
|
||||
@@ -18,10 +18,7 @@ import {
|
||||
} from "outline-icons";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
|
||||
const SSR = typeof window === "undefined";
|
||||
const isMac = !SSR && window.navigator.platform === "MacIntel";
|
||||
const mod = isMac ? "⌘" : "ctrl";
|
||||
import { metaDisplay } from "~/utils/keyboard";
|
||||
|
||||
export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
return [
|
||||
@@ -84,7 +81,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
name: "blockquote",
|
||||
title: dictionary.quote,
|
||||
icon: BlockQuoteIcon,
|
||||
shortcut: `${mod} ]`,
|
||||
shortcut: `${metaDisplay} ]`,
|
||||
},
|
||||
{
|
||||
name: "code_block",
|
||||
@@ -97,7 +94,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
name: "hr",
|
||||
title: dictionary.hr,
|
||||
icon: HorizontalRuleIcon,
|
||||
shortcut: `${mod} _`,
|
||||
shortcut: `${metaDisplay} _`,
|
||||
keywords: "horizontal rule break line",
|
||||
},
|
||||
{
|
||||
@@ -117,7 +114,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
name: "link",
|
||||
title: dictionary.link,
|
||||
icon: LinkIcon,
|
||||
shortcut: `${mod} k`,
|
||||
shortcut: `${metaDisplay} k`,
|
||||
keywords: "link url uri href",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import styled from "styled-components";
|
||||
import Document from "~/models/Document";
|
||||
import DocumentMeta from "~/components/DocumentMeta";
|
||||
import Flex from "~/components/Flex";
|
||||
import { hover } from "~/styles";
|
||||
import { NavigationNode } from "~/types";
|
||||
|
||||
type Props = {
|
||||
@@ -25,7 +26,7 @@ const DocumentLink = styled(Link)`
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&:hover,
|
||||
&:${hover},
|
||||
&:active,
|
||||
&:focus {
|
||||
background: ${(props) => props.theme.listItemHoverBackground};
|
||||
|
||||
@@ -8,6 +8,7 @@ import Fade from "~/components/Fade";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { hover } from "~/styles";
|
||||
import { searchUrl } from "~/utils/routeHelpers";
|
||||
|
||||
function RecentSearches() {
|
||||
@@ -90,7 +91,7 @@ const RecentSearch = styled(Link)`
|
||||
padding: 1px 4px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
&: ${hover} {
|
||||
color: ${(props) => props.theme.text};
|
||||
background: ${(props) => props.theme.secondaryBackground};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { slackAuth } from "@shared/utils/routeHelpers";
|
||||
import { slackAuth } from "@shared/utils/urlHelpers";
|
||||
import Button from "~/components/Button";
|
||||
import env from "~/env";
|
||||
|
||||
|
||||
8
app/styles/index.ts
Normal file
8
app/styles/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { isTouchDevice } from "~/utils/browser";
|
||||
|
||||
/**
|
||||
* Returns "hover" on a non-touch device and "active" on a touch device. To
|
||||
* avoid "sticky" hover on mobile. Use `&:${hover} {...}` instead of
|
||||
* using `&:hover {...}`.
|
||||
*/
|
||||
export const hover = isTouchDevice() ? "active" : "hover";
|
||||
17
app/utils/browser.ts
Normal file
17
app/utils/browser.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Returns true if the client is a touch device.
|
||||
*/
|
||||
export function isTouchDevice(): boolean {
|
||||
if (typeof window === "undefined") {
|
||||
return false;
|
||||
}
|
||||
return window.matchMedia?.("(hover: none) and (pointer: coarse)")?.matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the client is running on a Mac.
|
||||
*/
|
||||
export function isMac(): boolean {
|
||||
const SSR = typeof window === "undefined";
|
||||
return !SSR && window.navigator.platform === "MacIntel";
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
const isMac = window.navigator.platform === "MacIntel";
|
||||
import { isMac } from "~/utils/browser";
|
||||
|
||||
export const metaDisplay = isMac ? "⌘" : "Ctrl";
|
||||
export const metaDisplay = isMac() ? "⌘" : "Ctrl";
|
||||
|
||||
export const meta = isMac ? "cmd" : "ctrl";
|
||||
export const meta = isMac() ? "cmd" : "ctrl";
|
||||
|
||||
export function isModKey(
|
||||
event: KeyboardEvent | MouseEvent | React.KeyboardEvent
|
||||
) {
|
||||
return isMac ? event.metaKey : event.ctrlKey;
|
||||
return isMac() ? event.metaKey : event.ctrlKey;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Table, TBody, TR, TD } from "oy-vey";
|
||||
import * as React from "react";
|
||||
import theme from "@shared/theme";
|
||||
import { twitterUrl } from "@shared/utils/routeHelpers";
|
||||
import { twitterUrl } from "@shared/utils/urlHelpers";
|
||||
|
||||
type Props = {
|
||||
unsubscribeUrl?: string;
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
} from "sequelize-typescript";
|
||||
import isUUID from "validator/lib/isUUID";
|
||||
import { sortNavigationNodes } from "@shared/utils/collections";
|
||||
import { SLUG_URL_REGEX } from "@shared/utils/routeHelpers";
|
||||
import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers";
|
||||
import slugify from "@server/utils/slugify";
|
||||
import { NavigationNode, CollectionSort } from "~/types";
|
||||
import CollectionGroup from "./CollectionGroup";
|
||||
|
||||
@@ -32,8 +32,8 @@ import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||
import { DateFilter } from "@shared/types";
|
||||
import getTasks from "@shared/utils/getTasks";
|
||||
import parseTitle from "@shared/utils/parseTitle";
|
||||
import { SLUG_URL_REGEX } from "@shared/utils/routeHelpers";
|
||||
import unescape from "@shared/utils/unescape";
|
||||
import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers";
|
||||
import slugify from "@server/utils/slugify";
|
||||
import Backlink from "./Backlink";
|
||||
import Collection from "./Collection";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Router from "koa-router";
|
||||
import { signin } from "@shared/utils/routeHelpers";
|
||||
import { signin } from "@shared/utils/urlHelpers";
|
||||
import { requireDirectory } from "@server/utils/fs";
|
||||
|
||||
interface AuthenicationProvider {
|
||||
|
||||
@@ -32,7 +32,7 @@ export function githubIssuesUrl(): string {
|
||||
}
|
||||
|
||||
export function twitterUrl(): string {
|
||||
return "https://twitter.com/outlinewiki";
|
||||
return "https://twitter.com/getoutline";
|
||||
}
|
||||
|
||||
export function mailToUrl(): string {
|
||||
Reference in New Issue
Block a user