feat(editor): add dictation mic to page byline next to the info icon

The editor dictation button previously lived only in the fixed toolbar,
which is hidden by default (gated by the per-user editorToolbar
preference), so dictation was effectively unavailable in the editor. Add
the same dictation control to the always-rendered page byline row, right
next to the Details "i" icon, so voice input stays reachable.

It is shown only when workspace dictation is enabled, the page is
editable, and the editor is in edit mode. Reuses the existing
DictationGroup/MicButton and its caret-insertion logic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude_code
2026-06-22 02:14:46 +03:00
parent c83343d3a3
commit fae8418fa2

View File

@@ -14,8 +14,11 @@ import {
UnstyledButton,
} from "@mantine/core";
import { IconInfoCircle } from "@tabler/icons-react";
import { useAtom } from "jotai";
import { userAtom } from "@/features/user/atoms/current-user-atom.ts";
import { useAtom, useAtomValue } from "jotai";
import {
userAtom,
workspaceAtom,
} from "@/features/user/atoms/current-user-atom.ts";
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
import { useTranslation } from "react-i18next";
import { IContributor } from "@/features/page/types/page.types.ts";
@@ -24,7 +27,11 @@ import { PageEditMode } from "@/features/user/types/user.types.ts";
import { useAsideTriggerProps } from "@/hooks/use-toggle-aside.tsx";
import { DeletedPageBanner } from "@/features/page/trash/components/deleted-page-banner.tsx";
import clsx from "clsx";
import { currentPageEditModeAtom } from "@/features/editor/atoms/editor-atoms.ts";
import {
currentPageEditModeAtom,
pageEditorAtom,
} from "@/features/editor/atoms/editor-atoms.ts";
import { DictationGroup } from "@/features/editor/components/fixed-toolbar/groups/dictation-group";
const MemoizedTitleEditor = React.memo(TitleEditor);
const MemoizedPageEditor = React.memo(PageEditor);
@@ -65,6 +72,8 @@ export function FullEditor({
canComment,
}: FullEditorProps) {
const [user] = useAtom(userAtom);
const workspace = useAtomValue(workspaceAtom);
const isDictationEnabled = workspace?.settings?.ai?.dictation === true;
const fullPageWidth = user.settings?.preferences?.fullPageWidth;
const editorToolbarEnabled =
user.settings?.preferences?.editorToolbar ?? false;
@@ -104,6 +113,9 @@ export function FullEditor({
<PageByline
creator={creator}
contributors={contributors}
editable={editable}
isEditMode={isEditMode}
isDictationEnabled={isDictationEnabled}
/>
<MemoizedPageEditor
pageId={pageId}
@@ -118,11 +130,24 @@ export function FullEditor({
type PageBylineProps = {
creator?: PageUser;
contributors?: IContributor[];
editable?: boolean;
isEditMode?: boolean;
isDictationEnabled?: boolean;
};
function PageByline({ creator, contributors }: PageBylineProps) {
function PageByline({
creator,
contributors,
editable,
isEditMode,
isDictationEnabled,
}: PageBylineProps) {
const { t } = useTranslation();
const detailsTriggerProps = useAsideTriggerProps("details");
const editor = useAtomValue(pageEditorAtom);
const showDictation = Boolean(
isDictationEnabled && editable && isEditMode && editor,
);
const otherContributors = (contributors ?? []).filter(
(c) => c.id !== creator?.id,
@@ -207,6 +232,9 @@ function PageByline({ creator, contributors }: PageBylineProps) {
<IconInfoCircle size={20} stroke={1.5} />
</ActionIcon>
</Tooltip>
{/* Shown only in edit mode when workspace dictation is enabled, so
dictation stays reachable even when the fixed toolbar is hidden. */}
{showDictation && editor && <DictationGroup editor={editor} />}
</Group>
);
}