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