Mirror the #tag mark so the TipTap editor styles @username while typing and on load, matching the read-only view. A shared mention grammar (utils/mention-grammar.ts) feeds both the editor tokenizer and the read-only remark renderer so they can't drift. Styling only -- no autocomplete dropdown (ListUsers is admin-only and there is no user-search RPC). Bare emails still autolink to mailto: and are never treated as mentions: the tokenizer's start() skips email-glued @s so marked's GFM autolinker keeps seeing the whole address.
42 lines
1.9 KiB
TypeScript
42 lines
1.9 KiB
TypeScript
import { CheckIcon, ChevronDownIcon } from "lucide-react";
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
|
import VisibilityIcon from "@/components/VisibilityIcon";
|
|
import { Visibility } from "@/types/proto/api/v1/memo_service_pb";
|
|
import { useTranslate } from "@/utils/i18n";
|
|
import type { VisibilitySelectorProps } from "../types";
|
|
|
|
const VisibilitySelector = (props: VisibilitySelectorProps) => {
|
|
const { value, onChange } = props;
|
|
const t = useTranslate();
|
|
|
|
const visibilityOptions = [
|
|
{ value: Visibility.PRIVATE, label: t("memo.visibility.private") },
|
|
{ value: Visibility.PROTECTED, label: t("memo.visibility.protected") },
|
|
{ value: Visibility.PUBLIC, label: t("memo.visibility.public") },
|
|
] as const;
|
|
|
|
const currentLabel = visibilityOptions.find((option) => option.value === value)?.label || "";
|
|
|
|
return (
|
|
<DropdownMenu onOpenChange={props.onOpenChange}>
|
|
<DropdownMenuTrigger asChild>
|
|
<button className="inline-flex items-center h-8 px-2 rounded-md text-sm text-muted-foreground hover:bg-accent transition-colors">
|
|
<VisibilityIcon visibility={value} className="opacity-60 mr-1.5" />
|
|
<span>{currentLabel}</span>
|
|
<ChevronDownIcon className="ml-0.5 w-4 h-4 opacity-60" />
|
|
</button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start">
|
|
{visibilityOptions.map((option) => (
|
|
<DropdownMenuItem key={option.value} className="cursor-pointer gap-2" onClick={() => onChange(option.value)}>
|
|
<VisibilityIcon visibility={option.value} />
|
|
<span className="flex-1">{option.label}</span>
|
|
{value === option.value && <CheckIcon className="w-4 h-4 text-primary" />}
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
};
|
|
|
|
export default VisibilitySelector;
|