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:
mARTin 2026-04-05 11:43:57 +02:00
parent 7bc43d3c98
commit 388618e62c
2 changed files with 101 additions and 26 deletions

View File

@ -1,6 +1,6 @@
{
"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",
"main": "index.js",
"scripts": {

View File

@ -102,6 +102,7 @@ import { AVAILABLE_FONTS, isCustomFont } from "../lib/fontConstants";
import { translations } from "../lib/weeklyViewTranslations";
import { WeatherDisplayKey, WEATHER_DISPLAY_DEFAULTS } from "../lib/weeklyViewConstants";
const SettingsSidebar = dynamic(() => import("./SettingsSidebar"), { ssr: false });
const RichTextEditor = dynamic(() => import("./RichTextEditor"), { ssr: false });
// Cookie helpers for per-device settings
const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour"];
@ -8065,6 +8066,7 @@ export default function WeeklyView() {
onUpdate={(title) => updateTask(task.id, title)}
onDelete={() => deleteTask(task.id)}
onNotes={() => setSelectedTaskForNotes(task)}
onUrl={(url) => updateTaskUrl(task.id, url)}
onRollToggle={() => toggleTaskRolling(task.id)}
onRecurrence={() => setSelectedTaskForRecurrence(task)}
onDragStart={(e, t) => handleDragStart(e, t)}
@ -9268,6 +9270,7 @@ interface TaskItemProps {
onUpdate: (title: string) => void;
onDelete: () => void;
onNotes: (notes: string) => void;
onUrl?: (url: string) => void;
onRollToggle: () => void;
onRecurrence: () => void;
onDragStart: (e: DragEvent, task: Task) => void;
@ -9296,6 +9299,7 @@ function TaskItem({
onUpdate,
onDelete,
onNotes,
onUrl,
onRollToggle,
onRecurrence,
onDragStart,
@ -9318,6 +9322,9 @@ function TaskItem({
const [editValue, setEditValue] = useState(task.title);
const [isNotesOpen, setIsNotesOpen] = useState(false);
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 [isSubTasksOpen, setIsSubTasksOpen] = useState(false);
const [newSubTaskTitle, setNewSubTaskTitle] = useState("");
@ -9383,6 +9390,17 @@ function TaskItem({
}
}, [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) => {
e.preventDefault();
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 */}
{task.markdownContent && task.markdownContent.trim().length > 0 && (
<button
@ -9938,6 +9977,19 @@ function TaskItem({
</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 */}
<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"
@ -9967,35 +10019,58 @@ function TaskItem({
)}
</div>
{/* Inline Notes Editor with Toolbar */}
{
isNotesOpen && (
<div
className="weekly-notes-inline"
onClick={(e) => e.stopPropagation()}
>
<div className="notes-toolbar">
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("**", "**")} title="Bold" style={{ fontWeight: 700 }}>B</button>
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("*", "*")} title="Italic" style={{ fontStyle: "italic" }}>I</button>
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("[", "](url)")} title="Link">
<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>
{/* URL editor */}
{isUrlOpen && (
<div className="weekly-notes-inline" onClick={(e) => e.stopPropagation()} style={{ padding: "6px 8px", display: "flex", alignItems: "center", gap: "6px" }}>
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="#2563eb" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
<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>
<input
ref={urlInputRef}
type="url"
value={urlValue}
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 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>
</button>
<span style={{ marginLeft: "auto", fontSize: "0.65rem", color: "var(--weekly-text-muted, #999)", opacity: 0.6 }}>md</span>
)}
{urlValue && (
<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" }}>
<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>
<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}
onChange={(e) => setNotesValue(e.target.value)}
onBlur={handleNotesBlur}
onChange={(html) => { setNotesValue(html); onNotes(html); }}
placeholder="Add notes..."
minHeight="80px"
/>
</div>
)
}
</div>
)}
{/* Sub-tasks section */}
{