This commit is contained in:
Tom Moor
2025-02-08 15:56:07 -05:00
parent cc38c4fedb
commit 0e07d06a91
6 changed files with 77 additions and 11 deletions
@@ -3,10 +3,11 @@ import { observer } from "mobx-react";
import { PlusIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { useHistory } from "react-router-dom";
import styled from "styled-components";
import Icon from "@shared/components/Icon";
import { NavigationNode } from "@shared/types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { sortNavigationNodes } from "@shared/utils/collections";
import { DocumentValidation } from "@shared/validations";
import Collection from "~/models/Collection";
@@ -18,7 +19,7 @@ import useBoolean from "~/hooks/useBoolean";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import DocumentMenu from "~/menus/DocumentMenu";
import { newNestedDocumentPath } from "~/utils/routeHelpers";
import { documentEditPath } from "~/utils/routeHelpers";
import {
useDragDocument,
useDropToReorderDocument,
@@ -58,6 +59,7 @@ function InnerDocumentLink(
) {
const { documents, policies } = useStores();
const { t } = useTranslation();
const history = useHistory();
const canUpdate = usePolicy(node.id).update;
const isActiveDocument = activeDocument && activeDocument.id === node.id;
const hasChildDocuments =
@@ -216,6 +218,9 @@ function InnerDocumentLink(
[setExpanded, setCollapsed, hasChildren, expanded]
);
const [isAddingNewChild, setIsAddingNewChild, closeAddingNewChild] =
useBoolean();
return (
<>
<Relative ref={parentRef}>
@@ -282,8 +287,11 @@ function InnerDocumentLink(
<NudeButton
type={undefined}
aria-label={t("New nested document")}
as={Link}
to={newNestedDocumentPath(document.id)}
onClick={(ev) => {
ev.preventDefault();
setIsAddingNewChild();
setExpanded();
}}
>
<PlusIcon />
</NudeButton>
@@ -308,6 +316,38 @@ function InnerDocumentLink(
<DropCursor isActiveDrop={isOverReorder} innerRef={dropToReorder} />
)}
</Relative>
{isAddingNewChild && (
<SidebarLink
isActive={() => true}
depth={depth + 1}
label={
<EditableTitle
title=""
canUpdate
isEditing
placeholder={`${t("New doc")}`}
onEscape={closeAddingNewChild}
onSubmit={async (input) => {
const newDocument = await documents.create(
{
collectionId: collection?.id,
parentDocumentId: node.id,
fullWidth: doc?.fullWidth,
title: input,
data: ProsemirrorHelper.getEmptyDocument(),
},
{ publish: true }
);
collection?.addDocument(newDocument, node.id);
closeAddingNewChild();
history.replace(documentEditPath(newDocument));
}}
maxLength={DocumentValidation.maxTitleLength}
/>
}
/>
)}
<Folder expanded={expanded && !isDragging}>
{nodeChildren.map((childNode, index) => (
<DocumentLink
@@ -3,12 +3,14 @@ import { toast } from "sonner";
import styled from "styled-components";
import { s } from "@shared/styles";
type Props = {
onSubmit: (title: string) => Promise<void>;
type Props = Omit<React.HTMLAttributes<HTMLInputElement>, "onSubmit"> & {
onSubmit: (title: string) => Promise<void> | void;
onEditing?: (isEditing: boolean) => void;
onEscape?: () => void;
title: string;
canUpdate: boolean;
maxLength?: number;
isEditing?: boolean;
};
export type RefHandle = {
@@ -16,10 +18,10 @@ export type RefHandle = {
};
function EditableTitle(
{ title, onSubmit, canUpdate, onEditing, ...rest }: Props,
{ title, onSubmit, canUpdate, onEditing, onEscape, ...rest }: Props,
ref: React.RefObject<RefHandle>
) {
const [isEditing, setIsEditing] = React.useState(false);
const [isEditing, setIsEditing] = React.useState(rest.isEditing || false);
const [originalValue, setOriginalValue] = React.useState(title);
const [value, setValue] = React.useState(title);
@@ -59,6 +61,7 @@ function EditableTitle(
if (trimmedValue === originalValue || trimmedValue.length === 0) {
setValue(originalValue);
onEscape?.();
return;
}
@@ -83,6 +86,7 @@ function EditableTitle(
}
if (ev.key === "Escape") {
setIsEditing(false);
onEscape?.();
setValue(originalValue);
}
if (ev.key === "Enter") {
+18
View File
@@ -263,6 +263,24 @@ export default class Collection extends ParanoidModel {
});
}
@action
addDocument(document: Document, parentDocumentId?: string) {
if (!this.documents) {
return;
}
const travelNodes = (nodes: NavigationNode[]) =>
nodes.forEach((node) => {
if (node.id === parentDocumentId) {
node.children = [document.asNavigationNode, ...(node.children ?? [])];
} else {
travelNodes(node.children);
}
});
travelNodes(this.documents);
}
@action
updateIndex(index: string) {
this.index = index;
@@ -584,6 +584,7 @@ class DocumentScene extends React.Component<Props> {
readOnly={readOnly}
canUpdate={abilities.update}
canComment={abilities.comment}
autoFocus
>
{shareId ? (
<ReferencesWrapper>
+1 -1
View File
@@ -50,7 +50,7 @@ function DocumentNew({ template }: Props) {
user.getPreference(UserPreference.FullWidthDocuments),
templateId: query.get("templateId") ?? undefined,
template,
title: "",
title: query.get("title") ?? "",
data: ProsemirrorHelper.getEmptyDocument(),
},
{ publish: collection?.id || parentDocumentId ? true : undefined }
+5 -2
View File
@@ -107,8 +107,11 @@ export function newDocumentPath(
: `/doc/new?${queryString.stringify(params)}`;
}
export function newNestedDocumentPath(parentDocumentId?: string): string {
return `/doc/new?${queryString.stringify({ parentDocumentId })}`;
export function newNestedDocumentPath(
parentDocumentId?: string,
title?: string
): string {
return `/doc/new?${queryString.stringify({ parentDocumentId, title })}`;
}
export function searchPath(