feat: Inline collection editing (#1865)

This commit is contained in:
Tom Moor
2021-02-12 16:20:49 -08:00
committed by GitHub
parent 2611376b21
commit 1dbcc12648
14 changed files with 298 additions and 101 deletions

23
app/components/Arrow.js Normal file
View File

@ -0,0 +1,23 @@
// @flow
import * as React from "react";
export default function Arrow() {
return (
<svg
width="13"
height="30"
viewBox="0 0 13 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M7.40242 1.48635C8.23085 0.0650039 10.0656 -0.421985 11.5005 0.39863C12.9354 1.21924 13.427 3.03671 12.5986 4.45806L5.59858 16.4681C4.77015 17.8894 2.93538 18.3764 1.5005 17.5558C0.065623 16.7352 -0.426002 14.9177 0.402425 13.4964L7.40242 1.48635Z"
/>
<path
fill="currentColor"
d="M12.5986 25.5419C13.427 26.9633 12.9354 28.7808 11.5005 29.6014C10.0656 30.422 8.23087 29.935 7.40244 28.5136L0.402438 16.5036C-0.425989 15.0823 0.0656365 13.2648 1.50051 12.4442C2.93539 11.6236 4.77016 12.1106 5.59859 13.5319L12.5986 25.5419Z"
/>
</svg>
);
}

View File

@ -0,0 +1,212 @@
// @flow
import { observer } from "mobx-react";
import { transparentize } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Collection from "models/Collection";
import Arrow from "components/Arrow";
import ButtonLink from "components/ButtonLink";
import Editor from "components/Editor";
import LoadingIndicator from "components/LoadingIndicator";
import NudeButton from "components/NudeButton";
import useDebouncedCallback from "hooks/useDebouncedCallback";
import useStores from "hooks/useStores";
type Props = {|
collection: Collection,
|};
function CollectionDescription({ collection }: Props) {
const { collections, ui, policies } = useStores();
const { t } = useTranslation();
const [isExpanded, setExpanded] = React.useState(false);
const [isEditing, setEditing] = React.useState(false);
const [isDirty, setDirty] = React.useState(false);
const can = policies.abilities(collection.id);
const handleStartEditing = React.useCallback(() => {
setEditing(true);
}, []);
const handleStopEditing = React.useCallback(() => {
setEditing(false);
}, []);
const handleClickDisclosure = React.useCallback(
(event) => {
event.preventDefault();
if (isExpanded && document.activeElement) {
document.activeElement.blur();
}
setExpanded(!isExpanded);
},
[isExpanded]
);
const handleSave = useDebouncedCallback(async (getValue) => {
try {
await collection.save({
description: getValue(),
});
setDirty(false);
} catch (err) {
ui.showToast(
t("Sorry, an error occurred saving the collection", {
type: "error",
})
);
throw err;
}
}, 1000);
const handleChange = React.useCallback(
(getValue) => {
setDirty(true);
handleSave(getValue);
},
[handleSave]
);
React.useEffect(() => {
setEditing(false);
}, [collection.id]);
const placeholder = `${t("Add a description")}`;
const key = isEditing || isDirty ? "draft" : collection.updatedAt;
return (
<MaxHeight data-editing={isEditing} data-expanded={isExpanded}>
<Input
$isEditable={can.update}
data-editing={isEditing}
data-expanded={isExpanded}
>
<span onClick={can.update ? handleStartEditing : undefined}>
{collections.isSaving && <LoadingIndicator />}
{collection.hasDescription || isEditing || isDirty ? (
<React.Suspense fallback={<Placeholder>Loading</Placeholder>}>
<Editor
id={collection.id}
key={key}
defaultValue={collection.description}
onChange={handleChange}
placeholder={placeholder}
readOnly={!isEditing}
autoFocus={isEditing}
onBlur={handleStopEditing}
maxLength={1000}
disableEmbeds
readOnlyWriteCheckboxes
grow
/>
</React.Suspense>
) : (
can.update && <Placeholder>{placeholder}</Placeholder>
)}
</span>
</Input>
{!isEditing && (
<Disclosure
onClick={handleClickDisclosure}
aria-label={isExpanded ? t("Collapse") : t("Expand")}
size={30}
>
<Arrow />
</Disclosure>
)}
</MaxHeight>
);
}
const Disclosure = styled(NudeButton)`
opacity: 0;
color: ${(props) => props.theme.divider};
position: absolute;
top: calc(25vh - 50px);
left: 50%;
z-index: 1;
transform: rotate(-90deg) translateX(-50%);
transition: opacity 100ms ease-in-out;
&:focus,
&:hover {
opacity: 1;
}
&:active {
color: ${(props) => props.theme.sidebarText};
}
`;
const Placeholder = styled(ButtonLink)`
color: ${(props) => props.theme.placeholder};
cursor: text;
min-height: 27px;
`;
const MaxHeight = styled.div`
position: relative;
max-height: 25vh;
overflow: hidden;
margin: -8px;
padding: 8px;
&[data-editing="true"],
&[data-expanded="true"] {
max-height: initial;
overflow: initial;
${Disclosure} {
top: initial;
bottom: 0;
transform: rotate(90deg) translateX(-50%);
}
}
&:hover ${Disclosure} {
opacity: 1;
}
`;
const Input = styled.div`
margin: -8px;
padding: 8px;
border-radius: 8px;
transition: ${(props) => props.theme.backgroundTransition};
&:after {
content: "";
position: absolute;
top: calc(25vh - 50px);
left: 0;
right: 0;
height: 50px;
pointer-events: none;
background: linear-gradient(
180deg,
${(props) => transparentize(1, props.theme.background)} 0%,
${(props) => props.theme.background} 100%
);
}
&[data-editing="true"],
&[data-expanded="true"] {
&:after {
background: transparent;
}
}
&[data-editing="true"] {
background: ${(props) => props.theme.secondaryBackground};
}
.block-menu-trigger,
.heading-anchor {
display: none !important;
}
`;
export default observer(CollectionDescription);

View File

@ -27,13 +27,16 @@ export type Props = {|
autoFocus?: boolean, autoFocus?: boolean,
template?: boolean, template?: boolean,
placeholder?: string, placeholder?: string,
maxLength?: number,
scrollTo?: string, scrollTo?: string,
handleDOMEvents?: Object,
readOnlyWriteCheckboxes?: boolean, readOnlyWriteCheckboxes?: boolean,
onBlur?: (event: SyntheticEvent<>) => any, onBlur?: (event: SyntheticEvent<>) => any,
onFocus?: (event: SyntheticEvent<>) => any, onFocus?: (event: SyntheticEvent<>) => any,
onPublish?: (event: SyntheticEvent<>) => any, onPublish?: (event: SyntheticEvent<>) => any,
onSave?: ({ done?: boolean, autosave?: boolean, publish?: boolean }) => any, onSave?: ({ done?: boolean, autosave?: boolean, publish?: boolean }) => any,
onCancel?: () => any, onCancel?: () => any,
onDoubleClick?: () => any,
onChange?: (getValue: () => string) => any, onChange?: (getValue: () => string) => any,
onSearchLink?: (title: string) => any, onSearchLink?: (title: string) => any,
onHoverLink?: (event: MouseEvent) => any, onHoverLink?: (event: MouseEvent) => any,
@ -177,7 +180,7 @@ const StyledEditor = styled(RichMarkdownEditor)`
justify-content: start; justify-content: start;
> div { > div {
transition: ${(props) => props.theme.backgroundTransition}; background: transparent;
} }
& * { & * {

View File

@ -2,6 +2,7 @@
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import breakpoint from "styled-components-breakpoint"; import breakpoint from "styled-components-breakpoint";
import Arrow from "components/Arrow";
type Props = { type Props = {
direction: "left" | "right", direction: "left" | "right",
@ -14,22 +15,7 @@ const Toggle = React.forwardRef<Props, HTMLButtonElement>(
return ( return (
<Positioner style={style}> <Positioner style={style}>
<ToggleButton ref={ref} $direction={direction} onClick={onClick}> <ToggleButton ref={ref} $direction={direction} onClick={onClick}>
<svg <Arrow />
width="13"
height="30"
viewBox="0 0 13 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M7.40242 1.48635C8.23085 0.0650039 10.0656 -0.421985 11.5005 0.39863C12.9354 1.21924 13.427 3.03671 12.5986 4.45806L5.59858 16.4681C4.77015 17.8894 2.93538 18.3764 1.5005 17.5558C0.065623 16.7352 -0.426002 14.9177 0.402425 13.4964L7.40242 1.48635Z"
/>
<path
fill="currentColor"
d="M12.5986 25.5419C13.427 26.9633 12.9354 28.7808 11.5005 29.6014C10.0656 30.422 8.23087 29.935 7.40244 28.5136L0.402438 16.5036C-0.425989 15.0823 0.0656365 13.2648 1.50051 12.4442C2.93539 11.6236 4.77016 12.1106 5.59859 13.5319L12.5986 25.5419Z"
/>
</svg>
</ToggleButton> </ToggleButton>
</Positioner> </Positioner>
); );

View File

@ -2,7 +2,7 @@
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 Toast from "./components/Toast"; import Toast from "components/Toast";
import useStores from "hooks/useStores"; import useStores from "hooks/useStores";
function Toasts() { function Toasts() {

View File

@ -1,3 +0,0 @@
// @flow
import Toasts from "./Toasts";
export default Toasts;

View File

@ -0,0 +1,31 @@
// @flow
import * as React from "react";
export default function useDebouncedCallback(
callback: (any) => mixed,
wait: number
) {
// track args & timeout handle between calls
const argsRef = React.useRef();
const timeout = React.useRef();
function cleanup() {
if (timeout.current) {
clearTimeout(timeout.current);
}
}
// make sure our timeout gets cleared if consuming component gets unmounted
React.useEffect(() => cleanup, []);
return function (...args: any) {
argsRef.current = args;
cleanup();
timeout.current = setTimeout(() => {
if (argsRef.current) {
callback(...argsRef.current);
}
}, wait);
};
}

View File

@ -1,12 +1,11 @@
// @flow // @flow
import { observable } from "mobx"; import { observable } from "mobx";
import { observer, inject } from "mobx-react"; import { observer, inject } from "mobx-react";
import { NewDocumentIcon, PlusIcon, PinIcon, MoreIcon } from "outline-icons"; import { NewDocumentIcon, PlusIcon, PinIcon, MoreIcon } from "outline-icons";
import * as React from "react"; import * as React from "react";
import { withTranslation, Trans, type TFunction } from "react-i18next"; import { withTranslation, Trans, type TFunction } from "react-i18next";
import { Redirect, Link, Switch, Route, type Match } from "react-router-dom"; import { Redirect, Link, Switch, Route, type Match } from "react-router-dom";
import styled, { withTheme } from "styled-components"; import styled from "styled-components";
import CollectionsStore from "stores/CollectionsStore"; import CollectionsStore from "stores/CollectionsStore";
import DocumentsStore from "stores/DocumentsStore"; import DocumentsStore from "stores/DocumentsStore";
@ -20,9 +19,9 @@ import Search from "scenes/Search";
import Actions, { Action, Separator } from "components/Actions"; import Actions, { Action, Separator } from "components/Actions";
import Button from "components/Button"; import Button from "components/Button";
import CenteredContent from "components/CenteredContent"; import CenteredContent from "components/CenteredContent";
import CollectionDescription from "components/CollectionDescription";
import CollectionIcon from "components/CollectionIcon"; import CollectionIcon from "components/CollectionIcon";
import DocumentList from "components/DocumentList"; import DocumentList from "components/DocumentList";
import Editor from "components/Editor";
import Flex from "components/Flex"; import Flex from "components/Flex";
import Heading from "components/Heading"; import Heading from "components/Heading";
import HelpText from "components/HelpText"; import HelpText from "components/HelpText";
@ -37,7 +36,6 @@ import Tab from "components/Tab";
import Tabs from "components/Tabs"; import Tabs from "components/Tabs";
import Tooltip from "components/Tooltip"; import Tooltip from "components/Tooltip";
import CollectionMenu from "menus/CollectionMenu"; import CollectionMenu from "menus/CollectionMenu";
import { type Theme } from "types";
import { AuthorizationError } from "utils/errors"; import { AuthorizationError } from "utils/errors";
import { newDocumentUrl, collectionUrl } from "utils/routeHelpers"; import { newDocumentUrl, collectionUrl } from "utils/routeHelpers";
@ -47,7 +45,6 @@ type Props = {
collections: CollectionsStore, collections: CollectionsStore,
policies: PoliciesStore, policies: PoliciesStore,
match: Match, match: Match,
theme: Theme,
t: TFunction, t: TFunction,
}; };
@ -57,7 +54,6 @@ class CollectionScene extends React.Component<Props> {
@observable isFetching: boolean = true; @observable isFetching: boolean = true;
@observable permissionsModalOpen: boolean = false; @observable permissionsModalOpen: boolean = false;
@observable editModalOpen: boolean = false; @observable editModalOpen: boolean = false;
@observable redirectTo: ?string;
componentDidMount() { componentDidMount() {
const { id } = this.props.match.params; const { id } = this.props.match.params;
@ -108,14 +104,6 @@ class CollectionScene extends React.Component<Props> {
} }
}; };
onNewDocument = (ev: SyntheticEvent<>) => {
ev.preventDefault();
if (this.collection) {
this.redirectTo = newDocumentUrl(this.collection.id);
}
};
onPermissions = (ev: SyntheticEvent<>) => { onPermissions = (ev: SyntheticEvent<>) => {
ev.preventDefault(); ev.preventDefault();
this.permissionsModalOpen = true; this.permissionsModalOpen = true;
@ -157,7 +145,12 @@ class CollectionScene extends React.Component<Props> {
delay={500} delay={500}
placement="bottom" placement="bottom"
> >
<Button onClick={this.onNewDocument} icon={<PlusIcon />}> <Button
as={Link}
to={this.collection ? newDocumentUrl(this.collection.id) : ""}
disabled={!this.collection}
icon={<PlusIcon />}
>
{t("New doc")} {t("New doc")}
</Button> </Button>
</Tooltip> </Tooltip>
@ -186,9 +179,8 @@ class CollectionScene extends React.Component<Props> {
} }
render() { render() {
const { documents, theme, t } = this.props; const { documents, t } = this.props;
if (this.redirectTo) return <Redirect to={this.redirectTo} push />;
if (!this.isFetching && !this.collection) return <Search notFound />; if (!this.isFetching && !this.collection) return <Search notFound />;
const pinnedDocuments = this.collection const pinnedDocuments = this.collection
@ -197,7 +189,6 @@ class CollectionScene extends React.Component<Props> {
const collection = this.collection; const collection = this.collection;
const collectionName = collection ? collection.name : ""; const collectionName = collection ? collection.name : "";
const hasPinnedDocuments = !!pinnedDocuments.length; const hasPinnedDocuments = !!pinnedDocuments.length;
const hasDescription = collection ? collection.hasDescription : false;
return ( return (
<CenteredContent> <CenteredContent>
@ -218,7 +209,7 @@ class CollectionScene extends React.Component<Props> {
</HelpText> </HelpText>
<Wrapper> <Wrapper>
<Link to={newDocumentUrl(collection.id)}> <Link to={newDocumentUrl(collection.id)}>
<Button icon={<NewDocumentIcon color={theme.buttonText} />}> <Button icon={<NewDocumentIcon color="currentColor" />}>
{t("Create a document")} {t("Create a document")}
</Button> </Button>
</Link> </Link>
@ -257,17 +248,7 @@ class CollectionScene extends React.Component<Props> {
<CollectionIcon collection={collection} size={40} expanded />{" "} <CollectionIcon collection={collection} size={40} expanded />{" "}
{collection.name} {collection.name}
</Heading> </Heading>
<CollectionDescription collection={collection} />
{hasDescription && (
<React.Suspense fallback={<p>Loading</p>}>
<Editor
id={collection.id}
key={collection.description}
defaultValue={collection.description}
readOnly
/>
</React.Suspense>
)}
{hasPinnedDocuments && ( {hasPinnedDocuments && (
<> <>
@ -396,10 +377,5 @@ const Wrapper = styled(Flex)`
`; `;
export default withTranslation()<CollectionScene>( export default withTranslation()<CollectionScene>(
inject( inject("collections", "policies", "documents", "ui")(CollectionScene)
"collections",
"policies",
"documents",
"ui"
)(withTheme(CollectionScene))
); );

View File

@ -11,7 +11,6 @@ import Flex from "components/Flex";
import HelpText from "components/HelpText"; import HelpText from "components/HelpText";
import IconPicker from "components/IconPicker"; import IconPicker from "components/IconPicker";
import Input from "components/Input"; import Input from "components/Input";
import InputRich from "components/InputRich";
import InputSelect from "components/InputSelect"; import InputSelect from "components/InputSelect";
import Switch from "components/Switch"; import Switch from "components/Switch";
@ -27,7 +26,6 @@ type Props = {
class CollectionEdit extends React.Component<Props> { class CollectionEdit extends React.Component<Props> {
@observable name: string = this.props.collection.name; @observable name: string = this.props.collection.name;
@observable sharing: boolean = this.props.collection.sharing; @observable sharing: boolean = this.props.collection.sharing;
@observable description: string = this.props.collection.description;
@observable icon: string = this.props.collection.icon; @observable icon: string = this.props.collection.icon;
@observable color: string = this.props.collection.color || "#4E5C6E"; @observable color: string = this.props.collection.color || "#4E5C6E";
@observable private: boolean = this.props.collection.private; @observable private: boolean = this.props.collection.private;
@ -43,7 +41,6 @@ class CollectionEdit extends React.Component<Props> {
try { try {
await this.props.collection.save({ await this.props.collection.save({
name: this.name, name: this.name,
description: this.description,
icon: this.icon, icon: this.icon,
color: this.color, color: this.color,
private: this.private, private: this.private,
@ -69,10 +66,6 @@ class CollectionEdit extends React.Component<Props> {
} }
}; };
handleDescriptionChange = (getValue: () => string) => {
this.description = getValue();
};
handleNameChange = (ev: SyntheticInputEvent<*>) => { handleNameChange = (ev: SyntheticInputEvent<*>) => {
this.name = ev.target.value; this.name = ev.target.value;
}; };
@ -120,15 +113,6 @@ class CollectionEdit extends React.Component<Props> {
icon={this.icon} icon={this.icon}
/> />
</Flex> </Flex>
<InputRich
id={this.props.collection.id}
label={t("Description")}
onChange={this.handleDescriptionChange}
defaultValue={this.description || ""}
placeholder={t("More details about this collection…")}
minHeight={68}
maxHeight={200}
/>
<InputSelect <InputSelect
label={t("Sort in sidebar")} label={t("Sort in sidebar")}
options={[ options={[

View File

@ -14,7 +14,6 @@ import Flex from "components/Flex";
import HelpText from "components/HelpText"; import HelpText from "components/HelpText";
import IconPicker, { icons } from "components/IconPicker"; import IconPicker, { icons } from "components/IconPicker";
import Input from "components/Input"; import Input from "components/Input";
import InputRich from "components/InputRich";
import Switch from "components/Switch"; import Switch from "components/Switch";
type Props = { type Props = {
@ -29,7 +28,6 @@ type Props = {
@observer @observer
class CollectionNew extends React.Component<Props> { class CollectionNew extends React.Component<Props> {
@observable name: string = ""; @observable name: string = "";
@observable description: string = "";
@observable icon: string = ""; @observable icon: string = "";
@observable color: string = "#4E5C6E"; @observable color: string = "#4E5C6E";
@observable sharing: boolean = true; @observable sharing: boolean = true;
@ -43,7 +41,6 @@ class CollectionNew extends React.Component<Props> {
const collection = new Collection( const collection = new Collection(
{ {
name: this.name, name: this.name,
description: this.description,
sharing: this.sharing, sharing: this.sharing,
icon: this.icon, icon: this.icon,
color: this.color, color: this.color,
@ -90,10 +87,6 @@ class CollectionNew extends React.Component<Props> {
this.hasOpenedIconPicker = true; this.hasOpenedIconPicker = true;
}; };
handleDescriptionChange = (getValue: () => string) => {
this.description = getValue();
};
handlePrivateChange = (ev: SyntheticInputEvent<HTMLInputElement>) => { handlePrivateChange = (ev: SyntheticInputEvent<HTMLInputElement>) => {
this.private = ev.target.checked; this.private = ev.target.checked;
}; };
@ -115,9 +108,9 @@ class CollectionNew extends React.Component<Props> {
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<HelpText> <HelpText>
<Trans> <Trans>
Collections are for grouping your knowledge base. They work best Collections are for grouping your documents. They work best when
when organized around a topic or internal team Product or organized around a topic or internal team Product or Engineering
Engineering for example. for example.
</Trans> </Trans>
</HelpText> </HelpText>
<Flex> <Flex>
@ -138,14 +131,6 @@ class CollectionNew extends React.Component<Props> {
icon={this.icon} icon={this.icon}
/> />
</Flex> </Flex>
<InputRich
label={t("Description")}
onChange={this.handleDescriptionChange}
defaultValue={this.description || ""}
placeholder={t("More details about this collection…")}
minHeight={68}
maxHeight={200}
/>
<Switch <Switch
id="private" id="private"
label={t("Private collection")} label={t("Private collection")}

View File

@ -154,7 +154,7 @@
"react-waypoint": "^9.0.2", "react-waypoint": "^9.0.2",
"react-window": "^1.8.6", "react-window": "^1.8.6",
"reakit": "^1.3.4", "reakit": "^1.3.4",
"rich-markdown-editor": "^11.2.0-0", "rich-markdown-editor": "^11.3.0-0",
"semver": "^7.3.2", "semver": "^7.3.2",
"sequelize": "^6.3.4", "sequelize": "^6.3.4",
"sequelize-cli": "^6.2.0", "sequelize-cli": "^6.2.0",

View File

@ -8,6 +8,10 @@
"Drafts": "Drafts", "Drafts": "Drafts",
"Templates": "Templates", "Templates": "Templates",
"Deleted Collection": "Deleted Collection", "Deleted Collection": "Deleted Collection",
"Sorry, an error occurred saving the collection": "Sorry, an error occurred saving the collection",
"Add description": "Add description",
"Collapse": "Collapse",
"Expand": "Expand",
"Submenu": "Submenu", "Submenu": "Submenu",
"New": "New", "New": "New",
"Only visible to you": "Only visible to you", "Only visible to you": "Only visible to you",
@ -108,8 +112,6 @@
"Export Data": "Export Data", "Export Data": "Export Data",
"Integrations": "Integrations", "Integrations": "Integrations",
"Installation": "Installation", "Installation": "Installation",
"Expand": "Expand",
"Collapse": "Collapse",
"Unstar": "Unstar", "Unstar": "Unstar",
"Star": "Star", "Star": "Star",
"Appearance": "Appearance", "Appearance": "Appearance",
@ -204,8 +206,6 @@
"The collection was updated": "The collection was updated", "The collection was updated": "The collection was updated",
"You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.", "You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.",
"Name": "Name", "Name": "Name",
"Description": "Description",
"More details about this collection…": "More details about this collection…",
"Alphabetical": "Alphabetical", "Alphabetical": "Alphabetical",
"Private collection": "Private collection", "Private collection": "Private collection",
"A private collection will only be visible to invited team members.": "A private collection will only be visible to invited team members.", "A private collection will only be visible to invited team members.": "A private collection will only be visible to invited team members.",
@ -237,7 +237,7 @@
"Never signed in": "Never signed in", "Never signed in": "Never signed in",
"Invited": "Invited", "Invited": "Invited",
"Admin": "Admin", "Admin": "Admin",
"Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.", "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.",
"Creating": "Creating", "Creating": "Creating",
"Create": "Create", "Create": "Create",
"Recently viewed": "Recently viewed", "Recently viewed": "Recently viewed",

View File

@ -10833,10 +10833,10 @@ retry-as-promised@^3.2.0:
dependencies: dependencies:
any-promise "^1.3.0" any-promise "^1.3.0"
rich-markdown-editor@^11.2.0-0: rich-markdown-editor@^11.3.0-0:
version "11.2.0-0" version "11.3.0-0"
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.2.0-0.tgz#8f031e2367133f3aac22cb47d150e347460d4987" resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.3.0-0.tgz#0034da293928e4211c5c39544038fd28a7d78a67"
integrity sha512-qqL44VDToMEmTQZ68r+rv9ZjgtN6s5WiXtZFIOYRGq9pUO7brvd/+WWpXY0z4dNqlAMV5nLGXGIshwKRAMbg/g== integrity sha512-6iNmmiYYOXSoifkIemRG1GIp6gkEo6yAnw4FjkFTslOP0B/ZoHFworU7I3qqJ84efEhYPzEGfrGBclRPBaggAg==
dependencies: dependencies:
copy-to-clipboard "^3.0.8" copy-to-clipboard "^3.0.8"
lodash "^4.17.11" lodash "^4.17.11"