chore: Lint shared directory
This commit is contained in:
@ -142,7 +142,7 @@ const StyledNavLink = styled(NavLink)`
|
|||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
color: ${props => props.theme.text};
|
color: ${props => props.theme.text};
|
||||||
background: ${props => props.theme.sidebarItemBackground};
|
background: ${props => props.theme.black05};
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"build": "npm run clean && npm run build:webpack",
|
"build": "npm run clean && npm run build:webpack",
|
||||||
"start": "NODE_ENV=production node index.js",
|
"start": "NODE_ENV=production node index.js",
|
||||||
"dev": "NODE_ENV=development nodemon --watch server index.js",
|
"dev": "NODE_ENV=development nodemon --watch server index.js",
|
||||||
"lint": "eslint app server",
|
"lint": "eslint app server shared",
|
||||||
"flow": "flow",
|
"flow": "flow",
|
||||||
"deploy": "git push heroku master",
|
"deploy": "git push heroku master",
|
||||||
"heroku-postbuild": "npm run build && npm run sequelize:migrate",
|
"heroku-postbuild": "npm run build && npm run sequelize:migrate",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import OutlineLogo from './OutlineLogo';
|
import OutlineLogo from "./OutlineLogo";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
href?: string,
|
href?: string,
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import { observer, inject } from 'mobx-react';
|
import { observer, inject } from "mobx-react";
|
||||||
import breakpoint from 'styled-components-breakpoint';
|
import breakpoint from "styled-components-breakpoint";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from "react-router-dom";
|
||||||
import { PadlockIcon, GoToIcon, MoreIcon } from 'outline-icons';
|
import { PadlockIcon, GoToIcon, MoreIcon } from "outline-icons";
|
||||||
|
|
||||||
import Document from 'models/Document';
|
import Document from "models/Document";
|
||||||
import CollectionsStore from 'stores/CollectionsStore';
|
import CollectionsStore from "stores/CollectionsStore";
|
||||||
import { collectionUrl } from 'utils/routeHelpers';
|
import { collectionUrl } from "utils/routeHelpers";
|
||||||
import Flex from 'shared/components/Flex';
|
import Flex from "shared/components/Flex";
|
||||||
import BreadcrumbMenu from './BreadcrumbMenu';
|
import BreadcrumbMenu from "./BreadcrumbMenu";
|
||||||
import CollectionIcon from 'components/CollectionIcon';
|
import CollectionIcon from "components/CollectionIcon";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
document: Document,
|
document: Document,
|
||||||
@ -30,7 +30,7 @@ const Breadcrumb = observer(({ document, collections, onlyText }: Props) => {
|
|||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{collection.private && (
|
{collection.private && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<SmallPadlockIcon color="currentColor" size={16} />{' '}
|
<SmallPadlockIcon color="currentColor" size={16} />{" "}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
{collection.name}
|
{collection.name}
|
||||||
@ -51,7 +51,7 @@ const Breadcrumb = observer(({ document, collections, onlyText }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Wrapper justify="flex-start" align="center">
|
<Wrapper justify="flex-start" align="center">
|
||||||
<CollectionName to={collectionUrl(collection.id)}>
|
<CollectionName to={collectionUrl(collection.id)}>
|
||||||
<CollectionIcon collection={collection} expanded />{' '}
|
<CollectionIcon collection={collection} expanded />{" "}
|
||||||
<span>{collection.name}</span>
|
<span>{collection.name}</span>
|
||||||
</CollectionName>
|
</CollectionName>
|
||||||
{isNestedDocument && (
|
{isNestedDocument && (
|
||||||
@ -61,7 +61,7 @@ const Breadcrumb = observer(({ document, collections, onlyText }: Props) => {
|
|||||||
)}
|
)}
|
||||||
{lastPath && (
|
{lastPath && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Slash />{' '}
|
<Slash />{" "}
|
||||||
<Crumb to={lastPath.url} title={lastPath.title}>
|
<Crumb to={lastPath.url} title={lastPath.title}>
|
||||||
{lastPath.title}
|
{lastPath.title}
|
||||||
</Crumb>
|
</Crumb>
|
||||||
@ -74,7 +74,7 @@ const Breadcrumb = observer(({ document, collections, onlyText }: Props) => {
|
|||||||
const Wrapper = styled(Flex)`
|
const Wrapper = styled(Flex)`
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
${breakpoint('tablet')`
|
${breakpoint("tablet")`
|
||||||
display: flex;
|
display: flex;
|
||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
@ -130,4 +130,4 @@ const CollectionName = styled(Link)`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default inject('collections')(Breadcrumb);
|
export default inject("collections")(Breadcrumb);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from "react-router-dom";
|
||||||
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
|
import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: React.Node,
|
label: React.Node,
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
type JustifyValues =
|
type JustifyValues =
|
||||||
| 'center'
|
| "center"
|
||||||
| 'space-around'
|
| "space-around"
|
||||||
| 'space-between'
|
| "space-between"
|
||||||
| 'flex-start'
|
| "flex-start"
|
||||||
| 'flex-end';
|
| "flex-end";
|
||||||
|
|
||||||
type AlignValues =
|
type AlignValues =
|
||||||
| 'stretch'
|
| "stretch"
|
||||||
| 'center'
|
| "center"
|
||||||
| 'baseline'
|
| "baseline"
|
||||||
| 'flex-start'
|
| "flex-start"
|
||||||
| 'flex-end';
|
| "flex-end";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
column?: ?boolean,
|
column?: ?boolean,
|
||||||
@ -33,11 +33,11 @@ const Flex = (props: Props) => {
|
|||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: ${({ auto }) => (auto ? '1 1 auto' : 'initial')};
|
flex: ${({ auto }) => (auto ? "1 1 auto" : "initial")};
|
||||||
flex-direction: ${({ column }) => (column ? 'column' : 'row')};
|
flex-direction: ${({ column }) => (column ? "column" : "row")};
|
||||||
align-items: ${({ align }) => align};
|
align-items: ${({ align }) => align};
|
||||||
justify-content: ${({ justify }) => justify};
|
justify-content: ${({ justify }) => justify};
|
||||||
flex-shrink: ${({ shrink }) => (shrink ? 1 : 'initial')};
|
flex-shrink: ${({ shrink }) => (shrink ? 1 : "initial")};
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
`;
|
`;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
size?: number,
|
size?: number,
|
||||||
@ -7,7 +7,7 @@ type Props = {
|
|||||||
className?: string,
|
className?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function GithubLogo({ size = 34, fill = '#FFF', className }: Props) {
|
function GithubLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
fill={fill}
|
fill={fill}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
size?: number,
|
size?: number,
|
||||||
@ -7,7 +7,7 @@ type Props = {
|
|||||||
className?: string,
|
className?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function GoogleLogo({ size = 34, fill = '#FFF', className }: Props) {
|
function GoogleLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
fill={fill}
|
fill={fill}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
const Notice = styled.p`
|
const Notice = styled.p`
|
||||||
background: ${props =>
|
background: ${props =>
|
||||||
props.muted ? props.theme.sidebarBackground : props.theme.yellow};
|
props.muted ? props.theme.sidebarBackground : props.theme.yellow};
|
||||||
color: ${props =>
|
color: ${props =>
|
||||||
props.muted ? props.theme.sidebarText : 'hsla(46, 100%, 20%, 1)'};
|
props.muted ? props.theme.sidebarText : "hsla(46, 100%, 20%, 1)"};
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
`;
|
`;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
size?: number,
|
size?: number,
|
||||||
@ -7,7 +7,7 @@ type Props = {
|
|||||||
className?: string,
|
className?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function OutlineLogo({ size = 32, fill = '#333', className }: Props) {
|
function OutlineLogo({ size = 32, fill = "#333", className }: Props) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
fill={fill}
|
fill={fill}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
size?: number,
|
size?: number,
|
||||||
@ -7,7 +7,7 @@ type Props = {
|
|||||||
className?: string,
|
className?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function SlackLogo({ size = 34, fill = '#FFF', className }: Props) {
|
function SlackLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
fill={fill}
|
fill={fill}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
const TeamLogo = styled.img`
|
const TeamLogo = styled.img`
|
||||||
width: 38px;
|
width: 38px;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import Tooltip from 'components/Tooltip';
|
import Tooltip from "components/Tooltip";
|
||||||
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
|
||||||
import format from 'date-fns/format';
|
import format from "date-fns/format";
|
||||||
|
|
||||||
let callbacks = [];
|
let callbacks = [];
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class Time extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltip={format(this.props.dateTime, 'MMMM Do, YYYY h:mm a')}
|
tooltip={format(this.props.dateTime, "MMMM Do, YYYY h:mm a")}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<time dateTime={this.props.dateTime}>
|
<time dateTime={this.props.dateTime}>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { keyframes } from 'styled-components';
|
import { keyframes } from "styled-components";
|
||||||
|
|
||||||
export const fadeIn = keyframes`
|
export const fadeIn = keyframes`
|
||||||
from { opacity: 0; }
|
from { opacity: 0; }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import styledNormalize from 'styled-normalize';
|
import styledNormalize from "styled-normalize";
|
||||||
import { createGlobalStyle } from 'styled-components';
|
import { createGlobalStyle } from "styled-components";
|
||||||
|
|
||||||
export default createGlobalStyle`
|
export default createGlobalStyle`
|
||||||
${styledNormalize}
|
${styledNormalize}
|
||||||
|
@ -1,44 +1,44 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { darken, lighten } from 'polished';
|
import { darken, lighten } from "polished";
|
||||||
|
|
||||||
const colors = {
|
const colors = {
|
||||||
almostBlack: '#111319',
|
almostBlack: "#111319",
|
||||||
lightBlack: '#2F3336',
|
lightBlack: "#2F3336",
|
||||||
almostWhite: '#E6E6E6',
|
almostWhite: "#E6E6E6",
|
||||||
veryDarkBlue: '#08090C',
|
veryDarkBlue: "#08090C",
|
||||||
|
|
||||||
slate: '#9BA6B2',
|
slate: "#9BA6B2",
|
||||||
slateLight: '#DAE1E9',
|
slateLight: "#DAE1E9",
|
||||||
slateDark: '#4E5C6E',
|
slateDark: "#4E5C6E",
|
||||||
|
|
||||||
smoke: '#F4F7FA',
|
smoke: "#F4F7FA",
|
||||||
smokeLight: '#F9FBFC',
|
smokeLight: "#F9FBFC",
|
||||||
smokeDark: '#E8EBED',
|
smokeDark: "#E8EBED",
|
||||||
|
|
||||||
white: '#FFF',
|
white: "#FFF",
|
||||||
white10: 'rgba(255, 255, 255, 0.1)',
|
white10: "rgba(255, 255, 255, 0.1)",
|
||||||
white50: 'rgba(255, 255, 255, 0.5)',
|
white50: "rgba(255, 255, 255, 0.5)",
|
||||||
black: '#000',
|
black: "#000",
|
||||||
black05: 'rgba(0, 0, 0, 0.05)',
|
black05: "rgba(0, 0, 0, 0.05)",
|
||||||
black10: 'rgba(0, 0, 0, 0.1)',
|
black10: "rgba(0, 0, 0, 0.1)",
|
||||||
black50: 'rgba(0, 0, 0, 0.50)',
|
black50: "rgba(0, 0, 0, 0.50)",
|
||||||
primary: '#0366d6',
|
primary: "#0366d6",
|
||||||
yellow: '#FBCA04',
|
yellow: "#FBCA04",
|
||||||
warmGrey: '#EDF2F7',
|
warmGrey: "#EDF2F7",
|
||||||
|
|
||||||
danger: '#D0021B',
|
danger: "#D0021B",
|
||||||
warning: '#f08a24',
|
warning: "#f08a24",
|
||||||
success: '#2f3336',
|
success: "#2f3336",
|
||||||
info: '#a0d3e8',
|
info: "#a0d3e8",
|
||||||
};
|
};
|
||||||
|
|
||||||
const spacing = {
|
const spacing = {
|
||||||
padding: '1.5vw 1.875vw',
|
padding: "1.5vw 1.875vw",
|
||||||
vpadding: '1.5vw',
|
vpadding: "1.5vw",
|
||||||
hpadding: '1.875vw',
|
hpadding: "1.875vw",
|
||||||
sidebarWidth: '280px',
|
sidebarWidth: "280px",
|
||||||
sidebarMinWidth: '250px',
|
sidebarMinWidth: "250px",
|
||||||
sidebarMaxWidth: '350px',
|
sidebarMaxWidth: "350px",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const base = {
|
export const base = {
|
||||||
@ -49,29 +49,29 @@ export const base = {
|
|||||||
fontFamilyMono:
|
fontFamilyMono:
|
||||||
"'SFMono-Regular',Consolas,'Liberation Mono', Menlo, Courier,monospace",
|
"'SFMono-Regular',Consolas,'Liberation Mono', Menlo, Courier,monospace",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
backgroundTransition: 'background 100ms ease-in-out',
|
backgroundTransition: "background 100ms ease-in-out",
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
|
|
||||||
selected: colors.primary,
|
selected: colors.primary,
|
||||||
buttonBackground: colors.primary,
|
buttonBackground: colors.primary,
|
||||||
buttonText: colors.white,
|
buttonText: colors.white,
|
||||||
textHighlight: '#B3E7FF',
|
textHighlight: "#B3E7FF",
|
||||||
|
|
||||||
codeComment: '#6a737d',
|
codeComment: "#6a737d",
|
||||||
codePunctuation: '#5e6687',
|
codePunctuation: "#5e6687",
|
||||||
codeNumber: '#d73a49',
|
codeNumber: "#d73a49",
|
||||||
codeProperty: '#c08b30',
|
codeProperty: "#c08b30",
|
||||||
codeTag: '#3d8fd1',
|
codeTag: "#3d8fd1",
|
||||||
codeString: '#032f62',
|
codeString: "#032f62",
|
||||||
codeSelector: '#6679cc',
|
codeSelector: "#6679cc",
|
||||||
codeAttr: '#c76b29',
|
codeAttr: "#c76b29",
|
||||||
codeEntity: '#22a2c9',
|
codeEntity: "#22a2c9",
|
||||||
codeKeyword: '#d73a49',
|
codeKeyword: "#d73a49",
|
||||||
codeFunction: '#6f42c1',
|
codeFunction: "#6f42c1",
|
||||||
codeStatement: '#22a2c9',
|
codeStatement: "#22a2c9",
|
||||||
codePlaceholder: '#3d8fd1',
|
codePlaceholder: "#3d8fd1",
|
||||||
codeInserted: '#202746',
|
codeInserted: "#202746",
|
||||||
codeImportant: '#c94922',
|
codeImportant: "#c94922",
|
||||||
|
|
||||||
blockToolbarBackground: colors.white,
|
blockToolbarBackground: colors.white,
|
||||||
blockToolbarTrigger: colors.slate,
|
blockToolbarTrigger: colors.slate,
|
||||||
@ -98,16 +98,16 @@ export const light = {
|
|||||||
text: colors.almostBlack,
|
text: colors.almostBlack,
|
||||||
textSecondary: colors.slateDark,
|
textSecondary: colors.slateDark,
|
||||||
textTertiary: colors.slate,
|
textTertiary: colors.slate,
|
||||||
placeholder: '#B1BECC',
|
placeholder: "#B1BECC",
|
||||||
|
|
||||||
sidebarBackground: colors.warmGrey,
|
sidebarBackground: colors.warmGrey,
|
||||||
sidebarItemBackground: colors.black05,
|
sidebarItemBackground: colors.black10,
|
||||||
sidebarText: 'rgb(78, 92, 110)',
|
sidebarText: "rgb(78, 92, 110)",
|
||||||
shadow: 'rgba(0, 0, 0, 0.2)',
|
shadow: "rgba(0, 0, 0, 0.2)",
|
||||||
|
|
||||||
menuBackground: colors.white,
|
menuBackground: colors.white,
|
||||||
menuShadow:
|
menuShadow:
|
||||||
'0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 8px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.08)',
|
"0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 8px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.08)",
|
||||||
divider: colors.slateLight,
|
divider: colors.slateLight,
|
||||||
inputBorder: colors.slateLight,
|
inputBorder: colors.slateLight,
|
||||||
inputBorderFocused: colors.slate,
|
inputBorderFocused: colors.slate,
|
||||||
@ -120,7 +120,7 @@ export const light = {
|
|||||||
|
|
||||||
tableDivider: colors.smokeDark,
|
tableDivider: colors.smokeDark,
|
||||||
tableSelected: colors.primary,
|
tableSelected: colors.primary,
|
||||||
tableSelectedBackground: '#E5F7FF',
|
tableSelectedBackground: "#E5F7FF",
|
||||||
|
|
||||||
buttonNeutralBackground: colors.white,
|
buttonNeutralBackground: colors.white,
|
||||||
buttonNeutralText: colors.almostBlack,
|
buttonNeutralText: colors.almostBlack,
|
||||||
@ -135,7 +135,7 @@ export const light = {
|
|||||||
quote: colors.slateLight,
|
quote: colors.slateLight,
|
||||||
codeBackground: colors.smoke,
|
codeBackground: colors.smoke,
|
||||||
codeBorder: colors.smokeDark,
|
codeBorder: colors.smokeDark,
|
||||||
embedBorder: '#DDD #DDD #CCC',
|
embedBorder: "#DDD #DDD #CCC",
|
||||||
horizontalRule: colors.smokeDark,
|
horizontalRule: colors.smokeDark,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -153,12 +153,12 @@ export const dark = {
|
|||||||
sidebarBackground: colors.veryDarkBlue,
|
sidebarBackground: colors.veryDarkBlue,
|
||||||
sidebarItemBackground: colors.veryDarkBlue,
|
sidebarItemBackground: colors.veryDarkBlue,
|
||||||
sidebarText: colors.slate,
|
sidebarText: colors.slate,
|
||||||
shadow: 'rgba(0, 0, 0, 0.6)',
|
shadow: "rgba(0, 0, 0, 0.6)",
|
||||||
|
|
||||||
menuBorder: lighten(0.1, colors.almostBlack),
|
menuBorder: lighten(0.1, colors.almostBlack),
|
||||||
menuBackground: lighten(0.015, colors.almostBlack),
|
menuBackground: lighten(0.015, colors.almostBlack),
|
||||||
menuShadow:
|
menuShadow:
|
||||||
'0 0 0 1px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.08), inset 0 0 1px rgba(255,255,255,.2)',
|
"0 0 0 1px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.08), inset 0 0 1px rgba(255,255,255,.2)",
|
||||||
divider: darken(0.2, colors.slate),
|
divider: darken(0.2, colors.slate),
|
||||||
inputBorder: colors.slateDark,
|
inputBorder: colors.slateDark,
|
||||||
inputBorderFocused: colors.slate,
|
inputBorderFocused: colors.slate,
|
||||||
@ -171,7 +171,7 @@ export const dark = {
|
|||||||
|
|
||||||
tableDivider: colors.lightBlack,
|
tableDivider: colors.lightBlack,
|
||||||
tableSelected: colors.primary,
|
tableSelected: colors.primary,
|
||||||
tableSelectedBackground: '#002333',
|
tableSelectedBackground: "#002333",
|
||||||
|
|
||||||
buttonNeutralBackground: colors.almostBlack,
|
buttonNeutralBackground: colors.almostBlack,
|
||||||
buttonNeutralText: colors.white,
|
buttonNeutralText: colors.white,
|
||||||
@ -186,7 +186,7 @@ export const dark = {
|
|||||||
quote: colors.almostWhite,
|
quote: colors.almostWhite,
|
||||||
codeBackground: colors.black,
|
codeBackground: colors.black,
|
||||||
codeBorder: colors.black50,
|
codeBorder: colors.black50,
|
||||||
codeString: '#3d8fd1',
|
codeString: "#3d8fd1",
|
||||||
embedBorder: colors.black50,
|
embedBorder: colors.black50,
|
||||||
horizontalRule: darken(0.2, colors.slate),
|
horizontalRule: darken(0.2, colors.slate),
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { trim } from 'lodash';
|
import { trim } from "lodash";
|
||||||
|
|
||||||
type Domain = {
|
type Domain = {
|
||||||
tld: string,
|
tld: string,
|
||||||
@ -11,14 +11,14 @@ type Domain = {
|
|||||||
// a large list of possible TLD's which increase the size of the bundle
|
// a large list of possible TLD's which increase the size of the bundle
|
||||||
// unneccessarily for our usecase of trusted input.
|
// unneccessarily for our usecase of trusted input.
|
||||||
export function parseDomain(url: string): ?Domain {
|
export function parseDomain(url: string): ?Domain {
|
||||||
if (typeof url !== 'string') return null;
|
if (typeof url !== "string") return null;
|
||||||
|
|
||||||
// strip extermeties and whitespace from input
|
// strip extermeties and whitespace from input
|
||||||
const normalizedDomain = trim(url.replace(/(https?:)?\/\//, ''));
|
const normalizedDomain = trim(url.replace(/(https?:)?\/\//, ""));
|
||||||
const parts = normalizedDomain.split('.');
|
const parts = normalizedDomain.split(".");
|
||||||
|
|
||||||
// ensure the last part only includes something that looks like a TLD
|
// ensure the last part only includes something that looks like a TLD
|
||||||
function cleanTLD(tld = '') {
|
function cleanTLD(tld = "") {
|
||||||
return tld.split(/[/:?]/)[0];
|
return tld.split(/[/:?]/)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,14 +28,14 @@ export function parseDomain(url: string): ?Domain {
|
|||||||
return {
|
return {
|
||||||
subdomain: parts[0],
|
subdomain: parts[0],
|
||||||
domain: parts[1],
|
domain: parts[1],
|
||||||
tld: cleanTLD(parts.slice(2).join('.')),
|
tld: cleanTLD(parts.slice(2).join(".")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (parts.length === 2) {
|
if (parts.length === 2) {
|
||||||
return {
|
return {
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: parts[0],
|
domain: parts[0],
|
||||||
tld: cleanTLD(parts.slice(1).join('.')),
|
tld: cleanTLD(parts.slice(1).join(".")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ export function parseDomain(url: string): ?Domain {
|
|||||||
|
|
||||||
export function getCookieDomain(domain: string) {
|
export function getCookieDomain(domain: string) {
|
||||||
// TODO: All the process.env parsing needs centralizing
|
// TODO: All the process.env parsing needs centralizing
|
||||||
return process.env.SUBDOMAINS_ENABLED === 'true' ||
|
return process.env.SUBDOMAINS_ENABLED === "true" ||
|
||||||
process.env.SUBDOMAINS_ENABLED === true
|
process.env.SUBDOMAINS_ENABLED === true
|
||||||
? stripSubdomain(domain)
|
? stripSubdomain(domain)
|
||||||
: domain;
|
: domain;
|
||||||
@ -61,69 +61,69 @@ export function stripSubdomain(hostname: string) {
|
|||||||
export function isCustomSubdomain(hostname: string) {
|
export function isCustomSubdomain(hostname: string) {
|
||||||
const parsed = parseDomain(hostname);
|
const parsed = parseDomain(hostname);
|
||||||
if (!parsed) return false;
|
if (!parsed) return false;
|
||||||
if (!parsed.subdomain || parsed.subdomain === 'www') return false;
|
if (!parsed.subdomain || parsed.subdomain === "www") return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RESERVED_SUBDOMAINS = [
|
export const RESERVED_SUBDOMAINS = [
|
||||||
'about',
|
"about",
|
||||||
'account',
|
"account",
|
||||||
'admin',
|
"admin",
|
||||||
'advertising',
|
"advertising",
|
||||||
'api',
|
"api",
|
||||||
'assets',
|
"assets",
|
||||||
'archive',
|
"archive",
|
||||||
'beta',
|
"beta",
|
||||||
'billing',
|
"billing",
|
||||||
'blog',
|
"blog",
|
||||||
'cache',
|
"cache",
|
||||||
'cdn',
|
"cdn",
|
||||||
'code',
|
"code",
|
||||||
'community',
|
"community",
|
||||||
'dashboard',
|
"dashboard",
|
||||||
'developer',
|
"developer",
|
||||||
'developers',
|
"developers",
|
||||||
'forum',
|
"forum",
|
||||||
'help',
|
"help",
|
||||||
'home',
|
"home",
|
||||||
'http',
|
"http",
|
||||||
'https',
|
"https",
|
||||||
'imap',
|
"imap",
|
||||||
'localhost',
|
"localhost",
|
||||||
'mail',
|
"mail",
|
||||||
'marketing',
|
"marketing",
|
||||||
'mobile',
|
"mobile",
|
||||||
'new',
|
"new",
|
||||||
'news',
|
"news",
|
||||||
'newsletter',
|
"newsletter",
|
||||||
'ns1',
|
"ns1",
|
||||||
'ns2',
|
"ns2",
|
||||||
'ns3',
|
"ns3",
|
||||||
'ns4',
|
"ns4",
|
||||||
'password',
|
"password",
|
||||||
'profile',
|
"profile",
|
||||||
'sandbox',
|
"sandbox",
|
||||||
'script',
|
"script",
|
||||||
'scripts',
|
"scripts",
|
||||||
'setup',
|
"setup",
|
||||||
'signin',
|
"signin",
|
||||||
'signup',
|
"signup",
|
||||||
'site',
|
"site",
|
||||||
'smtp',
|
"smtp",
|
||||||
'support',
|
"support",
|
||||||
'status',
|
"status",
|
||||||
'static',
|
"static",
|
||||||
'stats',
|
"stats",
|
||||||
'test',
|
"test",
|
||||||
'update',
|
"update",
|
||||||
'updates',
|
"updates",
|
||||||
'ws',
|
"ws",
|
||||||
'wss',
|
"wss",
|
||||||
'web',
|
"web",
|
||||||
'websockets',
|
"websockets",
|
||||||
'www',
|
"www",
|
||||||
'www1',
|
"www1",
|
||||||
'www2',
|
"www2",
|
||||||
'www3',
|
"www3",
|
||||||
'www4',
|
"www4",
|
||||||
];
|
];
|
||||||
|
@ -1,149 +1,149 @@
|
|||||||
/* eslint-disable flowtype/require-valid-file-annotation */
|
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||||
import { stripSubdomain, parseDomain, isCustomSubdomain } from './domains';
|
import { stripSubdomain, parseDomain, isCustomSubdomain } from "./domains";
|
||||||
|
|
||||||
// test suite is based on subset of parse-domain module we want to support
|
// test suite is based on subset of parse-domain module we want to support
|
||||||
// https://github.com/peerigon/parse-domain/blob/master/test/parseDomain.test.js
|
// https://github.com/peerigon/parse-domain/blob/master/test/parseDomain.test.js
|
||||||
describe('#parseDomain', () => {
|
describe("#parseDomain", () => {
|
||||||
it('should remove the protocol', () => {
|
it("should remove the protocol", () => {
|
||||||
expect(parseDomain('http://example.com')).toMatchObject({
|
expect(parseDomain("http://example.com")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
expect(parseDomain('//example.com')).toMatchObject({
|
expect(parseDomain("//example.com")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
expect(parseDomain('https://example.com')).toMatchObject({
|
expect(parseDomain("https://example.com")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove sub-domains', () => {
|
it("should remove sub-domains", () => {
|
||||||
expect(parseDomain('www.example.com')).toMatchObject({
|
expect(parseDomain("www.example.com")).toMatchObject({
|
||||||
subdomain: 'www',
|
subdomain: "www",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove the path', () => {
|
it("should remove the path", () => {
|
||||||
expect(parseDomain('example.com/some/path?and&query')).toMatchObject({
|
expect(parseDomain("example.com/some/path?and&query")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
expect(parseDomain('example.com/')).toMatchObject({
|
expect(parseDomain("example.com/")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove the query string', () => {
|
it("should remove the query string", () => {
|
||||||
expect(parseDomain('example.com?and&query')).toMatchObject({
|
expect(parseDomain("example.com?and&query")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove special characters', () => {
|
it("should remove special characters", () => {
|
||||||
expect(parseDomain('http://m.example.com\r')).toMatchObject({
|
expect(parseDomain("http://m.example.com\r")).toMatchObject({
|
||||||
subdomain: 'm',
|
subdomain: "m",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove the port', () => {
|
it("should remove the port", () => {
|
||||||
expect(parseDomain('example.com:8080')).toMatchObject({
|
expect(parseDomain("example.com:8080")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow @ characters in the path', () => {
|
it("should allow @ characters in the path", () => {
|
||||||
expect(parseDomain('https://medium.com/@username/')).toMatchObject({
|
expect(parseDomain("https://medium.com/@username/")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'medium',
|
domain: "medium",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should also work with three-level domains like .co.uk', () => {
|
it("should also work with three-level domains like .co.uk", () => {
|
||||||
expect(parseDomain('www.example.co.uk')).toMatchObject({
|
expect(parseDomain("www.example.co.uk")).toMatchObject({
|
||||||
subdomain: 'www',
|
subdomain: "www",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'co.uk',
|
tld: "co.uk",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not include private domains like blogspot.com by default', () => {
|
it("should not include private domains like blogspot.com by default", () => {
|
||||||
expect(parseDomain('foo.blogspot.com')).toMatchObject({
|
expect(parseDomain("foo.blogspot.com")).toMatchObject({
|
||||||
subdomain: 'foo',
|
subdomain: "foo",
|
||||||
domain: 'blogspot',
|
domain: "blogspot",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should also work with the minimum', () => {
|
it("should also work with the minimum", () => {
|
||||||
expect(parseDomain('example.com')).toMatchObject({
|
expect(parseDomain("example.com")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'example',
|
domain: "example",
|
||||||
tld: 'com',
|
tld: "com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if the given value is not a string', () => {
|
it("should return null if the given value is not a string", () => {
|
||||||
expect(parseDomain(undefined)).toBe(null);
|
expect(parseDomain(undefined)).toBe(null);
|
||||||
expect(parseDomain({})).toBe(null);
|
expect(parseDomain({})).toBe(null);
|
||||||
expect(parseDomain('')).toBe(null);
|
expect(parseDomain("")).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with custom top-level domains (eg .local)', () => {
|
it("should work with custom top-level domains (eg .local)", () => {
|
||||||
expect(parseDomain('mymachine.local')).toMatchObject({
|
expect(parseDomain("mymachine.local")).toMatchObject({
|
||||||
subdomain: '',
|
subdomain: "",
|
||||||
domain: 'mymachine',
|
domain: "mymachine",
|
||||||
tld: 'local',
|
tld: "local",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#stripSubdomain', () => {
|
describe("#stripSubdomain", () => {
|
||||||
test('to work with localhost', () => {
|
test("to work with localhost", () => {
|
||||||
expect(stripSubdomain('localhost')).toBe('localhost');
|
expect(stripSubdomain("localhost")).toBe("localhost");
|
||||||
});
|
});
|
||||||
test('to return domains without a subdomain', () => {
|
test("to return domains without a subdomain", () => {
|
||||||
expect(stripSubdomain('example')).toBe('example');
|
expect(stripSubdomain("example")).toBe("example");
|
||||||
expect(stripSubdomain('example.com')).toBe('example.com');
|
expect(stripSubdomain("example.com")).toBe("example.com");
|
||||||
expect(stripSubdomain('example.org:3000')).toBe('example.org');
|
expect(stripSubdomain("example.org:3000")).toBe("example.org");
|
||||||
});
|
});
|
||||||
test('to remove subdomains', () => {
|
test("to remove subdomains", () => {
|
||||||
expect(stripSubdomain('test.example.com')).toBe('example.com');
|
expect(stripSubdomain("test.example.com")).toBe("example.com");
|
||||||
expect(stripSubdomain('test.example.com:3000')).toBe('example.com');
|
expect(stripSubdomain("test.example.com:3000")).toBe("example.com");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#isCustomSubdomain', () => {
|
describe("#isCustomSubdomain", () => {
|
||||||
test('to work with localhost', () => {
|
test("to work with localhost", () => {
|
||||||
expect(isCustomSubdomain('localhost')).toBe(false);
|
expect(isCustomSubdomain("localhost")).toBe(false);
|
||||||
});
|
});
|
||||||
test('to return false for domains without a subdomain', () => {
|
test("to return false for domains without a subdomain", () => {
|
||||||
expect(isCustomSubdomain('example')).toBe(false);
|
expect(isCustomSubdomain("example")).toBe(false);
|
||||||
expect(isCustomSubdomain('example.com')).toBe(false);
|
expect(isCustomSubdomain("example.com")).toBe(false);
|
||||||
expect(isCustomSubdomain('example.org:3000')).toBe(false);
|
expect(isCustomSubdomain("example.org:3000")).toBe(false);
|
||||||
});
|
});
|
||||||
test('to return false for www', () => {
|
test("to return false for www", () => {
|
||||||
expect(isCustomSubdomain('www.example.com')).toBe(false);
|
expect(isCustomSubdomain("www.example.com")).toBe(false);
|
||||||
expect(isCustomSubdomain('www.example.com:3000')).toBe(false);
|
expect(isCustomSubdomain("www.example.com:3000")).toBe(false);
|
||||||
});
|
});
|
||||||
test('to return true for subdomains', () => {
|
test("to return true for subdomains", () => {
|
||||||
expect(isCustomSubdomain('test.example.com')).toBe(true);
|
expect(isCustomSubdomain("test.example.com")).toBe(true);
|
||||||
expect(isCustomSubdomain('test.example.com:3000')).toBe(true);
|
expect(isCustomSubdomain("test.example.com:3000")).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
export default function getQueryVariable(variable: string) {
|
export default function getQueryVariable(variable: string) {
|
||||||
var query = window.location.search.substring(1);
|
const query = window.location.search.substring(1);
|
||||||
var vars = query.split('&');
|
const vars = query.split("&");
|
||||||
|
|
||||||
for (var i = 0; i < vars.length; i++) {
|
for (var i = 0; i < vars.length; i++) {
|
||||||
var pair = vars[i].split('=');
|
const pair = vars[i].split("=");
|
||||||
if (pair[0] == variable) {
|
|
||||||
|
if (pair[0] === variable) {
|
||||||
return pair[1];
|
return pair[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { deburr } from 'lodash';
|
import { deburr } from "lodash";
|
||||||
import naturalSort from 'natural-sort';
|
import naturalSort from "natural-sort";
|
||||||
|
|
||||||
type NaturalSortOptions = {
|
type NaturalSortOptions = {
|
||||||
caseSensitive?: boolean,
|
caseSensitive?: boolean,
|
||||||
direction?: 'asc' | 'desc',
|
direction?: "asc" | "desc",
|
||||||
};
|
};
|
||||||
|
|
||||||
const sorter = naturalSort();
|
const sorter = naturalSort();
|
||||||
@ -13,7 +13,7 @@ function getSortByField<T: Object>(
|
|||||||
item: T,
|
item: T,
|
||||||
keyOrCallback: string | (T => string)
|
keyOrCallback: string | (T => string)
|
||||||
) {
|
) {
|
||||||
if (typeof keyOrCallback === 'string') {
|
if (typeof keyOrCallback === "string") {
|
||||||
return deburr(item[keyOrCallback]);
|
return deburr(item[keyOrCallback]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { parser } from 'rich-markdown-editor';
|
import { parser } from "rich-markdown-editor";
|
||||||
|
|
||||||
export default function parseDocumentIds(text: string): string[] {
|
export default function parseDocumentIds(text: string): string[] {
|
||||||
const value = parser.parse(text);
|
const value = parser.parse(text);
|
||||||
@ -7,15 +7,15 @@ export default function parseDocumentIds(text: string): string[] {
|
|||||||
|
|
||||||
function findLinks(node) {
|
function findLinks(node) {
|
||||||
// get text nodes
|
// get text nodes
|
||||||
if (node.type.name === 'text') {
|
if (node.type.name === "text") {
|
||||||
// get marks for text nodes
|
// get marks for text nodes
|
||||||
node.marks.forEach(mark => {
|
node.marks.forEach(mark => {
|
||||||
// any of the marks links?
|
// any of the marks links?
|
||||||
if (mark.type.name === 'link') {
|
if (mark.type.name === "link") {
|
||||||
const { href } = mark.attrs;
|
const { href } = mark.attrs;
|
||||||
// any of the links to other docs?
|
// any of the links to other docs?
|
||||||
if (href.startsWith('/doc')) {
|
if (href.startsWith("/doc")) {
|
||||||
const tokens = href.replace(/\/$/, '').split('/');
|
const tokens = href.replace(/\/$/, "").split("/");
|
||||||
const lastToken = tokens[tokens.length - 1];
|
const lastToken = tokens[tokens.length - 1];
|
||||||
|
|
||||||
// don't return the same link more than once
|
// don't return the same link more than once
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
/* eslint-disable flowtype/require-valid-file-annotation */
|
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||||
import parseDocumentIds from './parseDocumentIds';
|
import parseDocumentIds from "./parseDocumentIds";
|
||||||
|
|
||||||
it('should not return non links', () => {
|
it("should not return non links", () => {
|
||||||
expect(parseDocumentIds(`# Header`).length).toBe(0);
|
expect(parseDocumentIds(`# Header`).length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an array of document ids', () => {
|
it("should return an array of document ids", () => {
|
||||||
const result = parseDocumentIds(`# Header
|
const result = parseDocumentIds(`# Header
|
||||||
|
|
||||||
[internal](/doc/test-456733)
|
[internal](/doc/test-456733)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(result.length).toBe(1);
|
expect(result.length).toBe(1);
|
||||||
expect(result[0]).toBe('test-456733');
|
expect(result[0]).toBe("test-456733");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return duplicate document ids', () => {
|
it("should not return duplicate document ids", () => {
|
||||||
expect(parseDocumentIds(`# Header`).length).toBe(0);
|
expect(parseDocumentIds(`# Header`).length).toBe(0);
|
||||||
|
|
||||||
const result = parseDocumentIds(`# Header
|
const result = parseDocumentIds(`# Header
|
||||||
@ -26,13 +26,13 @@ it('should not return duplicate document ids', () => {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
expect(result.length).toBe(1);
|
expect(result.length).toBe(1);
|
||||||
expect(result[0]).toBe('test-456733');
|
expect(result[0]).toBe("test-456733");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return non document links', () => {
|
it("should not return non document links", () => {
|
||||||
expect(parseDocumentIds(`[google](http://www.google.com)`).length).toBe(0);
|
expect(parseDocumentIds(`[google](http://www.google.com)`).length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return non document relative links', () => {
|
it("should not return non document relative links", () => {
|
||||||
expect(parseDocumentIds(`[relative](/developers)`).length).toBe(0);
|
expect(parseDocumentIds(`[relative](/developers)`).length).toBe(0);
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import emojiRegex from 'emoji-regex';
|
import emojiRegex from "emoji-regex";
|
||||||
import unescape from './unescape';
|
import unescape from "./unescape";
|
||||||
|
|
||||||
export default function parseTitle(text: string = '') {
|
export default function parseTitle(text: string = "") {
|
||||||
const regex = emojiRegex();
|
const regex = emojiRegex();
|
||||||
|
|
||||||
// find and extract title
|
// find and extract title
|
||||||
const firstLine = text.trim().split(/\r?\n/)[0];
|
const firstLine = text.trim().split(/\r?\n/)[0];
|
||||||
const trimmedTitle = firstLine.replace(/^#/, '').trim();
|
const trimmedTitle = firstLine.replace(/^#/, "").trim();
|
||||||
|
|
||||||
// remove any escape characters
|
// remove any escape characters
|
||||||
const title = unescape(trimmedTitle);
|
const title = unescape(trimmedTitle);
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
/* eslint-disable flowtype/require-valid-file-annotation */
|
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||||
import parseTitle from './parseTitle';
|
import parseTitle from "./parseTitle";
|
||||||
|
|
||||||
it('should trim the title', () => {
|
it("should trim the title", () => {
|
||||||
expect(parseTitle(`# Lots of space `).title).toBe('Lots of space');
|
expect(parseTitle(`# Lots of space `).title).toBe("Lots of space");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract first title', () => {
|
it("should extract first title", () => {
|
||||||
expect(parseTitle(`# Title one\n# Title two`).title).toBe('Title one');
|
expect(parseTitle(`# Title one\n# Title two`).title).toBe("Title one");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove escape characters', () => {
|
it("should remove escape characters", () => {
|
||||||
expect(parseTitle(`# Thing \\- one`).title).toBe('Thing - one');
|
expect(parseTitle(`# Thing \\- one`).title).toBe("Thing - one");
|
||||||
expect(parseTitle(`# \\[wip\\] Title`).title).toBe('[wip] Title');
|
expect(parseTitle(`# \\[wip\\] Title`).title).toBe("[wip] Title");
|
||||||
expect(parseTitle(`# \\> Title`).title).toBe('> Title');
|
expect(parseTitle(`# \\> Title`).title).toBe("> Title");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse emoji if first character', () => {
|
it("should parse emoji if first character", () => {
|
||||||
const parsed = parseTitle(`# 😀 Title`);
|
const parsed = parseTitle(`# 😀 Title`);
|
||||||
expect(parsed.title).toBe('😀 Title');
|
expect(parsed.title).toBe("😀 Title");
|
||||||
expect(parsed.emoji).toBe('😀');
|
expect(parsed.emoji).toBe("😀");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not parse emoji if not first character', () => {
|
it("should not parse emoji if not first character", () => {
|
||||||
const parsed = parseTitle(`# Title 🌈`);
|
const parsed = parseTitle(`# Title 🌈`);
|
||||||
expect(parsed.title).toBe('Title 🌈');
|
expect(parsed.title).toBe("Title 🌈");
|
||||||
expect(parsed.emoji).toBe(undefined);
|
expect(parsed.emoji).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
@ -3,54 +3,54 @@
|
|||||||
export function slackAuth(
|
export function slackAuth(
|
||||||
state: string,
|
state: string,
|
||||||
scopes: string[] = [
|
scopes: string[] = [
|
||||||
'identity.email',
|
"identity.email",
|
||||||
'identity.basic',
|
"identity.basic",
|
||||||
'identity.avatar',
|
"identity.avatar",
|
||||||
'identity.team',
|
"identity.team",
|
||||||
],
|
],
|
||||||
redirectUri: string = `${process.env.URL}/auth/slack.callback`
|
redirectUri: string = `${process.env.URL}/auth/slack.callback`
|
||||||
): string {
|
): string {
|
||||||
const baseUrl = 'https://slack.com/oauth/authorize';
|
const baseUrl = "https://slack.com/oauth/authorize";
|
||||||
const params = {
|
const params = {
|
||||||
client_id: process.env.SLACK_KEY,
|
client_id: process.env.SLACK_KEY,
|
||||||
scope: scopes ? scopes.join(' ') : '',
|
scope: scopes ? scopes.join(" ") : "",
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
const urlParams = Object.keys(params)
|
const urlParams = Object.keys(params)
|
||||||
.map(key => `${key}=${encodeURIComponent(params[key])}`)
|
.map(key => `${key}=${encodeURIComponent(params[key])}`)
|
||||||
.join('&');
|
.join("&");
|
||||||
|
|
||||||
return `${baseUrl}?${urlParams}`;
|
return `${baseUrl}?${urlParams}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function githubUrl(): string {
|
export function githubUrl(): string {
|
||||||
return 'https://www.github.com/outline';
|
return "https://www.github.com/outline";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function githubIssuesUrl(): string {
|
export function githubIssuesUrl(): string {
|
||||||
return 'https://www.github.com/outline/outline/issues';
|
return "https://www.github.com/outline/outline/issues";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function slackAppStoreUrl(): string {
|
export function slackAppStoreUrl(): string {
|
||||||
return 'https://goabstract.slack.com/apps/A0W3UMKBQ-outline';
|
return "https://goabstract.slack.com/apps/A0W3UMKBQ-outline";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function blogUrl(): string {
|
export function blogUrl(): string {
|
||||||
return 'https://medium.com/getoutline';
|
return "https://medium.com/getoutline";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function twitterUrl(): string {
|
export function twitterUrl(): string {
|
||||||
return 'https://twitter.com/outlinewiki';
|
return "https://twitter.com/outlinewiki";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function spectrumUrl(): string {
|
export function spectrumUrl(): string {
|
||||||
return 'https://spectrum.chat/outline';
|
return "https://spectrum.chat/outline";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mailToUrl(): string {
|
export function mailToUrl(): string {
|
||||||
return 'mailto:hello@getoutline.com';
|
return "mailto:hello@getoutline.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function features(): string {
|
export function features(): string {
|
||||||
@ -65,7 +65,7 @@ export function changelog(): string {
|
|||||||
return `https://www.getoutline.com/changelog`;
|
return `https://www.getoutline.com/changelog`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function signin(service: string = 'slack'): string {
|
export function signin(service: string = "slack"): string {
|
||||||
return `${process.env.URL}/auth/${service}`;
|
return `${process.env.URL}/auth/${service}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import slugify from 'slugify';
|
import slugify from "slugify";
|
||||||
|
|
||||||
// Slugify, escape, and remove periods from headings so that they are
|
// Slugify, escape, and remove periods from headings so that they are
|
||||||
// compatible with url hashes AND dom selectors
|
// compatible with url hashes AND dom selectors
|
||||||
export default function safeSlugify(text: string) {
|
export default function safeSlugify(text: string) {
|
||||||
return `h-${escape(slugify(text, { lower: true }).replace('.', '-'))}`;
|
return `h-${escape(slugify(text, { lower: true }).replace(".", "-"))}`;
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
const unescape = (text: string) => {
|
const unescape = (text: string) => {
|
||||||
return text
|
return text
|
||||||
.replace(/\\([\\`*{}[\]()#+\-.!_>])/g, '$1')
|
.replace(/\\([\\`*{}[\]()#+\-.!_>])/g, "$1")
|
||||||
.replace(/\n\\\n/g, '\n\n');
|
.replace(/\n\\\n/g, "\n\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
export default unescape;
|
export default unescape;
|
||||||
|
Reference in New Issue
Block a user