fix: Button elements in DocumentMeta incorrectly rendered as links (#13187)

* fix: Button elements in DocumentMeta rendered as links

* fix: Expose comments sidebar state on toggle button

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Tom Moor
2026-07-28 20:53:52 -04:00
committed by GitHub
co-authored by Claude
parent 2ad5762523
commit c1ae5aa86e
2 changed files with 57 additions and 44 deletions
+33 -1
View File
@@ -11,20 +11,32 @@ import type Revision from "~/models/Revision";
import DocumentBreadcrumb from "~/components/DocumentBreadcrumb";
import DocumentTasks from "~/components/DocumentTasks";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
import Time from "~/components/Time";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
type Props = {
/** Additional content appended to the end of the meta. */
children?: React.ReactNode;
/** Show the collection that the document belongs to. */
showCollection?: boolean;
/** Show the published time, even when the document has since been updated. */
showPublished?: boolean;
/** Show when the current user last viewed the document. */
showLastViewed?: boolean;
/** Show the number of documents nested under this one. */
showParentDocuments?: boolean;
/** The document to display meta information for. */
document: Document;
/** A revision of the document, when displaying meta for a point in history. */
revision?: Revision;
/** Replace the current history entry instead of pushing a new one when `to` is set. */
replace?: boolean;
/** Destination to link the meta content to. */
to?: LocationDescriptor;
/** Called when the meta content is clicked, renders it as a button. Takes precedence over `to`. */
onClick?: () => void;
};
const DocumentMeta: React.FC<Props> = ({
@@ -37,6 +49,7 @@ const DocumentMeta: React.FC<Props> = ({
children,
replace,
to,
onClick,
...rest
}: Props) => {
const { t } = useTranslation();
@@ -176,7 +189,9 @@ const DocumentMeta: React.FC<Props> = ({
return (
<Container align="center" $rtl={document.dir === "rtl"} {...rest} dir="ltr">
{to ? (
{onClick ? (
<MetaButton onClick={onClick}>{content}</MetaButton>
) : to ? (
<Link to={to} replace={replace}>
{content}
</Link>
@@ -212,6 +227,23 @@ const DocumentMeta: React.FC<Props> = ({
);
};
/** A button that visually matches the surrounding meta text. */
export const MetaButton = styled(NudeButton)`
display: inline-flex;
align-items: center;
gap: 2px;
width: auto;
height: auto;
border-radius: 0;
color: inherit;
font: inherit;
text-align: inherit;
&:hover {
text-decoration: underline;
}
`;
export const Separator = styled.span`
padding: 0 0.4em;
+24 -43
View File
@@ -1,15 +1,15 @@
import type { LocationDescriptor } from "history";
import { observer, useObserver } from "mobx-react";
import { CommentIcon } from "outline-icons";
import { useRef, Fragment } from "react";
import { useRef, Fragment, useCallback } 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 type Document from "~/models/Document";
import type Revision from "~/models/Revision";
import type Template from "~/models/Template";
import { openDocumentInsights } from "~/actions/definitions/documents";
import DocumentMeta, { Separator } from "~/components/DocumentMeta";
import DocumentMeta, { MetaButton, Separator } from "~/components/DocumentMeta";
import Fade from "~/components/Fade";
import { useSplitView } from "~/components/SplitView/context";
import useCurrentTeam from "~/hooks/useCurrentTeam";
@@ -18,7 +18,6 @@ import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import breakpoint from "styled-components-breakpoint";
import { documentPath } from "~/utils/routeHelpers";
import NudeButton from "~/components/NudeButton";
type Props = {
/* The document to display meta data for */
@@ -31,6 +30,7 @@ type Props = {
function TitleDocumentMeta({ to, document, revision, rtl, ...rest }: Props) {
const { views, comments, ui } = useStores();
const { t } = useTranslation();
const history = useHistory();
const { pane } = useSplitView();
const sidebarContext = useLocationSidebarContext();
const team = useCurrentTeam();
@@ -44,77 +44,58 @@ function TitleDocumentMeta({ to, document, revision, rtl, ...rest }: Props) {
const commentsCount = comments.unresolvedCommentsInDocumentCount(document.id);
const commentingEnabled = team.commentingEnabled;
const commentsOpen = ui.getRightSidebar(pane) === "comments";
const handleClickMeta = useCallback(() => {
if (to) {
history.replace(to);
}
}, [history, to]);
const handleClickComment = useCallback(() => {
history.push({
pathname: documentPath(document as Document),
state: { sidebarContext },
});
ui.setRightSidebar(commentsOpen ? null : "comments", pane);
}, [history, document, sidebarContext, ui, pane, commentsOpen]);
return (
<Meta
document={document as Document}
revision={revision}
to={to}
replace
onClick={to ? handleClickMeta : undefined}
$rtl={rtl}
{...rest}
>
{commentingEnabled && can.comment && (
<>
<Separator />
<CommentLink
to={{
pathname: documentPath(document as Document),
state: { sidebarContext },
}}
onClick={() =>
ui.setRightSidebar(
ui.getRightSidebar(pane) === "comments" ? null : "comments",
pane
)
}
>
<MetaButton onClick={handleClickComment} aria-expanded={commentsOpen}>
<CommentIcon size={18} />
{commentsCount
? t("{{ count }} comment", { count: commentsCount })
: t("Comment")}
</CommentLink>
</MetaButton>
</>
)}
{totalViewers && can.listViews && !(document as Document).isDraft ? (
<Wrapper>
<Separator />
<InsightsButton action={openDocumentInsights}>
<MetaButton action={openDocumentInsights}>
{t("Viewed by")}{" "}
{onlyYou
? t("only you")
: `${totalViewers} ${
totalViewers === 1 ? t("person") : t("people")
}`}
</InsightsButton>
</MetaButton>
</Wrapper>
) : null}
</Meta>
);
}
const CommentLink = styled(Link)`
display: inline-flex;
align-items: center;
gap: 2px;
`;
const InsightsButton = styled(NudeButton)`
background: none;
border: none;
padding: 0;
width: auto;
height: auto;
color: inherit;
font: inherit;
text-decoration: none;
cursor: var(--pointer);
&:hover {
text-decoration: underline;
}
`;
export const Meta = styled(DocumentMeta)<{ $rtl?: boolean }>`
justify-content: ${(props) => (props.$rtl ? "flex-end" : "flex-start")};
margin: -12px 0 2em 0;