mirror of
https://github.com/outline/outline.git
synced 2026-08-03 13:27:25 +03:00
Display model title in dialog header (#13196)
* Display model title in dialogs * feedback
This commit is contained in:
@@ -27,6 +27,7 @@ import { CollectionEdit } from "~/components/Collection/CollectionEdit";
|
||||
import { CollectionNew } from "~/components/Collection/CollectionNew";
|
||||
import CollectionDeleteDialog from "~/components/CollectionDeleteDialog";
|
||||
import ConfirmationDialog from "~/components/ConfirmationDialog";
|
||||
import { DialogTitle } from "~/components/DialogTitle";
|
||||
import DynamicCollectionIcon from "~/components/Icons/CollectionIcon";
|
||||
import { getHeaderExpandedKey } from "~/components/Sidebar/components/Header";
|
||||
import {
|
||||
@@ -105,7 +106,7 @@ export const editCollection = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Edit collection"),
|
||||
title: <DialogTitle title={t("Edit collection")} model={collection} />,
|
||||
content: (
|
||||
<CollectionEdit
|
||||
onSubmit={stores.dialogs.closeAllModals}
|
||||
@@ -131,7 +132,9 @@ export const editCollectionPermissions = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Share this collection"),
|
||||
title: (
|
||||
<DialogTitle title={t("Share this collection")} model={collection} />
|
||||
),
|
||||
content: (
|
||||
<SharePopover
|
||||
collection={collection}
|
||||
@@ -425,7 +428,7 @@ export const archiveCollection = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Archive collection"),
|
||||
title: <DialogTitle title={t("Archive collection")} model={collection} />,
|
||||
content: (
|
||||
<ConfirmationDialog
|
||||
onSubmit={async () => {
|
||||
@@ -477,7 +480,7 @@ export const deleteCollection = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Delete collection"),
|
||||
title: <DialogTitle title={t("Delete collection")} model={collection} />,
|
||||
content: (
|
||||
<CollectionDeleteDialog
|
||||
collection={collection}
|
||||
@@ -502,7 +505,7 @@ export const exportCollection = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Export collection"),
|
||||
title: <DialogTitle title={t("Export collection")} model={collection} />,
|
||||
content: (
|
||||
<ExportDialog
|
||||
collection={collection}
|
||||
|
||||
@@ -52,6 +52,7 @@ import DocumentPermanentDelete from "~/scenes/DocumentPermanentDelete";
|
||||
import DocumentPublish from "~/scenes/DocumentPublish";
|
||||
import DeleteDocumentsInTrash from "~/scenes/Trash/components/DeleteDocumentsInTrash";
|
||||
import ConfirmationDialog from "~/components/ConfirmationDialog";
|
||||
import { DialogTitle } from "~/components/DialogTitle";
|
||||
import DocumentCopy from "~/components/DocumentExplorer/DocumentCopy";
|
||||
import { DocumentDownload } from "~/components/DocumentDownload";
|
||||
import MarkdownIcon from "~/components/Icons/MarkdownIcon";
|
||||
@@ -531,7 +532,7 @@ export const publishDocument = createAction({
|
||||
);
|
||||
} else if (document) {
|
||||
stores.dialogs.openModal({
|
||||
title: t("Publish document"),
|
||||
title: <DialogTitle title={t("Publish document")} model={document} />,
|
||||
content: <DocumentPublish document={document} />,
|
||||
});
|
||||
}
|
||||
@@ -686,7 +687,7 @@ export const shareDocument = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Share this document"),
|
||||
title: <DialogTitle title={t("Share document")} model={document} />,
|
||||
content: (
|
||||
<SharePopover
|
||||
document={document}
|
||||
@@ -715,7 +716,7 @@ export const downloadDocument = createAction({
|
||||
invariant(document, "Document must exist");
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Download document"),
|
||||
title: <DialogTitle title={t("Download document")} model={document} />,
|
||||
content: (
|
||||
<DocumentDownload
|
||||
document={document}
|
||||
@@ -905,7 +906,7 @@ export const duplicateDocument = createAction({
|
||||
invariant(document, "Document must exist");
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Copy document"),
|
||||
title: <DialogTitle title={t("Copy document")} model={document} />,
|
||||
content: (
|
||||
<DocumentCopy
|
||||
document={document}
|
||||
@@ -1173,7 +1174,7 @@ export const importDocument = createAction({
|
||||
});
|
||||
|
||||
export const createTemplateFromDocument = createAction({
|
||||
name: ({ t }) => t("Templatize"),
|
||||
name: ({ t }) => `${t("Templatize")}…`,
|
||||
analyticsName: "Templatize document",
|
||||
section: ActiveDocumentSection,
|
||||
icon: <ShapesIcon />,
|
||||
@@ -1194,10 +1195,14 @@ export const createTemplateFromDocument = createAction({
|
||||
if (!activeDocumentId) {
|
||||
return;
|
||||
}
|
||||
const document = stores.documents.get(activeDocumentId);
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
stores.dialogs.openModal({
|
||||
title: t("Create template"),
|
||||
title: <DialogTitle title={t("Create template")} model={document} />,
|
||||
content: <DocumentTemplatizeDialog documentId={activeDocumentId} />,
|
||||
});
|
||||
},
|
||||
@@ -1255,9 +1260,14 @@ export const moveDocumentToCollection = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Move {{ documentType }}", {
|
||||
documentType: document.noun,
|
||||
}),
|
||||
title: (
|
||||
<DialogTitle
|
||||
title={t("Move {{ documentType }}", {
|
||||
documentType: document.noun,
|
||||
})}
|
||||
model={document}
|
||||
/>
|
||||
),
|
||||
content: <DocumentMove document={document} />,
|
||||
});
|
||||
}
|
||||
@@ -1303,7 +1313,12 @@ export const archiveDocument = createAction({
|
||||
}
|
||||
|
||||
dialogs.openModal({
|
||||
title: t("Are you sure you want to archive this document?"),
|
||||
title: (
|
||||
<DialogTitle
|
||||
title={t("Are you sure you want to archive this document?")}
|
||||
model={document}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<ConfirmationDialog
|
||||
onSubmit={async () => {
|
||||
@@ -1431,9 +1446,14 @@ export const deleteDocument = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Delete {{ documentName }}", {
|
||||
documentName: document.noun,
|
||||
}),
|
||||
title: (
|
||||
<DialogTitle
|
||||
title={t("Delete {{ documentName }}", {
|
||||
documentName: document.noun,
|
||||
})}
|
||||
model={document}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<DocumentDelete
|
||||
document={document}
|
||||
@@ -1465,9 +1485,14 @@ export const permanentlyDeleteDocument = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Permanently delete {{ documentName }}", {
|
||||
documentName: document.noun,
|
||||
}),
|
||||
title: (
|
||||
<DialogTitle
|
||||
title={t("Permanently delete {{ documentName }}", {
|
||||
documentName: document.noun,
|
||||
})}
|
||||
model={document}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<DocumentPermanentDelete
|
||||
document={document}
|
||||
@@ -1581,7 +1606,7 @@ export const openDocumentInsights = createAction({
|
||||
}
|
||||
|
||||
stores.dialogs.openModal({
|
||||
title: t("Insights"),
|
||||
title: <DialogTitle title={t("Insights")} model={document} />,
|
||||
content: <Insights document={document} />,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { DocumentIcon } from "outline-icons";
|
||||
import type * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Icon from "@shared/components/Icon";
|
||||
import type Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
import CollectionIcon from "~/components/Icons/CollectionIcon";
|
||||
import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
/** The title of the dialog. */
|
||||
title: React.ReactNode;
|
||||
/** The document or collection that the dialog acts upon. */
|
||||
model: Document | Collection;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a dialog title, additionally showing the name and icon of the model
|
||||
* being acted upon when it is not the one currently being viewed.
|
||||
*
|
||||
* @returns the dialog title, optionally subtitled with the model.
|
||||
*/
|
||||
export const DialogTitle = observer(function DialogTitle_({
|
||||
title,
|
||||
model,
|
||||
}: Props) {
|
||||
const { ui } = useStores();
|
||||
const isDocument = model instanceof Document;
|
||||
|
||||
if (
|
||||
isDocument
|
||||
? ui.activeDocumentId === model.id
|
||||
: ui.activeCollectionId === model.id
|
||||
) {
|
||||
return <>{title}</>;
|
||||
}
|
||||
|
||||
const icon = !isDocument ? (
|
||||
<CollectionIcon collection={model} />
|
||||
) : model.icon ? (
|
||||
<Icon
|
||||
value={model.icon}
|
||||
initial={model.initial}
|
||||
color={model.color ?? undefined}
|
||||
/>
|
||||
) : (
|
||||
<DocumentIcon outline={model.isDraft} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
{title}
|
||||
<Subject type="secondary" size="small">
|
||||
{icon}
|
||||
<Text type="secondary" ellipsis>
|
||||
{isDocument ? model.titleWithDefault : model.name}
|
||||
</Text>
|
||||
</Subject>
|
||||
</Wrapper>
|
||||
);
|
||||
});
|
||||
|
||||
const Wrapper = styled.span`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
margin-top: -4px;
|
||||
gap: 4px;
|
||||
`;
|
||||
|
||||
const Subject = styled(Text)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
margin-left: -4px;
|
||||
|
||||
> :first-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`;
|
||||
@@ -3,6 +3,7 @@ import { TrashIcon } from "outline-icons";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DocumentDelete from "~/scenes/DocumentDelete";
|
||||
import { DialogTitle } from "~/components/DialogTitle";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { trashPath } from "~/utils/routeHelpers";
|
||||
import type { DragObject } from "../hooks/useDragAndDrop";
|
||||
@@ -21,9 +22,14 @@ function TrashLink() {
|
||||
}
|
||||
|
||||
dialogs.openModal({
|
||||
title: t("Delete {{ documentName }}", {
|
||||
documentName: document?.noun,
|
||||
}),
|
||||
title: (
|
||||
<DialogTitle
|
||||
title={t("Delete {{ documentName }}", {
|
||||
documentName: document.noun,
|
||||
})}
|
||||
model={document}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<DocumentDelete
|
||||
document={document}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import invariant from "invariant";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import ConfirmationDialog from "~/components/ConfirmationDialog";
|
||||
@@ -47,15 +47,9 @@ function DocumentTemplatizeDialog({ documentId }: Props) {
|
||||
>
|
||||
<Flex column gap={12}>
|
||||
<div>
|
||||
<Trans
|
||||
defaults="Creating a template from <em>{{titleWithDefault}}</em> is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents."
|
||||
values={{
|
||||
titleWithDefault: document.titleWithDefault,
|
||||
}}
|
||||
components={{
|
||||
em: <strong />,
|
||||
}}
|
||||
/>
|
||||
{t(
|
||||
"Creating a template is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents."
|
||||
)}
|
||||
</div>
|
||||
<SelectLocation
|
||||
defaultCollectionId={collectionId}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import * as React from "react";
|
||||
|
||||
type DialogDefinition = {
|
||||
title: string;
|
||||
title: React.ReactNode;
|
||||
content: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
style?: React.CSSProperties;
|
||||
@@ -12,9 +12,15 @@ type DialogDefinition = {
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
type GuideDefinition = {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
export default class DialogsStore {
|
||||
@observable.shallow
|
||||
guide: DialogDefinition;
|
||||
guide: GuideDefinition;
|
||||
|
||||
@observable.shallow
|
||||
modalStack = new Map<string, DialogDefinition>();
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"Unpublish": "Unpublish",
|
||||
"Unpublished {{ documentName }}": "Unpublished {{ documentName }}",
|
||||
"Subscription inherited from collection": "Subscription inherited from collection",
|
||||
"Share this document": "Share this document",
|
||||
"Share document": "Share document",
|
||||
"Download": "Download",
|
||||
"Download document": "Download document",
|
||||
"Download as Markdown": "Download as Markdown",
|
||||
@@ -570,7 +570,7 @@
|
||||
"Highlight some text and use the <1></1> control to add placeholders that can be filled out when creating new documents": "Highlight some text and use the <1></1> control to add placeholders that can be filled out when creating new documents",
|
||||
"You’re editing a template": "You’re editing a template",
|
||||
"Template created, go ahead and customize it": "Template created, go ahead and customize it",
|
||||
"Creating a template from <em>{{titleWithDefault}}</em> is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.": "Creating a template from <em>{{titleWithDefault}}</em> is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.",
|
||||
"Creating a template is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.": "Creating a template is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.",
|
||||
"Enable other members to use the template immediately": "Enable other members to use the template immediately",
|
||||
"Location": "Location",
|
||||
"Admins can manage the workspace and access billing.": "Admins can manage the workspace and access billing.",
|
||||
|
||||
Reference in New Issue
Block a user