mirror of https://github.com/usememos/memos.git
resolving conflicts: color picker functionality and enhance memo components with color customization options
This commit is contained in:
parent
41405bc3fa
commit
2d25e518c8
|
|
@ -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.
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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 h1–h4 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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue