diff --git a/configs.js b/configs.js index 8c5437b..f732ae6 100644 --- a/configs.js +++ b/configs.js @@ -9,6 +9,8 @@ const configs = (function () { pointsName: "points", // string pointsPerTask: 10, // integer taskSeparator: [";", ","], // array of strings + openQuote: '"', // string + closeQuote: '"', // string testTasks: false, // true or false headerGoogleFont: true, // true: use google font, false: use system font taskGoogleFont: true, // true: use google font, false: use system font @@ -303,22 +305,22 @@ const configs = (function () { const responses = { // Responses related to task addition - taskAdded: 'The {taskName}(s) "{task}" has been added, {user}!', + taskAdded: "The {taskName}(s) {task} has been added, {user}!", noTaskAdded: "Looks like you already hit the limit of incomplete {taskName}s, {user}", 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} {message} up there {user}!", taskEdited: - '{taskName} "{originalTask}" has been edited to "{task}" successfully, {user}', + '{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}!', + "{taskName} {task} is now the task you are working on, {user}!", // Grouped by task progression responses taskNext: - "Good job on finishing the task '{oldTask}'! Now moving onto '{newTask}', {user}!", + "Good job on finishing the task {oldTask}! Now moving onto {newTask}, {user}!", nextNoContent: "Try using !next the-task-you-want-to-do-next {user}", taskNextFailed: "Unable to perform command with multiple incomplete {taskName}s, {user}!", @@ -327,7 +329,7 @@ const configs = (function () { clearedMyDone: "All of your completed {taskName}s have been cleared, {user}!", taskDeleted: - '{taskName}(s) "{task}" has been deleted successfully, {user}', + "{taskName}(s) {task} has been deleted successfully, {user}", specifyTaskIndex: "Try specifying the index of the incomplete {taskName}(s) {user}", clearTasksExceptBroadcaster: @@ -338,11 +340,11 @@ const configs = (function () { // Grouped by task completion responses taskFinished: - 'Good job on finishing the {taskName}(s) "{task}", {user}! You have earned {pointCount} {pointName} and completed {doneCount} {taskName}(s) so far!', + "Good job on finishing the {taskName}(s) {task}, {user}! You have earned {pointCount} {pointName} and completed {doneCount} {taskName}(s) so far!", allTasksFinished: "Good job on finishing all your {taskName}s, {user}! You have completed {doneCount} {taskName}(s) so far!", taskUnfinished: - '{taskName}(s) "{task}" has been unmarked as done, {user}!', + "{taskName}(s) {task} has been unmarked as done, {user}!", taskAlreadyFinished: "Looks like you already finished that {taskName} {user}", @@ -354,7 +356,7 @@ const configs = (function () { onlyOneFocus: "You can only focus one task at a time {user}!", specifyFocusTask: "Try specifying an incomplete {taskName} to focus {user}!", - taskFocused: 'Task "{task}" has been focused, {user}!', + taskFocused: "Task {task} has been focused, {user}!", // 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 151a7a9..bad1ea3 100644 --- a/scripts/defaultConfigs.js +++ b/scripts/defaultConfigs.js @@ -5,15 +5,17 @@ const defaultConfigs = (function () { enableLimit: false, // true or false limit: 10, // integer automaticDoneIndex: false, // true or false - Automatically assume first unfinished task is complete - taskName: "task", // string + taskName: "task", // string (singular) pointsName: "points", // string pointsPerTask: 10, // integer taskSeparator: [";", ","], // array of strings + openQuote: '"', // string + closeQuote: '"', // string testTasks: false, // true or false headerGoogleFont: true, // true: use google font, false: use system font taskGoogleFont: true, // true: use google font, false: use system font displayTaskCount: true, // true or false - enableTaskMaster: false, // true or false + enableTaskMaster: true, // true or false }; const animation = { @@ -303,31 +305,31 @@ const defaultConfigs = (function () { const responses = { // Responses related to task addition - taskAdded: 'The {taskName}(s) "{task}" has been added, {user}!', + taskAdded: "The {taskName}(s) {task} has been added, {user}!", noTaskAdded: "Looks like you already hit the limit of incomplete {taskName}s, {user}", 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 that {taskName} up there {user}!", taskEdited: - '{taskName} "{originalTask}" has been edited to "{task}" successfully, {user}', + '{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}!', + "{taskName} {task} is now the task you are working on, {user}!", // Grouped by task progression responses taskNext: - "Good job on finishing the task '{oldTask}'! Now moving onto '{newTask}', {user}!", + "Good job on finishing the task {oldTask}! Now moving onto {newTask}, {user}!", nextNoContent: "Try using !next the-task-you-want-to-do-next {user}", taskNextFailed: - "Unable to perform command with multiple incomplete tasks, {user}!", + "Unable to perform command with multiple incomplete {taskName}s, {user}!", // Grouped by task deletion responses clearedMyDone: "All of your completed {taskName}s have been cleared, {user}!", taskDeleted: - '{taskName}(s) "{task}" has been deleted successfully, {user}', + "{taskName}(s) {task} has been deleted successfully, {user}", specifyTaskIndex: "Try specifying the index of the incomplete {taskName}(s) {user}", clearTasksExceptBroadcaster: @@ -335,15 +337,14 @@ const defaultConfigs = (function () { clearedTasks: "All {taskName}s have been cleared, {user}!", adminDeleteTasks: "All of {mentioned}'s {taskName}s have been deleted", clearedAll: "All {taskName}s and points have been cleared, {user}!", - clearedDone: "All completed {taskName}s have been cleared, {user}!", // Grouped by task completion responses taskFinished: - 'Good job on finishing the {taskName}(s) "{task}", {user}! You have earned {pointCount} {pointName} and completed {doneCount} {taskName}(s) so far!', + "Good job on finishing the {taskName}(s) {task}, {user}! You have earned {pointCount} {pointName} and completed {doneCount} {taskName}(s) so far!", allTasksFinished: "Good job on finishing all your {taskName}s, {user}! You have completed {doneCount} {taskName}(s) so far!", taskUnfinished: - '{taskName}(s) "{task}" has been unmarked as done, {user}!', + "{taskName}(s) {task} has been unmarked as done, {user}!", taskAlreadyFinished: "Looks like you already finished that {taskName} {user}", @@ -355,7 +356,7 @@ const defaultConfigs = (function () { onlyOneFocus: "You can only focus one task at a time {user}!", specifyFocusTask: "Try specifying an incomplete {taskName} to focus {user}!", - taskFocused: 'Task "{task}" has been focused, {user}!', + taskFocused: "Task {task} has been focused, {user}!", // Grouped by task existence responses noTask: "Looks like you don't have a task up there {user}", @@ -379,7 +380,7 @@ const defaultConfigs = (function () { "Everyone has completed {doneCount} {taskName}(s) so far, {user}!", noCountAll: "Looks like no one has completed a {taskName} yet {user}", leaderboard: "Leaderboard: {leaderboard}", - setUserTaskCount: "{mentioned} now has {taskCount} {taskName}s!", + setUserTaskCount: "{mentioned} now has {taskCount} tasks!", // Grouped by taskmaster-related responses taskMaster: "{user} Task Master: {taskMaster} [{taskMasterCount}]", diff --git a/scripts/processCommand.js b/scripts/processCommand.js index 7e6e246..28c66d7 100644 --- a/scripts/processCommand.js +++ b/scripts/processCommand.js @@ -1,4 +1,6 @@ var commands = configs.commands; +var openQuote = getSetting("openQuote"); +var closeQuote = getSetting("closeQuote"); async function processCommand(user, command, message, flags, extra) { params = { @@ -17,7 +19,7 @@ async function processCommand(user, command, message, flags, extra) { } let addedResponse = getResponse("taskAdded"); - params.task = addRequest.body.task; + params.task = `${openQuote}${addRequest.body.task}${closeQuote}`; if (addRequest.body.tasksFailedToAdd !== "") { addedResponse += ` | Failed to add task(s): "${addRequest.body.tasksFailedToAdd}"`; @@ -35,8 +37,8 @@ async function processCommand(user, command, message, flags, extra) { let originalTask = editRequest.body.originalTask; let newTask = editRequest.body.newTask; - params.task = newTask; - params.originalTask = originalTask; + params.task = `${openQuote}${newTask}${closeQuote}`; + params.originalTask = `${openQuote}${originalTask}${closeQuote}`; respond(getResponse("taskEdited"), params); } else if (getCommand("deleteTaskCommands").includes(command)) { @@ -48,13 +50,13 @@ async function processCommand(user, command, message, flags, extra) { } let deletedResponse = getResponse("taskDeleted"); - let removedTasks = removeRequest.body.removedTasks; + let removedTasks = `${openQuote}${removeRequest.body.removedTasks}${closeQuote}`; let failedTasks = removeRequest.body.failedTasks; params.task = removedTasks; if (failedTasks !== "") { - deletedResponse += ` | Failed to delete task(s): "${failedTasks}"`; + deletedResponse += ` | Failed to delete task(s): ${openQuote}${failedTasks}${closeQuote}`; } respond(deletedResponse, params); @@ -69,6 +71,15 @@ async function processCommand(user, command, message, flags, extra) { let finishedAllResponse = getResponse("allTasksFinished"); params.doneCount = await completedTasksCount(user); + finishedAllResponse = finishedAllResponse.replaceAll( + "{pointName}", + getSetting("pointsName") + ); + + finishedAllResponse = finishedAllResponse.replaceAll( + "{taskName}", + getSetting("taskName") + ); respond(finishedAllResponse, params); return; @@ -82,7 +93,7 @@ async function processCommand(user, command, message, flags, extra) { } let finishedResponse = getResponse("taskFinished"); - params.task = finishRequest.body.markedTasks; + params.task = `${openQuote}${finishRequest.body.markedTasks}${closeQuote}`; params.doneCount = await completedTasksCount(user); params.pointCount = finishRequest.body.markedTasksCount * getSetting("pointsPerTask"); @@ -113,7 +124,7 @@ async function processCommand(user, command, message, flags, extra) { // task unfinished let unfinishedResponse = getResponse("taskUnfinished"); - params.task = unfinishRequest.body.markedTasks; + params.task = `${openQuote}${unfinishRequest.body.unmarkedTasks}${closeQuote}`; if (unfinishRequest.body.failedTasks !== "") { unfinishedResponse += ` | Failed to unfinish task(s): "${unfinishRequest.body.failedTasks}"`; @@ -124,15 +135,15 @@ async function processCommand(user, command, message, flags, extra) { let nextRequest = await nextTask(user, message); if (nextRequest.status !== 200) { - respond(nextRequest.body.error, params); + await respond(nextRequest.body.error, params); return; } // task next let nextResponse = getResponse("taskNext"); - params.oldTask = nextRequest.body.oldTask; - params.newTask = nextRequest.body.newTask; + params.oldTask = `${openQuote}${nextRequest.body.oldTask}${closeQuote}`; + params.newTask = `${openQuote}${nextRequest.body.newTask}${closeQuote}`; respond(nextResponse, params); } else if (getCommand("nowTaskCommands").includes(command)) { @@ -146,7 +157,7 @@ async function processCommand(user, command, message, flags, extra) { // task now let nowResponse = getResponse("nowTask"); - params.task = nowRequest.body.task; + params.task = `${openQuote}${nowRequest.body.task}${closeQuote}`; respond(nowResponse, params); } else if (getCommand("focusTaskCommands").includes(command)) { @@ -160,9 +171,9 @@ async function processCommand(user, command, message, flags, extra) { // task focused let focusedResponse = getResponse("taskFocused"); - params.task = focusRequest.body.focusedTask; + params.task = `${openQuote}${focusRequest.body.focusedTask}${closeQuote}`; - respond(focusedResponse, params); + await respond(focusedResponse, params); } else if (getCommand("unfocusTaskCommands").includes(command)) { let unfocusRequest = await unfocusTask(user); @@ -287,7 +298,7 @@ async function processCommand(user, command, message, flags, extra) { } await clearMemory(); - await clearAllExceptStreamer(configs.StreamerUsernames); + await clearAllExceptStreamer(auth.channel); respond(getResponse("clearTasksExceptBroadcaster"), params); } else if (getCommand("adminClearTasksCommands").includes(command)) { if (!isStreamer(flags)) { diff --git a/scripts/taskList.js b/scripts/taskList.js index f711b3b..6101b38 100644 --- a/scripts/taskList.js +++ b/scripts/taskList.js @@ -23,6 +23,8 @@ DB structure: const styles = configs.styles; const scrollSpeed = configs.animation.scrollSpeed; const responses = configs.responses; +var openQuote = getSetting("openQuote"); +var closeQuote = getSetting("closeQuote"); let scrolling = false; let primaryAnimation, secondaryAnimation; const taskSeparator = getSetting("taskSeparator"); @@ -809,7 +811,8 @@ async function addTask(username, userColor, task) { const taskExists = tasks[username].todos.find( (t) => t.text.toLowerCase() === task.toLowerCase() ); - const taskIsInvalid = !task || !task.trim() || task.toLowerCase() === "all"; + const taskIsInvalid = + !task || !task.trim() || task.toLowerCase() === "all" || isInt(task); const userHasReachedTaskLimit = incompleteTasksCount(username) >= getSetting("limit") && getSetting("enableLimit"); @@ -845,7 +848,11 @@ async function addTask(username, userColor, task) { const tasksFailedToAdd = []; tasksToAdd.forEach((t) => { - if (t && !tasks[username].todos.find((task) => task.text === t)) { + if ( + t && + !tasks[username].todos.find((task) => task.text === t) && + !isInt(t) + ) { tasks[username].todos.push({ text: t, done: false, focus: false }); taskListMemory.totalTaskCount++; } else { @@ -861,8 +868,9 @@ async function addTask(username, userColor, task) { return { status: 200, body: { - task: tasksToAdd.join('", "'), - tasksFailedToAdd: tasksFailedToAdd.join('", "') || "", + task: tasksToAdd.join(`${closeQuote}, ${openQuote}`), + tasksFailedToAdd: + tasksFailedToAdd.join(`${closeQuote}, ${openQuote}`) || "", }, }; } @@ -1105,6 +1113,14 @@ async function focusTask(username, task) { ); } + // if task is already completed, return 1 + if (tasks[username].todos[index].done) { + return createErrorResponse( + `@${username} task is already completed`, + responses.specifyTaskIndex + ); + } + // set all tasks to unfocused tasks[username].todos.forEach((task) => { task.focus = false; @@ -1267,8 +1283,10 @@ async function removeTask(username, task) { return { status: 200, body: { - removedTasks: tasksRemoved.join('", "'), - failedTasks: tasksFailedToRemove.join('", "'), + removedTasks: tasksRemoved.join(`${closeQuote}, ${openQuote}`), + failedTasks: tasksFailedToRemove.join( + `${closeQuote}, ${openQuote}` + ), }, }; } else { @@ -1318,6 +1336,82 @@ async function removeTask(username, task) { } } +async function getFocusedTask(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); + + if (!focusedTask) { + return { + status: 0, + body: { + "error message": `@${username} has no focused task`, + error: getResponse("noFocusedTask"), + }, + }; + } + + return { + status: 200, + body: { + focusedTask: focusedTask.text, + }, + }; +} + +async function checkIfTaskExists(username, task) { + 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 taskExistsBasedOnText = tasks[username].todos.find( + (t) => t.text.toLowerCase() === task.toLowerCase() + ) + ? true + : false; + + // check if task exists based on index, has to be incomplete + let taskExistsBasedOnIndex = false; + + if (tasks[username]) { + taskExistsBasedOnIndex = + isInt(task) && + parseInt(task) > 0 && + parseInt(task) <= tasks[username].todos.length; + } + + return { + text: taskExistsBasedOnText, + index: taskExistsBasedOnIndex, + }; +} + +/** + * This function marks the current task of a user as complete and adds a new task to the user's task list. + * + * @param {string} username - The name of the user who is completing the current task and adding a new one. + * @param {string} task - The text description of the new task to be added. + * + * @returns {Object} - An object containing the status of the operation and a body with either the old and new task or an error message. + */ /** * This function marks the current task of a user as complete and adds a new task to the user's task list. * @@ -1333,7 +1427,7 @@ async function nextTask(username, task) { status: 0, body: { "error message": `@${username} has no tasks`, - error: responses.noTask, + error: getResponse("noTask"), }, }; } @@ -1343,19 +1437,128 @@ async function nextTask(username, task) { status: 1, body: { "error message": `@${username} empty task`, - error: responses.nextNoContent, + error: getResponse("nextNoContent"), }, }; } const incompleteTasks = tasks[username].todos.filter((t) => !t.done); - if (incompleteTasks.length !== 1) { + const taskExists = await checkIfTaskExists(username, task); + const textTaskExists = taskExists.text; + const indexTaskExists = taskExists.index; + const focusedTaskExists = incompleteTasks.find((t) => t.focus); + + // check if there's a focused task + if (!focusedTaskExists && incompleteTasks.length > 1) { return { status: 0, body: { - "error message": `@${username} has more than one incomplete task`, - error: responses.taskNextFailed, + "error message": `@${username} does not have a focused task`, + error: getResponse("noFocusedTask"), + }, + }; + } else if (textTaskExists || indexTaskExists) { + let index = tasks[username].todos.findIndex((t) => t.focus); + let oldTask = tasks[username].todos[index].text; + + // if old task is the same as an existing task, return 1 + if (textTaskExists) { + if (tasks[username].todos.find((t) => t.text === task)) { + return { + status: 1, + body: { + "error message": `@${username} already has this task`, + error: getResponse("duplicateTask"), + }, + }; + } + } else { + if (tasks[username].todos[parseInt(task) - 1].done) { + return { + status: 1, + body: { + "error message": `@${username} already finished this task`, + error: getResponse("taskAlreadyFinished"), + }, + }; + } + } + + // mark focused task as complete then focus on the existing task + + // mark task as done + tasks[username].todos[index].done = true; + tasks[username].todos[index].focus = false; + + let newFocusTask = null; + + // focus on the new task + if (textTaskExists) { + let newIndex = tasks[username].todos.findIndex( + (t) => t.text.toLowerCase() === task.toLowerCase() + ); + newFocusTask = tasks[username].todos[newIndex]; + } else { + newFocusTask = tasks[username].todos[parseInt(task) - 1]; + } + + newFocusTask.focus = true; + + await addDoneCount(username, 1); + + await DBHandler.set("tasks", tasks); + + if (!scrolling) { + await renderTaskListToDOM(); + } + + return { + status: 200, + body: { + oldTask: oldTask, + newTask: newFocusTask.text, + }, + }; + } else if (focusedTaskExists) { + if (!indexTaskExists && isInt(task)) { + return { + status: 1, + body: { + "error message": `@${username} invalid input`, + error: getResponse("specifyTaskIndex"), + }, + }; + } + + // mark the focused task as complete, then add a new task "task" + + // find index of focused task + let index = tasks[username].todos.findIndex((t) => t.focus); + + let oldTask = tasks[username].todos[index].text; + + // mark task as done + tasks[username].todos[index].done = true; + tasks[username].todos[index].focus = false; + + // add new task + tasks[username].todos.push({ text: task, done: false, focus: true }); + taskListMemory.totalTaskCount++; + + await addDoneCount(username, 1); + + await DBHandler.set("tasks", tasks); + + if (!scrolling) { + await renderTaskListToDOM(); + } + + return { + status: 200, + body: { + oldTask: oldTask, + newTask: task, }, }; } else { @@ -1376,6 +1579,10 @@ async function nextTask(username, task) { await DBHandler.set("tasks", tasks); + if (!scrolling) { + await renderTaskListToDOM(); + } + return { status: 200, body: { @@ -1554,8 +1761,12 @@ async function markTaskDone(username, task) { return { status: 200, body: { - markedTasks: tasksMarkedComplete.join('", "'), - failedTasks: tasksFailedToComplete.join('", "'), + markedTasks: tasksMarkedComplete.join( + `${closeQuote}, ${openQuote}` + ), + failedTasks: tasksFailedToComplete.join( + `${closeQuote}, ${openQuote}` + ), markedTasksCount: tasksMarkedComplete.length, }, }; @@ -1756,8 +1967,12 @@ async function markTaskUndone(username, task) { return { status: 200, body: { - markedTasks: tasksMarkedUndone.join('", "'), - failedTasks: tasksFailedToMarkUndone.join('", "'), + markedTasks: tasksMarkedUndone.join( + `${closeQuote}, ${openQuote}` + ), + failedTasks: tasksFailedToMarkUndone.join( + `${closeQuote}, ${openQuote}` + ), }, }; } else { @@ -1919,17 +2134,27 @@ async function editTask(username, message) { /** * 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} username - The name of the user whose tasks are to be checked. + * @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(username) { +async function checkTasks(name) { const tasks = await DBHandler.get("tasks"); // Go through keys of tasks, find match of lowercased username - username = Object.keys(tasks).find( - (user) => user.toLowerCase() === username.toLowerCase() + let username = Object.keys(tasks).find( + (user) => user.toLowerCase() === name.toLowerCase() ); + if (!username) { + return { + status: 0, + body: { + "error message": `@${name} has no tasks`, + error: getResponse("noTask"), + }, + }; + } + if (!tasks[username]) { return { status: 0, @@ -1945,7 +2170,7 @@ async function checkTasks(username) { const completedTasks = tasks[username].todos.filter((t) => t.done); // format incomplete tasks into string: 1. task 1 | 2. task 2 | 3. task 3... - let reply = `incomplete {taskName}(s) (${incompleteTasks.length}) : `; + let reply = `${name}'s incomplete {taskName}s (${incompleteTasks.length}) : `; let taskIndex; for (let i = 0; i < incompleteTasks.length; i++) { // get index of task by task name @@ -2115,7 +2340,7 @@ async function clearAll() { * Clears all tasks from the local storage except for those belonging to specified streamer usernames. * After clearing, it also clears all done tasks, cancels any ongoing animation and re-renders the task list to the DOM. * - * @param {string[]} streamerUsernames - An array of streamer usernames whose tasks should not be cleared. + * @param {string[]} streamerUsername - An array of streamer usernames whose tasks should not be cleared. * @returns {Object} An object with a status property indicating the success of the operation (200 for success). */ async function clearAllExceptStreamer(streamerUsername) { @@ -2124,7 +2349,7 @@ async function clearAllExceptStreamer(streamerUsername) { for (const user in tasks) { if (user.toLowerCase() === "id") continue; - if (user.toLowerCase() !== auth.channel.toLowerCase()) { + if (user.toLowerCase() !== streamerUsername.toLowerCase()) { tasks[user].todos = []; } }