Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| edd2867a47 | |||
| 70ab5ef059 | |||
| adb151dc53 |
@@ -2,6 +2,11 @@
|
||||
|
||||
> 基于原作者v0.29.1代码修改
|
||||
|
||||
#### v0.29.1-3
|
||||
1. 修改设置页面commit基础链接
|
||||
2. 优化图标样式、备忘录菜单跳转
|
||||
3. 归档页面允许设置表情
|
||||
|
||||
#### v0.29.1-2
|
||||
1. 置顶内容新增自动折叠;置顶效果及图标改为全局显示
|
||||
2. 归档页面新增(取消)置顶、编辑、复制按钮
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArchiveIcon,
|
||||
ArchiveRestoreIcon,
|
||||
BookmarkMinusIcon,
|
||||
@@ -6,6 +7,7 @@ import {
|
||||
CheckCheckIcon,
|
||||
CopyIcon,
|
||||
Edit3Icon,
|
||||
EyeIcon,
|
||||
FileTextIcon,
|
||||
LinkIcon,
|
||||
ListChecksIcon,
|
||||
@@ -32,7 +34,7 @@ import { useMemoActionHandlers } from "./hooks";
|
||||
import type { MemoActionMenuProps } from "./types";
|
||||
|
||||
const MemoActionMenu = (props: MemoActionMenuProps) => {
|
||||
const { memo, readonly } = props;
|
||||
const { memo, readonly, isInMemoDetailPage, onBack, onViewDetail } = props;
|
||||
const t = useTranslate();
|
||||
|
||||
// Dialog state
|
||||
@@ -45,6 +47,8 @@ const MemoActionMenu = (props: MemoActionMenuProps) => {
|
||||
const canMutateTasks = !readonly && !isArchived && taskStats.total > 0;
|
||||
const hasOpenTasks = taskStats.completed < taskStats.total;
|
||||
const hasCompletedTasks = taskStats.completed > 0;
|
||||
const DetailActionIcon = isInMemoDetailPage ? ArrowLeftIcon : EyeIcon;
|
||||
const detailActionLabel = isInMemoDetailPage ? t("common.collapse") : t("memo.view-detail");
|
||||
|
||||
// Action handlers
|
||||
const {
|
||||
@@ -71,6 +75,11 @@ const MemoActionMenu = (props: MemoActionMenuProps) => {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" sideOffset={2}>
|
||||
<DropdownMenuItem onClick={isInMemoDetailPage ? onBack : onViewDetail}>
|
||||
<DetailActionIcon className="w-4 h-auto" />
|
||||
{detailActionLabel}
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Edit actions (non-readonly) */}
|
||||
{!readonly && (
|
||||
<>
|
||||
|
||||
@@ -4,5 +4,8 @@ export interface MemoActionMenuProps {
|
||||
memo: Memo;
|
||||
readonly?: boolean;
|
||||
className?: string;
|
||||
isInMemoDetailPage: boolean;
|
||||
onBack: () => void;
|
||||
onViewDetail: () => void;
|
||||
onEdit?: () => void;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { memo } from "react";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { State } from "@/types/proto/api/v1/common_pb";
|
||||
import type { Memo, Reaction } from "@/types/proto/api/v1/memo_service_pb";
|
||||
import { useReactionGroups } from "./hooks";
|
||||
import ReactionSelector from "./ReactionSelector";
|
||||
@@ -15,7 +14,6 @@ const MemoReactionListView = (props: Props) => {
|
||||
const { memo: memoData, reactions } = props;
|
||||
const currentUser = useCurrentUser();
|
||||
const reactionGroup = useReactionGroups(reactions);
|
||||
const readonly = memoData.state === State.ARCHIVED;
|
||||
|
||||
if (reactions.length === 0) {
|
||||
return null;
|
||||
@@ -26,7 +24,7 @@ const MemoReactionListView = (props: Props) => {
|
||||
{Array.from(reactionGroup).map(([reactionType, users]) => (
|
||||
<ReactionView key={`${reactionType.toString()} ${users.length}`} memo={memoData} reactionType={reactionType} users={users} />
|
||||
))}
|
||||
{!readonly && currentUser && <ReactionSelector memo={memoData} />}
|
||||
{currentUser && <ReactionSelector memo={memoData} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { State } from "@/types/proto/api/v1/common_pb";
|
||||
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
|
||||
import type { User } from "@/types/proto/api/v1/user_service_pb";
|
||||
import { formatReactionTooltip, useReactionActions } from "./hooks";
|
||||
@@ -16,16 +15,15 @@ const ReactionView = (props: Props) => {
|
||||
const { memo, reactionType, users } = props;
|
||||
const currentUser = useCurrentUser();
|
||||
const hasReaction = users.some((user) => currentUser && user.username === currentUser.username);
|
||||
const readonly = memo.state === State.ARCHIVED;
|
||||
|
||||
const { handleReactionClick } = useReactionActions({ memo });
|
||||
|
||||
const handleClick = () => {
|
||||
if (!currentUser || readonly) return;
|
||||
if (!currentUser) return;
|
||||
handleReactionClick(reactionType);
|
||||
};
|
||||
|
||||
const isClickable = currentUser && !readonly;
|
||||
const isClickable = Boolean(currentUser);
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
|
||||
@@ -27,7 +27,10 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
||||
const creator = useUser(memoData.creator).data;
|
||||
const isArchived = memoData.state === State.ARCHIVED;
|
||||
const readonly = memoData.creator !== currentUser?.name && !isSuperUser(currentUser);
|
||||
const parentPage = parentPageProp || "/";
|
||||
const location = useLocation();
|
||||
const isInMemoDetailPage = location.pathname.startsWith(`/${memoData.name}`) || location.pathname.startsWith("/memos/shares/");
|
||||
const currentPage = `${location.pathname}${location.search}${location.hash}`;
|
||||
const parentPage = parentPageProp || (isInMemoDetailPage ? "/" : currentPage);
|
||||
|
||||
// Blur content when any tag has blur_content enabled in the instance tag settings.
|
||||
const [showBlurredContent, setShowBlurredContent] = useState(false);
|
||||
@@ -39,8 +42,6 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
||||
const openEditor = useCallback(() => setShowEditor(true), []);
|
||||
const closeEditor = useCallback(() => setShowEditor(false), []);
|
||||
|
||||
const location = useLocation();
|
||||
const isInMemoDetailPage = location.pathname.startsWith(`/${memoData.name}`) || location.pathname.startsWith("/memos/shares/");
|
||||
const showCommentPreview = !isInMemoDetailPage && computeCommentAmount(memoData) > 0;
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BookmarkIcon } from "lucide-react";
|
||||
import { ArchiveIcon, BookmarkIcon } from "lucide-react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||
import i18n from "@/i18n";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -12,7 +12,6 @@ import MemoActionMenu from "../../MemoActionMenu";
|
||||
import { ReactionSelector } from "../../MemoReactionListView";
|
||||
import UserAvatar from "../../UserAvatar";
|
||||
import VisibilityIcon from "../../VisibilityIcon";
|
||||
import { useMemoActions } from "../hooks";
|
||||
import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext";
|
||||
import type { MemoHeaderProps } from "../types";
|
||||
|
||||
@@ -21,14 +20,37 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
|
||||
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
|
||||
|
||||
const { memo, creator, currentUser, parentPage, isArchived, readonly, openEditor } = useMemoViewContext();
|
||||
const { createTime, updateTime, displayTime: memoDisplayTime, isDisplayingUpdatedTime, relativeTimeFormat } = useMemoViewDerived();
|
||||
const {
|
||||
createTime,
|
||||
updateTime,
|
||||
displayTime: memoDisplayTime,
|
||||
isDisplayingUpdatedTime,
|
||||
isInMemoDetailPage,
|
||||
relativeTimeFormat,
|
||||
} = useMemoViewDerived();
|
||||
|
||||
const navigateTo = useNavigateTo();
|
||||
const navigate = useNavigate();
|
||||
const handleGotoMemoDetailPage = useCallback(() => {
|
||||
navigateTo(`/${memo.name}`, { state: { from: parentPage } });
|
||||
}, [memo.name, parentPage, navigateTo]);
|
||||
const handleBackToParentPage = useCallback(() => {
|
||||
const historyState = window.history.state as { idx?: number } | null;
|
||||
if (typeof historyState?.idx === "number" && historyState.idx > 0) {
|
||||
navigate(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
const { unpinMemo } = useMemoActions(memo);
|
||||
navigateTo(parentPage, { preventScrollReset: true });
|
||||
}, [parentPage, navigate, navigateTo]);
|
||||
const handleMemoTimeClick = useCallback(() => {
|
||||
if (isInMemoDetailPage) {
|
||||
handleBackToParentPage();
|
||||
return;
|
||||
}
|
||||
|
||||
handleGotoMemoDetailPage();
|
||||
}, [isInMemoDetailPage, handleBackToParentPage, handleGotoMemoDetailPage]);
|
||||
|
||||
const timeValue = isArchived ? (
|
||||
memoDisplayTime?.toLocaleString(i18n.language)
|
||||
@@ -51,14 +73,14 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
|
||||
<div className="w-full flex flex-row justify-between items-center gap-2">
|
||||
<div className="w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center">
|
||||
{showCreator && creator ? (
|
||||
<CreatorDisplay creator={creator} displayTime={displayTime} timeTooltip={timeTooltip} onGotoDetail={handleGotoMemoDetailPage} />
|
||||
<CreatorDisplay creator={creator} displayTime={displayTime} timeTooltip={timeTooltip} onTimeClick={handleMemoTimeClick} />
|
||||
) : (
|
||||
<TimeDisplay displayTime={displayTime} timeTooltip={timeTooltip} onGotoDetail={handleGotoMemoDetailPage} />
|
||||
<TimeDisplay displayTime={displayTime} timeTooltip={timeTooltip} onTimeClick={handleMemoTimeClick} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row justify-end items-center select-none shrink-0 gap-2">
|
||||
{currentUser && !isArchived && (
|
||||
{currentUser && (
|
||||
<ReactionSelector
|
||||
className={cn("border-none w-auto h-auto", reactionSelectorOpen && "block!", "block sm:hidden sm:group-hover:block")}
|
||||
memo={memo}
|
||||
@@ -66,6 +88,32 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
|
||||
/>
|
||||
)}
|
||||
|
||||
{showPinned && memo.pinned && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex justify-center items-center rounded-md">
|
||||
<BookmarkIcon className="w-4 h-auto text-muted-foreground" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("common.pinned")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isArchived && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex justify-center items-center rounded-md">
|
||||
<ArchiveIcon className="w-4 h-auto text-muted-foreground" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("common.archived")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{showVisibility && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
@@ -79,22 +127,14 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{showPinned && memo.pinned && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-pointer">
|
||||
<BookmarkIcon className="w-4 h-auto text-primary" onClick={unpinMemo} />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("common.unpin")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
<MemoActionMenu memo={memo} readonly={readonly} onEdit={openEditor} />
|
||||
<MemoActionMenu
|
||||
memo={memo}
|
||||
readonly={readonly}
|
||||
isInMemoDetailPage={isInMemoDetailPage}
|
||||
onBack={handleBackToParentPage}
|
||||
onViewDetail={handleGotoMemoDetailPage}
|
||||
onEdit={openEditor}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -104,10 +144,10 @@ interface CreatorDisplayProps {
|
||||
creator: User;
|
||||
displayTime: React.ReactNode;
|
||||
timeTooltip: TimeTooltipContent;
|
||||
onGotoDetail: () => void;
|
||||
onTimeClick: () => void;
|
||||
}
|
||||
|
||||
const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, timeTooltip, onGotoDetail }) => (
|
||||
const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, timeTooltip, onTimeClick }) => (
|
||||
<div className="w-full flex flex-row justify-start items-center">
|
||||
<Link className="w-auto hover:opacity-80 rounded-md transition-colors" to={`/u/${encodeURIComponent(creator.username)}`} viewTransition>
|
||||
<UserAvatar className="mr-2 shrink-0" avatarUrl={creator.avatarUrl} />
|
||||
@@ -124,7 +164,7 @@ const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, t
|
||||
<button
|
||||
type="button"
|
||||
className="w-auto -mt-0.5 text-xs leading-tight text-muted-foreground select-none cursor-pointer hover:opacity-80 transition-colors text-left"
|
||||
onClick={onGotoDetail}
|
||||
onClick={onTimeClick}
|
||||
>
|
||||
{displayTime}
|
||||
</button>
|
||||
@@ -151,15 +191,15 @@ const TimeTooltip = ({ children, content }: { children: React.ReactElement; cont
|
||||
interface TimeDisplayProps {
|
||||
displayTime: React.ReactNode;
|
||||
timeTooltip: TimeTooltipContent;
|
||||
onGotoDetail: () => void;
|
||||
onTimeClick: () => void;
|
||||
}
|
||||
|
||||
const TimeDisplay: React.FC<TimeDisplayProps> = ({ displayTime, timeTooltip, onGotoDetail }) => (
|
||||
const TimeDisplay: React.FC<TimeDisplayProps> = ({ displayTime, timeTooltip, onTimeClick }) => (
|
||||
<TimeTooltip content={timeTooltip}>
|
||||
<button
|
||||
type="button"
|
||||
className="w-auto text-sm leading-tight text-muted-foreground select-none cursor-pointer hover:text-foreground transition-colors text-left"
|
||||
onClick={onGotoDetail}
|
||||
onClick={onTimeClick}
|
||||
>
|
||||
{displayTime}
|
||||
</button>
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { useImagePreview } from "./useImagePreview";
|
||||
export { useMemoActions } from "./useMemoActions";
|
||||
export { useMemoHandlers } from "./useMemoHandlers";
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { useUpdateMemo } from "@/hooks/useMemoQueries";
|
||||
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
|
||||
|
||||
export const useMemoActions = (memo: Memo) => {
|
||||
const { mutateAsync: updateMemo } = useUpdateMemo();
|
||||
|
||||
const unpinMemo = async () => {
|
||||
if (!memo.pinned) return;
|
||||
await updateMemo({ update: { name: memo.name, pinned: false }, updateMask: ["pinned"] });
|
||||
};
|
||||
|
||||
return { unpinMemo };
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ import useMediaQuery from "@/hooks/useMediaQuery";
|
||||
import { User_Role } from "@/types/proto/api/v1/user_service_pb";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
const GITHUB_COMMIT_URL_PREFIX = "https://github.com/usememos/memos/commit/";
|
||||
const GITHUB_COMMIT_URL_PREFIX = "https://git.anian.cc/anian/memos/commit/";
|
||||
|
||||
const isCommitSha = (commit: string) => /^[0-9a-f]{7,40}$/i.test(commit);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user