fix: URL and rich-text notes now work in someday area tasks
TaskItem (used for all someday list tasks) was missing URL support entirely. Added: - onUrl prop wired to updateTaskUrl at both someday TaskItem render sites (slot tasks and unindexed/remaining tasks) - URL indicator (clickable link badge) shown inline on the task row - Link action button in the task-actions bar that opens an inline URL editor - URL popup with input, clear button, and open-in-new-tab shortcut Also upgraded TaskItem's notes editor from plain markdown textarea to RichTextEditor (same as GridTaskBlock), so inline hyperlinks in notes work in the someday area too. v1.82.1
This commit is contained in:
parent
7bc43d3c98
commit
388618e62c
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.82.0",
|
"version": "1.82.1",
|
||||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -102,6 +102,7 @@ import { AVAILABLE_FONTS, isCustomFont } from "../lib/fontConstants";
|
|||||||
import { translations } from "../lib/weeklyViewTranslations";
|
import { translations } from "../lib/weeklyViewTranslations";
|
||||||
import { WeatherDisplayKey, WEATHER_DISPLAY_DEFAULTS } from "../lib/weeklyViewConstants";
|
import { WeatherDisplayKey, WEATHER_DISPLAY_DEFAULTS } from "../lib/weeklyViewConstants";
|
||||||
const SettingsSidebar = dynamic(() => import("./SettingsSidebar"), { ssr: false });
|
const SettingsSidebar = dynamic(() => import("./SettingsSidebar"), { ssr: false });
|
||||||
|
const RichTextEditor = dynamic(() => import("./RichTextEditor"), { ssr: false });
|
||||||
|
|
||||||
// Cookie helpers for per-device settings
|
// Cookie helpers for per-device settings
|
||||||
const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour"];
|
const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour"];
|
||||||
@ -8065,6 +8066,7 @@ export default function WeeklyView() {
|
|||||||
onUpdate={(title) => updateTask(task.id, title)}
|
onUpdate={(title) => updateTask(task.id, title)}
|
||||||
onDelete={() => deleteTask(task.id)}
|
onDelete={() => deleteTask(task.id)}
|
||||||
onNotes={() => setSelectedTaskForNotes(task)}
|
onNotes={() => setSelectedTaskForNotes(task)}
|
||||||
|
onUrl={(url) => updateTaskUrl(task.id, url)}
|
||||||
onRollToggle={() => toggleTaskRolling(task.id)}
|
onRollToggle={() => toggleTaskRolling(task.id)}
|
||||||
onRecurrence={() => setSelectedTaskForRecurrence(task)}
|
onRecurrence={() => setSelectedTaskForRecurrence(task)}
|
||||||
onDragStart={(e, t) => handleDragStart(e, t)}
|
onDragStart={(e, t) => handleDragStart(e, t)}
|
||||||
@ -9268,6 +9270,7 @@ interface TaskItemProps {
|
|||||||
onUpdate: (title: string) => void;
|
onUpdate: (title: string) => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onNotes: (notes: string) => void;
|
onNotes: (notes: string) => void;
|
||||||
|
onUrl?: (url: string) => void;
|
||||||
onRollToggle: () => void;
|
onRollToggle: () => void;
|
||||||
onRecurrence: () => void;
|
onRecurrence: () => void;
|
||||||
onDragStart: (e: DragEvent, task: Task) => void;
|
onDragStart: (e: DragEvent, task: Task) => void;
|
||||||
@ -9296,6 +9299,7 @@ function TaskItem({
|
|||||||
onUpdate,
|
onUpdate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onNotes,
|
onNotes,
|
||||||
|
onUrl,
|
||||||
onRollToggle,
|
onRollToggle,
|
||||||
onRecurrence,
|
onRecurrence,
|
||||||
onDragStart,
|
onDragStart,
|
||||||
@ -9318,6 +9322,9 @@ function TaskItem({
|
|||||||
const [editValue, setEditValue] = useState(task.title);
|
const [editValue, setEditValue] = useState(task.title);
|
||||||
const [isNotesOpen, setIsNotesOpen] = useState(false);
|
const [isNotesOpen, setIsNotesOpen] = useState(false);
|
||||||
const [notesValue, setNotesValue] = useState(task.markdownContent || "");
|
const [notesValue, setNotesValue] = useState(task.markdownContent || "");
|
||||||
|
const [isUrlOpen, setIsUrlOpen] = useState(false);
|
||||||
|
const [urlValue, setUrlValue] = useState(task.url || "");
|
||||||
|
const urlInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [isSubTaskInputOpen, setIsSubTaskInputOpen] = useState(false);
|
const [isSubTaskInputOpen, setIsSubTaskInputOpen] = useState(false);
|
||||||
const [isSubTasksOpen, setIsSubTasksOpen] = useState(false);
|
const [isSubTasksOpen, setIsSubTasksOpen] = useState(false);
|
||||||
const [newSubTaskTitle, setNewSubTaskTitle] = useState("");
|
const [newSubTaskTitle, setNewSubTaskTitle] = useState("");
|
||||||
@ -9383,6 +9390,17 @@ function TaskItem({
|
|||||||
}
|
}
|
||||||
}, [isNotesOpen]);
|
}, [isNotesOpen]);
|
||||||
|
|
||||||
|
// Focus URL input when opened; sync when task.url changes
|
||||||
|
useEffect(() => { if (isUrlOpen) setTimeout(() => urlInputRef.current?.focus(), 50); }, [isUrlOpen]);
|
||||||
|
useEffect(() => { setUrlValue(task.url || ""); }, [task.url]);
|
||||||
|
|
||||||
|
const handleUrlSave = (val: string) => {
|
||||||
|
setIsUrlOpen(false);
|
||||||
|
if (val.trim() !== (task.url || "") && onUrl) {
|
||||||
|
onUrl(val.trim());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onUpdate(editValue);
|
onUpdate(editValue);
|
||||||
@ -9690,6 +9708,27 @@ function TaskItem({
|
|||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
|
{/* URL indicator */}
|
||||||
|
{task.url && (
|
||||||
|
<a
|
||||||
|
href={task.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
title={task.url}
|
||||||
|
style={{
|
||||||
|
display: "inline-flex", alignItems: "center",
|
||||||
|
padding: "2px 5px", borderRadius: "4px",
|
||||||
|
background: "rgba(37,99,235,0.10)", color: "#2563eb",
|
||||||
|
textDecoration: "none", flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
||||||
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
{/* Note indicator - toggles inline notes */}
|
{/* Note indicator - toggles inline notes */}
|
||||||
{task.markdownContent && task.markdownContent.trim().length > 0 && (
|
{task.markdownContent && task.markdownContent.trim().length > 0 && (
|
||||||
<button
|
<button
|
||||||
@ -9938,6 +9977,19 @@ function TaskItem({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Link */}
|
||||||
|
<button
|
||||||
|
className={`task-action-btn ${task.url || isUrlOpen ? "active" : ""}`}
|
||||||
|
onClick={(e) => { e.stopPropagation(); setIsUrlOpen(!isUrlOpen); }}
|
||||||
|
title={task.url || "Add link"}
|
||||||
|
style={task.url ? { color: "#2563eb" } : {}}
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
||||||
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Delete */}
|
{/* Delete */}
|
||||||
<button
|
<button
|
||||||
className="task-action-btn delete text-red-500 hover:text-red-700 hover:bg-red-100/50 dark:hover:bg-red-900/30 rounded"
|
className="task-action-btn delete text-red-500 hover:text-red-700 hover:bg-red-100/50 dark:hover:bg-red-900/30 rounded"
|
||||||
@ -9967,35 +10019,58 @@ function TaskItem({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Inline Notes Editor with Toolbar */}
|
{/* URL editor */}
|
||||||
{
|
{isUrlOpen && (
|
||||||
isNotesOpen && (
|
<div className="weekly-notes-inline" onClick={(e) => e.stopPropagation()} style={{ padding: "6px 8px", display: "flex", alignItems: "center", gap: "6px" }}>
|
||||||
<div
|
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="#2563eb" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
|
||||||
className="weekly-notes-inline"
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
||||||
onClick={(e) => e.stopPropagation()}
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
||||||
>
|
</svg>
|
||||||
<div className="notes-toolbar">
|
<input
|
||||||
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("**", "**")} title="Bold" style={{ fontWeight: 700 }}>B</button>
|
ref={urlInputRef}
|
||||||
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("*", "*")} title="Italic" style={{ fontStyle: "italic" }}>I</button>
|
type="url"
|
||||||
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("[", "](url)")} title="Link">
|
value={urlValue}
|
||||||
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
onChange={(e) => setUrlValue(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") { e.preventDefault(); handleUrlSave(urlValue); }
|
||||||
|
if (e.key === "Escape") { setIsUrlOpen(false); setUrlValue(task.url || ""); }
|
||||||
|
}}
|
||||||
|
onBlur={() => handleUrlSave(urlValue)}
|
||||||
|
placeholder="https://..."
|
||||||
|
style={{ flex: 1, fontSize: "0.78rem", border: "1px solid var(--weekly-border, #ddd)", borderRadius: "4px", padding: "3px 6px", background: "var(--weekly-bg, white)", color: "var(--weekly-text, #333)", outline: "none" }}
|
||||||
|
/>
|
||||||
|
{urlValue && (
|
||||||
|
<button onMouseDown={(e) => { e.preventDefault(); setUrlValue(""); if (onUrl) onUrl(""); setIsUrlOpen(false); }} title="Remove link" style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: "#999", flexShrink: 0 }}>
|
||||||
|
<svg viewBox="0 0 24 24" width="12" height="12" stroke="currentColor" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("- ")} title="List">
|
)}
|
||||||
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
|
{urlValue && (
|
||||||
</button>
|
<a href={urlValue.startsWith("http") ? urlValue : `https://${urlValue}`} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()} title="Open link" style={{ color: "#2563eb", flexShrink: 0, display: "inline-flex" }}>
|
||||||
<span style={{ marginLeft: "auto", fontSize: "0.65rem", color: "var(--weekly-text-muted, #999)", opacity: 0.6 }}>md</span>
|
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||||
|
<polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
)}
|
||||||
ref={notesRef}
|
|
||||||
className="weekly-notes-editor-inline"
|
{/* Inline Notes Editor */}
|
||||||
|
{isNotesOpen && (
|
||||||
|
<div className="weekly-notes-inline" onClick={(e) => e.stopPropagation()} style={{ padding: "0" }}>
|
||||||
|
<div style={{ display: "flex", justifyContent: "flex-end", padding: "4px 6px 0" }}>
|
||||||
|
<button onMouseDown={(e) => { e.preventDefault(); setIsNotesOpen(false); }} style={{ background: "none", border: "none", cursor: "pointer", fontSize: "1rem", lineHeight: 1, color: "#999", padding: "2px 4px" }} title="Close">×</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: "0 6px 8px" }}>
|
||||||
|
<RichTextEditor
|
||||||
value={notesValue}
|
value={notesValue}
|
||||||
onChange={(e) => setNotesValue(e.target.value)}
|
onChange={(html) => { setNotesValue(html); onNotes(html); }}
|
||||||
onBlur={handleNotesBlur}
|
|
||||||
placeholder="Add notes..."
|
placeholder="Add notes..."
|
||||||
|
minHeight="80px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
{/* Sub-tasks section */}
|
{/* Sub-tasks section */}
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user