feat: support linebreaks in task titles with Shift+Enter

Replace input elements with textarea for task editing and creation.
Shift+Enter inserts a newline, Enter saves. Display uses pre-wrap.

v1.10.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-02 08:18:35 +01:00
parent 163e9c1a07
commit 330d6fddc6
3 changed files with 50 additions and 19 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.9.0",
"version": "1.10.0",
"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

@ -195,23 +195,24 @@ export function GridTaskBlock({
<form
onSubmit={(e) => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem("title") as HTMLInputElement;
const input = e.currentTarget.elements.namedItem("title") as HTMLTextAreaElement;
updateTask(task.id, input.value || "");
}}
onClick={(e) => e.stopPropagation()}
style={{ width: "100%", paddingRight: "20px" }}
>
<input
<textarea
name="title"
autoFocus
defaultValue={task.title}
onBlur={(e) => updateTask(task.id, e.target.value)}
onKeyDown={(e) => {
if (e.key === "Escape") setEditingTaskId(null);
if (e.key === "Enter") e.currentTarget.blur();
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); e.currentTarget.blur(); }
}}
rows={(task.title?.split("\n").length) || 1}
className="weekly-task-text"
style={{ width: "100%", background: "transparent", border: "none", borderBottom: "1px solid var(--weekly-border)", outline: "none" }}
style={{ width: "100%", background: "transparent", border: "none", borderBottom: "1px solid var(--weekly-border)", outline: "none", resize: "none", overflow: "hidden", fontFamily: "inherit", lineHeight: "inherit" }}
/>
</form>
) : (
@ -221,7 +222,7 @@ export function GridTaskBlock({
alignItems: "flex-start",
gap: "3px",
overflow: "visible",
whiteSpace: "normal",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
flex: 1,
}}

View File

@ -5679,8 +5679,7 @@ interface TaskInputProps {
function TaskInput({ onAddTask, onDragOver, onDrop }: TaskInputProps) {
const [newTaskTitle, setNewTaskTitle] = useState("");
const handleAddTask = (e: React.FormEvent) => {
e.preventDefault();
const handleAddTask = () => {
if (newTaskTitle.trim()) {
onAddTask(newTaskTitle);
setNewTaskTitle("");
@ -5688,19 +5687,36 @@ function TaskInput({ onAddTask, onDragOver, onDrop }: TaskInputProps) {
};
return (
<form
onSubmit={handleAddTask}
<div
className="weekly-task-input"
onDragOver={onDragOver}
onDrop={onDrop}
>
<input
type="text"
<textarea
value={newTaskTitle}
onChange={(e) => setNewTaskTitle(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleAddTask();
}
}}
placeholder="Type a to-do..."
rows={newTaskTitle.split("\n").length || 1}
style={{
resize: "none",
overflow: "hidden",
fontFamily: "inherit",
lineHeight: "inherit",
width: "100%",
border: "none",
background: "transparent",
outline: "none",
padding: "inherit",
fontSize: "inherit",
}}
/>
</form>
</div>
);
}
@ -5820,7 +5836,7 @@ function TaskItem({
const [isSubTaskInputOpen, setIsSubTaskInputOpen] = useState(false);
const [isSubTasksOpen, setIsSubTasksOpen] = useState(false);
const [newSubTaskTitle, setNewSubTaskTitle] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);
const notesRef = useRef<HTMLTextAreaElement>(null);
const subTaskInputRef = useRef<HTMLInputElement>(null);
@ -5850,6 +5866,10 @@ function TaskItem({
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
(e.target as HTMLElement).blur();
}
if (e.key === "Escape") {
setEditValue(task.title);
onUpdate(task.title);
@ -5936,14 +5956,14 @@ function TaskItem({
{isEditing ? (
<form onSubmit={handleSubmit} style={{ flex: 1, display: "flex" }}>
{variant === "minimal" ? (
<input
<textarea
ref={inputRef}
type="text"
className="weekly-task-text"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => onUpdate(editValue)}
rows={editValue.split("\n").length || 1}
style={{
border: "none",
background: "transparent",
@ -5952,17 +5972,27 @@ function TaskItem({
padding: "0",
fontSize: "0.9375rem",
fontWeight: 500,
resize: "none",
overflow: "hidden",
fontFamily: "inherit",
lineHeight: "inherit",
}}
/>
) : (
<input
<textarea
ref={inputRef}
type="text"
className="weekly-task-text"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => onUpdate(editValue)}
rows={editValue.split("\n").length || 1}
style={{
resize: "none",
overflow: "hidden",
fontFamily: "inherit",
lineHeight: "inherit",
}}
/>
)}
</form>
@ -6024,7 +6054,7 @@ function TaskItem({
)}
<span
style={{
whiteSpace: "normal",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
overflow: "visible"
}}