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:
parent
163e9c1a07
commit
330d6fddc6
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"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",
|
"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": {
|
||||||
|
|||||||
@ -195,23 +195,24 @@ export function GridTaskBlock({
|
|||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
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 || "");
|
updateTask(task.id, input.value || "");
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
style={{ width: "100%", paddingRight: "20px" }}
|
style={{ width: "100%", paddingRight: "20px" }}
|
||||||
>
|
>
|
||||||
<input
|
<textarea
|
||||||
name="title"
|
name="title"
|
||||||
autoFocus
|
autoFocus
|
||||||
defaultValue={task.title}
|
defaultValue={task.title}
|
||||||
onBlur={(e) => updateTask(task.id, e.target.value)}
|
onBlur={(e) => updateTask(task.id, e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Escape") setEditingTaskId(null);
|
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"
|
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>
|
</form>
|
||||||
) : (
|
) : (
|
||||||
@ -221,7 +222,7 @@ export function GridTaskBlock({
|
|||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
gap: "3px",
|
gap: "3px",
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
whiteSpace: "normal",
|
whiteSpace: "pre-wrap",
|
||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -5679,8 +5679,7 @@ interface TaskInputProps {
|
|||||||
function TaskInput({ onAddTask, onDragOver, onDrop }: TaskInputProps) {
|
function TaskInput({ onAddTask, onDragOver, onDrop }: TaskInputProps) {
|
||||||
const [newTaskTitle, setNewTaskTitle] = useState("");
|
const [newTaskTitle, setNewTaskTitle] = useState("");
|
||||||
|
|
||||||
const handleAddTask = (e: React.FormEvent) => {
|
const handleAddTask = () => {
|
||||||
e.preventDefault();
|
|
||||||
if (newTaskTitle.trim()) {
|
if (newTaskTitle.trim()) {
|
||||||
onAddTask(newTaskTitle);
|
onAddTask(newTaskTitle);
|
||||||
setNewTaskTitle("");
|
setNewTaskTitle("");
|
||||||
@ -5688,19 +5687,36 @@ function TaskInput({ onAddTask, onDragOver, onDrop }: TaskInputProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<div
|
||||||
onSubmit={handleAddTask}
|
|
||||||
className="weekly-task-input"
|
className="weekly-task-input"
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
>
|
>
|
||||||
<input
|
<textarea
|
||||||
type="text"
|
|
||||||
value={newTaskTitle}
|
value={newTaskTitle}
|
||||||
onChange={(e) => setNewTaskTitle(e.target.value)}
|
onChange={(e) => setNewTaskTitle(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleAddTask();
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder="Type a to-do..."
|
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 [isSubTaskInputOpen, setIsSubTaskInputOpen] = useState(false);
|
||||||
const [isSubTasksOpen, setIsSubTasksOpen] = useState(false);
|
const [isSubTasksOpen, setIsSubTasksOpen] = useState(false);
|
||||||
const [newSubTaskTitle, setNewSubTaskTitle] = useState("");
|
const [newSubTaskTitle, setNewSubTaskTitle] = useState("");
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const notesRef = useRef<HTMLTextAreaElement>(null);
|
const notesRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const subTaskInputRef = useRef<HTMLInputElement>(null);
|
const subTaskInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
@ -5850,6 +5866,10 @@ function TaskItem({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
(e.target as HTMLElement).blur();
|
||||||
|
}
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
setEditValue(task.title);
|
setEditValue(task.title);
|
||||||
onUpdate(task.title);
|
onUpdate(task.title);
|
||||||
@ -5936,14 +5956,14 @@ function TaskItem({
|
|||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<form onSubmit={handleSubmit} style={{ flex: 1, display: "flex" }}>
|
<form onSubmit={handleSubmit} style={{ flex: 1, display: "flex" }}>
|
||||||
{variant === "minimal" ? (
|
{variant === "minimal" ? (
|
||||||
<input
|
<textarea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
|
||||||
className="weekly-task-text"
|
className="weekly-task-text"
|
||||||
value={editValue}
|
value={editValue}
|
||||||
onChange={(e) => setEditValue(e.target.value)}
|
onChange={(e) => setEditValue(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onBlur={() => onUpdate(editValue)}
|
onBlur={() => onUpdate(editValue)}
|
||||||
|
rows={editValue.split("\n").length || 1}
|
||||||
style={{
|
style={{
|
||||||
border: "none",
|
border: "none",
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
@ -5952,17 +5972,27 @@ function TaskItem({
|
|||||||
padding: "0",
|
padding: "0",
|
||||||
fontSize: "0.9375rem",
|
fontSize: "0.9375rem",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
|
resize: "none",
|
||||||
|
overflow: "hidden",
|
||||||
|
fontFamily: "inherit",
|
||||||
|
lineHeight: "inherit",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<textarea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
|
||||||
className="weekly-task-text"
|
className="weekly-task-text"
|
||||||
value={editValue}
|
value={editValue}
|
||||||
onChange={(e) => setEditValue(e.target.value)}
|
onChange={(e) => setEditValue(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onBlur={() => onUpdate(editValue)}
|
onBlur={() => onUpdate(editValue)}
|
||||||
|
rows={editValue.split("\n").length || 1}
|
||||||
|
style={{
|
||||||
|
resize: "none",
|
||||||
|
overflow: "hidden",
|
||||||
|
fontFamily: "inherit",
|
||||||
|
lineHeight: "inherit",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
@ -6024,7 +6054,7 @@ function TaskItem({
|
|||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
whiteSpace: "normal",
|
whiteSpace: "pre-wrap",
|
||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
overflow: "visible"
|
overflow: "visible"
|
||||||
}}
|
}}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user