Checklists: Follow-up (#6044)

This commit is contained in:
zubiden 2025-07-06 13:52:05 +02:00 committed by Alexander Zinchuk
parent 310b5490a5
commit 2236f3de37
10 changed files with 83 additions and 43 deletions

View File

@ -2049,7 +2049,6 @@
"MessageActionTodoCompletionsAsNotDoneMultipleYou" = "You marked {tasks} as not done"; "MessageActionTodoCompletionsAsNotDoneMultipleYou" = "You marked {tasks} as not done";
"MessageActionTodoTaskCount_one" = "{count} task"; "MessageActionTodoTaskCount_one" = "{count} task";
"MessageActionTodoTaskCount_other" = "{count} tasks"; "MessageActionTodoTaskCount_other" = "{count} tasks";
"ToDoListNewTasks" = "New Tasks";
"MenuButtonAppendTodoList" = "Add a Task"; "MenuButtonAppendTodoList" = "Add a Task";
"MessageActionAppendTodo" = "{peer} added a new task \"{task}\" to {list}"; "MessageActionAppendTodo" = "{peer} added a new task \"{task}\" to {list}";
"MessageActionAppendTodoYou" = "You added a new task \"{task}\" to {list}"; "MessageActionAppendTodoYou" = "You added a new task \"{task}\" to {list}";

View File

@ -33,7 +33,7 @@
min-width: 1.5rem; min-width: 1.5rem;
height: 1.5rem; height: 1.5rem;
padding: 0 0.4375rem !important; padding: 0 0.4375rem;
border-radius: 0.75rem; border-radius: 0.75rem;
font-size: 0.875rem !important; font-size: 0.875rem !important;

View File

@ -1,7 +1,7 @@
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import type { ElementRef } from '../../../lib/teact/teact'; import type { ElementRef } from '../../../lib/teact/teact';
import { import {
memo, useEffect, useRef, useState, memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
@ -18,6 +18,7 @@ import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fast
import { selectChatMessage } from '../../../global/selectors'; import { selectChatMessage } from '../../../global/selectors';
import captureEscKeyListener from '../../../util/captureEscKeyListener'; import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { generateUniqueNumberId } from '../../../util/generateUniqueId'; import { generateUniqueNumberId } from '../../../util/generateUniqueId';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -47,6 +48,7 @@ export type StateProps = {
type Item = { type Item = {
id: number; id: number;
text: string; text: string;
isDisabled?: boolean;
}; };
const MAX_LIST_HEIGHT = 320; const MAX_LIST_HEIGHT = 320;
@ -72,20 +74,34 @@ const ToDoListModal = ({
const [isOthersCanComplete, setIsOthersCanComplete] = useState(true); const [isOthersCanComplete, setIsOthersCanComplete] = useState(true);
const [hasErrors, setHasErrors] = useState<boolean>(false); const [hasErrors, setHasErrors] = useState<boolean>(false);
const lang = useLang();
const isOpen = Boolean(modal); const isOpen = Boolean(modal);
const renderingModal = useCurrentOrPrev(modal); const renderingModal = useCurrentOrPrev(modal);
const isAddTaskMode = renderingModal?.isAddTaskMode; // Treat "Add task" as edit mode for own checklists
const isAddTaskMode = renderingModal?.forNewTask && !editingMessage?.isOutgoing;
const lang = useLang();
const editingTodo = editingMessage?.content.todo?.todo; const editingTodo = editingMessage?.content.todo?.todo;
const frozenTasks = useMemo(() => {
if (!isAddTaskMode || !editingTodo) {
return MEMO_EMPTY_ARRAY;
}
return editingTodo.items.map((item) => ({
id: item.id,
text: item.title.text,
isDisabled: true,
}));
}, [isAddTaskMode, editingTodo]);
const focusInput = useLastCallback((ref: ElementRef<HTMLInputElement>) => { const focusInput = useLastCallback((ref: ElementRef<HTMLInputElement>) => {
if (isOpen && ref.current) { if (isOpen && ref.current) {
ref.current.focus(); ref.current.focus();
} }
}); });
useEffect(() => { useLayoutEffect(() => {
if (editingTodo) { if (editingTodo) {
setTitle(editingTodo.title.text); setTitle(editingTodo.title.text);
setIsOthersCanAppend(editingTodo.othersCanAppend ?? false); setIsOthersCanAppend(editingTodo.othersCanAppend ?? false);
@ -114,7 +130,20 @@ const ToDoListModal = ({
} }
}, [isOpen]); }, [isOpen]);
useEffect(() => focusInput(titleInputRef), [focusInput, isOpen]); useEffect(() => {
if (isOpen) {
// Wait for the DOM to be updated
requestMeasure(() => {
if (renderingModal?.forNewTask) {
const inputs = itemsListRef.current?.querySelectorAll('input');
const lastInput = inputs?.[inputs.length - 1];
lastInput?.focus();
} else {
focusInput(titleInputRef);
}
});
}
}, [focusInput, isOpen, renderingModal?.forNewTask]);
const addNewItem = useLastCallback((newItems: Item[]) => { const addNewItem = useLastCallback((newItems: Item[]) => {
const id = generateUniqueNumberId(); const id = generateUniqueNumberId();
@ -283,34 +312,37 @@ const ToDoListModal = ({
} }
function renderItems() { function renderItems() {
return items.map((item, index) => ( const tasksToRender = [...frozenTasks, ...items];
<div className="item-wrapper"> return tasksToRender.map((item, index) => {
<InputText const stateIndex = index - frozenTasks.length;
maxLength={MAX_OPTION_LENGTH} return (
label={index !== items.length - 1 || items.length === maxItemsCount <div className="item-wrapper">
? lang('TitleTask') <InputText
: lang('TitleAddTask')} maxLength={MAX_OPTION_LENGTH}
error={getItemsError(index)} label={index !== tasksToRender.length - 1 || tasksToRender.length === maxItemsCount
value={item.text} ? lang('TitleTask')
: lang('TitleAddTask')}
onChange={(e) => updateItem(index, e.currentTarget.value)} error={getItemsError(stateIndex)}
onKeyPress={handleKeyPress} value={item.text}
/> disabled={item.isDisabled}
{index !== items.length - 1 && ( onChange={(e) => updateItem(stateIndex, e.currentTarget.value)}
<Button onKeyPress={handleKeyPress}
className="item-remove-button" />
round {index !== tasksToRender.length - 1 && !item.isDisabled && (
color="translucent" <Button
size="smaller" className="item-remove-button"
ariaLabel={lang('Delete')} round
color="translucent"
onClick={() => removeItem(index)} size="smaller"
> ariaLabel={lang('Delete')}
<Icon name="close" /> onClick={() => removeItem(stateIndex)}
</Button> >
)} <Icon name="close" />
</div> </Button>
)); )}
</div>
);
});
} }
return ( return (
@ -334,7 +366,7 @@ const ToDoListModal = ({
<div className="options-list custom-scroll" ref={itemsListRef}> <div className="options-list custom-scroll" ref={itemsListRef}>
<h3 className="items-header"> <h3 className="items-header">
{lang(isAddTaskMode ? 'ToDoListNewTasks' : 'TitleToDoList')} {lang('TitleToDoList')}
</h3> </h3>
{renderItems()} {renderItems()}

View File

@ -469,7 +469,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
openTodoListModal({ openTodoListModal({
chatId: message.chatId, chatId: message.chatId,
messageId: message.id, messageId: message.id,
isAddTaskMode: true, forNewTask: true,
}); });
} }
closeMenu(); closeMenu();

View File

@ -93,6 +93,12 @@
.label { .label {
line-height: 1.3125rem; line-height: 1.3125rem;
} }
.subLabel {
margin-top: 0;
font-size: 0.75rem;
color: var(--secondary-color);
}
} }
input:checked ~ .Checkbox-main { input:checked ~ .Checkbox-main {

View File

@ -41,7 +41,7 @@ const TodoList = ({
isCurrentUserPremium, isCurrentUserPremium,
isSynced, isSynced,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { toggleTodoCompleted, showNotification } = getActions(); const { toggleTodoCompleted, showNotification, requestConfetti } = getActions();
const { todo, completions } = todoList; const { todo, completions } = todoList;
const { title, items, othersCanComplete } = todo; const { title, items, othersCanComplete } = todo;
const [completedTasks, setCompletedTasks] = useState<string[]>([]); const [completedTasks, setCompletedTasks] = useState<string[]>([]);
@ -83,6 +83,10 @@ const TodoList = ({
completedIds: newCompletedId ? [Number(newCompletedId)] : [], completedIds: newCompletedId ? [Number(newCompletedId)] : [],
incompletedIds: newIncompletedId ? [Number(newIncompletedId)] : [], incompletedIds: newIncompletedId ? [Number(newIncompletedId)] : [],
}); });
if (newCompletedTasks.length === items.length) {
requestConfetti({});
}
}); });
const isReadOnly = Boolean(message.forwardInfo) || (!othersCanComplete && !message.isOutgoing); const isReadOnly = Boolean(message.forwardInfo) || (!othersCanComplete && !message.isOutgoing);
const isOutgoing = message.isOutgoing; const isOutgoing = message.isOutgoing;

View File

@ -765,14 +765,14 @@ addActionHandler('closePollModal', (global, actions, payload): ActionReturnType
addActionHandler('openTodoListModal', (global, actions, payload): ActionReturnType => { addActionHandler('openTodoListModal', (global, actions, payload): ActionReturnType => {
const { const {
chatId, messageId, isAddTaskMode, tabId = getCurrentTabId(), chatId, messageId, forNewTask, tabId = getCurrentTabId(),
} = payload; } = payload;
return updateTabState(global, { return updateTabState(global, {
todoListModal: { todoListModal: {
chatId, chatId,
messageId, messageId,
isAddTaskMode, forNewTask,
}, },
}, tabId); }, tabId);
}); });

View File

@ -2203,7 +2203,7 @@ export interface ActionPayloads {
openTodoListModal: { openTodoListModal: {
chatId: string; chatId: string;
messageId?: number; messageId?: number;
isAddTaskMode?: boolean; forNewTask?: boolean;
} & WithTabId; } & WithTabId;
closeTodoListModal: WithTabId | undefined; closeTodoListModal: WithTabId | undefined;
requestConfetti: (ConfettiParams & WithTabId) | WithTabId; requestConfetti: (ConfettiParams & WithTabId) | WithTabId;

View File

@ -535,7 +535,7 @@ export type TabState = {
todoListModal?: { todoListModal?: {
chatId: string; chatId: string;
messageId?: number; messageId?: number;
isAddTaskMode?: boolean; forNewTask?: boolean;
}; };
preparedMessageModal?: { preparedMessageModal?: {

View File

@ -1550,7 +1550,6 @@ export interface LangPair {
'AriaToDoCancel': undefined; 'AriaToDoCancel': undefined;
'TitleGroupToDoList': undefined; 'TitleGroupToDoList': undefined;
'TitleYourToDoList': undefined; 'TitleYourToDoList': undefined;
'ToDoListNewTasks': undefined;
'MenuButtonAppendTodoList': undefined; 'MenuButtonAppendTodoList': undefined;
'PremiumMore': undefined; 'PremiumMore': undefined;
'SubscribeToTelegramPremiumForToggleTask': undefined; 'SubscribeToTelegramPremiumForToggleTask': undefined;