diff --git a/configs.js b/configs.js index c897ca8..44cc736 100644 --- a/configs.js +++ b/configs.js @@ -194,9 +194,11 @@ const configs = (function () { "!next", "!n", ], + logTaskCommands: ["!log", "!tasklog", "!logtask", "!logtasks"], // focus task commands - please add commands in the exact format focusTaskCommands: ["!focus", "!taskfocus", "!focustask"], + focusedTaskCommands: ["!focused", "!ongoing", "!checkfocus"], unfocusTaskCommands: ["!unfocus", "!taskunfocus", "!unfocustask"], nowTaskCommands: ["!now", "!tasknow", "!nowtask"], @@ -210,6 +212,14 @@ const configs = (function () { "!mytask", "!check", "!mytasks", + "!c", + ], + + checkCompletedCommands: [ + "!completed", + "!taskcompleted", + "!donetasks", + "!completedtasks", ], // List tasks commands - please add commands in the exact format @@ -315,12 +325,13 @@ const configs = (function () { noTaskContent: "Try using !task the-{taskName}-you-are-working-on {user}", duplicateTask: - "Looks like you already have the {taskName} {message} up there {user}!", + "Looks like you already have the {taskName} {task} up there {user}!", taskEdited: "{taskName} {originalTask} has been edited to {task} successfully, {user}", noTaskEdit: "Try doing !edit [index] [new task] {user}", nowTask: "{taskName} {task} is now the task you are working on, {user}!", + taskLogged: "Task(s) {task} has been logged, {user}!", // Grouped by task progression responses taskNext: @@ -361,6 +372,7 @@ const configs = (function () { specifyFocusTask: "Try specifying an incomplete {taskName} to focus {user}!", taskFocused: "Task {task} has been focused, {user}!", + focusedTask: "{user} Focused {taskName}: {task}", // Grouped by task existence responses noTask: "Looks like you don't have a task up there {user}", diff --git a/scripts/defaultConfigs.js b/scripts/defaultConfigs.js index 5a049fe..9992f71 100644 --- a/scripts/defaultConfigs.js +++ b/scripts/defaultConfigs.js @@ -74,10 +74,10 @@ const defaultConfigs = (function () { bodyBorderColor: "#fff", // hex or name bodyBorderRadius: "5px", // px - bodyMarginTop: "0px", // px + bodyMarginTop: "1px", // px // task - numberOfLines: 1, // number + numberOfLines: 2, // number lineHeight: 1.5, // number usernameFontWeight: "bold", // normal or bold or number usernameColor: "white", // hex or name or "" for twitch user color @@ -195,8 +195,11 @@ const defaultConfigs = (function () { "!n", ], + logTaskCommands: ["!log", "!tasklog", "!logtask", "!logtasks"], + // focus task commands - please add commands in the exact format focusTaskCommands: ["!focus", "!taskfocus", "!focustask"], + focusedTaskCommands: ["!focused", "!ongoing", "!checkfocus"], unfocusTaskCommands: ["!unfocus", "!taskunfocus", "!unfocustask"], nowTaskCommands: ["!now", "!tasknow", "!nowtask"], @@ -210,6 +213,14 @@ const defaultConfigs = (function () { "!mytask", "!check", "!mytasks", + "!c", + ], + + checkCompletedCommands: [ + "!completed", + "!taskcompleted", + "!donetasks", + "!completedtasks", ], // List tasks commands - please add commands in the exact format @@ -315,12 +326,13 @@ const defaultConfigs = (function () { noTaskContent: "Try using !task the-{taskName}-you-are-working-on {user}", duplicateTask: - "Looks like you already have the {taskName} {message} up there {user}!", + "Looks like you already have the {taskName} {task} up there {user}!", taskEdited: "{taskName} {originalTask} has been edited to {task} successfully, {user}", noTaskEdit: "Try doing !edit [index] [new task] {user}", nowTask: "{taskName} {task} is now the task you are working on, {user}!", + taskLogged: "Task(s) {task} has been logged, {user}!", // Grouped by task progression responses taskNext: @@ -361,6 +373,7 @@ const defaultConfigs = (function () { specifyFocusTask: "Try specifying an incomplete {taskName} to focus {user}!", taskFocused: "Task {task} has been focused, {user}!", + focusedTask: "{user} Focused {taskName}: {task}", // Grouped by task existence responses noTask: "Looks like you don't have a task up there {user}", diff --git a/scripts/processCommand.js b/scripts/processCommand.js index 8b74ced..8dde925 100644 --- a/scripts/processCommand.js +++ b/scripts/processCommand.js @@ -135,6 +135,7 @@ async function processCommand(user, command, message, flags, extra) { let nextRequest = await nextTask(user, message); if (nextRequest.status !== 200) { + params.task = `${openQuote}${nextRequest.body.task}${closeQuote}`; await respond(nextRequest.body.error, params); return; } @@ -159,7 +160,37 @@ async function processCommand(user, command, message, flags, extra) { params.task = `${openQuote}${nowRequest.body.task}${closeQuote}`; - respond(nowResponse, params); + await respond(nowResponse, params); + } else if (getCommand("logTaskCommands").includes(command)) { + let logRequest = await logTask(user, extra.userColor, message); + + if (logRequest.status !== 200) { + respond(logRequest.body.error, params); + return; + } + + // task logged + let loggedResponse = getResponse("taskLogged"); + + params.task = `${openQuote}${logRequest.body.task}${closeQuote}`; + + if (logRequest.body.tasksFailedToLog !== "") { + loggedResponse += ` | Failed to log task(s): ${openQuote}${logRequest.body.tasksFailedToLog}${closeQuote}`; + } + + await respond(loggedResponse, params); + + // let addRequest = await addTask(user, extra.userColor, message); + + // if (addRequest.status !== 200) { + // respond(addRequest.body.error, params); + // return; + // } + + // let addedResponse = getResponse("taskAdded"); + // params.task = `${openQuote}${addRequest.body.task}${closeQuote}`; + + // respond(addedResponse, params); } else if (getCommand("focusTaskCommands").includes(command)) { let focusRequest = await focusTask(user, message); @@ -173,6 +204,20 @@ async function processCommand(user, command, message, flags, extra) { params.task = `${openQuote}${focusRequest.body.focusedTask}${closeQuote}`; + await respond(focusedResponse, params); + } else if (getCommand("focusedTaskCommands").includes(command)) { + let focusedRequest = await focusedTask(user); + + if (focusedRequest.status !== 200) { + respond(focusedRequest.body.error, params); + return; + } + + // task focused + let focusedResponse = getResponse("focusedTask"); + + params.task = `${openQuote}${focusedRequest.body.focusedTask}${closeQuote}`; + await respond(focusedResponse, params); } else if (getCommand("unfocusTaskCommands").includes(command)) { let unfocusRequest = await unfocusTask(user); @@ -186,53 +231,65 @@ async function processCommand(user, command, message, flags, extra) { let unfocusedResponse = getResponse("clearFocused"); respond(unfocusedResponse, params); - } else if (getCommand("checkCommands").includes(command)) { - var checkingUser = user; + } else if ( + getCommand("checkCommands").includes(command) || + getCommand("checkCompletedCommands").includes(command) + ) { + let completed = getCommand("checkCompletedCommands").includes(command); + if (message === "") { - let checkRequest = await checkTasks(user); + let checkRequest = await checkTasks(user, completed); if (checkRequest.status !== 200) { // no tasks respond(checkRequest.body.error, params); return; } - // array of incomplete tasks - let incompleteTasks = checkRequest.body.reply.split(" | "); + // array of filtered tasks + let filteredTasks = checkRequest.body.reply.split(" | "); // respond every 10 tasks - for (let i = 0; i < incompleteTasks.length; i += 10) { - let tasks = incompleteTasks.slice(i, i + 10); + for (let i = 0; i < filteredTasks.length; i += 10) { + let tasks = filteredTasks.slice(i, i + 10); params.tasks = tasks .join(" | ") .replaceAll("{taskName}", getSetting("taskName")); + var checkingUser = user; params.checkingUser = checkingUser; - await respond(`{checkingUser} {tasks}`, params); + await respond(`{tasks}`, params); await sleep(200); } - - // return respond(checkRequest.body.reply, params); } else { let mentioned = message.replace("@", ""); - let checkRequest = await checkTasks(mentioned); + + if (mentioned.toLowerCase() === "id") { + await respond(getResponse("noTaskA"), params); + return; + } + + let checkRequest = await checkTasks(mentioned, completed); + + // array of incomplete tasks + let filteredTasks = checkRequest.body.reply.split(" | "); if (checkRequest.status !== 200) { // no tasks - respond(checkRequest.body.error, params); + respond(getResponse("noTaskA"), params); return; } - // array of incomplete tasks - let incompleteTasks = checkRequest.body.reply.split(" | "); // respond every 10 tasks - for (let i = 0; i < incompleteTasks.length; i += 10) { - let tasks = incompleteTasks.slice(i, i + 10); + for (let i = 0; i < filteredTasks.length; i += 10) { + let tasks = filteredTasks.slice(i, i + 10); params.tasks = tasks .join(" | ") .replaceAll("{taskName}", getSetting("taskName")); + var checkingUser = user; + params.checkingUser = checkingUser; await respond(`{checkingUser} {tasks}`, params); diff --git a/scripts/taskList.js b/scripts/taskList.js index e99d33d..7f1cfdd 100644 --- a/scripts/taskList.js +++ b/scripts/taskList.js @@ -839,7 +839,8 @@ async function addTask(username, userColor, task) { .split(taskSeparator.find((char) => task.includes(char))) .map((t) => t.trim()) : [task]; - const tasksFailedToAdd = []; + let tasksFailedToAdd = []; + let addedTaskIndices = []; // new array to store indices of added tasks tasksToAdd.forEach((t) => { if ( @@ -848,6 +849,9 @@ async function addTask(username, userColor, task) { !isInt(t) ) { tasks[username].todos.push({ text: t, done: false, focus: false }); + + let index = tasks[username].todos.length - 1; + addedTaskIndices.push({ index: index, task: t }); // store index of added task taskListMemory.totalTaskCount++; } else { tasksFailedToAdd.push(t); @@ -859,10 +863,14 @@ async function addTask(username, userColor, task) { await renderTaskListToDOM(); } + let displayedTasksAdded = addedTaskIndices.map((t) => { + return `(${t.index + 1}) ${t.task}`; + }); + return { status: 200, body: { - task: tasksToAdd.join(`${closeQuote}, ${openQuote}`), + task: displayedTasksAdded.join(`${closeQuote}, ${openQuote}`), tasksFailedToAdd: tasksFailedToAdd.join(`${closeQuote}, ${openQuote}`) || "", }, @@ -888,6 +896,14 @@ function createErrorResponse(errorMessage, errorType) { * @returns {Object} An object with a status and body. The status is 200 if successful, otherwise it indicates the error. The body contains the task text or an error message. */ async function nowTask(username, userColor, task) { + // if task is integer, return error + if (isInt(task)) { + return createErrorResponse( + `@${username} invalid input`, + getResponse("noTaskContent") + ); + } + const tasks = await DBHandler.get("tasks"); if (!tasks[username]) { tasks[username] = { @@ -941,6 +957,7 @@ async function nowTask(username, userColor, task) { }); tasks[username].todos.push({ text: task, done: false, focus: true }); + let index = tasks[username].todos.length; taskListMemory.totalTaskCount++; await DBHandler.set("tasks", tasks); @@ -951,7 +968,7 @@ async function nowTask(username, userColor, task) { return { status: 200, body: { - task: task, + task: `(${index}) ${task}`, }, }; } @@ -1288,11 +1305,18 @@ async function removeTask(username, task) { (t) => t.text.toLowerCase() === task.toLowerCase() ); + let focusedTask = tasks[username].todos.find((t) => t.focus); + if (index === -1) { if (isInt(task)) { index = parseInt(task) - 1; // ACTUAL INDEX } else if (incompleteTasks.length === 1) { index = tasks[username].todos.findIndex((t) => !t.done); + } else if (focusedTask) { + index = tasks[username].todos.findIndex( + (t) => + t.text.toLowerCase() === focusedTask.text.toLowerCase() + ); } else { return { status: 1, @@ -1458,10 +1482,14 @@ async function nextTask(username, task) { // if old task is the same as an existing task, return 1 if (textTaskExists) { - if (tasks[username].todos.find((t) => t.text === task)) { + let existingTextTask = tasks[username].todos.find( + (t) => t.text.toLowerCase() === task.toLowerCase() + ); + if (existingTextTask.done === true) { return { status: 1, body: { + task: task, "error message": `@${username} already has this task`, error: getResponse("duplicateTask"), }, @@ -1472,6 +1500,7 @@ async function nextTask(username, task) { return { status: 1, body: { + task: task, "error message": `@${username} already finished this task`, error: getResponse("taskAlreadyFinished"), }, @@ -1519,6 +1548,7 @@ async function nextTask(username, task) { return { status: 1, body: { + task: task, "error message": `@${username} invalid input`, error: getResponse("specifyTaskIndex"), }, @@ -1821,6 +1851,8 @@ async function markTaskDone(username, task) { task = tasks[username].todos[index].text; tasks[username].todos[index].done = true; + tasks[username].todos[index].focus = false; + await addDoneCount(username, 1); await DBHandler.set("tasks", tasks); @@ -2057,16 +2089,21 @@ async function editTask(username, message) { }; } - let focusedTask = tasks[username].todos.find((t) => t.focus); + let focusedTask = tasks[username].todos.find((t) => t.focus && !t.done); // example 'message': 1 new task - let index = parseInt(message.split(" ")[0]) - 1; // ACTUAL INDEX + let index = -1; + + if (/^\d+$/.test(message.split(" ")[0])) { + // The first part of the message is an integer + index = parseInt(message.split(" ")[0]) - 1; // ACTUAL INDEX + } let incompleteTaskCount = tasks[username].todos.filter( (t) => !t.done ).length; - if ((incompleteTaskCount === 1 || focusedTask) && index !== 0) { + if ((incompleteTaskCount === 1 || focusedTask) && index === -1) { noSpecifiedIndex = true; if (incompleteTaskCount === 1) { // find index of incomplete task @@ -2131,13 +2168,133 @@ async function editTask(username, message) { }; } +/** + * Retrieves the focused task for a given user. + * + * @async + * @function focusedTask + * @param {string} username - The username of the user whose focused task is to be retrieved. + * @returns {Promise} A promise that resolves to an object containing the status and body. + * The body contains either the focused task text or an error message. + * @throws Will throw an error if there is an issue with the database retrieval. + */ +async function focusedTask(username) { + const tasks = await DBHandler.get("tasks"); + + if (!tasks[username] || tasks[username].todos.length === 0) { + return { + status: 0, + body: { + "error message": `@${username} has no tasks`, + error: getResponse("noTask"), + }, + }; + } + + let focusedTask = tasks[username].todos.find((t) => t.focus); + + // Get index of focused task + let index = tasks[username].todos.findIndex((t) => t.focus); + + if (!focusedTask) { + return { + status: 0, + body: { + "error message": `@${username} has no focused task`, + error: getResponse("noFocusedTask"), + }, + }; + } + + return { + status: 200, + body: { + focusedTask: `(${index + 1}) ${focusedTask.text}`, + }, + }; +} + +async function logTask(username, userColor, task) { + // Basically a shortcut of add task -> mark task done + const tasks = await DBHandler.get("tasks"); + if (!tasks[username]) { + tasks[username] = { + todos: [], + done: [], + userColor: userColor, + }; + } + + const taskExists = tasks[username].todos.find( + (t) => t.text.toLowerCase() === task.toLowerCase() + ); + const taskIsInvalid = + !task || !task.trim() || task.toLowerCase() === "all" || isInt(task); + + if (taskExists) { + return createErrorResponse( + `@${username} already has this task`, + getResponse("duplicateTask") + ); + } + + if (taskIsInvalid) { + return createErrorResponse( + `@${username} empty task or reserved keyword used`, + getResponse("noTaskContent") + ); + } + + const tasksToAdd = taskSeparator.some((char) => task.includes(char)) + ? task + .split(taskSeparator.find((char) => task.includes(char))) + .map((t) => t.trim()) + : [task]; + let tasksFailedToAdd = []; + let addedTaskIndices = []; // new array to store indices of added tasks + + tasksToAdd.forEach((t) => { + if ( + t && + !tasks[username].todos.find((task) => task.text === t) && + !isInt(t) + ) { + tasks[username].todos.push({ text: t, done: true, focus: false }); + + let index = tasks[username].todos.length - 1; + addedTaskIndices.push({ index: index, task: t }); // store index of added task + taskListMemory.totalTaskCount++; + } else { + tasksFailedToAdd.push(t); + } + }); + + await DBHandler.set("tasks", tasks); + if (!scrolling) { + await renderTaskListToDOM(); + } + + let displayedTasksAdded = addedTaskIndices.map((t) => { + return `(${t.index + 1}) ${t.task}`; + }); + + return { + status: 200, + body: { + task: displayedTasksAdded.join(`${closeQuote}, ${openQuote}`), + tasksFailedToLog: + tasksFailedToAdd.join(`${closeQuote}, ${openQuote}`) || "", + }, + }; +} + /** * Checks the tasks of a given user. If the user has tasks, the function returns an object with a status of 200 and a formatted string of the user's tasks. If the user has no tasks, the function returns an object with a status indicating the error and an error message. * * @param {string} name - The name of the user whose tasks are to be checked. * @returns {Object} An object with a status and body. The status is 200 if the user has tasks, and the body contains a formatted string of the user's tasks. If the user has no tasks, the status indicates the error and the body contains an error message. */ -async function checkTasks(name) { +async function checkTasks(name, completed = false) { const tasks = await DBHandler.get("tasks"); // Go through keys of tasks, find match of lowercased username @@ -2166,24 +2323,26 @@ async function checkTasks(name) { } // filter completed tasks - const incompleteTasks = tasks[username].todos.filter((t) => !t.done); - const completedTasks = tasks[username].todos.filter((t) => t.done); + const filteredTasks = completed + ? tasks[username].todos.filter((t) => t.done) + : tasks[username].todos.filter((t) => !t.done); + + let label = completed ? "completed" : "incomplete"; // format incomplete tasks into string: 1. task 1 | 2. task 2 | 3. task 3... - let reply = `${name}'s incomplete {taskName}s (${incompleteTasks.length}) : `; + let reply = `${name}'s ${label} {taskName}s (${filteredTasks.length}) : `; let taskIndex; - for (let i = 0; i < incompleteTasks.length; i++) { + for (let i = 0; i < filteredTasks.length; i++) { // get index of task by task name taskIndex = tasks[username].todos.findIndex( (t) => - t.text.toLowerCase() === - incompleteTasks[i].text.toLowerCase() + t.text.toLowerCase() === filteredTasks[i].text.toLowerCase() ) + 1; - reply += `${taskIndex}. ${ - incompleteTasks[i].focus ? "(ongoing)" : "" - } ${incompleteTasks[i].text} | `; + reply += `${taskIndex}. ${filteredTasks[i].focus ? "(ongoing)" : ""} ${ + filteredTasks[i].text + } | `; } reply = reply.slice(0, -3); @@ -2371,7 +2530,6 @@ async function renderTaskListToDOM() { const tasks = await DBHandler.get("tasks"); const taskContainers = document.querySelectorAll(".task-container"); - console.log(getSetting("hideWhenNoTasks"), tasks); let hasTasks = false; @@ -2461,7 +2619,6 @@ async function renderTaskListToDOM() { // #main-container 0 opacity document.querySelector("#main-container").style.opacity = "0"; visible = false; - console.log("hiding"); } else if (getSetting("hideWhenNoTasks") && hasTasks && !visible) { // #main-container 1 opacity document.querySelector("#main-container").style.opacity = "1";