mirror of https://github.com/usememos/memos.git
[Feature] Color-Picker: Added Color Picker, For Memos and it's Comments as it's depending on the color pref. of specific memo
This commit is contained in:
parent
810b099707
commit
967febf131
Binary file not shown.
Binary file not shown.
|
|
@ -57,7 +57,7 @@ const MemoActionMenu = (props: MemoActionMenuProps) => {
|
|||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="size-4">
|
||||
<MoreVerticalIcon className="text-muted-foreground" />
|
||||
<MoreVerticalIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" sideOffset={2}>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const MemoContent = (props: MemoContentProps) => {
|
|||
const compactLabel = useCompactLabel(showCompactMode, t as (key: string) => string);
|
||||
|
||||
return (
|
||||
<div className={`w-full flex flex-col justify-start items-start text-foreground ${className || ""}`}>
|
||||
<div className={`w-full flex flex-col justify-start items-start ${className || ""}`}>
|
||||
<div
|
||||
ref={memoContentContainerRef}
|
||||
data-memo-content
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const ReactionSelector = (props: Props) => {
|
|||
className,
|
||||
)}
|
||||
>
|
||||
<SmilePlusIcon className="w-4 h-4 mx-auto text-muted-foreground" />
|
||||
<SmilePlusIcon className="w-4 h-4 mx-auto" />
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="center" className="max-w-[90vw] sm:max-w-md">
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const ReactionView = (props: Props) => {
|
|||
type="button"
|
||||
className={cn(
|
||||
"h-7 border px-2 py-0.5 rounded-full flex flex-row justify-center items-center gap-1",
|
||||
"text-sm text-muted-foreground",
|
||||
"text-sm",
|
||||
isClickable && "cursor-pointer",
|
||||
!isClickable && "cursor-default",
|
||||
hasReaction && "bg-accent border-border",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { memo, useCallback, useMemo, useRef, useState } from "react";
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { useUser } from "@/hooks/useUserQueries";
|
||||
|
|
@ -14,7 +14,16 @@ import { computeCommentAmount, MemoViewContext } from "./MemoViewContext";
|
|||
import type { MemoViewProps } from "./types";
|
||||
|
||||
const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
||||
const { memo: memoData, className, parentPage: parentPageProp, compact, showCreator, showVisibility, showPinned } = props;
|
||||
const {
|
||||
memo: memoData,
|
||||
className,
|
||||
parentPage: parentPageProp,
|
||||
compact,
|
||||
showCreator,
|
||||
showVisibility,
|
||||
showPinned,
|
||||
colorKey,
|
||||
} = props;
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
|
||||
|
|
@ -38,6 +47,61 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
|||
const isInMemoDetailPage = location.pathname.startsWith(`/${memoData.name}`);
|
||||
const showCommentPreview = !isInMemoDetailPage && computeCommentAmount(memoData) > 0;
|
||||
|
||||
const [customColors, setCustomColors] = useState<{ bgColor?: string; textColor?: string } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const storageKey = colorKey || memoData.name;
|
||||
|
||||
try {
|
||||
const stored = window.localStorage.getItem(storageKey);
|
||||
if (!stored) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(stored) as { bgColor?: string; textColor?: string };
|
||||
setCustomColors({
|
||||
bgColor: parsed.bgColor,
|
||||
textColor: parsed.textColor,
|
||||
});
|
||||
} catch {
|
||||
// Ignore malformed values
|
||||
}
|
||||
}, [colorKey, memoData.name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const storageKey = colorKey || memoData.name;
|
||||
|
||||
const handleColorChange = (event: Event) => {
|
||||
const customEvent = event as CustomEvent<{
|
||||
key: string;
|
||||
colors: { bgColor?: string; textColor?: string };
|
||||
}>;
|
||||
|
||||
if (!customEvent.detail || customEvent.detail.key !== storageKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCustomColors({
|
||||
bgColor: customEvent.detail.colors.bgColor,
|
||||
textColor: customEvent.detail.colors.textColor,
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("memo-colors-changed", handleColorChange as EventListener);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("memo-colors-changed", handleColorChange as EventListener);
|
||||
};
|
||||
}, [colorKey, memoData.name]);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
memo: memoData,
|
||||
|
|
@ -85,8 +149,20 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
|||
className={cn(MEMO_CARD_BASE_CLASSES, showCommentPreview ? "mb-0 rounded-b-none" : "mb-2", className)}
|
||||
ref={cardRef}
|
||||
tabIndex={readonly ? -1 : 0}
|
||||
style={
|
||||
customColors?.bgColor || customColors?.textColor
|
||||
? { backgroundColor: customColors?.bgColor, color: customColors?.textColor }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<MemoHeader showCreator={showCreator} showVisibility={showVisibility} showPinned={showPinned} />
|
||||
<MemoHeader
|
||||
name={memoData.name}
|
||||
showCreator={showCreator}
|
||||
showVisibility={showVisibility}
|
||||
showPinned={showPinned}
|
||||
showColorCustomizer={!memoData.parent}
|
||||
onColorPreferencesChange={(colors) => setCustomColors(colors)}
|
||||
/>
|
||||
|
||||
<MemoBody compact={compact} />
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ import { useEffect, useState } from "react";
|
|||
import { HexColorPicker } from "react-colorful";
|
||||
|
||||
interface Props {
|
||||
name:string;
|
||||
className?: string;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
onSavePreferences?: (colors: { bgColor: string; textColor: string }) => Promise<void> | void;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "memo-customize-color";
|
||||
const MIN_CONTRAST_RATIO = 4.5;
|
||||
|
||||
const parseHexColor = (hex: string) => {
|
||||
|
|
@ -63,7 +62,7 @@ const getContrastRatio = (foreground: string, background: string) => {
|
|||
};
|
||||
|
||||
function MemoCustomizeColor(props: Props) {
|
||||
const { className, onOpenChange, onSavePreferences } = props;
|
||||
const { className, onOpenChange, onSavePreferences,name } = props;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [bgColor, setBgColor] = useState("#121212");
|
||||
const [textColor, setTextColor] = useState("#FFFFFF");
|
||||
|
|
@ -76,7 +75,7 @@ function MemoCustomizeColor(props: Props) {
|
|||
}
|
||||
|
||||
try {
|
||||
const stored = window.localStorage.getItem(STORAGE_KEY);
|
||||
const stored = window.localStorage.getItem(name);
|
||||
if (!stored) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -96,25 +95,7 @@ function MemoCustomizeColor(props: Props) {
|
|||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to load memo color preferences", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.localStorage.setItem(
|
||||
STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
bgColor,
|
||||
textColor,
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
// Ignore write errors (e.g., private mode)
|
||||
}
|
||||
}, [bgColor, textColor]);
|
||||
}, [name]);
|
||||
|
||||
const bgPresets = ["#121212", "#2c2f33", "#1d3557", "#2d6a4f", "#601010", "#000000"];
|
||||
const textPresets = ["#FFFFFF", "#E1E8ED", "#89CFF0", "#C7F9CC", "#FEFAE0", "#FAD2E1"];
|
||||
|
|
@ -130,6 +111,32 @@ function MemoCustomizeColor(props: Props) {
|
|||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
window.localStorage.setItem(
|
||||
name,
|
||||
JSON.stringify({
|
||||
bgColor,
|
||||
textColor,
|
||||
}),
|
||||
);
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("memo-colors-changed", {
|
||||
detail: {
|
||||
key: name,
|
||||
colors: {
|
||||
bgColor,
|
||||
textColor,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
// Ignore write errors
|
||||
}
|
||||
}
|
||||
|
||||
if (onSavePreferences) {
|
||||
await onSavePreferences({
|
||||
bgColor,
|
||||
|
|
@ -152,7 +159,7 @@ function MemoCustomizeColor(props: Props) {
|
|||
className,
|
||||
)}
|
||||
>
|
||||
<Brush className="w-4 h-4 mx-auto text-muted-foreground" />
|
||||
<Brush className="w-4 h-4 mx-auto" />
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext";
|
|||
import type { MemoHeaderProps } from "../types";
|
||||
import MemoCustomizeColor from "./MemoCustomizeColor";
|
||||
|
||||
const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, showPinned }) => {
|
||||
const MemoHeader: React.FC<MemoHeaderProps> = ({ name, showCreator, showVisibility, showPinned, onColorPreferencesChange, showColorCustomizer = true }) => {
|
||||
const t = useTranslate();
|
||||
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
|
||||
const { memo, creator, currentUser, parentPage, isArchived, readonly, openEditor } = useMemoViewContext();
|
||||
|
|
@ -60,7 +60,13 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
|
|||
onOpenChange={setReactionSelectorOpen}
|
||||
/>
|
||||
)}
|
||||
<MemoCustomizeColor className="border-none w-auto h-auto" />
|
||||
{showColorCustomizer && (
|
||||
<MemoCustomizeColor
|
||||
name={name}
|
||||
className="border-none w-auto h-auto"
|
||||
onSavePreferences={onColorPreferencesChange}
|
||||
/>
|
||||
)}
|
||||
{showVisibility && memo.visibility !== Visibility.PRIVATE && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
|
|
@ -79,7 +85,7 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
|
|||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-pointer">
|
||||
<BookmarkIcon className="w-4 h-auto text-primary" onClick={unpinMemo} />
|
||||
<BookmarkIcon className="w-4 h-auto" onClick={unpinMemo} />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
|
@ -108,7 +114,7 @@ const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, o
|
|||
</Link>
|
||||
<div className="w-full flex flex-col justify-center items-start">
|
||||
<Link
|
||||
className="block leading-tight hover:opacity-80 rounded-md transition-colors truncate text-muted-foreground"
|
||||
className="block leading-tight hover:opacity-80 rounded-md transition-colors truncate"
|
||||
to={`/u/${encodeURIComponent(creator.username)}`}
|
||||
viewTransition
|
||||
>
|
||||
|
|
@ -116,7 +122,7 @@ const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, o
|
|||
</Link>
|
||||
<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"
|
||||
className="w-auto -mt-0.5 text-xs leading-tight select-none cursor-pointer hover:opacity-80 transition-colors text-left"
|
||||
onClick={onGotoDetail}
|
||||
>
|
||||
{displayTime}
|
||||
|
|
@ -133,7 +139,7 @@ interface TimeDisplayProps {
|
|||
const TimeDisplay: React.FC<TimeDisplayProps> = ({ displayTime, onGotoDetail }) => (
|
||||
<button
|
||||
type="button"
|
||||
className="w-full text-sm leading-tight text-muted-foreground select-none cursor-pointer hover:text-foreground transition-colors text-left"
|
||||
className="w-full text-sm leading-tight select-none cursor-pointer hover:opacity-80 transition-colors text-left"
|
||||
onClick={onGotoDetail}
|
||||
>
|
||||
{displayTime}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,16 @@ export interface MemoViewProps {
|
|||
showPinned?: boolean;
|
||||
className?: string;
|
||||
parentPage?: string;
|
||||
colorKey?: string;
|
||||
}
|
||||
|
||||
export interface MemoHeaderProps {
|
||||
name:string;
|
||||
showCreator?: boolean;
|
||||
showVisibility?: boolean;
|
||||
showPinned?: boolean;
|
||||
onColorPreferencesChange?: (colors: { bgColor: string; textColor: string }) => void;
|
||||
showColorCustomizer?: boolean;
|
||||
}
|
||||
|
||||
export interface MemoBodyProps {
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ const PagedMemoList = (props: Props) => {
|
|||
|
||||
// Flatten pages into a single array of memos
|
||||
const memos = useMemo(() => data?.pages.flatMap((page) => page.memos) || [], [data]);
|
||||
console.log("Memoss: ",memos);
|
||||
|
||||
// Apply custom sorting if provided, otherwise use memos directly
|
||||
const sortedMemoList = useMemo(() => (props.listSort ? props.listSort(memos) : memos), [memos, props.listSort]);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const VisibilityIcon = (props: Props) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
return <VIcon className={cn("w-4 h-auto text-muted-foreground", className)} />;
|
||||
return <VIcon className={cn("w-4 h-auto", className)} />;
|
||||
};
|
||||
|
||||
export default VisibilityIcon;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ export const memoKeys = {
|
|||
};
|
||||
|
||||
export function useMemos(request: Partial<ListMemosRequest> = {}) {
|
||||
console.log(`Memos: `,useQuery({
|
||||
queryKey: memoKeys.list(request),
|
||||
queryFn: async () => {
|
||||
const response = await memoServiceClient.listMemos(create(ListMemosRequestSchema, request as Record<string, unknown>));
|
||||
return response;
|
||||
},
|
||||
}));
|
||||
return useQuery({
|
||||
queryKey: memoKeys.list(request),
|
||||
queryFn: async () => {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ const MemoDetail = () => {
|
|||
showCreator
|
||||
showVisibility
|
||||
showPinned
|
||||
colorKey={memo.name}
|
||||
/>
|
||||
<div className="pt-8 pb-16 w-full">
|
||||
<h2 id="comments" className="sr-only">
|
||||
|
|
@ -165,7 +166,7 @@ const MemoDetail = () => {
|
|||
)}
|
||||
{comments.map((comment) => (
|
||||
<div className="w-full" key={`${comment.name}-${comment.displayTime}`} id={extractMemoIdFromName(comment.name)}>
|
||||
<MemoView memo={comment} parentPage={locationState?.from} showCreator compact />
|
||||
<MemoView memo={comment} parentPage={locationState?.from} showCreator compact colorKey={memo.name} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue