Display model title in dialog header (#13196)

* Display model title in dialogs

* feedback
This commit is contained in:
Tom Moor
2026-07-29 19:12:24 -04:00
committed by GitHub
parent 4c78dd8ade
commit f84ca3b9c3
7 changed files with 157 additions and 39 deletions
+8 -5
View File
@@ -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}
+42 -17
View File
@@ -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} />,
});
},
+84
View File
@@ -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}
+4 -10
View File
@@ -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}
+8 -2
View File
@@ -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>();
+2 -2
View File
@@ -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",
"Youre editing a template": "Youre 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.",