chore: Upgrade flow (#1854)
* wip: upgrade flow * chore: More sealed props improvements * Final fixes
This commit is contained in:
parent
ce2b246e60
commit
32f0589190
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"javascript.validate.enable": false,
|
"javascript.validate.enable": false,
|
||||||
|
"javascript.format.enable": false,
|
||||||
"typescript.validate.enable": false,
|
"typescript.validate.enable": false,
|
||||||
|
"typescript.format.enable": false,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"typescript.format.enable": false
|
|
||||||
}
|
}
|
|
@ -3,13 +3,17 @@ import { observable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import User from "models/User";
|
||||||
import placeholder from "./placeholder.png";
|
import placeholder from "./placeholder.png";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
src: string,
|
src: string,
|
||||||
size: number,
|
size: number,
|
||||||
icon?: React.Node,
|
icon?: React.Node,
|
||||||
};
|
user?: User,
|
||||||
|
onClick?: () => void,
|
||||||
|
className?: string,
|
||||||
|
|};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Avatar extends React.Component<Props> {
|
class Avatar extends React.Component<Props> {
|
||||||
|
|
|
@ -108,8 +108,8 @@ export const Inner = styled.span`
|
||||||
${(props) => props.hasIcon && !props.hasText && "padding: 0 4px;"};
|
${(props) => props.hasIcon && !props.hasText && "padding: 0 4px;"};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {|
|
||||||
type?: string,
|
type?: "button" | "submit",
|
||||||
value?: string,
|
value?: string,
|
||||||
icon?: React.Node,
|
icon?: React.Node,
|
||||||
iconColor?: string,
|
iconColor?: string,
|
||||||
|
@ -118,9 +118,21 @@ export type Props = {
|
||||||
innerRef?: React.ElementRef<any>,
|
innerRef?: React.ElementRef<any>,
|
||||||
disclosure?: boolean,
|
disclosure?: boolean,
|
||||||
neutral?: boolean,
|
neutral?: boolean,
|
||||||
|
danger?: boolean,
|
||||||
|
primary?: boolean,
|
||||||
|
disabled?: boolean,
|
||||||
fullwidth?: boolean,
|
fullwidth?: boolean,
|
||||||
|
autoFocus?: boolean,
|
||||||
|
style?: Object,
|
||||||
|
as?: React.ComponentType<any>,
|
||||||
|
to?: string,
|
||||||
|
onClick?: (event: SyntheticEvent<>) => mixed,
|
||||||
borderOnHover?: boolean,
|
borderOnHover?: boolean,
|
||||||
};
|
|
||||||
|
"data-on"?: string,
|
||||||
|
"data-event-category"?: string,
|
||||||
|
"data-event-action"?: string,
|
||||||
|
|};
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
type = "text",
|
type = "text",
|
||||||
|
|
|
@ -4,15 +4,18 @@ import { VisuallyHidden } from "reakit/VisuallyHidden";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import HelpText from "components/HelpText";
|
import HelpText from "components/HelpText";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {|
|
||||||
checked?: boolean,
|
checked?: boolean,
|
||||||
label?: string,
|
label?: string,
|
||||||
labelHidden?: boolean,
|
labelHidden?: boolean,
|
||||||
className?: string,
|
className?: string,
|
||||||
|
name?: string,
|
||||||
|
disabled?: boolean,
|
||||||
|
onChange: (event: SyntheticInputEvent<HTMLInputElement>) => mixed,
|
||||||
note?: string,
|
note?: string,
|
||||||
short?: boolean,
|
short?: boolean,
|
||||||
small?: boolean,
|
small?: boolean,
|
||||||
};
|
|};
|
||||||
|
|
||||||
const LabelText = styled.span`
|
const LabelText = styled.span`
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
@ -4,13 +4,16 @@ import * as React from "react";
|
||||||
import { MenuItem as BaseMenuItem } from "reakit/Menu";
|
import { MenuItem as BaseMenuItem } from "reakit/Menu";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
onClick?: (SyntheticEvent<>) => void | Promise<void>,
|
onClick?: (SyntheticEvent<>) => void | Promise<void>,
|
||||||
children?: React.Node,
|
children?: React.Node,
|
||||||
selected?: boolean,
|
selected?: boolean,
|
||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
|
to?: string,
|
||||||
|
href?: string,
|
||||||
|
target?: "_blank",
|
||||||
as?: string | React.ComponentType<*>,
|
as?: string | React.ComponentType<*>,
|
||||||
};
|
|};
|
||||||
|
|
||||||
const MenuItem = ({
|
const MenuItem = ({
|
||||||
onClick,
|
onClick,
|
||||||
|
|
|
@ -4,10 +4,15 @@ import * as React from "react";
|
||||||
import Document from "models/Document";
|
import Document from "models/Document";
|
||||||
import DocumentListItem from "components/DocumentListItem";
|
import DocumentListItem from "components/DocumentListItem";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
documents: Document[],
|
documents: Document[],
|
||||||
limit?: number,
|
limit?: number,
|
||||||
};
|
showCollection?: boolean,
|
||||||
|
showPublished?: boolean,
|
||||||
|
showPin?: boolean,
|
||||||
|
showDraft?: boolean,
|
||||||
|
showTemplate?: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
export default function DocumentList({ limit, documents, ...rest }: Props) {
|
export default function DocumentList({ limit, documents, ...rest }: Props) {
|
||||||
const items = limit ? documents.splice(0, limit) : documents;
|
const items = limit ? documents.splice(0, limit) : documents;
|
||||||
|
|
|
@ -23,14 +23,14 @@ const Modified = styled.span`
|
||||||
font-weight: ${(props) => (props.highlight ? "600" : "400")};
|
font-weight: ${(props) => (props.highlight ? "600" : "400")};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
showCollection?: boolean,
|
showCollection?: boolean,
|
||||||
showPublished?: boolean,
|
showPublished?: boolean,
|
||||||
showLastViewed?: boolean,
|
showLastViewed?: boolean,
|
||||||
document: Document,
|
document: Document,
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
to?: string,
|
to?: string,
|
||||||
};
|
|};
|
||||||
|
|
||||||
function DocumentMeta({
|
function DocumentMeta({
|
||||||
showPublished,
|
showPublished,
|
||||||
|
|
|
@ -16,14 +16,31 @@ const RichMarkdownEditor = React.lazy(() => import("rich-markdown-editor"));
|
||||||
|
|
||||||
const EMPTY_ARRAY = [];
|
const EMPTY_ARRAY = [];
|
||||||
|
|
||||||
type Props = {
|
export type Props = {|
|
||||||
id?: string,
|
id?: string,
|
||||||
|
value?: string,
|
||||||
defaultValue?: string,
|
defaultValue?: string,
|
||||||
readOnly?: boolean,
|
readOnly?: boolean,
|
||||||
grow?: boolean,
|
grow?: boolean,
|
||||||
disableEmbeds?: boolean,
|
disableEmbeds?: boolean,
|
||||||
ui?: UiStore,
|
ui?: UiStore,
|
||||||
};
|
autoFocus?: boolean,
|
||||||
|
template?: boolean,
|
||||||
|
placeholder?: string,
|
||||||
|
scrollTo?: string,
|
||||||
|
readOnlyWriteCheckboxes?: boolean,
|
||||||
|
onBlur?: (event: SyntheticEvent<>) => any,
|
||||||
|
onFocus?: (event: SyntheticEvent<>) => any,
|
||||||
|
onPublish?: (event: SyntheticEvent<>) => any,
|
||||||
|
onSave?: ({ done?: boolean, autosave?: boolean, publish?: boolean }) => any,
|
||||||
|
onCancel?: () => 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 & {
|
type PropsWithRef = Props & {
|
||||||
forwardedRef: React.Ref<any>,
|
forwardedRef: React.Ref<any>,
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cdnPath } from "utils/urls";
|
import { cdnPath } from "utils/urls";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
alt: string,
|
alt: string,
|
||||||
src: string,
|
src: string,
|
||||||
};
|
title?: string,
|
||||||
|
width?: number,
|
||||||
|
height?: number,
|
||||||
|
|};
|
||||||
|
|
||||||
export default function Image({ src, alt, ...rest }: Props) {
|
export default function Image({ src, alt, ...rest }: Props) {
|
||||||
return <img src={cdnPath(src)} alt={alt} {...rest} />;
|
return <img src={cdnPath(src)} alt={alt} {...rest} />;
|
||||||
|
|
|
@ -75,8 +75,8 @@ export const LabelText = styled.div`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {|
|
||||||
type?: string,
|
type?: "text" | "email" | "checkbox" | "search",
|
||||||
value?: string,
|
value?: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
className?: string,
|
className?: string,
|
||||||
|
@ -85,9 +85,18 @@ export type Props = {
|
||||||
short?: boolean,
|
short?: boolean,
|
||||||
margin?: string | number,
|
margin?: string | number,
|
||||||
icon?: React.Node,
|
icon?: React.Node,
|
||||||
|
name?: string,
|
||||||
|
minLength?: number,
|
||||||
|
maxLength?: number,
|
||||||
|
autoFocus?: boolean,
|
||||||
|
autoComplete?: boolean | string,
|
||||||
|
readOnly?: boolean,
|
||||||
|
required?: boolean,
|
||||||
|
placeholder?: string,
|
||||||
|
onChange?: (ev: SyntheticInputEvent<HTMLInputElement>) => mixed,
|
||||||
onFocus?: (ev: SyntheticEvent<>) => void,
|
onFocus?: (ev: SyntheticEvent<>) => void,
|
||||||
onBlur?: (ev: SyntheticEvent<>) => void,
|
onBlur?: (ev: SyntheticEvent<>) => void,
|
||||||
};
|
|};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Input extends React.Component<Props> {
|
class Input extends React.Component<Props> {
|
||||||
|
|
|
@ -8,13 +8,13 @@ import Editor from "components/Editor";
|
||||||
import HelpText from "components/HelpText";
|
import HelpText from "components/HelpText";
|
||||||
import { LabelText, Outline } from "components/Input";
|
import { LabelText, Outline } from "components/Input";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
label: string,
|
label: string,
|
||||||
minHeight?: number,
|
minHeight?: number,
|
||||||
maxHeight?: number,
|
maxHeight?: number,
|
||||||
readOnly?: boolean,
|
readOnly?: boolean,
|
||||||
ui: UiStore,
|
ui: UiStore,
|
||||||
};
|
|};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class InputRich extends React.Component<Props> {
|
class InputRich extends React.Component<Props> {
|
||||||
|
|
|
@ -36,6 +36,8 @@ export type Props = {
|
||||||
className?: string,
|
className?: string,
|
||||||
labelHidden?: boolean,
|
labelHidden?: boolean,
|
||||||
options: Option[],
|
options: Option[],
|
||||||
|
onBlur?: () => void,
|
||||||
|
onFocus?: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
|
|
@ -4,10 +4,10 @@ import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
label: React.Node | string,
|
label: React.Node | string,
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
};
|
|};
|
||||||
|
|
||||||
const Labeled = ({ label, children, ...props }: Props) => (
|
const Labeled = ({ label, children, ...props }: Props) => (
|
||||||
<Flex column {...props}>
|
<Flex column {...props}>
|
||||||
|
|
|
@ -5,10 +5,10 @@ import { randomInteger } from "shared/random";
|
||||||
import { pulsate } from "shared/styles/animations";
|
import { pulsate } from "shared/styles/animations";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
header?: boolean,
|
header?: boolean,
|
||||||
height?: number,
|
height?: number,
|
||||||
};
|
|};
|
||||||
|
|
||||||
class Mask extends React.Component<Props> {
|
class Mask extends React.Component<Props> {
|
||||||
width: number;
|
width: number;
|
||||||
|
@ -23,7 +23,7 @@ class Mask extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Redacted width={this.width} {...this.props} />;
|
return <Redacted width={this.width} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,12 @@ import Scrollable from "components/Scrollable";
|
||||||
|
|
||||||
ReactModal.setAppElement("#root");
|
ReactModal.setAppElement("#root");
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
children?: React.Node,
|
children?: React.Node,
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
title?: string,
|
title?: string,
|
||||||
onRequestClose: () => void,
|
onRequestClose: () => void,
|
||||||
};
|
|};
|
||||||
|
|
||||||
const GlobalStyles = createGlobalStyle`
|
const GlobalStyles = createGlobalStyle`
|
||||||
.ReactModal__Overlay {
|
.ReactModal__Overlay {
|
||||||
|
|
|
@ -5,13 +5,18 @@ import Document from "models/Document";
|
||||||
import DocumentListItem from "components/DocumentListItem";
|
import DocumentListItem from "components/DocumentListItem";
|
||||||
import PaginatedList from "components/PaginatedList";
|
import PaginatedList from "components/PaginatedList";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
documents: Document[],
|
documents: Document[],
|
||||||
fetch: (options: ?Object) => Promise<void>,
|
fetch: (options: ?Object) => Promise<void>,
|
||||||
options?: Object,
|
options?: Object,
|
||||||
heading?: React.Node,
|
heading?: React.Node,
|
||||||
empty?: React.Node,
|
empty?: React.Node,
|
||||||
};
|
showCollection?: boolean,
|
||||||
|
showPublished?: boolean,
|
||||||
|
showPin?: boolean,
|
||||||
|
showDraft?: boolean,
|
||||||
|
showTemplate?: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class PaginatedDocumentList extends React.Component<Props> {
|
class PaginatedDocumentList extends React.Component<Props> {
|
||||||
|
|
|
@ -5,12 +5,13 @@ import styled from "styled-components";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
import TeamLogo from "components/TeamLogo";
|
import TeamLogo from "components/TeamLogo";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
teamName: string,
|
teamName: string,
|
||||||
subheading: React.Node,
|
subheading: React.Node,
|
||||||
showDisclosure?: boolean,
|
showDisclosure?: boolean,
|
||||||
|
onClick: (event: SyntheticEvent<>) => void,
|
||||||
logoUrl: string,
|
logoUrl: string,
|
||||||
};
|
|};
|
||||||
|
|
||||||
const HeaderBlock = React.forwardRef<Props, any>(
|
const HeaderBlock = React.forwardRef<Props, any>(
|
||||||
({ showDisclosure, teamName, subheading, logoUrl, ...rest }: Props, ref) => (
|
({ showDisclosure, teamName, subheading, logoUrl, ...rest }: Props, ref) => (
|
||||||
|
|
|
@ -3,12 +3,15 @@ import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { LabelText } from "components/Input";
|
import { LabelText } from "components/Input";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
width?: number,
|
width?: number,
|
||||||
height?: number,
|
height?: number,
|
||||||
label?: string,
|
label?: string,
|
||||||
|
checked?: boolean,
|
||||||
|
disabled?: boolean,
|
||||||
|
onChange: (event: SyntheticInputEvent<HTMLInputElement>) => mixed,
|
||||||
id?: string,
|
id?: string,
|
||||||
};
|
|};
|
||||||
|
|
||||||
function Switch({ width = 38, height = 20, label, ...props }: Props) {
|
function Switch({ width = 38, height = 20, label, ...props }: Props) {
|
||||||
const component = (
|
const component = (
|
||||||
|
|
|
@ -3,14 +3,14 @@ import Tippy from "@tippy.js/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
tooltip: React.Node,
|
tooltip: React.Node,
|
||||||
shortcut?: React.Node,
|
shortcut?: React.Node,
|
||||||
placement?: "top" | "bottom" | "left" | "right",
|
placement?: "top" | "bottom" | "left" | "right",
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
delay?: number,
|
delay?: number,
|
||||||
className?: string,
|
className?: string,
|
||||||
};
|
|};
|
||||||
|
|
||||||
class Tooltip extends React.Component<Props> {
|
class Tooltip extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -27,7 +27,6 @@ function NewDocumentMenu() {
|
||||||
as={Link}
|
as={Link}
|
||||||
to={newDocumentUrl(collections.orderedData[0].id)}
|
to={newDocumentUrl(collections.orderedData[0].id)}
|
||||||
icon={<PlusIcon />}
|
icon={<PlusIcon />}
|
||||||
small
|
|
||||||
>
|
>
|
||||||
{t("New doc")}
|
{t("New doc")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default class BaseModel {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
save = async (params: ?Object) => {
|
save = async (params: Object = {}) => {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -142,7 +142,7 @@ export default class Document extends BaseModel {
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateFromJson = (data) => {
|
updateFromJson = (data: Object) => {
|
||||||
set(this, data);
|
set(this, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ export default class Document extends BaseModel {
|
||||||
return this.store.archive(this);
|
return this.store.archive(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
restore = (options) => {
|
restore = (options: { revisionId?: string, collectionId?: string }) => {
|
||||||
return this.store.restore(this, options);
|
return this.store.restore(this, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ export default class Document extends BaseModel {
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
save = async (options: SaveOptions) => {
|
save = async (options: SaveOptions = {}) => {
|
||||||
if (this.isSaving) return this;
|
if (this.isSaving) return this;
|
||||||
|
|
||||||
const isCreating = !this.id;
|
const isCreating = !this.id;
|
||||||
|
@ -246,7 +246,9 @@ export default class Document extends BaseModel {
|
||||||
collectionId: this.collectionId,
|
collectionId: this.collectionId,
|
||||||
title: this.title,
|
title: this.title,
|
||||||
text: this.text,
|
text: this.text,
|
||||||
...options,
|
publish: options.publish,
|
||||||
|
done: options.done,
|
||||||
|
autosave: options.autosave,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +259,9 @@ export default class Document extends BaseModel {
|
||||||
text: this.text,
|
text: this.text,
|
||||||
templateId: this.templateId,
|
templateId: this.templateId,
|
||||||
lastRevision: options.lastRevision,
|
lastRevision: options.lastRevision,
|
||||||
...options,
|
publish: options.publish,
|
||||||
|
done: options.done,
|
||||||
|
autosave: options.autosave,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ type Props = {
|
||||||
document: Document,
|
document: Document,
|
||||||
revision: Revision,
|
revision: Revision,
|
||||||
readOnly: boolean,
|
readOnly: boolean,
|
||||||
onCreateLink: (title: string) => string,
|
onCreateLink: (title: string) => Promise<string>,
|
||||||
onSearchLink: (term: string) => any,
|
onSearchLink: (term: string) => any,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
auth: AuthStore,
|
auth: AuthStore,
|
||||||
|
|
|
@ -9,24 +9,24 @@ import parseTitle from "shared/utils/parseTitle";
|
||||||
import Document from "models/Document";
|
import Document from "models/Document";
|
||||||
import ClickablePadding from "components/ClickablePadding";
|
import ClickablePadding from "components/ClickablePadding";
|
||||||
import DocumentMetaWithViews from "components/DocumentMetaWithViews";
|
import DocumentMetaWithViews from "components/DocumentMetaWithViews";
|
||||||
import Editor from "components/Editor";
|
import Editor, { type Props as EditorProps } from "components/Editor";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
import HoverPreview from "components/HoverPreview";
|
import HoverPreview from "components/HoverPreview";
|
||||||
import Star, { AnimatedStar } from "components/Star";
|
import Star, { AnimatedStar } from "components/Star";
|
||||||
import { isModKey } from "utils/keyboard";
|
import { isModKey } from "utils/keyboard";
|
||||||
import { documentHistoryUrl } from "utils/routeHelpers";
|
import { documentHistoryUrl } from "utils/routeHelpers";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
|
...EditorProps,
|
||||||
onChangeTitle: (event: SyntheticInputEvent<>) => void,
|
onChangeTitle: (event: SyntheticInputEvent<>) => void,
|
||||||
title: string,
|
title: string,
|
||||||
defaultValue: string,
|
|
||||||
document: Document,
|
document: Document,
|
||||||
isDraft: boolean,
|
isDraft: boolean,
|
||||||
isShare: boolean,
|
isShare: boolean,
|
||||||
readOnly?: boolean,
|
grow?: boolean,
|
||||||
onSave: ({ publish?: boolean, done?: boolean, autosave?: boolean }) => mixed,
|
onSave: ({ done?: boolean, autosave?: boolean, publish?: boolean }) => any,
|
||||||
innerRef: { current: any },
|
innerRef: { current: any },
|
||||||
};
|
|};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class DocumentEditor extends React.Component<Props> {
|
class DocumentEditor extends React.Component<Props> {
|
||||||
|
@ -98,6 +98,7 @@ class DocumentEditor extends React.Component<Props> {
|
||||||
isShare,
|
isShare,
|
||||||
readOnly,
|
readOnly,
|
||||||
innerRef,
|
innerRef,
|
||||||
|
...rest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { emoji } = parseTitle(title);
|
const { emoji } = parseTitle(title);
|
||||||
|
@ -135,12 +136,12 @@ class DocumentEditor extends React.Component<Props> {
|
||||||
/>
|
/>
|
||||||
<Editor
|
<Editor
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
autoFocus={title && !this.props.defaultValue}
|
autoFocus={!!title && !this.props.defaultValue}
|
||||||
placeholder="…the rest is up to you"
|
placeholder="…the rest is up to you"
|
||||||
onHoverLink={this.handleLinkActive}
|
onHoverLink={this.handleLinkActive}
|
||||||
scrollTo={window.location.hash}
|
scrollTo={window.location.hash}
|
||||||
grow
|
grow
|
||||||
{...this.props}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
{!readOnly && <ClickablePadding onClick={this.focusAtEnd} grow />}
|
{!readOnly && <ClickablePadding onClick={this.focusAtEnd} grow />}
|
||||||
{this.activeLinkEvent && !isShare && readOnly && (
|
{this.activeLinkEvent && !isShare && readOnly && (
|
||||||
|
|
|
@ -240,7 +240,6 @@ class Header extends React.Component<Props> {
|
||||||
<Button
|
<Button
|
||||||
onClick={this.handleSave}
|
onClick={this.handleSave}
|
||||||
disabled={savingIsDisabled}
|
disabled={savingIsDisabled}
|
||||||
isSaving={isSaving}
|
|
||||||
neutral={isDraft}
|
neutral={isDraft}
|
||||||
>
|
>
|
||||||
{isDraft ? t("Save Draft") : t("Done Editing")}
|
{isDraft ? t("Save Draft") : t("Done Editing")}
|
||||||
|
@ -311,7 +310,6 @@ class Header extends React.Component<Props> {
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={this.handlePublish}
|
onClick={this.handlePublish}
|
||||||
title={t("Publish document")}
|
|
||||||
disabled={publishingIsDisabled}
|
disabled={publishingIsDisabled}
|
||||||
>
|
>
|
||||||
{isPublishing ? `${t("Publishing")}…` : t("Publish")}
|
{isPublishing ? `${t("Publishing")}…` : t("Publish")}
|
||||||
|
|
|
@ -7,11 +7,11 @@ import Document from "models/Document";
|
||||||
import DocumentMeta from "components/DocumentMeta";
|
import DocumentMeta from "components/DocumentMeta";
|
||||||
import type { NavigationNode } from "types";
|
import type { NavigationNode } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
document: Document | NavigationNode,
|
document: Document | NavigationNode,
|
||||||
anchor?: string,
|
anchor?: string,
|
||||||
showCollection?: boolean,
|
showCollection?: boolean,
|
||||||
};
|
|};
|
||||||
|
|
||||||
const DocumentLink = styled(Link)`
|
const DocumentLink = styled(Link)`
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -43,7 +43,7 @@ class DocumentShare extends React.Component<Props> {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await share.save({ published: event.target.checked });
|
await share.save({ published: event.currentTarget.checked });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.props.ui.showToast(err.message, { type: "error" });
|
this.props.ui.showToast(err.message, { type: "error" });
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -87,7 +87,6 @@ class AddPeopleToGroup extends React.Component<Props> {
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
.
|
.
|
||||||
</HelpText>
|
</HelpText>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
placeholder={`${t("Search by name")}…`}
|
placeholder={`${t("Search by name")}…`}
|
||||||
|
|
|
@ -17,10 +17,9 @@ function Zapier() {
|
||||||
</HelpText>
|
</HelpText>
|
||||||
<p>
|
<p>
|
||||||
<Button
|
<Button
|
||||||
as="a"
|
onClick={() =>
|
||||||
href="https://zapier.com/apps/outline"
|
(window.location.href = "https://zapier.com/apps/outline")
|
||||||
rel="noopener noreferrer"
|
}
|
||||||
target="_blank"
|
|
||||||
>
|
>
|
||||||
Open Zapier →
|
Open Zapier →
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -34,10 +34,10 @@ class UserDelete extends React.Component<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { auth, ...rest } = this.props;
|
const { onRequestClose } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen title="Delete Account" {...rest}>
|
<Modal isOpen title="Delete Account" onRequestClose={onRequestClose}>
|
||||||
<Flex column>
|
<Flex column>
|
||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
<HelpText>
|
<HelpText>
|
||||||
|
|
|
@ -19,11 +19,11 @@ import Subheading from "components/Subheading";
|
||||||
import useCurrentUser from "hooks/useCurrentUser";
|
import useCurrentUser from "hooks/useCurrentUser";
|
||||||
import useStores from "hooks/useStores";
|
import useStores from "hooks/useStores";
|
||||||
|
|
||||||
type Props = {
|
type Props = {|
|
||||||
user: User,
|
user: User,
|
||||||
history: RouterHistory,
|
history: RouterHistory,
|
||||||
onRequestClose: () => void,
|
onRequestClose: () => void,
|
||||||
};
|
|};
|
||||||
|
|
||||||
function UserProfile(props: Props) {
|
function UserProfile(props: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
|
@ -4,6 +4,7 @@ import invariant from "invariant";
|
||||||
import { observable, action, computed, autorun, runInAction } from "mobx";
|
import { observable, action, computed, autorun, runInAction } from "mobx";
|
||||||
import { getCookie, setCookie, removeCookie } from "tiny-cookie";
|
import { getCookie, setCookie, removeCookie } from "tiny-cookie";
|
||||||
import RootStore from "stores/RootStore";
|
import RootStore from "stores/RootStore";
|
||||||
|
import Policy from "models/Policy";
|
||||||
import Team from "models/Team";
|
import Team from "models/Team";
|
||||||
import User from "models/User";
|
import User from "models/User";
|
||||||
import env from "env";
|
import env from "env";
|
||||||
|
@ -13,17 +14,17 @@ import { getCookieDomain } from "utils/domains";
|
||||||
const AUTH_STORE = "AUTH_STORE";
|
const AUTH_STORE = "AUTH_STORE";
|
||||||
const NO_REDIRECT_PATHS = ["/", "/create", "/home"];
|
const NO_REDIRECT_PATHS = ["/", "/create", "/home"];
|
||||||
|
|
||||||
type Service = {
|
type Service = {|
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
authUrl: string,
|
authUrl: string,
|
||||||
};
|
|};
|
||||||
|
|
||||||
type Config = {
|
type Config = {|
|
||||||
name?: string,
|
name?: string,
|
||||||
hostname?: string,
|
hostname?: string,
|
||||||
services: Service[],
|
services: Service[],
|
||||||
};
|
|};
|
||||||
|
|
||||||
export default class AuthStore {
|
export default class AuthStore {
|
||||||
@observable user: ?User;
|
@observable user: ?User;
|
||||||
|
@ -88,7 +89,7 @@ export default class AuthStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addPolicies = (policies) => {
|
addPolicies = (policies: Policy[]) => {
|
||||||
if (policies) {
|
if (policies) {
|
||||||
policies.forEach((policy) => this.rootStore.policies.add(policy));
|
policies.forEach((policy) => this.rootStore.policies.add(policy));
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,13 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
return this.drafts().length;
|
return this.drafts().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
drafts = (options = {}): Document[] => {
|
drafts = (
|
||||||
|
options: {
|
||||||
|
...PaginationParams,
|
||||||
|
dateFilter?: "day" | "week" | "month" | "year",
|
||||||
|
collectionId?: string,
|
||||||
|
} = {}
|
||||||
|
): Document[] => {
|
||||||
let drafts = filter(
|
let drafts = filter(
|
||||||
orderBy(this.all, "updatedAt", "desc"),
|
orderBy(this.all, "updatedAt", "desc"),
|
||||||
(doc) => !doc.publishedAt
|
(doc) => !doc.publishedAt
|
||||||
|
@ -185,7 +191,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
drafts,
|
drafts,
|
||||||
(draft) =>
|
(draft) =>
|
||||||
new Date(draft.updatedAt) >=
|
new Date(draft.updatedAt) >=
|
||||||
subtractDate(new Date(), options.dateFilter)
|
subtractDate(new Date(), options.dateFilter || "year")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +251,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
@action
|
@action
|
||||||
fetchNamedPage = async (
|
fetchNamedPage = async (
|
||||||
request: string = "list",
|
request: string = "list",
|
||||||
options: ?PaginationParams
|
options: ?Object
|
||||||
): Promise<?(Document[])> => {
|
): Promise<?(Document[])> => {
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
|
@ -338,10 +344,9 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
searchTitles = async (query: string, options: PaginationParams = {}) => {
|
searchTitles = async (query: string) => {
|
||||||
const res = await client.get("/documents.search_titles", {
|
const res = await client.get("/documents.search_titles", {
|
||||||
query,
|
query,
|
||||||
...options,
|
|
||||||
});
|
});
|
||||||
invariant(res && res.data, "Search response should be available");
|
invariant(res && res.data, "Search response should be available");
|
||||||
|
|
||||||
|
@ -354,7 +359,15 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
@action
|
@action
|
||||||
search = async (
|
search = async (
|
||||||
query: string,
|
query: string,
|
||||||
options: PaginationParams = {}
|
options: {
|
||||||
|
offset?: number,
|
||||||
|
limit?: number,
|
||||||
|
dateFilter?: "day" | "week" | "month" | "year",
|
||||||
|
includeArchived?: boolean,
|
||||||
|
includeDrafts?: boolean,
|
||||||
|
collectionId?: string,
|
||||||
|
userId?: string,
|
||||||
|
}
|
||||||
): Promise<SearchResult[]> => {
|
): Promise<SearchResult[]> => {
|
||||||
const compactedOptions = omitBy(options, (o) => !o);
|
const compactedOptions = omitBy(options, (o) => !o);
|
||||||
const res = await client.get("/documents.search", {
|
const res = await client.get("/documents.search", {
|
||||||
|
@ -601,10 +614,14 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
restore = async (document: Document, options = {}) => {
|
restore = async (
|
||||||
|
document: Document,
|
||||||
|
options: { revisionId?: string, collectionId?: string } = {}
|
||||||
|
) => {
|
||||||
const res = await client.post("/documents.restore", {
|
const res = await client.post("/documents.restore", {
|
||||||
id: document.id,
|
id: document.id,
|
||||||
...options,
|
revisionId: options.revisionId,
|
||||||
|
collectionId: options.collectionId,
|
||||||
});
|
});
|
||||||
runInAction("Document#restore", () => {
|
runInAction("Document#restore", () => {
|
||||||
invariant(res && res.data, "Data should be available");
|
invariant(res && res.data, "Data should be available");
|
||||||
|
|
|
@ -182,13 +182,15 @@ class UiStore {
|
||||||
@action
|
@action
|
||||||
showToast = (
|
showToast = (
|
||||||
message: string,
|
message: string,
|
||||||
options?: {
|
options: {
|
||||||
type?: "warning" | "error" | "info" | "success",
|
type: "warning" | "error" | "info" | "success",
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
action?: {
|
action?: {
|
||||||
text: string,
|
text: string,
|
||||||
onClick: () => void,
|
onClick: () => void,
|
||||||
},
|
},
|
||||||
|
} = {
|
||||||
|
type: "info",
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
@ -204,7 +206,14 @@ class UiStore {
|
||||||
|
|
||||||
const id = v4();
|
const id = v4();
|
||||||
const createdAt = new Date().toISOString();
|
const createdAt = new Date().toISOString();
|
||||||
this.toasts.set(id, { message, createdAt, id, ...options });
|
this.toasts.set(id, {
|
||||||
|
id,
|
||||||
|
message,
|
||||||
|
createdAt,
|
||||||
|
type: options.type,
|
||||||
|
timeout: options.timeout,
|
||||||
|
action: options.action,
|
||||||
|
});
|
||||||
this.lastToastId = id;
|
this.lastToastId = id;
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ export type LocationWithState = Location & {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Toast = {
|
export type Toast = {|
|
||||||
id: string,
|
id: string,
|
||||||
createdAt: string,
|
createdAt: string,
|
||||||
message: string,
|
message: string,
|
||||||
|
@ -22,7 +22,7 @@ export type Toast = {
|
||||||
text: string,
|
text: string,
|
||||||
onClick: () => void,
|
onClick: () => void,
|
||||||
},
|
},
|
||||||
};
|
|};
|
||||||
|
|
||||||
export type FetchOptions = {
|
export type FetchOptions = {
|
||||||
prefetch?: boolean,
|
prefetch?: boolean,
|
||||||
|
@ -31,12 +31,12 @@ export type FetchOptions = {
|
||||||
force?: boolean,
|
force?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NavigationNode = {
|
export type NavigationNode = {|
|
||||||
id: string,
|
id: string,
|
||||||
title: string,
|
title: string,
|
||||||
url: string,
|
url: string,
|
||||||
children: NavigationNode[],
|
children: NavigationNode[],
|
||||||
};
|
|};
|
||||||
|
|
||||||
// Pagination response in an API call
|
// Pagination response in an API call
|
||||||
export type Pagination = {
|
export type Pagination = {
|
||||||
|
@ -46,12 +46,12 @@ export type Pagination = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pagination request params
|
// Pagination request params
|
||||||
export type PaginationParams = {
|
export type PaginationParams = {|
|
||||||
limit?: number,
|
limit?: number,
|
||||||
offset?: number,
|
offset?: number,
|
||||||
sort?: string,
|
sort?: string,
|
||||||
direction?: "ASC" | "DESC",
|
direction?: "ASC" | "DESC",
|
||||||
};
|
|};
|
||||||
|
|
||||||
export type SearchResult = {
|
export type SearchResult = {
|
||||||
ranking: number,
|
ranking: number,
|
||||||
|
|
|
@ -110,6 +110,7 @@ export default function download(
|
||||||
// $FlowIssue
|
// $FlowIssue
|
||||||
if (navigator.msSaveBlob) {
|
if (navigator.msSaveBlob) {
|
||||||
// IE10+ : (has Blob, but not a[download] or URL)
|
// IE10+ : (has Blob, but not a[download] or URL)
|
||||||
|
// $FlowIssue
|
||||||
return navigator.msSaveBlob(blob, fn);
|
return navigator.msSaveBlob(blob, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
"start": "node ./build/server/index.js",
|
"start": "node ./build/server/index.js",
|
||||||
"dev": "nodemon --exec \"yarn build:server && yarn build:i18n && node build/server/index.js\" -e js --ignore build/ --ignore app/",
|
"dev": "nodemon --exec \"yarn build:server && yarn build:i18n && node build/server/index.js\" -e js --ignore build/ --ignore app/",
|
||||||
"lint": "eslint app server shared",
|
"lint": "eslint app server shared",
|
||||||
"flow": "flow",
|
|
||||||
"deploy": "git push heroku master",
|
"deploy": "git push heroku master",
|
||||||
"heroku-postbuild": "yarn build && yarn sequelize:migrate",
|
"heroku-postbuild": "yarn build && yarn sequelize:migrate",
|
||||||
"sequelize:create-migration": "sequelize migration:create",
|
"sequelize:create-migration": "sequelize migration:create",
|
||||||
|
@ -192,7 +191,7 @@
|
||||||
"eslint-plugin-react": "^7.20.0",
|
"eslint-plugin-react": "^7.20.0",
|
||||||
"eslint-plugin-react-hooks": "^4.1.0",
|
"eslint-plugin-react-hooks": "^4.1.0",
|
||||||
"fetch-test-server": "^1.1.0",
|
"fetch-test-server": "^1.1.0",
|
||||||
"flow-bin": "^0.104.0",
|
"flow-bin": "^0.124.0",
|
||||||
"html-webpack-plugin": "3.2.0",
|
"html-webpack-plugin": "3.2.0",
|
||||||
"i18next-parser": "^3.3.0",
|
"i18next-parser": "^3.3.0",
|
||||||
"jest-cli": "^26.0.0",
|
"jest-cli": "^26.0.0",
|
||||||
|
|
|
@ -5181,10 +5181,10 @@ flatted@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
|
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
|
||||||
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
|
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
|
||||||
|
|
||||||
flow-bin@^0.104.0:
|
flow-bin@^0.124.0:
|
||||||
version "0.104.0"
|
version "0.124.0"
|
||||||
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.104.0.tgz#ef5b3600dfd36abe191a87d19f66e481bad2e235"
|
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.124.0.tgz#24b2e55874e1e2041f9247f42473b3db2ef32758"
|
||||||
integrity sha512-EZXRRmf7m7ET5Lcnwm/I/T8G3d427Bq34vmO3qIlRcPIYloGuVoqRCwjaeezLRDntHkdciagAKbhJ+NTbDjnkw==
|
integrity sha512-KEtDJ7CFUjcuhw6N52FTZshDd1krf1fxpp4APSIrwhVm+IrlcKJ+EMXpeXKM1kKNSZ347dYGh8wEvXQl4pHZEA==
|
||||||
|
|
||||||
flow-typed@^2.6.2:
|
flow-typed@^2.6.2:
|
||||||
version "2.6.2"
|
version "2.6.2"
|
||||||
|
|
Reference in New Issue