feat: Inline collection editing (#1865)
This commit is contained in:
23
app/components/Arrow.js
Normal file
23
app/components/Arrow.js
Normal 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>
|
||||
);
|
||||
}
|
212
app/components/CollectionDescription.js
Normal file
212
app/components/CollectionDescription.js
Normal 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);
|
@ -27,13 +27,16 @@ export type Props = {|
|
||||
autoFocus?: boolean,
|
||||
template?: boolean,
|
||||
placeholder?: string,
|
||||
maxLength?: number,
|
||||
scrollTo?: string,
|
||||
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,
|
||||
@ -177,7 +180,7 @@ const StyledEditor = styled(RichMarkdownEditor)`
|
||||
justify-content: start;
|
||||
|
||||
> div {
|
||||
transition: ${(props) => props.theme.backgroundTransition};
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
& * {
|
||||
|
@ -2,6 +2,7 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Arrow from "components/Arrow";
|
||||
|
||||
type Props = {
|
||||
direction: "left" | "right",
|
||||
@ -14,22 +15,7 @@ const Toggle = React.forwardRef<Props, HTMLButtonElement>(
|
||||
return (
|
||||
<Positioner style={style}>
|
||||
<ToggleButton ref={ref} $direction={direction} onClick={onClick}>
|
||||
<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>
|
||||
<Arrow />
|
||||
</ToggleButton>
|
||||
</Positioner>
|
||||
);
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Toast from "./components/Toast";
|
||||
import Toast from "components/Toast";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
function Toasts() {
|
@ -1,3 +0,0 @@
|
||||
// @flow
|
||||
import Toasts from "./Toasts";
|
||||
export default Toasts;
|
31
app/hooks/useDebouncedCallback.js
Normal file
31
app/hooks/useDebouncedCallback.js
Normal 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);
|
||||
};
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
|
||||
import { NewDocumentIcon, PlusIcon, PinIcon, MoreIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { withTranslation, Trans, type TFunction } from "react-i18next";
|
||||
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 DocumentsStore from "stores/DocumentsStore";
|
||||
@ -20,9 +19,9 @@ import Search from "scenes/Search";
|
||||
import Actions, { Action, Separator } from "components/Actions";
|
||||
import Button from "components/Button";
|
||||
import CenteredContent from "components/CenteredContent";
|
||||
import CollectionDescription from "components/CollectionDescription";
|
||||
import CollectionIcon from "components/CollectionIcon";
|
||||
import DocumentList from "components/DocumentList";
|
||||
import Editor from "components/Editor";
|
||||
import Flex from "components/Flex";
|
||||
import Heading from "components/Heading";
|
||||
import HelpText from "components/HelpText";
|
||||
@ -37,7 +36,6 @@ import Tab from "components/Tab";
|
||||
import Tabs from "components/Tabs";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import CollectionMenu from "menus/CollectionMenu";
|
||||
import { type Theme } from "types";
|
||||
import { AuthorizationError } from "utils/errors";
|
||||
import { newDocumentUrl, collectionUrl } from "utils/routeHelpers";
|
||||
|
||||
@ -47,7 +45,6 @@ type Props = {
|
||||
collections: CollectionsStore,
|
||||
policies: PoliciesStore,
|
||||
match: Match,
|
||||
theme: Theme,
|
||||
t: TFunction,
|
||||
};
|
||||
|
||||
@ -57,7 +54,6 @@ class CollectionScene extends React.Component<Props> {
|
||||
@observable isFetching: boolean = true;
|
||||
@observable permissionsModalOpen: boolean = false;
|
||||
@observable editModalOpen: boolean = false;
|
||||
@observable redirectTo: ?string;
|
||||
|
||||
componentDidMount() {
|
||||
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<>) => {
|
||||
ev.preventDefault();
|
||||
this.permissionsModalOpen = true;
|
||||
@ -157,7 +145,12 @@ class CollectionScene extends React.Component<Props> {
|
||||
delay={500}
|
||||
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")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@ -186,9 +179,8 @@ class CollectionScene extends React.Component<Props> {
|
||||
}
|
||||
|
||||
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 />;
|
||||
|
||||
const pinnedDocuments = this.collection
|
||||
@ -197,7 +189,6 @@ class CollectionScene extends React.Component<Props> {
|
||||
const collection = this.collection;
|
||||
const collectionName = collection ? collection.name : "";
|
||||
const hasPinnedDocuments = !!pinnedDocuments.length;
|
||||
const hasDescription = collection ? collection.hasDescription : false;
|
||||
|
||||
return (
|
||||
<CenteredContent>
|
||||
@ -218,7 +209,7 @@ class CollectionScene extends React.Component<Props> {
|
||||
</HelpText>
|
||||
<Wrapper>
|
||||
<Link to={newDocumentUrl(collection.id)}>
|
||||
<Button icon={<NewDocumentIcon color={theme.buttonText} />}>
|
||||
<Button icon={<NewDocumentIcon color="currentColor" />}>
|
||||
{t("Create a document")}
|
||||
</Button>
|
||||
</Link>
|
||||
@ -257,17 +248,7 @@ class CollectionScene extends React.Component<Props> {
|
||||
<CollectionIcon collection={collection} size={40} expanded />{" "}
|
||||
{collection.name}
|
||||
</Heading>
|
||||
|
||||
{hasDescription && (
|
||||
<React.Suspense fallback={<p>Loading…</p>}>
|
||||
<Editor
|
||||
id={collection.id}
|
||||
key={collection.description}
|
||||
defaultValue={collection.description}
|
||||
readOnly
|
||||
/>
|
||||
</React.Suspense>
|
||||
)}
|
||||
<CollectionDescription collection={collection} />
|
||||
|
||||
{hasPinnedDocuments && (
|
||||
<>
|
||||
@ -396,10 +377,5 @@ const Wrapper = styled(Flex)`
|
||||
`;
|
||||
|
||||
export default withTranslation()<CollectionScene>(
|
||||
inject(
|
||||
"collections",
|
||||
"policies",
|
||||
"documents",
|
||||
"ui"
|
||||
)(withTheme(CollectionScene))
|
||||
inject("collections", "policies", "documents", "ui")(CollectionScene)
|
||||
);
|
||||
|
@ -11,7 +11,6 @@ import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import IconPicker from "components/IconPicker";
|
||||
import Input from "components/Input";
|
||||
import InputRich from "components/InputRich";
|
||||
import InputSelect from "components/InputSelect";
|
||||
import Switch from "components/Switch";
|
||||
|
||||
@ -27,7 +26,6 @@ type Props = {
|
||||
class CollectionEdit extends React.Component<Props> {
|
||||
@observable name: string = this.props.collection.name;
|
||||
@observable sharing: boolean = this.props.collection.sharing;
|
||||
@observable description: string = this.props.collection.description;
|
||||
@observable icon: string = this.props.collection.icon;
|
||||
@observable color: string = this.props.collection.color || "#4E5C6E";
|
||||
@observable private: boolean = this.props.collection.private;
|
||||
@ -43,7 +41,6 @@ class CollectionEdit extends React.Component<Props> {
|
||||
try {
|
||||
await this.props.collection.save({
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
icon: this.icon,
|
||||
color: this.color,
|
||||
private: this.private,
|
||||
@ -69,10 +66,6 @@ class CollectionEdit extends React.Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
handleDescriptionChange = (getValue: () => string) => {
|
||||
this.description = getValue();
|
||||
};
|
||||
|
||||
handleNameChange = (ev: SyntheticInputEvent<*>) => {
|
||||
this.name = ev.target.value;
|
||||
};
|
||||
@ -120,15 +113,6 @@ class CollectionEdit extends React.Component<Props> {
|
||||
icon={this.icon}
|
||||
/>
|
||||
</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
|
||||
label={t("Sort in sidebar")}
|
||||
options={[
|
||||
|
@ -14,7 +14,6 @@ import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import IconPicker, { icons } from "components/IconPicker";
|
||||
import Input from "components/Input";
|
||||
import InputRich from "components/InputRich";
|
||||
import Switch from "components/Switch";
|
||||
|
||||
type Props = {
|
||||
@ -29,7 +28,6 @@ type Props = {
|
||||
@observer
|
||||
class CollectionNew extends React.Component<Props> {
|
||||
@observable name: string = "";
|
||||
@observable description: string = "";
|
||||
@observable icon: string = "";
|
||||
@observable color: string = "#4E5C6E";
|
||||
@observable sharing: boolean = true;
|
||||
@ -43,7 +41,6 @@ class CollectionNew extends React.Component<Props> {
|
||||
const collection = new Collection(
|
||||
{
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
sharing: this.sharing,
|
||||
icon: this.icon,
|
||||
color: this.color,
|
||||
@ -90,10 +87,6 @@ class CollectionNew extends React.Component<Props> {
|
||||
this.hasOpenedIconPicker = true;
|
||||
};
|
||||
|
||||
handleDescriptionChange = (getValue: () => string) => {
|
||||
this.description = getValue();
|
||||
};
|
||||
|
||||
handlePrivateChange = (ev: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
this.private = ev.target.checked;
|
||||
};
|
||||
@ -115,9 +108,9 @@ class CollectionNew extends React.Component<Props> {
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<HelpText>
|
||||
<Trans>
|
||||
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.
|
||||
</Trans>
|
||||
</HelpText>
|
||||
<Flex>
|
||||
@ -138,14 +131,6 @@ class CollectionNew extends React.Component<Props> {
|
||||
icon={this.icon}
|
||||
/>
|
||||
</Flex>
|
||||
<InputRich
|
||||
label={t("Description")}
|
||||
onChange={this.handleDescriptionChange}
|
||||
defaultValue={this.description || ""}
|
||||
placeholder={t("More details about this collection…")}
|
||||
minHeight={68}
|
||||
maxHeight={200}
|
||||
/>
|
||||
<Switch
|
||||
id="private"
|
||||
label={t("Private collection")}
|
||||
|
@ -154,7 +154,7 @@
|
||||
"react-waypoint": "^9.0.2",
|
||||
"react-window": "^1.8.6",
|
||||
"reakit": "^1.3.4",
|
||||
"rich-markdown-editor": "^11.2.0-0",
|
||||
"rich-markdown-editor": "^11.3.0-0",
|
||||
"semver": "^7.3.2",
|
||||
"sequelize": "^6.3.4",
|
||||
"sequelize-cli": "^6.2.0",
|
||||
|
@ -8,6 +8,10 @@
|
||||
"Drafts": "Drafts",
|
||||
"Templates": "Templates",
|
||||
"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",
|
||||
"New": "New",
|
||||
"Only visible to you": "Only visible to you",
|
||||
@ -108,8 +112,6 @@
|
||||
"Export Data": "Export Data",
|
||||
"Integrations": "Integrations",
|
||||
"Installation": "Installation",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Unstar": "Unstar",
|
||||
"Star": "Star",
|
||||
"Appearance": "Appearance",
|
||||
@ -204,8 +206,6 @@
|
||||
"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.",
|
||||
"Name": "Name",
|
||||
"Description": "Description",
|
||||
"More details about this collection…": "More details about this collection…",
|
||||
"Alphabetical": "Alphabetical",
|
||||
"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.",
|
||||
@ -237,7 +237,7 @@
|
||||
"Never signed in": "Never signed in",
|
||||
"Invited": "Invited",
|
||||
"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",
|
||||
"Create": "Create",
|
||||
"Recently viewed": "Recently viewed",
|
||||
|
@ -10833,10 +10833,10 @@ retry-as-promised@^3.2.0:
|
||||
dependencies:
|
||||
any-promise "^1.3.0"
|
||||
|
||||
rich-markdown-editor@^11.2.0-0:
|
||||
version "11.2.0-0"
|
||||
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.2.0-0.tgz#8f031e2367133f3aac22cb47d150e347460d4987"
|
||||
integrity sha512-qqL44VDToMEmTQZ68r+rv9ZjgtN6s5WiXtZFIOYRGq9pUO7brvd/+WWpXY0z4dNqlAMV5nLGXGIshwKRAMbg/g==
|
||||
rich-markdown-editor@^11.3.0-0:
|
||||
version "11.3.0-0"
|
||||
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.3.0-0.tgz#0034da293928e4211c5c39544038fd28a7d78a67"
|
||||
integrity sha512-6iNmmiYYOXSoifkIemRG1GIp6gkEo6yAnw4FjkFTslOP0B/ZoHFworU7I3qqJ84efEhYPzEGfrGBclRPBaggAg==
|
||||
dependencies:
|
||||
copy-to-clipboard "^3.0.8"
|
||||
lodash "^4.17.11"
|
||||
|
Reference in New Issue
Block a user