mirror of
https://github.com/outline/outline.git
synced 2026-06-13 19:35:02 +03:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9398348f19 | |||
| 5a68f542e8 | |||
| 891c487a31 | |||
| cbd4a3a01d | |||
| 06a7b828a0 | |||
| f20b53e66c |
@@ -2,6 +2,7 @@
|
||||
"presets": [
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-flow",
|
||||
"@babel/preset-typescript",
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
|
||||
@@ -94,10 +94,6 @@ FORCE_HTTPS=true
|
||||
# the maintainers
|
||||
ENABLE_UPDATES=true
|
||||
|
||||
# How many processes should be spawned. As a reasonable rule divide your servers
|
||||
# available memory by 512 for a rough estimate
|
||||
WEB_CONCURRENCY=1
|
||||
|
||||
# Override the maxium size of document imports, could be required if you have
|
||||
# especially large Word documents with embedded imagery
|
||||
MAXIMUM_IMPORT_SIZE=5120000
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
"react-app",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:flowtype/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"prettier",
|
||||
"flowtype"
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"eqeqeq": 2,
|
||||
@@ -55,21 +54,6 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"flowtype/require-valid-file-annotation": [
|
||||
2,
|
||||
"always",
|
||||
{
|
||||
"annotationStyle": "line"
|
||||
}
|
||||
],
|
||||
"flowtype/space-after-type-colon": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"flowtype/space-before-type-colon": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
@@ -84,16 +68,22 @@
|
||||
"pragma": "React",
|
||||
"version": "detect"
|
||||
},
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [
|
||||
".ts",
|
||||
".tsx"
|
||||
]
|
||||
},
|
||||
"import/resolver": {
|
||||
"typescript": {
|
||||
"alwaysTryTypes": true
|
||||
},
|
||||
"node": {
|
||||
"paths": [
|
||||
"app",
|
||||
"."
|
||||
]
|
||||
}
|
||||
},
|
||||
"flowtype": {
|
||||
"onlyFilesWithFlowAnnotation": false
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"javascript.validate.enable": false,
|
||||
"javascript.format.enable": false,
|
||||
"typescript.validate.enable": false,
|
||||
"typescript.format.enable": false,
|
||||
"typescript.validate.enable": true,
|
||||
"typescript.format.enable": true,
|
||||
"editor.formatOnSave": true,
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Flex from "components/Flex";
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
/* global ga */
|
||||
import * as React from "react";
|
||||
import env from "env";
|
||||
|
||||
type Props = {
|
||||
children?: React.Node,
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export default class Analytics extends React.Component<Props> {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
export default function Arrow() {
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
size?: number;
|
||||
fill?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function GoogleLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||
+3
-4
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
size?: number;
|
||||
fill?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function MicrosoftLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
size?: number;
|
||||
fill?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function SlackLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||
@@ -1,14 +1,13 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import GoogleLogo from "./GoogleLogo";
|
||||
import MicrosoftLogo from "./MicrosoftLogo";
|
||||
import SlackLogo from "./SlackLogo";
|
||||
|
||||
type Props = {|
|
||||
providerName: string,
|
||||
size?: number,
|
||||
|};
|
||||
type Props = {
|
||||
providerName: string;
|
||||
size?: number;
|
||||
};
|
||||
|
||||
function AuthLogo({ providerName, size = 16 }: Props) {
|
||||
switch (providerName) {
|
||||
@@ -1,16 +1,14 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import { isCustomSubdomain } from "shared/utils/domains";
|
||||
import LoadingIndicator from "components/LoadingIndicator";
|
||||
import useStores from "../hooks/useStores";
|
||||
import { changeLanguage } from "../utils/language";
|
||||
import env from "env";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
children: React.Node,
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
const Authenticated = ({ children }: Props) => {
|
||||
@@ -21,7 +19,11 @@ const Authenticated = ({ children }: Props) => {
|
||||
// Watching for language changes here as this is the earliest point we have
|
||||
// the user available and means we can start loading translations faster
|
||||
React.useEffect(() => {
|
||||
changeLanguage(language, i18n);
|
||||
if (language && i18n.language !== language) {
|
||||
// Languages are stored in en_US format in the database, however the
|
||||
// frontend translation framework (i18next) expects en-US
|
||||
i18n.changeLanguage(language.replace("_", "-"));
|
||||
}
|
||||
}, [i18n, language]);
|
||||
|
||||
if (auth.authenticated) {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
@@ -6,18 +5,19 @@ import styled from "styled-components";
|
||||
import User from "models/User";
|
||||
import placeholder from "./placeholder.png";
|
||||
|
||||
type Props = {|
|
||||
src: string,
|
||||
size: number,
|
||||
icon?: React.Node,
|
||||
user?: User,
|
||||
onClick?: () => void,
|
||||
className?: string,
|
||||
|};
|
||||
type Props = {
|
||||
src: string;
|
||||
size: number;
|
||||
icon?: React.ReactNode;
|
||||
user?: User;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@observer
|
||||
class Avatar extends React.Component<Props> {
|
||||
@observable error: boolean;
|
||||
@observable
|
||||
error: boolean;
|
||||
|
||||
static defaultProps = {
|
||||
size: 24,
|
||||
+10
-9
@@ -1,9 +1,9 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { EditIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { withTranslation, type TFunction } from "react-i18next";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { TFunction } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import User from "models/User";
|
||||
import UserProfile from "scenes/UserProfile";
|
||||
@@ -11,17 +11,18 @@ import Avatar from "components/Avatar";
|
||||
import Tooltip from "components/Tooltip";
|
||||
|
||||
type Props = {
|
||||
user: User,
|
||||
isPresent: boolean,
|
||||
isEditing: boolean,
|
||||
isCurrentUser: boolean,
|
||||
profileOnClick: boolean,
|
||||
t: TFunction,
|
||||
user: User;
|
||||
isPresent: boolean;
|
||||
isEditing: boolean;
|
||||
isCurrentUser: boolean;
|
||||
profileOnClick: boolean;
|
||||
t: TFunction;
|
||||
};
|
||||
|
||||
@observer
|
||||
class AvatarWithPresence extends React.Component<Props> {
|
||||
@observable isOpen: boolean = false;
|
||||
@observable
|
||||
isOpen: boolean = false;
|
||||
|
||||
handleOpenProfile = () => {
|
||||
this.isOpen = true;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import Avatar from "./Avatar";
|
||||
import AvatarWithPresence from "./AvatarWithPresence";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Badge = styled.span`
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import OutlineLogo from "./OutlineLogo";
|
||||
import env from "env";
|
||||
|
||||
type Props = {
|
||||
href?: string,
|
||||
href?: string;
|
||||
};
|
||||
|
||||
function Branding({ href = env.URL }: Props) {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { GoToIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -6,18 +5,18 @@ import styled from "styled-components";
|
||||
import Flex from "components/Flex";
|
||||
import BreadcrumbMenu from "menus/BreadcrumbMenu";
|
||||
|
||||
type MenuItem = {|
|
||||
icon?: React.Node,
|
||||
title: React.Node,
|
||||
to?: string,
|
||||
|};
|
||||
type MenuItem = {
|
||||
icon?: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
to?: string;
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
items: MenuItem[],
|
||||
max?: number,
|
||||
children?: React.Node,
|
||||
highlightFirstItem?: boolean,
|
||||
|};
|
||||
type Props = {
|
||||
items: MenuItem[];
|
||||
max?: number;
|
||||
children?: React.ReactNode;
|
||||
highlightFirstItem?: boolean;
|
||||
};
|
||||
|
||||
function Breadcrumb({ items, highlightFirstItem, children, max = 2 }: Props) {
|
||||
const totalItems = items.length;
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { bounceIn } from "styles/animations";
|
||||
import { bounceIn } from "shared/styles/animations";
|
||||
|
||||
type Props = {|
|
||||
count: number,
|
||||
|};
|
||||
type Props = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
const Bubble = ({ count }: Props) => {
|
||||
if (!count) {
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import { ExpandedIcon } from "outline-icons";
|
||||
import { darken } from "polished";
|
||||
import * as React from "react";
|
||||
import { SyntheticEvent } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const RealButton = styled.button`
|
||||
@@ -108,36 +108,35 @@ export const Inner = styled.span`
|
||||
${(props) => props.hasIcon && !props.hasText && "padding: 0 4px;"};
|
||||
`;
|
||||
|
||||
export type Props = {|
|
||||
type?: "button" | "submit",
|
||||
value?: string,
|
||||
icon?: React.Node,
|
||||
iconColor?: string,
|
||||
className?: string,
|
||||
children?: React.Node,
|
||||
innerRef?: React.ElementRef<any>,
|
||||
disclosure?: boolean,
|
||||
neutral?: boolean,
|
||||
danger?: boolean,
|
||||
primary?: boolean,
|
||||
disabled?: boolean,
|
||||
fullwidth?: boolean,
|
||||
autoFocus?: boolean,
|
||||
style?: Object,
|
||||
as?: React.ComponentType<any>,
|
||||
to?: string,
|
||||
onClick?: (event: SyntheticEvent<>) => mixed,
|
||||
borderOnHover?: boolean,
|
||||
export type Props = {
|
||||
type?: "button" | "submit";
|
||||
value?: string;
|
||||
icon?: React.ReactNode;
|
||||
iconColor?: string;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
innerRef?: React.RefObject<any>;
|
||||
disclosure?: boolean;
|
||||
neutral?: boolean;
|
||||
danger?: boolean;
|
||||
primary?: boolean;
|
||||
disabled?: boolean;
|
||||
fullwidth?: boolean;
|
||||
autoFocus?: boolean;
|
||||
style?: any;
|
||||
as?: React.ComponentType<any>;
|
||||
to?: string;
|
||||
onClick?: (event: SyntheticEvent) => unknown;
|
||||
borderOnHover?: boolean;
|
||||
"data-on"?: string;
|
||||
"data-event-category"?: string;
|
||||
"data-event-action"?: string;
|
||||
};
|
||||
|
||||
"data-on"?: string,
|
||||
"data-event-category"?: string,
|
||||
"data-event-action"?: string,
|
||||
|};
|
||||
|
||||
const Button = React.forwardRef<Props, HTMLButtonElement>(
|
||||
const Button = React.forwardRef<HTMLButtonElement>(
|
||||
(
|
||||
{
|
||||
type = "text",
|
||||
type = "button",
|
||||
icon,
|
||||
children,
|
||||
value,
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import Button, { Inner } from "./Button";
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { SyntheticEvent } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
onClick: (ev: SyntheticEvent<>) => void,
|
||||
children: React.Node,
|
||||
onClick: (ev: SyntheticEvent) => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function ButtonLink(props: Props) {
|
||||
return <Button {...props} />;
|
||||
}
|
||||
|
||||
const Button = styled.button`
|
||||
const ButtonLink = styled.button<Props>`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
@@ -21,3 +17,5 @@ const Button = styled.button`
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export default ButtonLink;
|
||||
@@ -1,14 +1,13 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
|
||||
type Props = {|
|
||||
children?: React.Node,
|
||||
withStickyHeader?: boolean,
|
||||
|};
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
withStickyHeader?: boolean;
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
const Container = styled.div<Props>`
|
||||
width: 100%;
|
||||
max-width: 100vw;
|
||||
padding: ${(props) => (props.withStickyHeader ? "4px 12px" : "60px 12px")};
|
||||
@@ -1,29 +1,33 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { ChangeEvent } from "react";
|
||||
import { VisuallyHidden } from "reakit/VisuallyHidden";
|
||||
import styled from "styled-components";
|
||||
import HelpText from "components/HelpText";
|
||||
|
||||
export type Props = {|
|
||||
checked?: boolean,
|
||||
label?: React.Node,
|
||||
labelHidden?: boolean,
|
||||
className?: string,
|
||||
name?: string,
|
||||
disabled?: boolean,
|
||||
onChange: (event: SyntheticInputEvent<HTMLInputElement>) => mixed,
|
||||
note?: string,
|
||||
short?: boolean,
|
||||
small?: boolean,
|
||||
|};
|
||||
export type Props = {
|
||||
checked?: boolean;
|
||||
label?: React.ReactNode;
|
||||
labelHidden?: boolean;
|
||||
className?: string;
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
onChange: (event: ChangeEvent<HTMLInputElement>) => unknown;
|
||||
note?: string;
|
||||
short?: boolean;
|
||||
small?: boolean;
|
||||
};
|
||||
|
||||
const LabelText = styled.span`
|
||||
type LabelTextProps = { small: boolean };
|
||||
|
||||
const LabelText = styled.span<LabelTextProps>`
|
||||
font-weight: 500;
|
||||
margin-left: ${(props) => (props.small ? "6px" : "10px")};
|
||||
${(props) => (props.small ? `color: ${props.theme.textSecondary}` : "")};
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
type WrapperProps = { small: boolean };
|
||||
|
||||
const Wrapper = styled.div<WrapperProps>`
|
||||
padding-bottom: 8px;
|
||||
${(props) => (props.small ? "font-size: 14px" : "")};
|
||||
width: 100%;
|
||||
@@ -1,78 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
|
||||
const cleanPercentage = (percentage) => {
|
||||
const tooLow = !Number.isFinite(+percentage) || percentage < 0;
|
||||
const tooHigh = percentage > 100;
|
||||
return tooLow ? 0 : tooHigh ? 100 : +percentage;
|
||||
};
|
||||
|
||||
const Circle = ({
|
||||
color,
|
||||
percentage,
|
||||
offset,
|
||||
}: {
|
||||
color: string,
|
||||
percentage?: number,
|
||||
offset: number,
|
||||
}) => {
|
||||
const radius = offset * 0.7;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
let strokePercentage;
|
||||
if (percentage) {
|
||||
// because the circle is so small, anything greater than 85% appears like 100%
|
||||
percentage = percentage > 85 && percentage < 100 ? 85 : percentage;
|
||||
strokePercentage = percentage
|
||||
? ((100 - percentage) * circumference) / 100
|
||||
: 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<circle
|
||||
r={radius}
|
||||
cx={offset}
|
||||
cy={offset}
|
||||
fill="none"
|
||||
stroke={strokePercentage !== circumference ? color : ""}
|
||||
strokeWidth={2.5}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={percentage ? strokePercentage : 0}
|
||||
strokeLinecap="round"
|
||||
style={{ transition: "stroke-dashoffset 0.6s ease 0s" }}
|
||||
></circle>
|
||||
);
|
||||
};
|
||||
|
||||
const CircularProgressBar = ({
|
||||
percentage,
|
||||
size = 16,
|
||||
}: {
|
||||
percentage: number,
|
||||
size?: number,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
percentage = cleanPercentage(percentage);
|
||||
const offset = Math.floor(size / 2);
|
||||
|
||||
return (
|
||||
<SVG width={size} height={size}>
|
||||
<g transform={`rotate(-90 ${offset} ${offset})`}>
|
||||
<Circle color={theme.progressBarBackground} offset={offset} />
|
||||
{percentage > 0 && (
|
||||
<Circle
|
||||
color={theme.primary}
|
||||
percentage={percentage}
|
||||
offset={offset}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
</SVG>
|
||||
);
|
||||
};
|
||||
|
||||
const SVG = styled.svg`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
export default CircularProgressBar;
|
||||
@@ -1,7 +1,10 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const ClickablePadding = styled.div`
|
||||
type Props = {
|
||||
grow: boolean;
|
||||
};
|
||||
|
||||
const ClickablePadding = styled.div<Props>`
|
||||
min-height: 10em;
|
||||
cursor: ${({ onClick }) => (onClick ? "text" : "default")};
|
||||
${({ grow }) => grow && `flex-grow: 100;`};
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { sortBy, filter, uniq } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
@@ -14,10 +13,10 @@ import NudeButton from "components/NudeButton";
|
||||
import Popover from "components/Popover";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
currentUserId: string,
|
||||
|};
|
||||
type Props = {
|
||||
document: Document;
|
||||
currentUserId: string;
|
||||
};
|
||||
|
||||
function Collaborators(props: Props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { transparentize } from "polished";
|
||||
import * as React from "react";
|
||||
@@ -12,15 +11,13 @@ import LoadingIndicator from "components/LoadingIndicator";
|
||||
import NudeButton from "components/NudeButton";
|
||||
import useDebouncedCallback from "hooks/useDebouncedCallback";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
collection: Collection,
|
||||
|};
|
||||
type Props = {
|
||||
collection: Collection;
|
||||
};
|
||||
|
||||
function CollectionDescription({ collection }: Props) {
|
||||
const { collections, policies } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { collections, ui, policies } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setExpanded] = React.useState(false);
|
||||
const [isEditing, setEditing] = React.useState(false);
|
||||
@@ -55,7 +52,7 @@ function CollectionDescription({ collection }: Props) {
|
||||
});
|
||||
setDirty(false);
|
||||
} catch (err) {
|
||||
showToast(
|
||||
ui.showToast(
|
||||
t("Sorry, an error occurred saving the collection", {
|
||||
type: "error",
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { CollectionIcon } from "outline-icons";
|
||||
import { getLuminance } from "polished";
|
||||
@@ -8,9 +7,9 @@ import { icons } from "components/IconPicker";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
collection: Collection,
|
||||
expanded?: boolean,
|
||||
size?: number,
|
||||
collection: Collection;
|
||||
expanded?: boolean;
|
||||
size?: number;
|
||||
};
|
||||
|
||||
function ResolvedCollectionIcon({ collection, expanded, size }: Props) {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Header = styled.h3`
|
||||
@@ -1,22 +1,22 @@
|
||||
// @flow
|
||||
import { CheckmarkIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { MenuItem as BaseMenuItem } from "reakit/Menu";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
|
||||
type Props = {|
|
||||
onClick?: (SyntheticEvent<>) => void | Promise<void>,
|
||||
children?: React.Node,
|
||||
selected?: boolean,
|
||||
disabled?: boolean,
|
||||
to?: string,
|
||||
href?: string,
|
||||
target?: "_blank",
|
||||
as?: string | React.ComponentType<*>,
|
||||
hide?: () => void,
|
||||
level?: number,
|
||||
|};
|
||||
import { SyntheticEvent } from "react";
|
||||
|
||||
type Props = {
|
||||
onClick?: (a: SyntheticEvent) => void | Promise<void>;
|
||||
children?: React.ReactNode;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
to?: string;
|
||||
href?: string;
|
||||
target?: "_blank";
|
||||
as?: string | React.ComponentType<any>;
|
||||
hide?: () => void;
|
||||
};
|
||||
|
||||
const MenuItem = ({
|
||||
onClick,
|
||||
@@ -27,29 +27,20 @@ const MenuItem = ({
|
||||
hide,
|
||||
...rest
|
||||
}: Props) => {
|
||||
const handleClick = React.useCallback(
|
||||
// We bind to mousedown instead of onClick here as otherwise menu items do not
|
||||
// work in Firefox which triggers the hideOnClickOutside handler first via
|
||||
// mousedown.
|
||||
const handleMouseDown = React.useCallback(
|
||||
(ev) => {
|
||||
if (onClick) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
onClick(ev);
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
hide();
|
||||
}
|
||||
},
|
||||
[onClick, hide]
|
||||
[onClick]
|
||||
);
|
||||
|
||||
// Preventing default mousedown otherwise menu items do not work in Firefox,
|
||||
// which triggers the hideOnClickOutside handler first via mousedown – hiding
|
||||
// and un-rendering the menu contents.
|
||||
const handleMouseDown = React.useCallback((ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseMenuItem
|
||||
onClick={disabled ? undefined : onClick}
|
||||
@@ -62,8 +53,8 @@ const MenuItem = ({
|
||||
{...props}
|
||||
$toggleable={selected !== undefined}
|
||||
as={onClick ? "button" : as}
|
||||
onClick={handleClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
onClick={hide}
|
||||
>
|
||||
{selected !== undefined && (
|
||||
<>
|
||||
@@ -89,7 +80,6 @@ export const MenuAnchor = styled.a`
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 12px;
|
||||
padding-left: ${(props) => 12 + props.level * 10}px;
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
background: none;
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { MoreIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { MenuButton } from "reakit/Menu";
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { MenuSeparator } from "reakit/Menu";
|
||||
import styled from "styled-components";
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { ExpandedIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -9,15 +8,55 @@ import {
|
||||
MenuItem as BaseMenuItem,
|
||||
} from "reakit/Menu";
|
||||
import styled from "styled-components";
|
||||
import Header from "./Header";
|
||||
import MenuItem, { MenuAnchor } from "./MenuItem";
|
||||
import Separator from "./Separator";
|
||||
import ContextMenu from ".";
|
||||
import { type MenuItem as TMenuItem } from "types";
|
||||
|
||||
type Props = {|
|
||||
items: TMenuItem[],
|
||||
|};
|
||||
import { SyntheticEvent } from "react";
|
||||
|
||||
type TMenuItem =
|
||||
| {
|
||||
title: React.ReactNode;
|
||||
to: string;
|
||||
visible?: boolean;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
| {
|
||||
title: React.ReactNode;
|
||||
onClick: (event: SyntheticEvent) => void | Promise<void>;
|
||||
visible?: boolean;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
| {
|
||||
title: React.ReactNode;
|
||||
href: string;
|
||||
visible?: boolean;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
| {
|
||||
title: React.ReactNode;
|
||||
visible?: boolean;
|
||||
disabled?: boolean;
|
||||
style?: any;
|
||||
hover?: boolean;
|
||||
items: TMenuItem[];
|
||||
}
|
||||
| {
|
||||
type: "separator";
|
||||
visible?: boolean;
|
||||
}
|
||||
| {
|
||||
type: "heading";
|
||||
visible?: boolean;
|
||||
title: React.ReactNode;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
items: TMenuItem[];
|
||||
};
|
||||
|
||||
const Disclosure = styled(ExpandedIcon)`
|
||||
transform: rotate(270deg);
|
||||
@@ -45,7 +84,7 @@ const Submenu = React.forwardRef(({ templateItems, title, ...rest }, ref) => {
|
||||
);
|
||||
});
|
||||
|
||||
export function filterTemplateItems(items: TMenuItem[]): TMenuItem[] {
|
||||
function Template({ items, ...menu }: Props): React.ReactNode {
|
||||
let filtered = items.filter((item) => item.visible !== false);
|
||||
|
||||
// this block literally just trims unneccessary separators
|
||||
@@ -63,11 +102,7 @@ export function filterTemplateItems(items: TMenuItem[]): TMenuItem[] {
|
||||
return [...acc, item];
|
||||
}, []);
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function Template({ items, ...menu }: Props): React.Node {
|
||||
return filterTemplateItems(items).map((item, index) => {
|
||||
return filtered.map((item, index) => {
|
||||
if (item.to) {
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -90,8 +125,7 @@ function Template({ items, ...menu }: Props): React.Node {
|
||||
key={index}
|
||||
disabled={item.disabled}
|
||||
selected={item.selected}
|
||||
level={item.level}
|
||||
target={item.href.startsWith("#") ? undefined : "_blank"}
|
||||
target="_blank"
|
||||
{...menu}
|
||||
>
|
||||
{item.title}
|
||||
@@ -130,11 +164,6 @@ function Template({ items, ...menu }: Props): React.Node {
|
||||
return <Separator key={index} />;
|
||||
}
|
||||
|
||||
if (item.type === "heading") {
|
||||
return <Header>{item.title}</Header>;
|
||||
}
|
||||
|
||||
console.warn("Unrecognized menu item", item);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@@ -1,26 +1,23 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { Portal } from "react-portal";
|
||||
import { Menu } from "reakit/Menu";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import usePrevious from "hooks/usePrevious";
|
||||
import {
|
||||
fadeIn,
|
||||
fadeAndSlideUp,
|
||||
fadeAndSlideDown,
|
||||
mobileContextMenu,
|
||||
} from "styles/animations";
|
||||
fadeAndScaleIn,
|
||||
fadeAndSlideIn,
|
||||
} from "shared/styles/animations";
|
||||
import usePrevious from "hooks/usePrevious";
|
||||
|
||||
type Props = {|
|
||||
"aria-label": string,
|
||||
visible?: boolean,
|
||||
placement?: string,
|
||||
animating?: boolean,
|
||||
children: React.Node,
|
||||
onOpen?: () => void,
|
||||
onClose?: () => void,
|
||||
|};
|
||||
type Props = {
|
||||
"aria-label": string;
|
||||
visible?: boolean;
|
||||
animating?: boolean;
|
||||
children: React.ReactNode;
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export default function ContextMenu({
|
||||
children,
|
||||
@@ -46,25 +43,13 @@ export default function ContextMenu({
|
||||
return (
|
||||
<>
|
||||
<Menu hideOnClickOutside preventBodyScroll {...rest}>
|
||||
{(props) => {
|
||||
// kind of hacky, but this is an effective way of telling which way
|
||||
// the menu will _actually_ be placed when taking into account screen
|
||||
// positioning.
|
||||
const topAnchor = props.style.top === "0";
|
||||
const rightAnchor = props.placement === "bottom-end";
|
||||
|
||||
return (
|
||||
<Position {...props}>
|
||||
<Background
|
||||
dir="auto"
|
||||
topAnchor={topAnchor}
|
||||
rightAnchor={rightAnchor}
|
||||
>
|
||||
{rest.visible || rest.animating ? children : null}
|
||||
</Background>
|
||||
</Position>
|
||||
);
|
||||
}}
|
||||
{(props) => (
|
||||
<Position {...props}>
|
||||
<Background dir="auto">
|
||||
{rest.visible || rest.animating ? children : null}
|
||||
</Background>
|
||||
</Position>
|
||||
)}
|
||||
</Menu>
|
||||
{(rest.visible || rest.animating) && (
|
||||
<Portal>
|
||||
@@ -105,7 +90,7 @@ const Position = styled.div`
|
||||
`;
|
||||
|
||||
const Background = styled.div`
|
||||
animation: ${mobileContextMenu} 200ms ease;
|
||||
animation: ${fadeAndSlideIn} 200ms ease;
|
||||
transform-origin: 50% 100%;
|
||||
max-width: 100%;
|
||||
background: ${(props) => props.theme.menuBackground};
|
||||
@@ -123,10 +108,9 @@ const Background = styled.div`
|
||||
}
|
||||
|
||||
${breakpoint("tablet")`
|
||||
animation: ${(props) =>
|
||||
props.topAnchor ? fadeAndSlideDown : fadeAndSlideUp} 200ms ease;
|
||||
animation: ${fadeAndScaleIn} 200ms ease;
|
||||
transform-origin: ${(props) =>
|
||||
props.rightAnchor === "bottom-end" ? "75%" : "25%"} 0;
|
||||
props.left !== undefined ? "25%" : "75%"} 0;
|
||||
max-width: 276px;
|
||||
background: ${(props) => props.theme.menuBackground};
|
||||
box-shadow: ${(props) => props.theme.menuShadow};
|
||||
@@ -1,16 +1,17 @@
|
||||
// @flow
|
||||
import copy from "copy-to-clipboard";
|
||||
import * as React from "react";
|
||||
|
||||
import { SyntheticEvent } from "react";
|
||||
|
||||
type Props = {
|
||||
text: string,
|
||||
children?: React.Node,
|
||||
onClick?: () => void,
|
||||
onCopy: () => void,
|
||||
text: string;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
onCopy: () => void;
|
||||
};
|
||||
|
||||
class CopyToClipboard extends React.PureComponent<Props> {
|
||||
onClick = (ev: SyntheticEvent<>) => {
|
||||
onClick = (ev: SyntheticEvent) => {
|
||||
const { text, onCopy, children } = this.props;
|
||||
const elem = React.Children.only(children);
|
||||
copy(text, {
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
delay?: number,
|
||||
children: React.Node,
|
||||
delay?: number;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export default function DelayedMount({ delay = 250, children }: Props) {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Divider = styled.hr`
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import {
|
||||
ArchiveIcon,
|
||||
@@ -16,11 +15,11 @@ import CollectionIcon from "components/CollectionIcon";
|
||||
import useStores from "hooks/useStores";
|
||||
import { collectionUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
children?: React.Node,
|
||||
onlyText: boolean,
|
||||
|};
|
||||
type Props = {
|
||||
document: Document;
|
||||
children?: React.ReactNode;
|
||||
onlyText: boolean;
|
||||
};
|
||||
|
||||
function useCategory(document) {
|
||||
const { t } = useTranslation();
|
||||
+18
-13
@@ -1,10 +1,10 @@
|
||||
// @flow
|
||||
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
|
||||
import { action, observable } from "mobx";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { CloseIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { type Match, Redirect, type RouterHistory } from "react-router-dom";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import { Match, RouterHistory } from "react-router-dom";
|
||||
import { Waypoint } from "react-waypoint";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -15,24 +15,29 @@ import RevisionsStore from "stores/RevisionsStore";
|
||||
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import PlaceholderList from "components/List/Placeholder";
|
||||
import { ListPlaceholder } from "components/LoadingPlaceholder";
|
||||
import Revision from "./components/Revision";
|
||||
import { documentHistoryUrl, documentUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
match: Match,
|
||||
documents: DocumentsStore,
|
||||
revisions: RevisionsStore,
|
||||
history: RouterHistory,
|
||||
match: Match;
|
||||
documents: DocumentsStore;
|
||||
revisions: RevisionsStore;
|
||||
history: RouterHistory;
|
||||
};
|
||||
|
||||
@observer
|
||||
class DocumentHistory extends React.Component<Props> {
|
||||
@observable isLoaded: boolean = false;
|
||||
@observable isFetching: boolean = false;
|
||||
@observable offset: number = 0;
|
||||
@observable allowLoadMore: boolean = true;
|
||||
@observable redirectTo: ?string;
|
||||
@observable
|
||||
isLoaded: boolean = false;
|
||||
@observable
|
||||
isFetching: boolean = false;
|
||||
@observable
|
||||
offset: number = 0;
|
||||
@observable
|
||||
allowLoadMore: boolean = true;
|
||||
@observable
|
||||
redirectTo: string | undefined | null;
|
||||
|
||||
async componentDidMount() {
|
||||
await this.loadMoreResults();
|
||||
@@ -120,7 +125,7 @@ class DocumentHistory extends React.Component<Props> {
|
||||
</Header>
|
||||
{showLoading ? (
|
||||
<Loading>
|
||||
<PlaceholderList count={5} />
|
||||
<ListPlaceholder count={5} />
|
||||
</Loading>
|
||||
) : (
|
||||
<ArrowKeyNavigation
|
||||
+6
-7
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { format } from "date-fns";
|
||||
import * as React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
@@ -10,16 +9,16 @@ import Avatar from "components/Avatar";
|
||||
import Flex from "components/Flex";
|
||||
import Time from "components/Time";
|
||||
import RevisionMenu from "menus/RevisionMenu";
|
||||
import { type Theme } from "types";
|
||||
import { Theme } from "types";
|
||||
|
||||
import { documentHistoryUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
theme: Theme,
|
||||
showMenu: boolean,
|
||||
selected: boolean,
|
||||
document: Document,
|
||||
revision: Revision,
|
||||
theme: Theme;
|
||||
showMenu: boolean;
|
||||
selected: boolean;
|
||||
document: Document;
|
||||
revision: Revision;
|
||||
};
|
||||
|
||||
class RevisionListItem extends React.Component<Props> {
|
||||
@@ -1,3 +1,2 @@
|
||||
// @flow
|
||||
import DocumentHistory from "./DocumentHistory";
|
||||
export default DocumentHistory;
|
||||
@@ -1,18 +1,17 @@
|
||||
// @flow
|
||||
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
|
||||
import * as React from "react";
|
||||
import Document from "models/Document";
|
||||
import DocumentListItem from "components/DocumentListItem";
|
||||
|
||||
type Props = {|
|
||||
documents: Document[],
|
||||
limit?: number,
|
||||
showCollection?: boolean,
|
||||
showPublished?: boolean,
|
||||
showPin?: boolean,
|
||||
showDraft?: boolean,
|
||||
showTemplate?: boolean,
|
||||
|};
|
||||
type Props = {
|
||||
documents: Document[];
|
||||
limit?: number;
|
||||
showCollection?: boolean;
|
||||
showPublished?: boolean;
|
||||
showPin?: boolean;
|
||||
showDraft?: boolean;
|
||||
showTemplate?: boolean;
|
||||
};
|
||||
|
||||
export default function DocumentList({ limit, documents, ...rest }: Props) {
|
||||
const items = limit ? documents.splice(0, limit) : documents;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { PlusIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
@@ -15,24 +14,23 @@ import Flex from "components/Flex";
|
||||
import Highlight from "components/Highlight";
|
||||
import StarButton, { AnimatedStar } from "components/Star";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import useBoolean from "hooks/useBoolean";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useCurrentUser from "hooks/useCurrentUser";
|
||||
import useStores from "hooks/useStores";
|
||||
import DocumentMenu from "menus/DocumentMenu";
|
||||
import { newDocumentUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
highlight?: ?string,
|
||||
context?: ?string,
|
||||
showNestedDocuments?: boolean,
|
||||
showCollection?: boolean,
|
||||
showPublished?: boolean,
|
||||
showPin?: boolean,
|
||||
showDraft?: boolean,
|
||||
showTemplate?: boolean,
|
||||
|};
|
||||
type Props = {
|
||||
document: Document;
|
||||
highlight?: string | null;
|
||||
context?: string | null;
|
||||
showNestedDocuments?: boolean;
|
||||
showCollection?: boolean;
|
||||
showPublished?: boolean;
|
||||
showPin?: boolean;
|
||||
showDraft?: boolean;
|
||||
showTemplate?: boolean;
|
||||
};
|
||||
|
||||
const SEARCH_RESULT_REGEX = /<b\b[^>]*>(.*?)<\/b>/gi;
|
||||
|
||||
@@ -47,7 +45,7 @@ function DocumentListItem(props: Props, ref) {
|
||||
const { policies } = useStores();
|
||||
const currentUser = useCurrentUser();
|
||||
const currentTeam = useCurrentTeam();
|
||||
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
|
||||
const [menuOpen, setMenuOpen] = React.useState(false);
|
||||
const {
|
||||
document,
|
||||
showNestedDocuments,
|
||||
@@ -144,8 +142,8 @@ function DocumentListItem(props: Props, ref) {
|
||||
<DocumentMenu
|
||||
document={document}
|
||||
showPin={showPin}
|
||||
onOpen={handleMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
onOpen={() => setMenuOpen(true)}
|
||||
onClose={() => setMenuOpen(false)}
|
||||
modal={false}
|
||||
/>
|
||||
</Actions>
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -6,10 +5,8 @@ import { Link } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import Document from "models/Document";
|
||||
import DocumentBreadcrumb from "components/DocumentBreadcrumb";
|
||||
import DocumentTasks from "components/DocumentTasks";
|
||||
import Flex from "components/Flex";
|
||||
import Time from "components/Time";
|
||||
import useCurrentUser from "hooks/useCurrentUser";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
const Container = styled(Flex)`
|
||||
@@ -31,15 +28,15 @@ const Modified = styled.span`
|
||||
font-weight: ${(props) => (props.highlight ? "600" : "400")};
|
||||
`;
|
||||
|
||||
type Props = {|
|
||||
showCollection?: boolean,
|
||||
showPublished?: boolean,
|
||||
showLastViewed?: boolean,
|
||||
showNestedDocuments?: boolean,
|
||||
document: Document,
|
||||
children: React.Node,
|
||||
to?: string,
|
||||
|};
|
||||
type Props = {
|
||||
showCollection?: boolean;
|
||||
showPublished?: boolean;
|
||||
showLastViewed?: boolean;
|
||||
showNestedDocuments?: boolean;
|
||||
document: Document;
|
||||
children: React.ReactNode;
|
||||
to?: string;
|
||||
};
|
||||
|
||||
function DocumentMeta({
|
||||
showPublished,
|
||||
@@ -52,9 +49,7 @@ function DocumentMeta({
|
||||
...rest
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { collections } = useStores();
|
||||
const user = useCurrentUser();
|
||||
|
||||
const { collections, auth } = useStores();
|
||||
const {
|
||||
modifiedSinceViewed,
|
||||
updatedAt,
|
||||
@@ -65,8 +60,6 @@ function DocumentMeta({
|
||||
deletedAt,
|
||||
isDraft,
|
||||
lastViewedAt,
|
||||
isTasks,
|
||||
isTemplate,
|
||||
} = document;
|
||||
|
||||
// Prevent meta information from displaying if updatedBy is not available.
|
||||
@@ -75,8 +68,6 @@ function DocumentMeta({
|
||||
return null;
|
||||
}
|
||||
|
||||
const collection = collections.get(document.collectionId);
|
||||
const lastUpdatedByCurrentUser = user.id === updatedBy.id;
|
||||
let content;
|
||||
|
||||
if (deletedAt) {
|
||||
@@ -111,16 +102,14 @@ function DocumentMeta({
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Modified highlight={modifiedSinceViewed && !lastUpdatedByCurrentUser}>
|
||||
<Modified highlight={modifiedSinceViewed}>
|
||||
{t("updated")} <Time dateTime={updatedAt} addSuffix />
|
||||
</Modified>
|
||||
);
|
||||
}
|
||||
|
||||
const nestedDocumentsCount = collection
|
||||
? collection.getDocumentChildren(document.id).length
|
||||
: 0;
|
||||
const canShowProgressBar = isTasks && !isTemplate;
|
||||
const collection = collections.get(document.collectionId);
|
||||
const updatedByMe = auth.user && auth.user.id === updatedBy.id;
|
||||
|
||||
const timeSinceNow = () => {
|
||||
if (isDraft || !showLastViewed) {
|
||||
@@ -141,9 +130,13 @@ function DocumentMeta({
|
||||
);
|
||||
};
|
||||
|
||||
const nestedDocumentsCount = collection
|
||||
? collection.getDocumentChildren(document.id).length
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<Container align="center" rtl={document.dir === "rtl"} {...rest} dir="ltr">
|
||||
{lastUpdatedByCurrentUser ? t("You") : updatedBy.name}
|
||||
{updatedByMe ? t("You") : updatedBy.name}
|
||||
{to ? <Link to={to}>{content}</Link> : content}
|
||||
{showCollection && collection && (
|
||||
<span>
|
||||
@@ -155,17 +148,11 @@ function DocumentMeta({
|
||||
)}
|
||||
{showNestedDocuments && nestedDocumentsCount > 0 && (
|
||||
<span>
|
||||
• {nestedDocumentsCount}{" "}
|
||||
· {nestedDocumentsCount}{" "}
|
||||
{t("nested document", { count: nestedDocumentsCount })}
|
||||
</span>
|
||||
)}
|
||||
{timeSinceNow()}
|
||||
{canShowProgressBar && (
|
||||
<>
|
||||
•
|
||||
<DocumentTasks document={document} />
|
||||
</>
|
||||
)}
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { useObserver } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -8,14 +7,14 @@ import Document from "models/Document";
|
||||
import DocumentMeta from "components/DocumentMeta";
|
||||
import DocumentViews from "components/DocumentViews";
|
||||
import Popover from "components/Popover";
|
||||
import useStores from "../hooks/useStores";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
isDraft: boolean,
|
||||
to?: string,
|
||||
rtl?: boolean,
|
||||
|};
|
||||
type Props = {
|
||||
document: Document;
|
||||
isDraft: boolean;
|
||||
to?: string;
|
||||
rtl?: boolean;
|
||||
};
|
||||
|
||||
function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
|
||||
const { views } = useStores();
|
||||
@@ -42,7 +41,7 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
|
||||
<PopoverDisclosure {...popover}>
|
||||
{(props) => (
|
||||
<>
|
||||
•
|
||||
·
|
||||
<a {...props}>
|
||||
{t("Viewed by")}{" "}
|
||||
{onlyYou
|
||||
@@ -62,7 +61,7 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const Meta = styled(DocumentMeta)`
|
||||
const Meta = styled(DocumentMeta)<{ rtl: boolean }>`
|
||||
justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")};
|
||||
margin: -12px 0 2em 0;
|
||||
font-size: 14px;
|
||||
@@ -1,59 +0,0 @@
|
||||
// @flow
|
||||
import { DoneIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import CircularProgressBar from "components/CircularProgressBar";
|
||||
import usePrevious from "../hooks/usePrevious";
|
||||
import Document from "../models/Document";
|
||||
import { bounceIn } from "styles/animations";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
|};
|
||||
|
||||
function getMessage(t, total, completed) {
|
||||
if (completed === 0) {
|
||||
return t(`{{ total }} task`, { total, count: total });
|
||||
} else if (completed === total) {
|
||||
return t(`{{ completed }} task done`, { completed, count: completed });
|
||||
} else {
|
||||
return t(`{{ completed }} of {{ total }} tasks`, {
|
||||
total,
|
||||
completed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function DocumentTasks({ document }: Props) {
|
||||
const { tasks, tasksPercentage } = document;
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { completed, total } = tasks;
|
||||
const done = completed === total;
|
||||
const previousDone = usePrevious(done);
|
||||
const message = getMessage(t, total, completed);
|
||||
|
||||
return (
|
||||
<>
|
||||
{completed === total ? (
|
||||
<Done
|
||||
color={theme.primary}
|
||||
size={20}
|
||||
$animated={done && previousDone === false}
|
||||
/>
|
||||
) : (
|
||||
<CircularProgressBar percentage={tasksPercentage} />
|
||||
)}
|
||||
{message}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const Done = styled(DoneIcon)`
|
||||
margin: -1px;
|
||||
animation: ${(props) => (props.$animated ? bounceIn : "none")} 600ms;
|
||||
transform-origin: center center;
|
||||
`;
|
||||
|
||||
export default DocumentTasks;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { sortBy } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
@@ -10,10 +9,10 @@ import ListItem from "components/List/Item";
|
||||
import PaginatedList from "components/PaginatedList";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
isOpen?: boolean,
|
||||
|};
|
||||
type Props = {
|
||||
document: Document;
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
function DocumentViews({ document, isOpen }: Props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -36,9 +35,10 @@ function DocumentViews({ document, isOpen }: Props) {
|
||||
(view) => !presentIds.includes(view.user.id)
|
||||
);
|
||||
|
||||
const users = React.useMemo(() => sortedViews.map((v) => v.user), [
|
||||
sortedViews,
|
||||
]);
|
||||
const users = React.useMemo(
|
||||
() => sortedViews.map((v) => v.user),
|
||||
[sortedViews]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1,67 +1,73 @@
|
||||
// @flow
|
||||
import { lighten } from "polished";
|
||||
import * as React from "react";
|
||||
import { SyntheticEvent } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { withRouter, type RouterHistory } from "react-router-dom";
|
||||
import { withRouter, RouterHistory } from "react-router-dom";
|
||||
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import { light } from "shared/theme";
|
||||
import { light } from "shared/styles/theme";
|
||||
import UiStore from "stores/UiStore";
|
||||
import ErrorBoundary from "components/ErrorBoundary";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import embeds from "../embeds";
|
||||
import useMediaQuery from "hooks/useMediaQuery";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import { type Theme } from "types";
|
||||
import { Theme } from "types";
|
||||
import { isModKey } from "utils/keyboard";
|
||||
import { uploadFile } from "utils/uploadFile";
|
||||
import { isInternalUrl } from "utils/urls";
|
||||
|
||||
const RichMarkdownEditor = React.lazy(() =>
|
||||
import(/* webpackChunkName: "rich-markdown-editor" */ "rich-markdown-editor")
|
||||
const RichMarkdownEditor = React.lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "rich-markdown-editor" */ "rich-markdown-editor"
|
||||
)
|
||||
);
|
||||
|
||||
const EMPTY_ARRAY = [];
|
||||
|
||||
export type Props = {|
|
||||
id?: string,
|
||||
value?: string,
|
||||
defaultValue?: string,
|
||||
readOnly?: boolean,
|
||||
grow?: boolean,
|
||||
disableEmbeds?: boolean,
|
||||
ui?: UiStore,
|
||||
shareId?: ?string,
|
||||
autoFocus?: boolean,
|
||||
template?: boolean,
|
||||
placeholder?: string,
|
||||
maxLength?: number,
|
||||
scrollTo?: string,
|
||||
theme?: Theme,
|
||||
handleDOMEvents?: Object,
|
||||
readOnlyWriteCheckboxes?: boolean,
|
||||
onBlur?: (event: SyntheticEvent<>) => any,
|
||||
onFocus?: (event: SyntheticEvent<>) => any,
|
||||
onPublish?: (event: SyntheticEvent<>) => any,
|
||||
onSave?: ({ done?: boolean, autosave?: boolean, publish?: boolean }) => any,
|
||||
onCancel?: () => any,
|
||||
onDoubleClick?: () => any,
|
||||
onChange?: (getValue: () => string) => any,
|
||||
onSearchLink?: (title: string) => any,
|
||||
onHoverLink?: (event: MouseEvent) => any,
|
||||
onCreateLink?: (title: string) => Promise<string>,
|
||||
onImageUploadStart?: () => any,
|
||||
onImageUploadStop?: () => any,
|
||||
|};
|
||||
export type Props = {
|
||||
id?: string;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
readOnly?: boolean;
|
||||
grow?: boolean;
|
||||
disableEmbeds?: boolean;
|
||||
ui?: UiStore;
|
||||
shareId?: string | null;
|
||||
autoFocus?: boolean;
|
||||
template?: boolean;
|
||||
placeholder?: string;
|
||||
maxLength?: number;
|
||||
scrollTo?: string;
|
||||
theme?: Theme;
|
||||
handleDOMEvents?: any;
|
||||
readOnlyWriteCheckboxes?: boolean;
|
||||
onBlur?: (event: SyntheticEvent) => any;
|
||||
onFocus?: (event: SyntheticEvent) => any;
|
||||
onPublish?: (event: SyntheticEvent) => any;
|
||||
onSave?: (a: {
|
||||
done?: boolean;
|
||||
autosave?: boolean;
|
||||
publish?: boolean;
|
||||
}) => any;
|
||||
onCancel?: () => any;
|
||||
onDoubleClick?: () => any;
|
||||
onChange?: (getValue: () => string) => any;
|
||||
onSearchLink?: (title: string) => any;
|
||||
onHoverLink?: (event: MouseEvent) => any;
|
||||
onCreateLink?: (title: string) => Promise<string>;
|
||||
onImageUploadStart?: () => any;
|
||||
onImageUploadStop?: () => any;
|
||||
};
|
||||
|
||||
type PropsWithRef = Props & {
|
||||
forwardedRef: React.Ref<any>,
|
||||
history: RouterHistory,
|
||||
forwardedRef: React.Ref<any>;
|
||||
history: RouterHistory;
|
||||
};
|
||||
|
||||
function Editor(props: PropsWithRef) {
|
||||
const { id, shareId, history } = props;
|
||||
const { id, ui, shareId, history } = props;
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
const isPrinting = useMediaQuery("print");
|
||||
|
||||
const onUploadImage = React.useCallback(
|
||||
@@ -108,9 +114,11 @@ function Editor(props: PropsWithRef) {
|
||||
|
||||
const onShowToast = React.useCallback(
|
||||
(message: string) => {
|
||||
showToast(message);
|
||||
if (ui) {
|
||||
ui.showToast(message);
|
||||
}
|
||||
},
|
||||
[showToast]
|
||||
[ui]
|
||||
);
|
||||
|
||||
const dictionary = React.useMemo(() => {
|
||||
@@ -148,7 +156,6 @@ function Editor(props: PropsWithRef) {
|
||||
hr: t("Divider"),
|
||||
image: t("Image"),
|
||||
imageUploadError: t("Sorry, an error occurred uploading the image"),
|
||||
imageCaptionPlaceholder: t("Write a caption"),
|
||||
info: t("Info"),
|
||||
infoNotice: t("Info notice"),
|
||||
link: t("Link"),
|
||||
@@ -260,6 +267,6 @@ const Span = styled.span`
|
||||
|
||||
const EditorWithRouterAndTheme = withRouter(withTheme(Editor));
|
||||
|
||||
export default React.forwardRef<Props, typeof Editor>((props, ref) => (
|
||||
export default React.forwardRef<typeof Editor>((props, ref) => (
|
||||
<EditorWithRouterAndTheme {...props} forwardedRef={ref} />
|
||||
));
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Empty = styled.p`
|
||||
@@ -1,9 +1,7 @@
|
||||
// @flow
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { withTranslation, type TFunction, Trans } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Button from "components/Button";
|
||||
import CenteredContent from "components/CenteredContent";
|
||||
@@ -12,18 +10,19 @@ import PageTitle from "components/PageTitle";
|
||||
import { githubIssuesUrl } from "../../shared/utils/routeHelpers";
|
||||
import env from "env";
|
||||
|
||||
type Props = {|
|
||||
children: React.Node,
|
||||
reloadOnChunkMissing?: boolean,
|
||||
t: TFunction,
|
||||
|};
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
reloadOnChunkMissing?: boolean;
|
||||
};
|
||||
|
||||
@observer
|
||||
class ErrorBoundary extends React.Component<Props> {
|
||||
@observable error: ?Error;
|
||||
@observable showDetails: boolean = false;
|
||||
@observable
|
||||
error: Error | undefined | null;
|
||||
@observable
|
||||
showDetails: boolean = false;
|
||||
|
||||
componentDidCatch(error: Error, info: Object) {
|
||||
componentDidCatch(error: Error, info: any) {
|
||||
this.error = error;
|
||||
console.error(error);
|
||||
|
||||
@@ -57,8 +56,6 @@ class ErrorBoundary extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
if (this.error) {
|
||||
const error = this.error;
|
||||
const isReported = !!env.SENTRY_DSN && env.DEPLOYMENT === "hosted";
|
||||
@@ -67,21 +64,15 @@ class ErrorBoundary extends React.Component<Props> {
|
||||
if (isChunkError) {
|
||||
return (
|
||||
<CenteredContent>
|
||||
<PageTitle title={t("Module failed to load")} />
|
||||
<h1>
|
||||
<Trans>Loading Failed</Trans>
|
||||
</h1>
|
||||
<PageTitle title="Module failed to load" />
|
||||
<h1>Loading Failed</h1>
|
||||
<HelpText>
|
||||
<Trans>
|
||||
Sorry, part of the application failed to load. This may be
|
||||
because it was updated since you opened the tab or because of a
|
||||
failed network request. Please try reloading.
|
||||
</Trans>
|
||||
Sorry, part of the application failed to load. This may be because
|
||||
it was updated since you opened the tab or because of a failed
|
||||
network request. Please try reloading.
|
||||
</HelpText>
|
||||
<p>
|
||||
<Button onClick={this.handleReload}>
|
||||
<Trans>Reload</Trans>
|
||||
</Button>
|
||||
<Button onClick={this.handleReload}>Reload</Button>
|
||||
</p>
|
||||
</CenteredContent>
|
||||
);
|
||||
@@ -89,32 +80,23 @@ class ErrorBoundary extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<CenteredContent>
|
||||
<PageTitle title={t("Something Unexpected Happened")} />
|
||||
<h1>
|
||||
<Trans>Something Unexpected Happened</Trans>
|
||||
</h1>
|
||||
<PageTitle title="Something Unexpected Happened" />
|
||||
<h1>Something Unexpected Happened</h1>
|
||||
<HelpText>
|
||||
<Trans
|
||||
defaults="Sorry, an unrecoverable error occurred{{notified}}. Please try reloading the page, it may have been a temporary glitch."
|
||||
values={{
|
||||
notified: isReported
|
||||
? ` – ${t("our engineers have been notified")}`
|
||||
: undefined,
|
||||
}}
|
||||
/>
|
||||
Sorry, an unrecoverable error occurred
|
||||
{isReported && " – our engineers have been notified"}. Please try
|
||||
reloading the page, it may have been a temporary glitch.
|
||||
</HelpText>
|
||||
{this.showDetails && <Pre>{error.toString()}</Pre>}
|
||||
<p>
|
||||
<Button onClick={this.handleReload}>
|
||||
<Trans>Reload</Trans>
|
||||
</Button>{" "}
|
||||
<Button onClick={this.handleReload}>Reload</Button>{" "}
|
||||
{this.showDetails ? (
|
||||
<Button onClick={this.handleReportBug} neutral>
|
||||
<Trans>Report a Bug</Trans>…
|
||||
Report a Bug…
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={this.handleShowDetails} neutral>
|
||||
<Trans>Show Detail</Trans>…
|
||||
Show Details…
|
||||
</Button>
|
||||
)}
|
||||
</p>
|
||||
@@ -133,4 +115,4 @@ const Pre = styled.pre`
|
||||
white-space: pre-wrap;
|
||||
`;
|
||||
|
||||
export default withTranslation()<ErrorBoundary>(ErrorBoundary);
|
||||
export default ErrorBoundary;
|
||||
@@ -1,14 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { SyntheticEvent } from "react";
|
||||
|
||||
type Props = {
|
||||
children: React.Node,
|
||||
className?: string,
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function EventBoundary({ children, className }: Props) {
|
||||
const handleClick = React.useCallback((event: SyntheticEvent<>) => {
|
||||
const handleClick = React.useCallback((event: SyntheticEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
@@ -6,13 +5,15 @@ import User from "models/User";
|
||||
import Avatar from "components/Avatar";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {|
|
||||
users: User[],
|
||||
size?: number,
|
||||
overflow: number,
|
||||
onClick?: (event: SyntheticEvent<>) => mixed,
|
||||
renderAvatar?: (user: User) => React.Node,
|
||||
|};
|
||||
import { SyntheticEvent } from "react";
|
||||
|
||||
type Props = {
|
||||
users: User[];
|
||||
size?: number;
|
||||
overflow: number;
|
||||
onClick?: (event: SyntheticEvent) => unknown;
|
||||
renderAvatar?: (user: User) => React.ReactNode;
|
||||
};
|
||||
|
||||
function Facepile({
|
||||
users,
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import { fadeIn } from "styles/animations";
|
||||
import { fadeIn } from "shared/styles/animations";
|
||||
|
||||
const Fade = styled.span`
|
||||
animation: ${fadeIn} ${(props) => props.timing || "250ms"} ease-in-out;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { find } from "lodash";
|
||||
import * as React from "react";
|
||||
import { useMenuState, MenuButton } from "reakit/Menu";
|
||||
@@ -8,20 +7,20 @@ import ContextMenu from "components/ContextMenu";
|
||||
import MenuItem from "components/ContextMenu/MenuItem";
|
||||
import HelpText from "components/HelpText";
|
||||
|
||||
type TFilterOption = {|
|
||||
key: string,
|
||||
label: string,
|
||||
note?: string,
|
||||
|};
|
||||
type TFilterOption = {
|
||||
key: string;
|
||||
label: string;
|
||||
note?: string;
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
options: TFilterOption[],
|
||||
activeKey: ?string,
|
||||
defaultLabel?: string,
|
||||
selectedPrefix?: string,
|
||||
className?: string,
|
||||
onSelect: (key: ?string) => void,
|
||||
|};
|
||||
type Props = {
|
||||
options: TFilterOption[];
|
||||
activeKey: string | undefined | null;
|
||||
defaultLabel?: string;
|
||||
selectedPrefix?: string;
|
||||
className?: string;
|
||||
onSelect: (key?: string | null) => void;
|
||||
};
|
||||
|
||||
const FilterOptions = ({
|
||||
options,
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -8,7 +7,6 @@ type JustifyValues =
|
||||
| "space-between"
|
||||
| "flex-start"
|
||||
| "flex-end";
|
||||
|
||||
type AlignValues =
|
||||
| "stretch"
|
||||
| "center"
|
||||
@@ -16,36 +14,26 @@ type AlignValues =
|
||||
| "flex-start"
|
||||
| "flex-end";
|
||||
|
||||
type Props = {|
|
||||
column?: ?boolean,
|
||||
shrink?: ?boolean,
|
||||
align?: AlignValues,
|
||||
justify?: JustifyValues,
|
||||
auto?: ?boolean,
|
||||
className?: string,
|
||||
children?: React.Node,
|
||||
role?: string,
|
||||
gap?: number,
|
||||
|};
|
||||
type Props = {
|
||||
column?: boolean | null;
|
||||
shrink?: boolean | null;
|
||||
align?: AlignValues;
|
||||
justify?: JustifyValues;
|
||||
auto?: boolean | null;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
role?: string;
|
||||
gap?: number;
|
||||
};
|
||||
|
||||
const Flex = React.forwardRef<Props, HTMLDivElement>((props: Props, ref) => {
|
||||
const { children, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<Container ref={ref} {...restProps}>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
|
||||
const Container = styled.div`
|
||||
const Flex = styled.div<Props>`
|
||||
display: flex;
|
||||
flex: ${({ auto }) => (auto ? "1 1 auto" : "initial")};
|
||||
flex-direction: ${({ column }) => (column ? "column" : "row")};
|
||||
align-items: ${({ align }) => align};
|
||||
justify-content: ${({ justify }) => justify};
|
||||
flex-shrink: ${({ shrink }) => (shrink ? 1 : "initial")};
|
||||
gap: ${({ gap }) => (gap ? `${gap}px` : "initial")};
|
||||
gap: ${({ gap }) => `${gap}px` || "initial"};
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
`;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Empty from "components/Empty";
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
size?: number;
|
||||
fill?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function GithubLogo({ size = 34, fill = "#FFF", className }: Props) {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { GroupIcon } from "outline-icons";
|
||||
@@ -15,17 +14,18 @@ import ListItem from "components/List/Item";
|
||||
import Modal from "components/Modal";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
groupMemberships: GroupMembershipsStore,
|
||||
membership?: CollectionGroupMembership,
|
||||
showFacepile?: boolean,
|
||||
showAvatar?: boolean,
|
||||
renderActions: ({ openMembersModal: () => void }) => React.Node,
|
||||
group: Group;
|
||||
groupMemberships: GroupMembershipsStore;
|
||||
membership?: CollectionGroupMembership;
|
||||
showFacepile?: boolean;
|
||||
showAvatar?: boolean;
|
||||
renderActions: (a: { openMembersModal: () => void }) => React.ReactNode;
|
||||
};
|
||||
|
||||
@observer
|
||||
class GroupListItem extends React.Component<Props> {
|
||||
@observable membersModalOpen: boolean = false;
|
||||
@observable
|
||||
membersModalOpen: boolean = false;
|
||||
|
||||
handleMembersModalOpen = () => {
|
||||
this.membersModalOpen = true;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { Dialog, DialogBackdrop, useDialogState } from "reakit/Dialog";
|
||||
@@ -6,12 +5,12 @@ import styled from "styled-components";
|
||||
import Scrollable from "components/Scrollable";
|
||||
import usePrevious from "hooks/usePrevious";
|
||||
|
||||
type Props = {|
|
||||
children?: React.Node,
|
||||
isOpen: boolean,
|
||||
title?: string,
|
||||
onRequestClose: () => void,
|
||||
|};
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
title?: string;
|
||||
onRequestClose: () => void;
|
||||
};
|
||||
|
||||
const Guide = ({
|
||||
children,
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { throttle } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import { transparentize } from "polished";
|
||||
@@ -8,11 +7,11 @@ import breakpoint from "styled-components-breakpoint";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {|
|
||||
breadcrumb?: React.Node,
|
||||
title: React.Node,
|
||||
actions?: React.Node,
|
||||
|};
|
||||
type Props = {
|
||||
breadcrumb?: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
actions?: React.ReactNode;
|
||||
};
|
||||
|
||||
function Header({ breadcrumb, title, actions }: Props) {
|
||||
const [isScrolled, setScrolled] = React.useState(false);
|
||||
@@ -72,10 +71,6 @@ const Actions = styled(Flex)`
|
||||
flex-basis: 0;
|
||||
min-width: auto;
|
||||
padding-left: 8px;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
position: unset;
|
||||
`};
|
||||
`;
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
@@ -88,12 +83,12 @@ const Wrapper = styled(Flex)`
|
||||
transform: translate3d(0, 0, 0);
|
||||
backdrop-filter: blur(20px);
|
||||
min-height: 56px;
|
||||
justify-content: flex-start;
|
||||
|
||||
@media print {
|
||||
display: none;
|
||||
}
|
||||
|
||||
justify-content: flex-start;
|
||||
${breakpoint("tablet")`
|
||||
padding: ${(props) => (props.isCompact ? "12px" : `24px 24px 0`)};
|
||||
justify-content: "center";
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Heading = styled.h1`
|
||||
@@ -1,7 +1,10 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const HelpText = styled.p`
|
||||
type Props = {
|
||||
small?: boolean;
|
||||
};
|
||||
|
||||
const HelpText = styled.p<Props>`
|
||||
margin-top: 0;
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
font-size: ${(props) => (props.small ? "13px" : "inherit")};
|
||||
@@ -1,13 +1,12 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import replace from "string-replace-to-array";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
highlight: ?string | RegExp,
|
||||
processResult?: (tag: string) => string,
|
||||
text: string,
|
||||
caseSensitive?: boolean,
|
||||
highlight: string | undefined | null | RegExp;
|
||||
processResult?: (tag: string) => string;
|
||||
text: string;
|
||||
caseSensitive?: boolean;
|
||||
};
|
||||
|
||||
function Highlight({
|
||||
@@ -1,23 +1,22 @@
|
||||
// @flow
|
||||
import { inject } from "mobx-react";
|
||||
import { transparentize } from "polished";
|
||||
import * as React from "react";
|
||||
import { Portal } from "react-portal";
|
||||
import styled from "styled-components";
|
||||
import { fadeAndSlideIn } from "shared/styles/animations";
|
||||
import parseDocumentSlug from "shared/utils/parseDocumentSlug";
|
||||
import DocumentsStore from "stores/DocumentsStore";
|
||||
import HoverPreviewDocument from "components/HoverPreviewDocument";
|
||||
import { fadeAndSlideDown } from "styles/animations";
|
||||
import { isInternalUrl } from "utils/urls";
|
||||
|
||||
const DELAY_OPEN = 300;
|
||||
const DELAY_CLOSE = 300;
|
||||
|
||||
type Props = {
|
||||
node: HTMLAnchorElement,
|
||||
event: MouseEvent,
|
||||
documents: DocumentsStore,
|
||||
onClose: () => void,
|
||||
node: HTMLAnchorElement;
|
||||
event: MouseEvent;
|
||||
documents: DocumentsStore;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function HoverPreviewInternal({ node, documents, onClose, event }: Props) {
|
||||
@@ -26,7 +25,7 @@ function HoverPreviewInternal({ node, documents, onClose, event }: Props) {
|
||||
const [isVisible, setVisible] = React.useState(false);
|
||||
const timerClose = React.useRef();
|
||||
const timerOpen = React.useRef();
|
||||
const cardRef = React.useRef<?HTMLDivElement>();
|
||||
const cardRef = React.useRef<HTMLDivElement | undefined | null>();
|
||||
|
||||
const startCloseTimer = () => {
|
||||
stopOpenTimer();
|
||||
@@ -136,7 +135,7 @@ function HoverPreview({ node, ...rest }: Props) {
|
||||
}
|
||||
|
||||
const Animate = styled.div`
|
||||
animation: ${fadeAndSlideDown} 150ms ease;
|
||||
animation: ${fadeAndSlideIn} 150ms ease;
|
||||
|
||||
@media print {
|
||||
display: none;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -9,8 +8,8 @@ import Editor from "components/Editor";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
url: string,
|
||||
children: (React.Node) => React.Node,
|
||||
url: string;
|
||||
children: (a: React.ReactNode) => React.ReactNode;
|
||||
};
|
||||
|
||||
function HoverPreviewDocument({ url, children }: Props) {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import {
|
||||
CollectionIcon,
|
||||
CoinsIcon,
|
||||
@@ -126,12 +125,12 @@ const colors = [
|
||||
"#2F362F",
|
||||
];
|
||||
|
||||
type Props = {|
|
||||
onOpen?: () => void,
|
||||
onChange: (color: string, icon: string) => void,
|
||||
icon: string,
|
||||
color: string,
|
||||
|};
|
||||
type Props = {
|
||||
onOpen?: () => void;
|
||||
onChange: (color: string, icon: string) => void;
|
||||
icon: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
function IconPicker({ onOpen, icon, color, onChange }: Props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -1,14 +1,13 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { cdnPath } from "utils/urls";
|
||||
|
||||
type Props = {|
|
||||
alt: string,
|
||||
src: string,
|
||||
title?: string,
|
||||
width?: number,
|
||||
height?: number,
|
||||
|};
|
||||
type Props = {
|
||||
alt: string;
|
||||
src: string;
|
||||
title?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
export default function Image({ src, alt, ...rest }: Props) {
|
||||
return <img src={cdnPath(src)} alt={alt} {...rest} />;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
@@ -7,6 +6,8 @@ import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
import { SyntheticEvent, ChangeEvent, KeyboardEvent } from "react";
|
||||
|
||||
const RealTextarea = styled.textarea`
|
||||
border: 0;
|
||||
flex: 1;
|
||||
@@ -29,10 +30,6 @@ const RealInput = styled.input`
|
||||
background: none;
|
||||
color: ${(props) => props.theme.text};
|
||||
height: 30px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:disabled,
|
||||
&::placeholder {
|
||||
@@ -88,46 +85,47 @@ export const LabelText = styled.div`
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
export type Props = {|
|
||||
type?: "text" | "email" | "checkbox" | "search" | "textarea",
|
||||
value?: string,
|
||||
label?: string,
|
||||
className?: string,
|
||||
labelHidden?: boolean,
|
||||
flex?: boolean,
|
||||
short?: boolean,
|
||||
margin?: string | number,
|
||||
icon?: React.Node,
|
||||
name?: string,
|
||||
minLength?: number,
|
||||
maxLength?: number,
|
||||
autoFocus?: boolean,
|
||||
autoComplete?: boolean | string,
|
||||
readOnly?: boolean,
|
||||
required?: boolean,
|
||||
disabled?: boolean,
|
||||
placeholder?: string,
|
||||
export type Props = {
|
||||
type?: "text" | "email" | "checkbox" | "search" | "textarea";
|
||||
value?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
labelHidden?: boolean;
|
||||
flex?: boolean;
|
||||
short?: boolean;
|
||||
margin?: string | number;
|
||||
icon?: React.ReactNode;
|
||||
name?: string;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
autoFocus?: boolean;
|
||||
autoComplete?: boolean | string;
|
||||
readOnly?: boolean;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
onChange?: (
|
||||
ev: SyntheticInputEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => mixed,
|
||||
onKeyDown?: (ev: SyntheticKeyboardEvent<HTMLInputElement>) => mixed,
|
||||
onFocus?: (ev: SyntheticEvent<>) => mixed,
|
||||
onBlur?: (ev: SyntheticEvent<>) => mixed,
|
||||
|};
|
||||
ev: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => unknown;
|
||||
onKeyDown?: (ev: KeyboardEvent<HTMLInputElement>) => unknown;
|
||||
onFocus?: (ev: SyntheticEvent) => unknown;
|
||||
onBlur?: (ev: SyntheticEvent) => unknown;
|
||||
};
|
||||
|
||||
@observer
|
||||
class Input extends React.Component<Props> {
|
||||
input: ?HTMLInputElement;
|
||||
@observable focused: boolean = false;
|
||||
input: HTMLInputElement | undefined | null;
|
||||
@observable
|
||||
focused: boolean = false;
|
||||
|
||||
handleBlur = (ev: SyntheticEvent<>) => {
|
||||
handleBlur = (ev: SyntheticEvent) => {
|
||||
this.focused = false;
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur(ev);
|
||||
}
|
||||
};
|
||||
|
||||
handleFocus = (ev: SyntheticEvent<>) => {
|
||||
handleFocus = (ev: SyntheticEvent) => {
|
||||
this.focused = true;
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(ev);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
import Input from "./Input";
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import Editor from "components/Editor";
|
||||
import HelpText from "components/HelpText";
|
||||
import { LabelText, Outline } from "components/Input";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {|
|
||||
label: string,
|
||||
minHeight?: number,
|
||||
maxHeight?: number,
|
||||
readOnly?: boolean,
|
||||
|};
|
||||
|
||||
function InputRich({ label, minHeight, maxHeight, ...rest }: Props) {
|
||||
const [focused, setFocused] = React.useState<boolean>(false);
|
||||
const { ui } = useStores();
|
||||
|
||||
const handleBlur = React.useCallback(() => {
|
||||
setFocused(false);
|
||||
}, []);
|
||||
|
||||
const handleFocus = React.useCallback(() => {
|
||||
setFocused(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<LabelText>{label}</LabelText>
|
||||
<StyledOutline
|
||||
maxHeight={maxHeight}
|
||||
minHeight={minHeight}
|
||||
focused={focused}
|
||||
>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<HelpText>
|
||||
<Trans>Loading editor</Trans>…
|
||||
</HelpText>
|
||||
}
|
||||
>
|
||||
<Editor
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
ui={ui}
|
||||
grow
|
||||
{...rest}
|
||||
/>
|
||||
</React.Suspense>
|
||||
</StyledOutline>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledOutline = styled(Outline)`
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : "0")};
|
||||
max-height: ${({ maxHeight }) => (maxHeight ? `${maxHeight}px` : "auto")};
|
||||
overflow-y: auto;
|
||||
|
||||
> * {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
export default observer(withTheme(InputRich));
|
||||
@@ -0,0 +1,71 @@
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import UiStore from "stores/UiStore";
|
||||
import Editor from "components/Editor";
|
||||
import HelpText from "components/HelpText";
|
||||
import { LabelText, Outline } from "components/Input";
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
minHeight?: number;
|
||||
maxHeight?: number;
|
||||
readOnly?: boolean;
|
||||
ui: UiStore;
|
||||
};
|
||||
|
||||
@observer
|
||||
class InputRich extends React.Component<Props> {
|
||||
@observable
|
||||
editorComponent: React.ComponentType<any>;
|
||||
@observable
|
||||
focused: boolean = false;
|
||||
|
||||
handleBlur = () => {
|
||||
this.focused = false;
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
this.focused = true;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { label, minHeight, maxHeight, ui, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<LabelText>{label}</LabelText>
|
||||
<StyledOutline
|
||||
maxHeight={maxHeight}
|
||||
minHeight={minHeight}
|
||||
focused={this.focused}
|
||||
>
|
||||
<React.Suspense fallback={<HelpText>Loading editor…</HelpText>}>
|
||||
<Editor
|
||||
onBlur={this.handleBlur}
|
||||
onFocus={this.handleFocus}
|
||||
ui={ui}
|
||||
grow
|
||||
{...rest}
|
||||
/>
|
||||
</React.Suspense>
|
||||
</StyledOutline>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const StyledOutline = styled(Outline)`
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : "0")};
|
||||
max-height: ${({ maxHeight }) => (maxHeight ? `${maxHeight}px` : "auto")};
|
||||
overflow-y: auto;
|
||||
|
||||
> * {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
export default inject("ui")(withTheme(InputRich));
|
||||
@@ -1,17 +1,19 @@
|
||||
// @flow
|
||||
import { SearchIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "styled-components";
|
||||
import Input, { type Props as InputProps } from "./Input";
|
||||
import Input from "./Input";
|
||||
|
||||
type Props = {|
|
||||
...InputProps,
|
||||
placeholder?: string,
|
||||
value?: string,
|
||||
onChange: (event: SyntheticInputEvent<>) => mixed,
|
||||
onKeyDown?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => mixed,
|
||||
|};
|
||||
import { Props as InputProps } from "./Input";
|
||||
|
||||
import { ChangeEvent, KeyboardEvent } from "react";
|
||||
|
||||
type Props = {
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
onChange: (event: ChangeEvent) => unknown;
|
||||
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => unknown;
|
||||
} & InputProps;
|
||||
|
||||
export default function InputSearch(props: Props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -1,38 +1,38 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { SearchIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { withTranslation, type TFunction } from "react-i18next";
|
||||
import { SyntheticEvent, ChangeEvent, KeyboardEvent } from "react";
|
||||
import { withTranslation, TFunction } from "react-i18next";
|
||||
import keydown from "react-keydown";
|
||||
import { withRouter, type RouterHistory } from "react-router-dom";
|
||||
import { withRouter, RouteComponentProps } from "react-router-dom";
|
||||
import styled, { withTheme } from "styled-components";
|
||||
import Input from "./Input";
|
||||
import { type Theme } from "types";
|
||||
import { Theme } from "types";
|
||||
import { meta } from "utils/keyboard";
|
||||
import { searchUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
history: RouterHistory,
|
||||
theme: Theme,
|
||||
source: string,
|
||||
placeholder?: string,
|
||||
label?: string,
|
||||
labelHidden?: boolean,
|
||||
collectionId?: string,
|
||||
value: string,
|
||||
onChange: (event: SyntheticInputEvent<>) => mixed,
|
||||
onKeyDown: (event: SyntheticKeyboardEvent<HTMLInputElement>) => mixed,
|
||||
t: TFunction,
|
||||
type Props = RouteComponentProps & {
|
||||
theme: Theme;
|
||||
source: string;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
labelHidden?: boolean;
|
||||
collectionId?: string;
|
||||
value: string;
|
||||
onChange: (event: ChangeEvent) => unknown;
|
||||
onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => unknown;
|
||||
t: TFunction;
|
||||
};
|
||||
|
||||
@observer
|
||||
class InputSearchPage extends React.Component<Props> {
|
||||
input: ?Input;
|
||||
@observable focused: boolean = false;
|
||||
input: Input | undefined | null;
|
||||
@observable
|
||||
focused: boolean = false;
|
||||
|
||||
@keydown(`${meta}+f`)
|
||||
focus(ev: SyntheticEvent<>) {
|
||||
focus(ev: SyntheticEvent) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.input) {
|
||||
@@ -40,7 +40,7 @@ class InputSearchPage extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchInput = (ev: SyntheticInputEvent<>) => {
|
||||
handleSearchInput = (ev: ChangeEvent) => {
|
||||
ev.preventDefault();
|
||||
this.props.history.push(
|
||||
searchUrl(ev.target.value, {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
@@ -31,22 +30,26 @@ const Wrapper = styled.label`
|
||||
max-width: ${(props) => (props.short ? "350px" : "100%")};
|
||||
`;
|
||||
|
||||
export type Option = { label: string, value: string };
|
||||
export type Option = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type Props = {
|
||||
value?: string,
|
||||
label?: string,
|
||||
short?: boolean,
|
||||
className?: string,
|
||||
labelHidden?: boolean,
|
||||
options: Option[],
|
||||
onBlur?: () => void,
|
||||
onFocus?: () => void,
|
||||
value?: string;
|
||||
label?: string;
|
||||
short?: boolean;
|
||||
className?: string;
|
||||
labelHidden?: boolean;
|
||||
options: Option[];
|
||||
onBlur?: () => void;
|
||||
onFocus?: () => void;
|
||||
};
|
||||
|
||||
@observer
|
||||
class InputSelect extends React.Component<Props> {
|
||||
@observable focused: boolean = false;
|
||||
@observable
|
||||
focused: boolean = false;
|
||||
|
||||
handleBlur = () => {
|
||||
this.focused = false;
|
||||
@@ -57,14 +60,8 @@ class InputSelect extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
className,
|
||||
labelHidden,
|
||||
options,
|
||||
short,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { label, className, labelHidden, options, short, ...rest } =
|
||||
this.props;
|
||||
|
||||
const wrappedLabel = <LabelText>{label}</LabelText>;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InputSelect, { type Props, type Option } from "./InputSelect";
|
||||
import InputSelect from "./InputSelect";
|
||||
|
||||
export default function InputSelectPermission(
|
||||
props: $Rest<Props, { options: Array<Option> }>
|
||||
) {
|
||||
import { Props, Option } from "./InputSelect";
|
||||
|
||||
export default function InputSelectPermission(props: Omit<Props, "options">) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Key = styled.kbd`
|
||||
@@ -1,13 +1,12 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {|
|
||||
label: React.Node | string,
|
||||
children: React.Node,
|
||||
|};
|
||||
type Props = {
|
||||
label: React.ReactNode | string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const Labeled = ({ label, children, ...props }: Props) => (
|
||||
<Flex column {...props}>
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { find } from "lodash";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
@@ -1,18 +1,20 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { MenuIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { SyntheticEvent } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { withTranslation, type TFunction } from "react-i18next";
|
||||
import { withTranslation, TFunction } from "react-i18next";
|
||||
|
||||
import keydown from "react-keydown";
|
||||
import {
|
||||
Switch,
|
||||
Route,
|
||||
Redirect,
|
||||
withRouter,
|
||||
type RouterHistory,
|
||||
RouterHistory,
|
||||
} from "react-router-dom";
|
||||
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
@@ -39,24 +41,26 @@ import {
|
||||
} from "utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
documents: DocumentsStore,
|
||||
children?: ?React.Node,
|
||||
actions?: ?React.Node,
|
||||
title?: ?React.Node,
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
history: RouterHistory,
|
||||
policies: PoliciesStore,
|
||||
notifications?: React.Node,
|
||||
i18n: Object,
|
||||
t: TFunction,
|
||||
documents: DocumentsStore;
|
||||
children?: React.ReactNode | null;
|
||||
actions?: React.ReactNode | null;
|
||||
title?: React.ReactNode | null;
|
||||
auth: AuthStore;
|
||||
ui: UiStore;
|
||||
history: RouterHistory;
|
||||
policies: PoliciesStore;
|
||||
notifications?: React.ReactNode;
|
||||
i18n: any;
|
||||
t: TFunction;
|
||||
};
|
||||
|
||||
@observer
|
||||
class Layout extends React.Component<Props> {
|
||||
scrollable: ?HTMLDivElement;
|
||||
@observable redirectTo: ?string;
|
||||
@observable keyboardShortcutsOpen: boolean = false;
|
||||
scrollable: HTMLDivElement | undefined | null;
|
||||
@observable
|
||||
redirectTo: string | undefined | null;
|
||||
@observable
|
||||
keyboardShortcutsOpen: boolean = false;
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.redirectTo) {
|
||||
@@ -79,7 +83,7 @@ class Layout extends React.Component<Props> {
|
||||
};
|
||||
|
||||
@keydown(["t", "/", `${meta}+k`])
|
||||
goToSearch(ev: SyntheticEvent<>) {
|
||||
goToSearch(ev: SyntheticEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.redirectTo = searchUrl();
|
||||
@@ -1,15 +1,14 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {
|
||||
image?: React.Node,
|
||||
title: React.Node,
|
||||
subtitle?: React.Node,
|
||||
actions?: React.Node,
|
||||
border?: boolean,
|
||||
small?: boolean,
|
||||
image?: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
subtitle?: React.ReactNode;
|
||||
actions?: React.ReactNode;
|
||||
border?: boolean;
|
||||
small?: boolean;
|
||||
};
|
||||
|
||||
const ListItem = ({
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const List = styled.ol`
|
||||
@@ -0,0 +1,28 @@
|
||||
import { times } from "lodash";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "components/Flex";
|
||||
import Mask from "components/Mask";
|
||||
|
||||
type Props = {
|
||||
count?: number;
|
||||
};
|
||||
|
||||
const Placeholder = ({ count }: Props) => {
|
||||
return (
|
||||
<Fade>
|
||||
{times(count || 2, (index) => (
|
||||
<Item key={index} column auto>
|
||||
<Mask />
|
||||
</Item>
|
||||
))}
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = styled(Flex)`
|
||||
padding: 15px 0 16px;
|
||||
`;
|
||||
|
||||
export default Placeholder;
|
||||
@@ -1,3 +1,2 @@
|
||||
// @flow
|
||||
import List from "./List";
|
||||
export default List;
|
||||
@@ -1,25 +0,0 @@
|
||||
// @flow
|
||||
import { inject, observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import UiStore from "stores/UiStore";
|
||||
|
||||
type Props = {
|
||||
ui: UiStore,
|
||||
};
|
||||
|
||||
@observer
|
||||
class LoadingIndicator extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.props.ui.enableProgressBar();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.ui.disableProgressBar();
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default inject("ui")(LoadingIndicator);
|
||||
@@ -0,0 +1,16 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
function LoadingIndicator() {
|
||||
const { ui } = useStores();
|
||||
|
||||
React.useEffect(() => {
|
||||
ui.enableProgressBar();
|
||||
return () => ui.disableProgressBar();
|
||||
}, [ui]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default observer(LoadingIndicator);
|
||||
+5
-4
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
|
||||
@@ -11,14 +10,16 @@ const LoadingIndicatorBar = () => {
|
||||
};
|
||||
|
||||
const loadingFrame = keyframes`
|
||||
from { margin-left: -100%; }
|
||||
to { margin-left: 100%; }
|
||||
from {margin-left: -100%; z-index:100;}
|
||||
to {margin-left: 100%; }
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: ${(props) => props.theme.depths.loadingIndicatorBar};
|
||||
|
||||
background-color: #03a9f4;
|
||||
width: 100%;
|
||||
animation: ${loadingFrame} 4s ease-in-out infinite;
|
||||
animation-delay: 250ms;
|
||||
@@ -28,7 +29,7 @@ const Container = styled.div`
|
||||
const Loader = styled.div`
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: ${(props) => props.theme.primary};
|
||||
background-color: #03a9f4;
|
||||
`;
|
||||
|
||||
export default LoadingIndicatorBar;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import LoadingIndicator from "./LoadingIndicator";
|
||||
import LoadingIndicatorBar from "./LoadingIndicatorBar";
|
||||
export default LoadingIndicator;
|
||||
+4
-5
@@ -1,13 +1,12 @@
|
||||
// @flow
|
||||
import { times } from "lodash";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "components/Flex";
|
||||
import PlaceholderText from "components/PlaceholderText";
|
||||
import Mask from "components/Mask";
|
||||
|
||||
type Props = {
|
||||
count?: number,
|
||||
count?: number;
|
||||
};
|
||||
|
||||
const ListPlaceHolder = ({ count }: Props) => {
|
||||
@@ -15,8 +14,8 @@ const ListPlaceHolder = ({ count }: Props) => {
|
||||
<Fade>
|
||||
{times(count || 2, (index) => (
|
||||
<Item key={index} column auto>
|
||||
<PlaceholderText header delay={0.2 * index} />
|
||||
<PlaceholderText delay={0.2 * index} />
|
||||
<Mask header />
|
||||
<Mask />
|
||||
</Item>
|
||||
))}
|
||||
</Fade>
|
||||
+6
-8
@@ -1,22 +1,20 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import DelayedMount from "components/DelayedMount";
|
||||
import Fade from "components/Fade";
|
||||
import Flex from "components/Flex";
|
||||
import PlaceholderText from "components/PlaceholderText";
|
||||
import Mask from "components/Mask";
|
||||
|
||||
export default function PlaceholderDocument(props: Object) {
|
||||
export default function LoadingPlaceholder(props: any) {
|
||||
return (
|
||||
<DelayedMount>
|
||||
<Wrapper>
|
||||
<Flex column auto {...props}>
|
||||
<PlaceholderText height={34} maxWidth={70} />
|
||||
<PlaceholderText delay={0.2} maxWidth={40} />
|
||||
<Mask height={34} />
|
||||
<br />
|
||||
<PlaceholderText delay={0.2} />
|
||||
<PlaceholderText delay={0.4} />
|
||||
<PlaceholderText delay={0.6} />
|
||||
<Mask />
|
||||
<Mask />
|
||||
<Mask />
|
||||
</Flex>
|
||||
</Wrapper>
|
||||
</DelayedMount>
|
||||
@@ -0,0 +1,5 @@
|
||||
import ListPlaceholder from "./ListPlaceholder";
|
||||
import LoadingPlaceholder from "./LoadingPlaceholder";
|
||||
|
||||
export default LoadingPlaceholder;
|
||||
export { ListPlaceholder };
|
||||
@@ -1,20 +1,19 @@
|
||||
// @flow
|
||||
import { format, formatDistanceToNow } from "date-fns";
|
||||
import {
|
||||
format,
|
||||
formatDistanceToNow,
|
||||
enUS,
|
||||
de,
|
||||
faIR,
|
||||
fr,
|
||||
es,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
ptBR,
|
||||
pt,
|
||||
zhCN,
|
||||
zhTW,
|
||||
ru,
|
||||
} from "date-fns/locale";
|
||||
} from "date-fns";
|
||||
|
||||
import * as React from "react";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import useUserLocale from "hooks/useUserLocale";
|
||||
@@ -23,10 +22,8 @@ const locales = {
|
||||
en_US: enUS,
|
||||
de_DE: de,
|
||||
es_ES: es,
|
||||
fa_IR: faIR,
|
||||
fr_FR: fr,
|
||||
it_IT: it,
|
||||
ja_JP: ja,
|
||||
ko_KR: ko,
|
||||
pt_BR: ptBR,
|
||||
pt_PT: pt,
|
||||
@@ -52,11 +49,11 @@ function eachMinute(fn) {
|
||||
}
|
||||
|
||||
type Props = {
|
||||
dateTime: string,
|
||||
children?: React.Node,
|
||||
tooltipDelay?: number,
|
||||
addSuffix?: boolean,
|
||||
shorten?: boolean,
|
||||
dateTime: string;
|
||||
children?: React.ReactNode;
|
||||
tooltipDelay?: number;
|
||||
addSuffix?: boolean;
|
||||
shorten?: boolean;
|
||||
};
|
||||
|
||||
function LocaleTime({
|
||||
@@ -68,7 +65,7 @@ function LocaleTime({
|
||||
}: Props) {
|
||||
const userLocale = useUserLocale();
|
||||
const [_, setMinutesMounted] = React.useState(0); // eslint-disable-line no-unused-vars
|
||||
const callback = React.useRef();
|
||||
const callback = React.useRef<Function>();
|
||||
|
||||
React.useEffect(() => {
|
||||
callback.current = eachMinute(() => {
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { randomInteger } from "shared/random";
|
||||
import { pulsate } from "shared/styles/animations";
|
||||
import Flex from "components/Flex";
|
||||
|
||||
type Props = {
|
||||
header?: boolean;
|
||||
height?: number;
|
||||
minWidth?: number;
|
||||
maxWidth?: number;
|
||||
};
|
||||
|
||||
class Mask extends React.Component<Props> {
|
||||
width: number;
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.width = randomInteger(props.minWidth || 75, props.maxWidth || 100);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Redacted width={this.width} height={this.props.height} />;
|
||||
}
|
||||
}
|
||||
|
||||
const Redacted = styled(Flex)`
|
||||
width: ${(props) => (props.header ? props.width / 2 : props.width)}%;
|
||||
height: ${(props) =>
|
||||
props.height ? props.height : props.header ? 24 : 18}px;
|
||||
margin-bottom: 6px;
|
||||
background-color: ${(props) => props.theme.divider};
|
||||
animation: ${pulsate} 1.3s infinite;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Mask;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { observer } from "mobx-react";
|
||||
import { CloseIcon, BackIcon } from "outline-icons";
|
||||
import { transparentize } from "polished";
|
||||
@@ -7,21 +6,21 @@ import { useTranslation } from "react-i18next";
|
||||
import { Dialog, DialogBackdrop, useDialogState } from "reakit/Dialog";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { fadeAndScaleIn } from "shared/styles/animations";
|
||||
import Flex from "components/Flex";
|
||||
import NudeButton from "components/NudeButton";
|
||||
import Scrollable from "components/Scrollable";
|
||||
import usePrevious from "hooks/usePrevious";
|
||||
import useUnmount from "hooks/useUnmount";
|
||||
import { fadeAndScaleIn } from "styles/animations";
|
||||
|
||||
let openModals = 0;
|
||||
|
||||
type Props = {|
|
||||
children?: React.Node,
|
||||
isOpen: boolean,
|
||||
title?: string,
|
||||
onRequestClose: () => void,
|
||||
|};
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
title?: string;
|
||||
onRequestClose: () => void;
|
||||
};
|
||||
|
||||
const Modal = ({
|
||||
children,
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Notice = styled.p`
|
||||
@@ -1,8 +1,11 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import Notice from "components/Notice";
|
||||
|
||||
export default function AlertNotice({ children }: { children: React.Node }) {
|
||||
export default function AlertNotice({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Notice muted>
|
||||
<svg
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import styled from "styled-components";
|
||||
|
||||
const Notice = styled.p`
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -14,6 +13,6 @@ const Button = styled.button`
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export default React.forwardRef<any, typeof Button>(
|
||||
export default React.forwardRef<typeof Button>(
|
||||
({ size = 24, ...props }, ref) => <Button size={size} {...props} ref={ref} />
|
||||
);
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
size?: number;
|
||||
fill?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function OutlineLogo({ size = 32, fill = "#333", className }: Props) {
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { useTheme } from "styled-components";
|
||||
import useStores from "hooks/useStores";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user