Feature: Mobile bottom-sheet drawer (slide-up from bottom, swipe-down to close, overlay), update AGENTS.md to v4.0
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import React, { useState, useRef, useCallback, useEffect } from "react";
|
||||
import { marked } from "marked";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
||||
@@ -13,6 +13,17 @@ export function MarkdownNoteEditor({ value, onChange, onSave }: MarkdownNoteEdit
|
||||
const { t } = useI18n();
|
||||
const [mode, setMode] = useState<"edit" | "preview">("preview");
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const previewRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Configure marked to open links in new tabs safely
|
||||
useEffect(() => {
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.link = ({ href, title, text }: { href: string; title?: string | null; text: string }) => {
|
||||
const titleAttr = title ? ` title="${title}"` : "";
|
||||
return `<a href="${href}" target="_blank" rel="noopener noreferrer"${titleAttr} class="markdown-link">${text}</a>`;
|
||||
};
|
||||
marked.use({ renderer, breaks: true, gfm: true });
|
||||
}, []);
|
||||
|
||||
// Insert markdown syntax at selection or cursor position
|
||||
const insertSyntax = useCallback(
|
||||
@@ -74,14 +85,23 @@ export function MarkdownNoteEditor({ value, onChange, onSave }: MarkdownNoteEdit
|
||||
onChange(newLines.join("\n"));
|
||||
};
|
||||
|
||||
// Convert markdown to sanitized HTML with interactive checkboxes
|
||||
// Convert markdown to sanitized HTML with interactive checkboxes and autolinks
|
||||
const renderMarkdownHtml = () => {
|
||||
if (!value || !value.trim()) {
|
||||
return `<p style="color: var(--text-tertiary); font-style: italic;">${t("notesPlaceholder").split("\n")[0]}</p>`;
|
||||
}
|
||||
|
||||
try {
|
||||
let rawHtml = marked.parse(value, { breaks: true, gfm: true }) as string;
|
||||
// Auto-detect plain URLs and wrap them in markdown links if not already wrapped
|
||||
const textWithLinks = value.replace(
|
||||
/(^|[^"'])(https?:\/\/[^\s<]+)/g,
|
||||
(match, prefix, url) => {
|
||||
if (match.includes("](") || match.includes('href="')) return match;
|
||||
return `${prefix}[${url}](${url})`;
|
||||
}
|
||||
);
|
||||
|
||||
let rawHtml = marked.parse(textWithLinks, { breaks: true, gfm: true }) as string;
|
||||
|
||||
// Replace task list checkboxes with interactive ones
|
||||
let cbIdx = 0;
|
||||
@@ -303,10 +323,22 @@ export function MarkdownNoteEditor({ value, onChange, onSave }: MarkdownNoteEdit
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
ref={previewRef}
|
||||
className="markdown-body"
|
||||
dangerouslySetInnerHTML={{ __html: renderMarkdownHtml() }}
|
||||
onClick={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
// Handle link clicks cleanly
|
||||
const link = target.closest("a");
|
||||
if (link && link.href) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(link.href, "_blank", "noopener,noreferrer");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle interactive checkbox clicks
|
||||
if (target.classList.contains("markdown-checkbox-box")) {
|
||||
const idxStr = target.getAttribute("data-idx");
|
||||
if (idxStr) {
|
||||
|
||||
Reference in New Issue
Block a user