resolving conflicts: color picker functionality and enhance memo components with color customization options

This commit is contained in:
Ahmed-Elgendy25 2026-04-01 10:44:27 +02:00
parent 41405bc3fa
commit 2d25e518c8
12 changed files with 19 additions and 698 deletions

18
.hintrc Normal file
View File

@ -0,0 +1,18 @@
{
"extends": [
"development"
],
"hints": {
"compat-api/html": [
"default",
{
"ignore": [
"meta[name=theme-color]"
]
}
],
"meta-viewport": "off",
"axe/name-role-value": "off",
"axe/forms": "off"
}
}

Binary file not shown.

View File

@ -57,11 +57,7 @@ const MemoActionMenu = (props: MemoActionMenuProps) => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="size-4">
<<<<<<< HEAD
<MoreVerticalIcon />
=======
<MoreVerticalIcon className="text-muted-foreground" />
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={2}>

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
import type { Element } from "hast";
import { ChevronDown, ChevronUp } from "lucide-react";
import { memo } from "react";
@ -131,137 +130,3 @@ const MemoContent = (props: MemoContentProps) => {
};
export default memo(MemoContent);
=======
import type { Element } from "hast";
import { ChevronDown, ChevronUp } from "lucide-react";
import { memo } from "react";
import ReactMarkdown from "react-markdown";
import rehypeKatex from "rehype-katex";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
import remarkBreaks from "remark-breaks";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { cn } from "@/lib/utils";
import { useTranslate } from "@/utils/i18n";
import { remarkDisableSetext } from "@/utils/remark-plugins/remark-disable-setext";
import { remarkPreserveType } from "@/utils/remark-plugins/remark-preserve-type";
import { remarkTag } from "@/utils/remark-plugins/remark-tag";
import { CodeBlock } from "./CodeBlock";
import { isTagNode, isTaskListItemNode } from "./ConditionalComponent";
import { COMPACT_MODE_CONFIG, SANITIZE_SCHEMA } from "./constants";
import { useCompactLabel, useCompactMode } from "./hooks";
import { Blockquote, Heading, HorizontalRule, Image, InlineCode, Link, List, ListItem, Paragraph } from "./markdown";
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "./Table";
import { Tag } from "./Tag";
import { TaskListItem } from "./TaskListItem";
import type { MemoContentProps } from "./types";
const MemoContent = (props: MemoContentProps) => {
const { className, contentClassName, content, onClick, onDoubleClick } = props;
const t = useTranslate();
const {
containerRef: memoContentContainerRef,
mode: showCompactMode,
toggle: toggleCompactMode,
} = useCompactMode(Boolean(props.compact));
const compactLabel = useCompactLabel(showCompactMode, t as (key: string) => string);
return (
<div className={`w-full flex flex-col justify-start items-start ${className || ""}`}>
<div
ref={memoContentContainerRef}
data-memo-content
className={cn(
"relative w-full max-w-full wrap-break-word text-base leading-6",
"[&>*:last-child]:mb-0",
showCompactMode === "ALL" && "overflow-hidden",
contentClassName,
)}
style={showCompactMode === "ALL" ? { maxHeight: `${COMPACT_MODE_CONFIG.maxHeightVh}vh` } : undefined}
onMouseUp={onClick}
onDoubleClick={onDoubleClick}
>
<ReactMarkdown
remarkPlugins={[remarkDisableSetext, remarkMath, remarkGfm, remarkBreaks, remarkTag, remarkPreserveType]}
rehypePlugins={[rehypeRaw, [rehypeSanitize, SANITIZE_SCHEMA], [rehypeKatex, { throwOnError: false, strict: false }]]}
components={{
// Child components consume from MemoViewContext directly
input: ((inputProps: React.ComponentProps<"input"> & { node?: Element }) => {
if (inputProps.node && isTaskListItemNode(inputProps.node)) {
return <TaskListItem {...inputProps} />;
}
return <input {...inputProps} />;
}) as React.ComponentType<React.ComponentProps<"input">>,
span: ((spanProps: React.ComponentProps<"span"> & { node?: Element }) => {
const { node, ...rest } = spanProps;
if (node && isTagNode(node)) {
return <Tag {...spanProps} />;
}
return <span {...rest} />;
}) as React.ComponentType<React.ComponentProps<"span">>,
// Headings
h1: ({ children }) => <Heading level={1}>{children}</Heading>,
h2: ({ children }) => <Heading level={2}>{children}</Heading>,
h3: ({ children }) => <Heading level={3}>{children}</Heading>,
h4: ({ children }) => <Heading level={4}>{children}</Heading>,
h5: ({ children }) => <Heading level={5}>{children}</Heading>,
h6: ({ children }) => <Heading level={6}>{children}</Heading>,
// Block elements
p: ({ children }) => <Paragraph>{children}</Paragraph>,
blockquote: ({ children }) => <Blockquote>{children}</Blockquote>,
hr: () => <HorizontalRule />,
// Lists
ul: ({ children, ...props }) => <List {...props}>{children}</List>,
ol: ({ children, ...props }) => (
<List ordered {...props}>
{children}
</List>
),
li: ({ children, ...props }) => <ListItem {...props}>{children}</ListItem>,
// Inline elements
a: ({ children, ...props }) => <Link {...props}>{children}</Link>,
code: ({ children }) => <InlineCode>{children}</InlineCode>,
img: ({ ...props }) => <Image {...props} />,
// Code blocks
pre: CodeBlock,
// Tables
table: ({ children }) => <Table>{children}</Table>,
thead: ({ children }) => <TableHead>{children}</TableHead>,
tbody: ({ children }) => <TableBody>{children}</TableBody>,
tr: ({ children }) => <TableRow>{children}</TableRow>,
th: ({ children, ...props }) => <TableHeaderCell {...props}>{children}</TableHeaderCell>,
td: ({ children, ...props }) => <TableCell {...props}>{children}</TableCell>,
}}
>
{content}
</ReactMarkdown>
{showCompactMode === "ALL" && (
<div
className={cn(
"absolute inset-x-0 bottom-0 pointer-events-none",
COMPACT_MODE_CONFIG.gradientHeight,
"bg-linear-to-t from-background from-0% via-background/60 via-40% to-transparent to-100%",
)}
/>
)}
</div>
{showCompactMode !== undefined && (
<div className="relative w-full mt-2">
<button
type="button"
className="group inline-flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
onClick={toggleCompactMode}
>
<span>{compactLabel}</span>
{showCompactMode === "ALL" ? <ChevronDown className="w-3 h-3" /> : <ChevronUp className="w-3 h-3" />}
</button>
</div>
)}
</div>
);
};
export default memo(MemoContent);
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)

View File

@ -35,11 +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",
<<<<<<< HEAD
"text-sm",
=======
"text-sm text-muted-foreground",
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
isClickable && "cursor-pointer",
!isClickable && "cursor-default",
hasReaction && "bg-accent border-border",

View File

@ -1,8 +1,4 @@
<<<<<<< HEAD
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
=======
import { memo, useCallback, useMemo, useRef, useState } from "react";
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
import { useLocation } from "react-router-dom";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useUser } from "@/hooks/useUserQueries";
@ -19,20 +15,7 @@ import type { MemoViewProps } from "./types";
import MemoFooter from "./components/MemoFooter";
const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
<<<<<<< HEAD
const {
memo: memoData,
className,
parentPage: parentPageProp,
compact,
showCreator,
showVisibility,
showPinned,
colorKey,
} = props;
=======
const { memo: memoData, className, parentPage: parentPageProp, compact, showCreator, showVisibility, showPinned } = props;
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
const cardRef = useRef<HTMLDivElement>(null);
const [showEditor, setShowEditor] = useState(false);
@ -56,64 +39,6 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
const isInMemoDetailPage = location.pathname.startsWith(`/${memoData.name}`);
const showCommentPreview = !isInMemoDetailPage && computeCommentAmount(memoData) > 0;
<<<<<<< HEAD
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]);
=======
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
const contextValue = useMemo(
() => ({
memo: memoData,
@ -161,25 +86,8 @@ 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}
<<<<<<< HEAD
style={
customColors?.bgColor || customColors?.textColor
? { backgroundColor: customColors?.bgColor, color: customColors?.textColor }
: undefined
}
>
<MemoHeader
name={memoData.name}
showCreator={showCreator}
showVisibility={showVisibility}
showPinned={showPinned}
showColorCustomizer={!memoData.parent}
onColorPreferencesChange={(colors) => setCustomColors(colors)}
/>
=======
>
<MemoHeader showCreator={showCreator} showVisibility={showVisibility} showPinned={showPinned} />
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
<MemoBody compact={compact} />
<MemoFooter/>

View File

@ -1,72 +1,3 @@
<<<<<<< HEAD
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { AlertCircle, Brush } from "lucide-react";
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 MIN_CONTRAST_RATIO = 4.5;
const parseHexColor = (hex: string) => {
const normalized = hex.trim().replace("#", "");
if (normalized.length !== 6) {
return null;
}
const r = Number.parseInt(normalized.slice(0, 2), 16);
const g = Number.parseInt(normalized.slice(2, 4), 16);
const b = Number.parseInt(normalized.slice(4, 6), 16);
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
return null;
}
return { r, g, b };
};
const relativeLuminance = (hex: string) => {
const rgb = parseHexColor(hex);
if (!rgb) {
return null;
}
const transform = (channel: number) => {
const sRgb = channel / 255;
return sRgb <= 0.03928 ? sRgb / 12.92 : Math.pow((sRgb + 0.055) / 1.055, 2.4);
};
const r = transform(rgb.r);
const g = transform(rgb.g);
const b = transform(rgb.b);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const getContrastRatio = (foreground: string, background: string) => {
const l1 = relativeLuminance(foreground);
const l2 = relativeLuminance(background);
if (l1 == null || l2 == null) {
return null;
}
const light = Math.max(l1, l2);
const dark = Math.min(l1, l2);
return (light + 0.05) / (dark + 0.05);
};
function MemoCustomizeColor(props: Props) {
const { className, onOpenChange, onSavePreferences,name } = props;
const [open, setOpen] = useState(false);
const [bgColor, setBgColor] = useState("#121212");
=======
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { cn } from "@/lib/utils";
import { Brush } from "lucide-react"
@ -83,42 +14,10 @@ function MemoCustomizeColor(props:Props) {
const {className,onOpenChange} = props;
const [open, setOpen] = useState(false);
const [bgColor, setBgColor] = useState("#121212");
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
const [textColor, setTextColor] = useState("#FFFFFF");
const [showBgPicker, setShowBgPicker] = useState(false);
const [showTextPicker, setShowTextPicker] = useState(false);
<<<<<<< HEAD
useEffect(() => {
if (typeof window === "undefined") {
return;
}
try {
const stored = window.localStorage.getItem(name);
if (!stored) {
return;
}
const parsed = JSON.parse(stored) as {
bgColor?: string;
textColor?: string;
};
if (parsed.bgColor) {
setBgColor(parsed.bgColor);
}
if (parsed.textColor) {
setTextColor(parsed.textColor);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error("Failed to load memo color preferences", error);
}
}, [name]);
=======
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
const bgPresets = ["#121212", "#2c2f33", "#1d3557", "#2d6a4f", "#601010", "#000000"];
const textPresets = ["#FFFFFF", "#E1E8ED", "#89CFF0", "#C7F9CC", "#FEFAE0", "#FAD2E1"];
@ -126,72 +25,6 @@ const [open, setOpen] = useState(false);
setOpen(newOpen);
onOpenChange?.(newOpen);
};
<<<<<<< HEAD
const handleCancel = () => {
handleOpenChange(false);
};
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,
textColor,
});
}
handleOpenChange(false);
} catch (error) {
console.error("Failed to save memo color preferences", error);
}
};
const contrastRatio = getContrastRatio(textColor, bgColor);
return (
<Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
<span
className={cn(
"h-7 w-7 flex justify-center items-center rounded-full border cursor-pointer transition-all hover:opacity-80",
className,
)}
>
<Brush className="w-4 h-4 mx-auto" />
</span>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="end"
className="max-w-[90vw] sm:max-w-md data-[state=open]:animate-none data-[state=closed]:animate-none"
>
=======
return (
<Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
@ -212,7 +45,6 @@ const [open, setOpen] = useState(false);
<PopoverContent align="start" className="max-w-[90vw] sm:max-w-md ">
<div className="flex items-center justify-center ">
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
<div className="w-full max-w-md bg-[#1a1a1a] rounded-xl border border-gray-800 overflow-hidden shadow-2xl">
{/* Header */}
<div className="p-4 border-b border-gray-800">
@ -230,25 +62,12 @@ const [open, setOpen] = useState(false);
<div className="flex gap-2">🎨 </div>
</div>
<p className="text-xl font-bold mb-4" style={{ color: textColor }}>
<<<<<<< HEAD
Content Should be here...
=======
EL DONIA DH BTA3TIIIIIIIIII
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
</p>
<div className="flex gap-2">
<span className="bg-gray-800/50 p-1 px-3 rounded-full text-xs">👍 1</span>
<span className="bg-gray-800/50 p-1 px-3 rounded-full text-xs"> 1</span>
</div>
<<<<<<< HEAD
{contrastRatio != null && contrastRatio < MIN_CONTRAST_RATIO && (
<div className="mt-3 flex items-center gap-2 text-xs text-amber-400">
<AlertCircle className="w-3 h-3" />
<span>Low contrast may affect readability.</span>
</div>
)}
=======
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
</div>
{/* Background Color Section */}
@ -310,31 +129,13 @@ const [open, setOpen] = useState(false);
{/* Footer Actions */}
<div className="flex justify-end gap-3 p-4 bg-black/20 border-t border-gray-800">
<<<<<<< HEAD
<button
type="button"
className="text-gray-400 hover:text-white px-4 py-2 transition text-sm"
onClick={handleCancel}
>
Cancel
</button>
<button
type="button"
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-medium transition text-sm"
onClick={handleSave}
>
=======
<button className="text-gray-400 hover:text-white px-4 py-2 transition text-sm">Cancel</button>
<button className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-medium transition text-sm">
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
Save Preferences
</button>
</div>
</div>
<<<<<<< HEAD
=======
</div>
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
</PopoverContent>
</Popover>

View File

@ -19,16 +19,10 @@ import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext";
import type { MemoHeaderProps } from "../types";
import MemoCustomizeColor from "./MemoCustomizeColor";
<<<<<<< HEAD
const MemoHeader: React.FC<MemoHeaderProps> = ({ name, showCreator, showVisibility, showPinned, onColorPreferencesChange, showColorCustomizer = true }) => {
const t = useTranslate();
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
=======
const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, showPinned }) => {
const t = useTranslate();
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
const [customizeColorToggle,setCustomizeColorToggle]= useState(false);
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
const { memo, creator, currentUser, parentPage, isArchived, readonly, openEditor } = useMemoViewContext();
const { relativeTimeFormat } = useMemoViewDerived();
@ -67,20 +61,10 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
onOpenChange={setReactionSelectorOpen}
/>
)}
<<<<<<< HEAD
{showColorCustomizer && (
<MemoCustomizeColor
name={name}
className="border-none w-auto h-auto"
onSavePreferences={onColorPreferencesChange}
/>
)}
=======
<MemoCustomizeColor
className={cn("border-none w-auto h-auto", customizeColorToggle && "block!", "block sm:hidden sm:group-hover:block")}
onOpenChange={setCustomizeColorToggle}
/>
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
{showVisibility && memo.visibility !== Visibility.PRIVATE && (
<Tooltip>
<TooltipTrigger>
@ -99,11 +83,7 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-pointer">
<<<<<<< HEAD
<BookmarkIcon className="w-4 h-auto" onClick={unpinMemo} />
=======
<BookmarkIcon className="w-4 h-auto text-primary" onClick={unpinMemo} />
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
</span>
</TooltipTrigger>
<TooltipContent>
@ -132,11 +112,7 @@ const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, o
</Link>
<div className="w-full flex flex-col justify-center items-start">
<Link
<<<<<<< HEAD
className="block leading-tight hover:opacity-80 rounded-md transition-colors truncate"
=======
className="block leading-tight hover:opacity-80 rounded-md transition-colors truncate text-muted-foreground"
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
to={`/u/${encodeURIComponent(creator.username)}`}
viewTransition
>
@ -144,11 +120,7 @@ const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, o
</Link>
<button
type="button"
<<<<<<< HEAD
className="w-auto -mt-0.5 text-xs leading-tight select-none cursor-pointer hover:opacity-80 transition-colors text-left"
=======
className="w-auto -mt-0.5 text-xs leading-tight text-muted-foreground select-none cursor-pointer hover:opacity-80 transition-colors text-left"
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
onClick={onGotoDetail}
>
{displayTime}
@ -165,11 +137,7 @@ interface TimeDisplayProps {
const TimeDisplay: React.FC<TimeDisplayProps> = ({ displayTime, onGotoDetail }) => (
<button
type="button"
<<<<<<< HEAD
className="w-full text-sm leading-tight select-none cursor-pointer hover:opacity-80 transition-colors text-left"
=======
className="w-full text-sm leading-tight text-muted-foreground select-none cursor-pointer hover:text-foreground transition-colors text-left"
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
onClick={onGotoDetail}
>
{displayTime}

View File

@ -8,25 +8,12 @@ export interface MemoViewProps {
showPinned?: boolean;
className?: string;
parentPage?: string;
<<<<<<< HEAD
colorKey?: string;
}
export interface MemoHeaderProps {
name:string;
showCreator?: boolean;
showVisibility?: boolean;
showPinned?: boolean;
onColorPreferencesChange?: (colors: { bgColor: string; textColor: string }) => void;
showColorCustomizer?: boolean;
=======
}
export interface MemoHeaderProps {
showCreator?: boolean;
showVisibility?: boolean;
showPinned?: boolean;
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
}
export interface MemoBodyProps {

View File

@ -97,10 +97,6 @@ const PagedMemoList = (props: Props) => {
// Flatten pages into a single array of memos
const memos = useMemo(() => data?.pages.flatMap((page) => page.memos) || [], [data]);
<<<<<<< HEAD
console.log("Memoss: ",memos);
=======
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
// Apply custom sorting if provided, otherwise use memos directly
const sortedMemoList = useMemo(() => (props.listSort ? props.listSort(memos) : memos), [memos, props.listSort]);

View File

@ -17,16 +17,6 @@ export const memoKeys = {
};
export function useMemos(request: Partial<ListMemosRequest> = {}) {
<<<<<<< HEAD
console.log(`Memos: `,useQuery({
queryKey: memoKeys.list(request),
queryFn: async () => {
const response = await memoServiceClient.listMemos(create(ListMemosRequestSchema, request as Record<string, unknown>));
return response;
},
}));
=======
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)
return useQuery({
queryKey: memoKeys.list(request),
queryFn: async () => {

View File

@ -1,8 +1,7 @@
<<<<<<< HEAD
// Utilities for manipulating markdown strings using AST parsing
// Uses mdast for accurate task detection that properly handles code blocks
import type { Heading, ListItem } from "mdast";
import type { ListItem } from "mdast";
import { fromMarkdown } from "mdast-util-from-markdown";
import { gfmFromMarkdown } from "mdast-util-gfm";
import { gfm } from "micromark-extension-gfm";
@ -106,65 +105,6 @@ export interface TaskItem {
indentation: number;
}
export interface HeadingItem {
text: string;
level: 1 | 2 | 3 | 4;
slug: string;
}
/**
* Slugify a string into a URL-friendly anchor ID.
*/
export function slugify(text: string): string {
return text
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "")
.replace(/[\s_]+/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}
/**
* Extract h1h4 headings from markdown content for outline navigation.
*/
export function extractHeadings(markdown: string): HeadingItem[] {
const tree = fromMarkdown(markdown, {
extensions: [gfm()],
mdastExtensions: [gfmFromMarkdown()],
});
const headings: HeadingItem[] = [];
const slugCounts = new Map<string, number>();
visit(tree, "heading", (node: Heading) => {
if (node.depth < 1 || node.depth > 4) return;
const text = getNodeText(node as unknown as MdastNode);
if (!text) return;
let slug = slugify(text);
const count = slugCounts.get(slug) || 0;
slugCounts.set(slug, count + 1);
if (count > 0) slug = `${slug}-${count}`;
headings.push({ text, level: node.depth as 1 | 2 | 3 | 4, slug });
});
return headings;
}
interface MdastNode {
value?: string;
children?: MdastNode[];
}
function getNodeText(node: MdastNode): string {
if (node.value) return node.value;
if (node.children) return node.children.map(getNodeText).join("");
return "";
}
export function extractTasks(markdown: string): TaskItem[] {
const tree = fromMarkdown(markdown, {
extensions: [gfm()],
@ -200,147 +140,3 @@ export function extractTasks(markdown: string): TaskItem[] {
return tasks;
}
=======
// Utilities for manipulating markdown strings using AST parsing
// Uses mdast for accurate task detection that properly handles code blocks
import type { ListItem } from "mdast";
import { fromMarkdown } from "mdast-util-from-markdown";
import { gfmFromMarkdown } from "mdast-util-gfm";
import { gfm } from "micromark-extension-gfm";
import { visit } from "unist-util-visit";
interface TaskInfo {
lineNumber: number;
checked: boolean;
}
// Extract all task list items from markdown using AST parsing
// This correctly ignores task-like patterns inside code blocks
function extractTasksFromAst(markdown: string): TaskInfo[] {
const tree = fromMarkdown(markdown, {
extensions: [gfm()],
mdastExtensions: [gfmFromMarkdown()],
});
const tasks: TaskInfo[] = [];
visit(tree, "listItem", (node: ListItem) => {
// Only process actual task list items (those with a checkbox)
if (typeof node.checked === "boolean" && node.position?.start.line) {
tasks.push({
lineNumber: node.position.start.line - 1, // Convert to 0-based
checked: node.checked,
});
}
});
return tasks;
}
export function toggleTaskAtLine(markdown: string, lineNumber: number, checked: boolean): string {
const lines = markdown.split("\n");
if (lineNumber < 0 || lineNumber >= lines.length) {
return markdown;
}
const line = lines[lineNumber];
// Match task list patterns: - [ ], - [x], - [X], etc.
const taskPattern = /^(\s*[-*+]\s+)\[([ xX])\](\s+.*)$/;
const match = line.match(taskPattern);
if (!match) {
return markdown;
}
const [, prefix, , suffix] = match;
const newCheckmark = checked ? "x" : " ";
lines[lineNumber] = `${prefix}[${newCheckmark}]${suffix}`;
return lines.join("\n");
}
export function toggleTaskAtIndex(markdown: string, taskIndex: number, checked: boolean): string {
const tasks = extractTasksFromAst(markdown);
if (taskIndex < 0 || taskIndex >= tasks.length) {
return markdown;
}
const task = tasks[taskIndex];
return toggleTaskAtLine(markdown, task.lineNumber, checked);
}
export function countTasks(markdown: string): {
total: number;
completed: number;
incomplete: number;
} {
const tasks = extractTasksFromAst(markdown);
const total = tasks.length;
const completed = tasks.filter((t) => t.checked).length;
return {
total,
completed,
incomplete: total - completed,
};
}
export function getTaskLineNumber(markdown: string, taskIndex: number): number {
const tasks = extractTasksFromAst(markdown);
if (taskIndex < 0 || taskIndex >= tasks.length) {
return -1;
}
return tasks[taskIndex].lineNumber;
}
export interface TaskItem {
lineNumber: number;
taskIndex: number;
checked: boolean;
content: string;
indentation: number;
}
export function extractTasks(markdown: string): TaskItem[] {
const tree = fromMarkdown(markdown, {
extensions: [gfm()],
mdastExtensions: [gfmFromMarkdown()],
});
const lines = markdown.split("\n");
const tasks: TaskItem[] = [];
let taskIndex = 0;
visit(tree, "listItem", (node: ListItem) => {
if (typeof node.checked === "boolean" && node.position?.start.line) {
const lineNumber = node.position.start.line - 1;
const line = lines[lineNumber];
// Extract indentation
const indentMatch = line.match(/^(\s*)/);
const indentation = indentMatch ? indentMatch[1].length : 0;
// Extract content (text after the checkbox)
const contentMatch = line.match(/^\s*[-*+]\s+\[[ xX]\]\s+(.*)/);
const content = contentMatch ? contentMatch[1] : "";
tasks.push({
lineNumber,
taskIndex: taskIndex++,
checked: node.checked,
content,
indentation,
});
}
});
return tasks;
}
>>>>>>> 89d43a2e (Developed Color Picker Feature for memos)