diff --git a/configs.js b/configs.js index 981fefc..1d00d91 100644 --- a/configs.js +++ b/configs.js @@ -4,7 +4,7 @@ const configs = (function () { const settings = { enableLimit: false, // true or false limit: 10, // integer - automaticDoneIndex: true, // true or false - Automatically assume first unfinished task is complete + automaticDoneIndex: false, // true or false - Automatically assume first unfinished task is complete pointsName: "points", // string pointsPerTask: 10, // integer taskSeparator: [";", ","], // array of strings @@ -286,14 +286,7 @@ const configs = (function () { adminResetUsersCount: ["!resetuserscount"], - adminClearAllCommands: [ - "!clearall", - "!allclear", - "!adminclearall", - "!adminallclear", - "!aclearall", - "!aclear", - ], + adminClearAllCommands: ["!clearallnoregrets"], adminSetBoardCount: ["!setboardcount", "!setallcount"], additionalCommands: { @@ -331,6 +324,7 @@ const configs = (function () { "All tasks have been cleared except for the streamer's, {user}!", clearedTasks: "All tasks have been cleared, {user}!", adminDeleteTasks: "All of {mentioned}'s tasks have been deleted", + clearedAll: "All tasks and points have been cleared, {user}!", // Grouped by task completion responses taskFinished: @@ -379,6 +373,11 @@ const configs = (function () { // Help response help: `{user} Use the following commands to help you out - !task !edit !remove !done. For mods, you can do !adel @user. More commmands here: https://github.com/liyunze-coding/Chat-Task-Tic-Overlay/blob/main/MultiTask.md/`, + + "chat-task-tic": + "Chat-Task-Tic widget is a free and open source task widget developed by @RythonDev ! https://github.com/liyunze-coding/chat-task-tic-overlay-infinity", + multitask: + "If you want to add the multi-task widget to your stream, you can buy it from ko-fi! https://ko-fi.com/s/94e7e8dc81", // to edit check task command, go to // scripts/taskList.js // function checkTasks(username) diff --git a/index.html b/index.html index cca9678..acaca72 100644 --- a/index.html +++ b/index.html @@ -1,32 +1,33 @@ + + + + + + - - - - - - + + + + + + - - - - + - + Task List + - Task List - - - -
-
-
!taskhelp
-
0/0
-
-
-
- +
+
-
- - - - \ No newline at end of file + + diff --git a/scripts/defaultConfigs.js b/scripts/defaultConfigs.js new file mode 100644 index 0000000..77766ed --- /dev/null +++ b/scripts/defaultConfigs.js @@ -0,0 +1,363 @@ +const defaultConfigs = (function () { + "use strict"; + + const StreamerUsernames = ["RythonDev"]; + + // settings + const settings = { + enableLimit: false, // true or false + limit: 10, // integer + automaticDoneIndex: false, // true or false - Automatically assume first unfinished task is complete + pointsName: "points", // string + pointsPerTask: 10, // integer + taskSeparator: [";", ",", "|"], // array of strings + testTasks: false, // true or false + headerGoogleFont: false, // true: use google font, false: use system font + taskGoogleFont: false, // true: use google font, false: use system font + displayTaskCount: true, // true or false + }; + + // styles + const styles = { + // task list + taskListBackgroundColor: "#000000", // hex only + taskListBackgroundOpacity: 0, // 0.0 - 1.0 + + taskListBorderWidth: "0px", // px + taskListBorderColor: "#ffffff", // hex or name + taskListBorderRadius: "0px", // px + + taskListHorizontalPadding: "0px", // px + taskListVerticalPadding: "0px", // px + + // header + headerHorizontalPadding: "10px", // px + headerVerticalPadding: "20px", // px + + headerBackgroundColor: "#000", // hex only + headerBackgroundOpacity: 0, // 0.0 - 1.0 + + headerBorderWidth: "0px", // px + headerBorderColor: "#ffffff", // hex or name + headerBorderRadius: "3px", // px + + headerFontFamily: "monospace", // font name + headerFontSize: "2rem", // px + headerFontWeight: "bold", // normal or bold or number + headerFontColor: "#fff", // hex or name + + taskCountMarginRight: "175px", // px + + // body + bodyBackgroundColor: "#fff", // hex only + bodyBackgroundOpacity: 0, // 0.0 - 1.0 + + bodyBorderWidth: "0px", // px + bodyBorderColor: "#fff", // hex or name + bodyBorderRadius: "5px", // px + + // task + lineHeight: 1.5, // number + usernameFontWeight: "bold", // normal or bold or number + usernameColor: "lime", // hex or name or "" for twitch user color + usernameFontSize: "1.5rem", // px + + taskWidth: "100%", + + taskBackgroundColor: "#000", // hex only + taskBackgroundOpacity: 0, // 0.0 - 1.0 + + taskFontFamily: "monospace", // font name + taskFontSize: "1.5rem", // px + taskFontColor: "#fff", // hex or name + + taskBorderColor: "#fff", // hex or name + taskBorderWidth: "0px", // px + taskBorderRadius: "10px", // px + + taskMarginBottom: "10px", // px + taskHorizontalPadding: "20px", // px + taskVerticalPadding: "10px", // px + + // done task + doneTaskFontColor: "#bbb", // hex or name + + // focus task + focusTaskBackgroundColor: "#fff", // hex only + focusTaskBackgroundOpacity: 1, // 0.0 - 1.0 + focusTaskBorderRadius: "5px", // px + + focusTaskFontColor: "#000", // hex or name + + focusTaskHorizontalPadding: "7px", // px + focusTaskVerticalPadding: "0px", // px + }; + + // animation + const animation = { + scrollSpeed: 50, // px + gapBetweenScrolls: 50, // px + titles: [ + "!multitask", + "!taskhelp", + "!task", + "!edit", + "!remove", + "!done", + "!undone", + "!botcred", + "!count", + "!points", + "!boardcount", + ], + }; + + const commands = { + addTaskCommands: [ + "!taska", + "!taskadd", + "!atask", + "!addtask", + "!task", + "!add", + "!todo", + "!a", + ], + editTaskCommands: [ + "!taske", + "!taskedit", + "!etask", + "!edittask", + "!edit", + "!e", + ], + deleteTaskCommands: [ + "!taskdel", + "!taskdelete", + "!deltask", + "!deletetask", + "!taskr", + "!taskremove", + "!rtask", + "!removetask", + "!remove", + "!delete", + "!r", + ], + finishTaskCommands: [ + "!taskf", + "!taskfinish", + "!ftask", + "!finishtask", + "!taskd", + "!taskdone", + "!donetask", + "!dtask", + "!finish", + "!done", + "!finished", + "!f", + ], + unfinishTaskCommands: [ + "!tasku", + "!taskunfinish", + "!utask", + "!unfinishtask", + "!taskud", + "!taskundone", + "!undonetask", + "!undone", + "!unfinish", + "!unfinished", + "!u", + ], + nextTaskCommands: [ + "!taskn", + "!tasknext", + "!ntask", + "!nexttask", + "!next", + "!n", + ], + focusTaskCommands: ["!focus", "!taskfocus", "!focustask"], + unfocusTaskCommands: ["!unfocus", "!taskunfocus", "!unfocustask"], + nowTaskCommands: ["!now", "!tasknow", "!nowtask"], + helpCommands: [ + "!taskh", + "!taskhelp", + "!htask", + "!helptask", + "!tasks", + "!help", + ], + checkCommands: [ + "!taskc", + "!taskcheck", + "!ctask", + "!checktask", + "!mytask", + "!check", + "!mytasks", + ], + listCommands: ["!taskl", "!tasklist", "!listtasks", "!list"], + clearMyDoneCommands: ["!clearmydone"], + checkCountCommands: ["!taskcount", "!count", "!checkcount", "!mycount"], + checkAllCountCommands: [ + "!taskallcount", + "!allcount", + "!checkallcount", + "!boardcount", + "!checkboardcount", + "!taskboardcount", + ], + checkMyPointsCommands: [ + "!taskpoints", + "!points", + "!mypoints", + "!checkpoints", + ], + syncCountPointsCommands: ["!syncpoints", "!synccount"], + addPointsCommands: ["!addpoints", "!givepoints", "!adduserpoints"], + reducePointsCommands: ["!reducepoints", "!takepoints"], + setUserPointsCommands: [ + "!setuserpoints", + "!setpoints", + "!setpointsuser", + "!setuserpoint", + ], + setUserTaskCountCommands: [ + "!setusertaskcount", + "!settaskcount", + "!settaskcountuser", + "!setusertask", + ], + leaderboardCommands: ["!leaderboard", "!lb", "!top", "!toppoints"], + adminDeleteCommands: ["!taskadel", "!adel", "!adelete", "!admindelete"], + adminClearNotStreamerCommands: [ + "!clearnotstreamer", + "!aclearnotstreamer", + "!adminclearnotstreamer", + "!clearns", + ], + adminClearDoneCommands: [ + "!acleardone", + "!admincleardone", + "!cleardone", + ], + adminClearAllCommands: ["!clearallnoregrets"], + adminClearTasksCommands: ["!cleartasks", "!clearalltasks", "!clearall"], + adminSetBoardCount: ["!setboardcount", "!setallcount"], + adminResetBoardCount: ["!resetboardcount", "!resetallcount"], + adminResetUsersCount: ["!resetuserscount"], + adminBackupCommands: ["!backup", "!backupdata", "!setbackup"], + adminLoadBackupCommands: [ + "!loadbackup", + "!loaddata", + "!loadbackupdata", + ], + additionalCommands: { + "!botcred": + "{user} Ryan is the creator of this bot! You can find him on https://github.com/liyunze-coding or https://www.twitch.tv/RythonDev", + "!multitask": + "If you want to add the multi-task widget to your stream, you can buy it from ko-fi! https://ko-fi.com/s/94e7e8dc81", + }, + }; + + const responses = { + // Grouped by task-related responses + // Grouped by task creation and modification responses + taskAdded: 'The task(s) "{task}" has been added, {user}!', + noTaskAdded: + "Looks like you already hit the limit of incomplete tasks, {user}", + noTaskContent: "Try using !task the-task-you-are-working-on {user}", + duplicateTask: + "Looks like you already have the task '{message}' up there {user}!", + taskEdited: + 'Task "{originalTask}" has been edited to "{task}" successfully, {user}', + noTaskEdit: "Try doing !edit [index] [new task] {user}", + nowTask: 'Task "{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}!", + nextNoContent: "Try using !next the-task-you-want-to-do-next {user}", + taskNextFailed: + "Unable to perform command with multiple incomplete tasks, {user}!", + + // Grouped by task deletion responses + clearedMyDone: "All of your completed tasks have been cleared, {user}!", + taskDeleted: 'Task(s) "{task}" has been deleted successfully, {user}', + specifyTaskIndex: + "Try specifying the index of the incomplete task(s) {user}", + clearTasksExceptBroadcaster: + "All tasks have been cleared except for the streamer's, {user}!", + adminDeleteTasks: "All of {mentioned}'s tasks have been deleted", + clearedDone: "All completed tasks have been cleared, {user}!", + clearedAll: "All tasks and counts have been cleared, {user}!", + + // Grouped by task completion responses + taskFinished: + 'Good job on finishing the task(s) "{task}", {user}! You have earned {pointCount} {pointName} and completed {doneCount} task(s) so far!', + allTasksFinished: + "Good job on finishing all your tasks, {user}! You have completed {doneCount} task(s) so far!", + taskUnfinished: 'Task(s) "{task}" has been unmarked as done, {user}!', + taskAlreadyFinished: "Looks like you already finished that task {user}", + + // Grouped by task focus responses + clearFocused: "Task has been unfocused, {user}!", + noFocusedTask: "Looks like you don't have a focused task {user}!", + alreadyFocusedTask: + "Looks like you already have that task set to focus {user}!", + onlyOneFocus: "You can only focus one task at a time {user}!", + specifyFocusTask: "Try specifying an incomplete task to focus {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}", + noTaskA: "Looks like there is no task from that user there {user}", + + // Grouped by point-related responses + checkMyPoints: "You have {pointCount} {pointName}, {user}!", + checkUserPoints: "{mentioned} has {pointCount} {pointName}, {user}!", + syncCountPoints: "Points have been synced with count, {user}!", + addPoints: "{mentioned} has been given {pointCount} {pointName}!", + reducePoints: "{mentioned} has lost {pointCount} {pointName}!", + setUserPoints: "{mentioned} now has {pointCount} {pointName}!", + specifyPoints: "Try specifying a number of points {user}", + + // Grouped by count-related responses + checkYourCount: + "You have completed {doneCount} task(s) so far, {user}!", + checkUserCount: + "{mentioned} has completed {doneCount} task(s) so far, {user}!", + checkAllCount: + "Everyone has completed {doneCount} task(s) so far, {user}!", + noCountAll: "Looks like no one has completed a task yet {user}", + leaderboard: "Leaderboard: {leaderboard}", + setUserTaskCount: "{mentioned} now has {taskCount} tasks!", + + // backup-related responses + backupStorage: "Data has been backed up, {user}!", + loadBackup: "Data has been loaded, {user}!", + + // Grouped by permission-related responses + notMod: "Permission denied, {user}; Mods only", + notStreamer: "Permission denied, {user}; Streamer only", + + // Grouped by help-related responses + help: `{user} Use the following commands to help you out - !task !edit !remove !done. For mods, you can do !adel @user. More commmands here: https://github.com/liyunze-coding/Chat-Task-Tic-Overlay/blob/main/MultiTask.md/`, + + // to edit check task command, go to + // scripts/taskList.js + // function checkTasks(username) + }; + + return { + StreamerUsernames, + settings, + styles, + animation, + commands, + responses, + }; +})(); diff --git a/scripts/functions.js b/scripts/functions.js new file mode 100644 index 0000000..6334a82 --- /dev/null +++ b/scripts/functions.js @@ -0,0 +1,12 @@ +function getResponse(responseName) { + return ( + configs.responses[responseName] ?? + defaultConfigs.responses[responseName] + ); +} + +function getCommand(commandName) { + return ( + configs.commands[commandName] ?? defaultConfigs.commands[commandName] + ); +} diff --git a/scripts/index.js b/scripts/index.js index e353d9e..1094d7f 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -18,7 +18,7 @@ function isStreamer(flags) { return flags.broadcaster; } -function processCommand(user, command, message, flags, extra) { +async function processCommand(user, command, message, flags, extra) { params = { user: user, message: message, @@ -26,14 +26,14 @@ function processCommand(user, command, message, flags, extra) { }; if (commands.addTaskCommands.includes(command)) { - let addRequest = addTask(user, extra.userColor, message); + let addRequest = await addTask(user, extra.userColor, message); if (addRequest.status !== 200) { respond(addRequest.body.error, params); return; } - let addedResponse = responses.taskAdded; + let addedResponse = getResponse("taskAdded"); params.task = addRequest.body.task; if (addRequest.body.tasksFailedToAdd !== "") { @@ -42,7 +42,7 @@ function processCommand(user, command, message, flags, extra) { respond(addedResponse, params); } else if (commands.editTaskCommands.includes(command)) { - let editRequest = editTask(user, message); + let editRequest = await editTask(user, message); if (editRequest.status !== 200) { respond(editRequest.body.error, params); @@ -55,15 +55,15 @@ function processCommand(user, command, message, flags, extra) { params.task = newTask; params.originalTask = originalTask; - respond(responses.taskEdited, params); + respond(getResponse("taskEdited"), params); } else if (commands.deleteTaskCommands.includes(command)) { - let removeRequest = removeTask(user, message); + let removeRequest = await removeTask(user, message); if (removeRequest.status !== 200) { respond(removeRequest.body.error, params); return; } - let deletedResponse = responses.taskDeleted; + let deletedResponse = getResponse("taskDeleted"); let removedTasks = removeRequest.body.removedTasks; let failedTasks = removeRequest.body.failedTasks; @@ -77,31 +77,31 @@ function processCommand(user, command, message, flags, extra) { respond(deletedResponse, params); } else if (commands.finishTaskCommands.includes(command)) { if (message === "all") { - let finishAllRequest = markAllTasksAsDone(user); + let finishAllRequest = await markAllTasksAsDone(user); if (finishAllRequest.status !== 200) { respond(finishAllRequest.body.error, params); return; } - let finishedAllResponse = responses.allTasksFinished; - params.doneCount = completedTasksCount(user); + let finishedAllResponse = getResponse("allTasksFinished"); + params.doneCount = await completedTasksCount(user); respond(finishedAllResponse, params); return; } - let finishRequest = markTaskDone(user, message); + let finishRequest = await markTaskDone(user, message); if (finishRequest.status !== 200) { respond(finishRequest.body.error, params); return; } - let finishedResponse = responses.taskFinished; + let finishedResponse = getResponse("taskFinished"); params.task = finishRequest.body.markedTasks; - params.doneCount = completedTasksCount(user); + params.doneCount = await completedTasksCount(user); params.pointCount = finishRequest.body.markedTasksCount * configs.settings.pointsPerTask; @@ -112,7 +112,7 @@ function processCommand(user, command, message, flags, extra) { respond(finishedResponse, params); } else if (commands.unfinishTaskCommands.includes(command)) { - let unfinishRequest = markTaskUndone(user, message); + let unfinishRequest = await markTaskUndone(user, message); if (unfinishRequest.status !== 200) { respond(unfinishRequest.body.error, params); @@ -120,7 +120,7 @@ function processCommand(user, command, message, flags, extra) { } // task unfinished - let unfinishedResponse = responses.taskUnfinished; + let unfinishedResponse = getResponse("taskUnfinished"); params.task = unfinishRequest.body.markedTasks; @@ -130,7 +130,7 @@ function processCommand(user, command, message, flags, extra) { respond(unfinishedResponse, params); } else if (commands.nextTaskCommands.includes(command)) { - let nextRequest = nextTask(user, message); + let nextRequest = await nextTask(user, message); if (nextRequest.status !== 200) { respond(nextRequest.body.error, params); @@ -138,14 +138,14 @@ function processCommand(user, command, message, flags, extra) { } // task next - let nextResponse = responses.taskNext; + let nextResponse = getResponse("taskNext"); params.oldTask = nextRequest.body.oldTask; params.newTask = nextRequest.body.newTask; respond(nextResponse, params); } else if (commands.nowTaskCommands.includes(command)) { - let nowRequest = nowTask(user, extra.userColor, message); + let nowRequest = await nowTask(user, extra.userColor, message); if (nowRequest.status !== 200) { respond(nowRequest.body.error, params); @@ -153,13 +153,13 @@ function processCommand(user, command, message, flags, extra) { } // task now - let nowResponse = responses.nowTask; + let nowResponse = getResponse("nowTask"); params.task = nowRequest.body.task; respond(nowResponse, params); } else if (commands.focusTaskCommands.includes(command)) { - let focusRequest = focusTask(user, message); + let focusRequest = await focusTask(user, message); if (focusRequest.status !== 200) { respond(focusRequest.body.error, params); @@ -167,13 +167,13 @@ function processCommand(user, command, message, flags, extra) { } // task focused - let focusedResponse = responses.taskFocused; + let focusedResponse = getResponse("taskFocused"); params.task = focusRequest.body.focusedTask; respond(focusedResponse, params); } else if (commands.unfocusTaskCommands.includes(command)) { - let unfocusRequest = unfocusTask(user); + let unfocusRequest = await unfocusTask(user); if (unfocusRequest.status !== 200) { respond(unfocusRequest.body.error, params); @@ -181,12 +181,12 @@ function processCommand(user, command, message, flags, extra) { } // task unfocused - let unfocusedResponse = responses.clearFocused; + let unfocusedResponse = getResponse("clearFocused"); respond(unfocusedResponse, params); } else if (commands.checkCommands.includes(command)) { if (message === "") { - let checkRequest = checkTasks(user); + let checkRequest = await checkTasks(user); if (checkRequest.status !== 200) { // no tasks respond(checkRequest.body.error, params); @@ -196,7 +196,7 @@ function processCommand(user, command, message, flags, extra) { return respond(checkRequest.body.reply, params); } else { let mentioned = message.replace("@", ""); - let checkRequest = checkTasks(mentioned); + let checkRequest = await checkTasks(mentioned); if (checkRequest.status !== 200) { // no tasks @@ -208,74 +208,76 @@ function processCommand(user, command, message, flags, extra) { } } else if (commands.adminDeleteCommands.includes(command)) { if (!isMod(flags)) { - respond(responses.notMod, params); + respond(getResponse("notMod"), params); return; } let mentioned = message.replace("@", ""); if (mentioned === "") { - respond(responses.specifyUser, params); + respond(getResponse("specifyUser"), params); return; } - let clearUserTaskResponse = clearUserTasks(mentioned); + let clearUserTaskResponse = await clearUserTasks(mentioned); params.mentioned = mentioned; if (clearUserTaskResponse.status === 0) { // no tasks - respond(responses.noTaskA, params); + respond(getResponse("noTaskA"), params); return; } - respond(responses.adminDeleteTasks, params); + respond(getResponse("adminDeleteTasks"), params); return; } else if (commands.adminClearDoneCommands.includes(command)) { if (!isStreamer(flags)) { - respond(responses.notStreamer, params); + respond(getResponse("notStreamer"), params); return; } clearAllDoneTasks(); - respond(responses.clearedDone, params); + respond(getResponse("clearedDone"), params); return; } else if (commands.adminClearAllCommands.includes(command)) { if (!isStreamer(flags)) { - respond(responses.notStreamer, params); + respond(getResponse("notStreamer"), params); return; } clearAll(); - respond(responses.clearedAll, params); + respond(getResponse("clearedAll"), params); return; } else if (commands.clearMyDoneCommands.includes(command)) { - let clearOwnDoneResponse = clearOwnDoneTasks(user); + let clearOwnDoneResponse = await clearOwnDoneTasks(user); if (clearOwnDoneResponse.status !== 200) { // no tasks - respond(responses.noTask, params); + respond(getResponse("noTask"), params); return; } - respond(responses.clearedMyDone, params); + respond(getResponse("clearedMyDone"), params); } else if (commands.adminClearNotStreamerCommands.includes(command)) { if (!isStreamer(flags)) { - respond(responses.notStreamer, params); + respond(getResponse("notStreamer"), params); return; } + await clearMemory(); - clearAllExceptStreamer(auth.channel); - respond(responses.clearTasksExceptBroadcaster, params); + await clearAllExceptStreamer(configs.StreamerUsernames); + respond(getResponse("clearTasksExceptBroadcaster"), params); } else if (commands.adminClearTasksCommands.includes(command)) { if (!isStreamer(flags)) { - respond(responses.notStreamer, params); + respond(getResponse("notStreamer"), params); return; } + await clearMemory(); - clearAllTasks(); - respond(responses.clearedTasks, params); + await clearAllTasks(); + respond(getResponse("clearedTasks"), params); } else if (commands.listCommands.includes(command)) { - let listTaskResponse = listTasks(user); + let listTaskResponse = await listTasks(user); if (listTaskResponse.status !== 200) { // no tasks @@ -286,187 +288,187 @@ function processCommand(user, command, message, flags, extra) { respond(listTaskResponse.body.reply, params); } else if (commands.checkCountCommands.includes(command)) { if (message === "") { - let count = completedTasksCount(user); + let count = await completedTasksCount(user); params.doneCount = count; - return respond(responses.checkYourCount, params); + return respond(getResponse("checkYourCount"), params); } else { let mentioned = message.replace("@", ""); - let count = completedTasksCount(mentioned); + let count = await completedTasksCount(mentioned); params.doneCount = count; params.mentioned = mentioned; - return respond(responses.checkUserCount, params); + return respond(getResponse("checkUserCount"), params); } } else if (commands.checkAllCountCommands.includes(command)) { let count = getBoardTotalTaskCount(); if (count === 0) { - respond(responses.noCountAll, params); + respond(getResponse("noCountAll"), params); return; } params.doneCount = count; - respond(responses.checkAllCount, params); + respond(getResponse("checkAllCount"), params); } else if (commands.checkMyPointsCommands.includes(command)) { if (message === "") { - let points = getUserPoints(user); + let points = await getUserPoints(user); params.pointCount = points; - return respond(responses.checkMyPoints, params); + return respond(getResponse("checkMyPoints"), params); } else { let mentioned = message.replace("@", ""); - let points = getUserPoints(mentioned); + let points = await getUserPoints(mentioned); params.pointCount = points; params.mentioned = mentioned; - return respond(responses.checkUserPoints, params); + return respond(getResponse("checkUserPoints"), params); } } else if (commands.syncCountPointsCommands.includes(command)) { if (!isStreamer(flags)) { - respond(responses.notStreamer, params); + respond(getResponse("notStreamer"), params); return; } - syncPointsToCount(); - respond(responses.syncCountPoints, params); + await syncPointsToCount(); + respond(getResponse("syncCountPoints"), params); } else if (commands.addPointsCommands.includes(command)) { // !addpoints @user 100 if (!isMod(flags)) { - respond(responses.notMod, params); + respond(getResponse("notMod"), params); return; } let mentioned = message.split(" ")[0].replace("@", ""); if (mentioned === "") { - respond(responses.specifyUser, params); + respond(getResponse("specifyUser"), params); return; } let points = message.split(" ")[1]; if (points === undefined) { - respond(responses.specifyPoints, params); + respond(getResponse("specifyPoints"), params); return; } if (!/^\d+$/.test(points)) { - respond(responses.invalidNumber, params); + respond(getResponse("invalidNumber"), params); return; } points = parseInt(points); - addPoints(mentioned, points); + await addPoints(mentioned, points); params.mentioned = mentioned; params.pointCount = points; - respond(responses.addPoints, params); + respond(getResponse("addPoints"), params); } else if (commands.reducePointsCommands.includes(command)) { if (!isMod(flags)) { - respond(responses.notMod, params); + respond(getResponse("notMod"), params); return; } let mentioned = message.split(" ")[0].replace("@", ""); if (mentioned === "") { - respond(responses.specifyUser, params); + respond(getResponse("specifyUser"), params); return; } let points = message.split(" ")[1]; - if (points === undefined) { - respond(responses.specifyPoints, params); + if (points == undefined) { + respond(getResponse("specifyPoints"), params); return; } if (!/^\d+$/.test(points)) { - respond(responses.invalidNumber, params); + respond(getResponse("invalidNumber"), params); return; } - reducePoints(mentioned, points); + await reducePoints(mentioned, points); params.mentioned = mentioned; params.pointCount = points; - respond(responses.reducePoints, params); + respond(getResponse("reducePoints"), params); } else if (commands.setUserPointsCommands.includes(command)) { if (!isMod(flags)) { - respond(responses.notMod, params); + respond(getResponse("notMod"), params); return; } let mentioned = message.split(" ")[0].replace("@", ""); if (mentioned === "") { - respond(responses.specifyUser, params); + respond(getResponse("specifyUser"), params); return; } let points = message.split(" ")[1]; if (points === undefined) { - respond(responses.specifyPoints, params); + respond(getResponse("specifyPoints"), params); return; } if (!/^\d+$/.test(points)) { - respond(responses.invalidNumber, params); + respond(getResponse("invalidNumber"), params); return; } points = parseInt(points); - setUserPoints(mentioned, points); + await setUserPoints(mentioned, points); params.mentioned = mentioned; params.pointCount = points; - respond(responses.setUserPoints, params); + respond(getResponse("setUserPoints"), params); } else if (commands.setUserTaskCountCommands.includes(commands)) { if (!isMod(flags)) { - respond(responses.notMod, params); + respond(getResponse("notMod"), params); return; } let mentioned = message.split(" ")[0].replace("@", ""); if (mentioned === "") { - respond(responses.specifyUser, params); + respond(getResponse("specifyUser"), params); return; } let count = message.split(" ")[1]; if (count === undefined) { - respond(responses.specifyCount, params); + respond(getResponse("specifyCount"), params); return; } if (!/^\d+$/.test(count)) { - respond(responses.invalidNumber, params); + respond(getResponse("invalidNumber"), params); return; } points = parseInt(points); - setUserTaskCount(mentioned, count); + await setUserTaskCount(mentioned, count); params.mentioned = mentioned; params.pointCount = count; - respond(responses.setUserTaskCount, params); + respond(getResponse("setUserTaskCount"), params); } else if (commands.leaderboardCommands.includes(command)) { let leaderboardResponse = leaderboardTaskCount(5); @@ -480,39 +482,55 @@ function processCommand(user, command, message, flags, extra) { respond(leaderboard, params); } else if (commands.adminSetBoardCount.includes(command)) { if (!isStreamer(flags)) { - respond(responses.notStreamer, params); + respond(getResponse("notStreamer"), params); return; } // check if message is a number using regex if (!/^\d+$/.test(message)) { - respond(responses.invalidNumber, params); + respond(getResponse("invalidNumber"), params); return; } params.count = message; - setTotalCompleteCount(message); + await setTotalCompleteCount(message); - respond(responses.setBoardCount, params); + respond(getResponse("setBoardCount"), params); } else if (commands.adminResetBoardCount.includes(command)) { if (!isStreamer(flags)) { - respond(responses.notStreamer, params); + respond(getResponse("notStreamer"), params); return; } - resetBoardCount(); - respond(responses.clearedBoardCount, params); + await resetBoardCount(); + respond(getResponse("clearedBoardCount"), params); } else if (commands.adminResetUsersCount.includes(command)) { if (!isStreamer(flags)) { - respond(responses.notStreamer, params); + respond(getResponse("notStreamer"), params); return; } - resetUsersCount(); - respond(responses.clearedUsersCount, params); + await resetUsersCount(); + respond(getResponse("clearedUsersCount"), params); } else if (commands.helpCommands.includes(command)) { - respond(responses.help, params); + respond(getResponse("help"), params); + } else if (command === "!clearlocalstorage") { + if (!isStreamer(flags)) { + respond(getResponse("notStreamer"), params); + return; + } + + clearLocalStorage(); + + respond(getResponse("clearLocalStorage"), params); + } else if (["!transferdata"].includes(command)) { + if (!isStreamer(flags)) { + respond(getResponse("notStreamer"), params); + return; + } + + await transferLocalStorageToIndexedDB(); } else if (commands.additionalCommands[command]) { respond(commands.additionalCommands[command], params); } else { diff --git a/scripts/taskList.js b/scripts/taskList.js index 3d67529..67b6936 100644 --- a/scripts/taskList.js +++ b/scripts/taskList.js @@ -22,6 +22,118 @@ let scrolling = false; let primaryAnimation, secondaryAnimation; const taskSeparator = configs.settings.taskSeparator; +const DBHandler = { + db: null, + open: function () { + return new Promise((resolve, reject) => { + let request = indexedDB.open("tasksDB", 1); + + request.onupgradeneeded = function (e) { + let db = e.target.result; + if (!db.objectStoreNames.contains("tasks")) { + db.createObjectStore("tasks", { autoIncrement: true }); + } + }; + + request.onsuccess = function (e) { + DBHandler.db = e.target.result; + resolve(); + }; + + request.onerror = function (e) { + console.log("Error opening db", e); + reject(e); + }; + }); + }, + get: function (key) { + return new Promise((resolve, reject) => { + let transaction = DBHandler.db.transaction(["tasks"], "readonly"); + let store = transaction.objectStore("tasks"); + let request = store.get(key); + + request.onsuccess = function (e) { + resolve(e.target.result); + }; + + request.onerror = function (e) { + reject("Error getting item", e); + }; + }); + }, + set: function (key, value) { + return new Promise((resolve, reject) => { + let transaction = DBHandler.db.transaction(["tasks"], "readwrite"); + let store = transaction.objectStore("tasks"); + let request = store.put(value, key); + + request.onsuccess = function (e) { + resolve(e.target.result); + }; + + request.onerror = function (e) { + reject("Error setting item", e); + }; + }); + }, + remove: function (key) { + return new Promise((resolve, reject) => { + let transaction = DBHandler.db.transaction(["tasks"], "readwrite"); + let store = transaction.objectStore("tasks"); + let request = store.delete(key); + + request.onsuccess = function (e) { + resolve(e.target.result); + }; + + request.onerror = function (e) { + reject("Error removing item", e); + }; + }); + }, + clear: function () { + return new Promise((resolve, reject) => { + let transaction = DBHandler.db.transaction(["tasks"], "readwrite"); + let store = transaction.objectStore("tasks"); + let request = store.clear(); + + request.onsuccess = function (e) { + resolve(e.target.result); + }; + + request.onerror = function (e) { + reject("Error clearing store", e); + }; + }); + }, +}; + +async function transferLocalStorageToIndexedDB() { + // Open the database + await DBHandler.open(); + + // Get all keys in localStorage + let keys = Object.keys(localStorage); + + // For each key, get the value from localStorage, parse it as JSON, and store it in IndexedDB + for (let key of keys) { + let value = JSON.parse(localStorage.getItem(key)); + await DBHandler.set(key, value); + } + + console.log("Transfer complete"); + + localStorage.removeItem("tasks"); + localStorage.removeItem("counts"); + + await renderTaskListToDOM(); +} + +function clearLocalStorage() { + localStorage.removeItem("tasks"); + localStorage.removeItem("counts"); +} + function loadGoogleFont(font) { WebFont.load({ google: { @@ -76,6 +188,21 @@ function hexToRgb(hex) { return `${r}, ${g}, ${b}`; } +/** + * Returns the current month and year. + * + * @returns {Object} An object containing the current month as a number and the current year as a number. + */ +function getMonthYear() { + let date = new Date(); + let month = date.toLocaleString("default", { month: "numeric" }); + let year = date.getFullYear(); + return { + month: month, + year: year, + }; +} + /** * import styles from configs */ @@ -159,22 +286,22 @@ function importStyles() { } /** - * Resets the entire localstorage + * Resets the entire DB */ -function resetDB() { - localStorage.clear(); +async function resetDB() { + await DBHandler.clear(); setupDB(); } /** * Sets up the local storage database. */ -function setupDB() { - if (!localStorage.tasks) { - localStorage.setItem(`tasks`, "{}"); +async function setupDB() { + if (!(await DBHandler.get("tasks"))) { + await DBHandler.set("tasks", {}); } - if (!localStorage.counts) { - localStorage.setItem(`counts`, JSON.stringify({ users: {} })); + if (!(await DBHandler.get("counts"))) { + await DBHandler.set("counts", { users: {} }); } } @@ -184,8 +311,8 @@ function setupDB() { * @param {string} username - The name of the user whose tasks are to be counted. * @returns {number} The number of incomplete tasks for the user. */ -function incompleteTasksCount(username) { - const tasks = JSON.parse(localStorage.tasks); +async function incompleteTasksCount(username) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username]) { return 0; } @@ -198,8 +325,8 @@ function incompleteTasksCount(username) { * @param {string} username - The name of the user whose tasks are to be counted. * @returns {number} The number of completed tasks for the user. */ -function completedTasksCount(username) { - const counts = JSON.parse(localStorage.counts); +async function completedTasksCount(username) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { return 0; } @@ -222,8 +349,8 @@ function getUserTotalTaskCount(username) { * @param {string} username - The name of the user whose points are to be calculated. * @returns {number} The total points for the user. */ -function calculatePoints(username) { - const counts = JSON.parse(localStorage.counts); +async function calculatePoints(username) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { return 0; @@ -241,8 +368,8 @@ function calculatePoints(username) { * @param {string} username - The name of the user. * @returns {number} The points of the user. If the user does not exist, returns 0. */ -function getUserPoints(username) { - const counts = JSON.parse(localStorage.counts); +async function getUserPoints(username) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { return 0; @@ -257,8 +384,8 @@ function getUserPoints(username) { * @param {string} username - The name of the user whose completed task count is to be incremented. * @param {number} value - The value to be added to the completed task count. */ -function addDoneCount(username, value) { - const counts = JSON.parse(localStorage.counts); +async function addDoneCount(username, value) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { counts.users[username.toLowerCase()] = { @@ -283,7 +410,7 @@ function addDoneCount(username, value) { counts.users[username.toLowerCase()].points += value * settings.pointsPerTask; - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); } /** @@ -293,8 +420,8 @@ function addDoneCount(username, value) { * @param {int} value * @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message. */ -function setUserCompleteCount(username, value) { - const counts = JSON.parse(localStorage.counts); +async function setUserCompleteCount(username, value) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { counts.users[username.toLowerCase()] = { @@ -302,13 +429,9 @@ function setUserCompleteCount(username, value) { }; } - if (!counts.users[username.toLowerCase()].completeCount) { - counts.users[username.toLowerCase()].completeCount = 0; - } - counts.users[username.toLowerCase()].completeCount = parseInt(value); - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -324,12 +447,12 @@ function setUserCompleteCount(username, value) { * @param {number} value - The new total count of completed tasks. * @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message. */ -function setTotalCompleteCount(value) { - const counts = JSON.parse(localStorage.counts); +async function setTotalCompleteCount(value) { + const counts = await DBHandler.get("counts"); counts.totalCompleteCount = parseInt(value); - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -346,8 +469,8 @@ function setTotalCompleteCount(value) { * @param {number} value - The number of points to add. * @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message. */ -function addPoints(username, value) { - const counts = JSON.parse(localStorage.counts); +async function addPoints(username, value) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { counts.users[username.toLowerCase()] = { @@ -361,7 +484,7 @@ function addPoints(username, value) { counts.users[username.toLowerCase()].points += value; - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -378,8 +501,8 @@ function addPoints(username, value) { * @param {number} pointsCount - The new points count for the user. * @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message. */ -function setUserPoints(username, pointsCount) { - const counts = JSON.parse(localStorage.counts); +async function setUserPoints(username, pointsCount) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { counts.users[username.toLowerCase()] = { @@ -387,13 +510,9 @@ function setUserPoints(username, pointsCount) { }; } - if (!counts.users[username.toLowerCase()].points) { - counts.users[username.toLowerCase()].points = 0; - } - counts.users[username.toLowerCase()].points = pointsCount; - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -404,14 +523,14 @@ function setUserPoints(username, pointsCount) { } /** - * Reduces the points of a specific user in local storage by a specified value. + * Reduces a specified number of points from a user's total in local storage. * * @param {string} username - The name of the user. - * @param {number} value - The value by which the user's points are to be reduced. + * @param {number} value - The number of points to reduce. * @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message. */ -function reducePoints(username, value) { - const counts = JSON.parse(localStorage.counts); +async function reducePoints(username, value) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { counts.users[username.toLowerCase()] = { @@ -425,7 +544,7 @@ function reducePoints(username, value) { counts.users[username.toLowerCase()].points -= parseInt(value); - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -436,12 +555,12 @@ function reducePoints(username, value) { } /** - * Gets the total number of completed tasks across all users. + * Retrieves the total count of tasks from the board. * - * @returns {number} The total number of completed tasks. + * @returns {number} The total count of tasks. If no tasks are found, returns 0. */ -function getBoardTotalTaskCount() { - const counts = JSON.parse(localStorage.counts); +async function getBoardTotalTaskCount() { + const counts = await DBHandler.get("counts"); if (!counts.totalCompleteCount) { return 0; @@ -457,8 +576,8 @@ function getBoardTotalTaskCount() { * @param {number} value - The new task count for the user. * @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains a success message. */ -function setUserTaskCount(username, value) { - const counts = JSON.parse(localStorage.counts); +async function setUserTaskCount(username, value) { + const counts = await DBHandler.get("counts"); if (!counts.users[username.toLowerCase()]) { counts.users[username.toLowerCase()] = { @@ -472,7 +591,7 @@ function setUserTaskCount(username, value) { counts.users[username.toLowerCase()].taskCount = parseInt(value); - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -487,14 +606,14 @@ function setUserTaskCount(username, value) { * * @returns {Object} An object with a status and body. The status is 200 if the synchronization is successful, and the body contains a success message. */ -function syncPointsToCount() { - const counts = JSON.parse(localStorage.counts); +async function syncPointsToCount() { + const counts = await DBHandler.get("counts"); for (const user in counts.users) { counts.users[user].points = calculatePoints(user); } - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -510,8 +629,8 @@ function syncPointsToCount() { * @param {number} limit - The maximum number of users to include in the leaderboard. * @returns {Object} An object with a status and body. The status is 200 if successful, and the body contains an array of user objects, each with a username and task count, sorted in descending order of task count. */ -function leaderboardTaskCount(limit) { - const counts = JSON.parse(localStorage.counts); +async function leaderboardTaskCount(limit) { + const counts = await DBHandler.get("counts"); let leaderboardArray = []; @@ -548,12 +667,12 @@ function leaderboardTaskCount(limit) { /** * Resets the total count of completed tasks in the board to zero. */ -function resetBoardCount() { - const counts = JSON.parse(localStorage.counts); +async function resetBoardCount() { + const counts = await DBHandler.get("counts"); counts.totalCompleteCount = 0; - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -566,12 +685,12 @@ function resetBoardCount() { /** * Resets the task count for all users. */ -function resetUsersCount() { - const counts = JSON.parse(localStorage.counts); +async function resetUsersCount() { + const counts = await DBHandler.get("counts"); counts.users = {}; - localStorage.setItem(`counts`, JSON.stringify(counts)); + await DBHandler.set("counts", counts); return { status: 200, @@ -589,8 +708,8 @@ function resetUsersCount() { * @param {string} task - The task text. * @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. */ -function addTask(username, userColor, task) { - const tasks = JSON.parse(localStorage.tasks); +async function addTask(username, userColor, task) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username]) { tasks[username] = { todos: [], @@ -599,149 +718,85 @@ function addTask(username, userColor, task) { }; } - if ( + const taskExists = tasks[username].todos.find( + (t) => t.text.toLowerCase() === task.toLowerCase() + ); + const taskIsInvalid = !task || !task.trim() || task.toLowerCase() === "all"; + const userHasReachedTaskLimit = incompleteTasksCount(username) >= settings.limit && - settings.enableLimit - ) { - return { - status: 0, - body: { - "error message": `@${username} has reached the limit of ${settings.limit} tasks`, - error: responses.noTaskAdded, - }, - }; + settings.enableLimit; + + if (taskExists) { + return createErrorResponse( + `@${username} already has this task`, + responses.duplicateTask + ); } - if ( - tasks[username].todos.find( - (t) => t.text.toLowerCase() === task.toLowerCase() - ) - ) { - return { - status: 1, - body: { - "error message": `@${username} already has this task`, - error: responses.duplicateTask, - }, - }; + if (taskIsInvalid) { + return createErrorResponse( + `@${username} empty task or reserved keyword used`, + responses.noTaskContent + ); } - // if task is whitespace or empty - if (!task || !task.trim()) { - return { - status: 2, - body: { - "error message": `@${username} empty task`, - error: responses.noTaskContent, - }, - }; + if (userHasReachedTaskLimit) { + return createErrorResponse( + `@${username} has reached the limit of ${settings.limit} tasks`, + responses.noTaskAdded + ); } - // if task is "all", return error - if (task.toLowerCase() === "all") { - return { - status: 2, - body: { - "error message": `@${username} all is a reserved keyword`, - error: responses.noTaskContent, - }, - }; - } - - let tasksFailedToAdd = []; - - // if task has commas - if (taskSeparator.some((char) => task.includes(char))) { - let tasksToAdd = []; - - let char = taskSeparator.find((element) => task.includes(element)); - - tasksToAdd = task.split(char).map((t) => t.trim()); - - if ( - incompleteTasksCount(username) + tasksToAdd.length > - settings.limit && - settings.enableLimit - ) { - tasksFailedToAdd = tasksToAdd.slice( - settings.limit - incompleteTasksCount(username), - tasksToAdd.length - ); - - tasksToAdd = tasksToAdd.slice( - 0, - settings.limit - incompleteTasksCount(username) - ); - } - - for (const t of tasksToAdd) { - if (t === "") { - continue; - } - if (t === "all") { - tasksFailedToAdd.push(t); - // remove from tasksToAdd - tasksToAdd = tasksToAdd.filter((task) => task !== t); - continue; - } - if (tasks[username].todos.find((task) => task.text === t)) { - tasksToAdd = tasksToAdd.filter((task) => task !== t); - tasksFailedToAdd.push(t); - continue; - } + const tasksToAdd = taskSeparator.some((char) => task.includes(char)) + ? task + .split(taskSeparator.find((char) => task.includes(char))) + .map((t) => t.trim()) + : [task]; + const tasksFailedToAdd = []; + tasksToAdd.forEach((t) => { + if (t && !tasks[username].todos.find((task) => task.text === t)) { + tasks[username].todos.push({ text: t, done: false, focus: false }); taskListMemory.totalTaskCount++; - - tasks[username].todos.push({ - text: t, - done: false, - focus: false, - }); + } else { + tasksFailedToAdd.push(t); } + }); - localStorage.setItem(`tasks`, JSON.stringify(tasks)); - - if (!scrolling) { - renderTaskListToDOM(); - } - - return { - status: 200, - body: { - task: tasksToAdd.join('", "'), - tasksFailedToAdd: tasksFailedToAdd.join('", "') || "", - }, - }; - } else { - tasks[username].todos.push({ text: task, done: false, focus: false }); - taskListMemory.totalTaskCount++; - - localStorage.setItem(`tasks`, JSON.stringify(tasks)); - if (!scrolling) { - renderTaskListToDOM(); - } - - return { - status: 200, - body: { - task: task, - tasksFailedToAdd: "", - }, - }; + await DBHandler.set("tasks", tasks); + if (!scrolling) { + renderTaskListToDOM(); } + + return { + status: 200, + body: { + task: tasksToAdd.join('", "'), + tasksFailedToAdd: tasksFailedToAdd.join('", "') || "", + }, + }; +} + +function createErrorResponse(errorMessage, errorType) { + return { + status: 0, + body: { + "error message": errorMessage, + error: errorType, + }, + }; } /** - * This function adds a new task to a user's task list and sets it as the currently focused task. + * Adds a task to a user's task list and sets it as the current task. * - * @param {string} username - The name of the user for whom the task is being added. - * @param {string} userColor - The color associated with the user. - * @param {string} task - The text description of the task to be added. - * - * @returns {Object} - An object containing the status of the operation and a body with either the task added or an error message. + * @param {string} username - The user's name. + * @param {string} userColor - The user's associated color. + * @param {string} task - The task text. + * @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. */ -function nowTask(username, userColor, task) { - const tasks = JSON.parse(localStorage.tasks); +async function nowTask(username, userColor, task) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username]) { tasks[username] = { todos: [], @@ -750,77 +805,51 @@ function nowTask(username, userColor, task) { }; } - if ( + const taskExists = tasks[username].todos.find( + (t) => t.text.toLowerCase() === task.toLowerCase() + ); + const taskIsInvalid = !task || !task.trim() || task.toLowerCase() === "all"; + const userHasReachedTaskLimit = incompleteTasksCount(username) >= settings.limit && - settings.enableLimit - ) { - return { - status: 0, - body: { - "error message": `@${username} has reached the limit of ${settings.limit} tasks`, - error: responses.noTaskAdded, - }, - }; + settings.enableLimit; + const taskHasSeparators = taskSeparator.some((char) => task.includes(char)); + + if (taskExists) { + return createErrorResponse( + `@${username} already has this task`, + responses.duplicateTask + ); } - if ( - tasks[username].todos.find( - (t) => t.text.toLowerCase() === task.toLowerCase() - ) - ) { - return { - status: 1, - body: { - "error message": `@${username} already has this task`, - error: responses.duplicateTask, - }, - }; + if (taskIsInvalid) { + return createErrorResponse( + `@${username} empty task or reserved keyword used`, + responses.noTaskContent + ); } - // if task is whitespace or empty - if (!task || !task.trim()) { - return { - status: 2, - body: { - "error message": `@${username} empty task`, - error: responses.noTaskContent, - }, - }; + if (userHasReachedTaskLimit) { + return createErrorResponse( + `@${username} has reached the limit of ${settings.limit} tasks`, + responses.noTaskAdded + ); } - // if task is "all", return error - if (task.toLowerCase() === "all") { - return { - status: 2, - body: { - "error message": `@${username} all is a reserved keyword`, - error: responses.noTaskContent, - }, - }; + if (taskHasSeparators) { + return createErrorResponse( + `@${username} cannot add multiple tasks with now`, + responses.noTaskContent + ); } - // if task has commas - if (taskSeparator.some((char) => task.includes(char))) { - // return an error because you can't add multiple tasks with now - return { - status: 2, - body: { - "error message": `@${username} cannot add multiple tasks with now`, - error: responses.noTaskContent, - }, - }; - } - - // set all tasks to unfocused - for (const task of tasks[username].todos) { + tasks[username].todos.forEach((task) => { task.focus = false; - } + }); tasks[username].todos.push({ text: task, done: false, focus: true }); taskListMemory.totalTaskCount++; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); - + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -842,8 +871,8 @@ function nowTask(username, userColor, task) { * * @returns {Object} - An object containing the status of the operation and a body with either the task added or an error message. */ -function nowTask(username, userColor, task) { - const tasks = JSON.parse(localStorage.tasks); +async function nowTask(username, userColor, task) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username]) { tasks[username] = { todos: [], @@ -852,77 +881,51 @@ function nowTask(username, userColor, task) { }; } - if ( + const taskExists = tasks[username].todos.find( + (t) => t.text.toLowerCase() === task.toLowerCase() + ); + const taskIsInvalid = !task || !task.trim() || task.toLowerCase() === "all"; + const userHasReachedTaskLimit = incompleteTasksCount(username) >= settings.limit && - settings.enableLimit - ) { - return { - status: 0, - body: { - "error message": `@${username} has reached the limit of ${settings.limit} tasks`, - error: responses.noTaskAdded, - }, - }; + settings.enableLimit; + const taskHasSeparators = taskSeparator.some((char) => task.includes(char)); + + if (taskExists) { + return createErrorResponse( + `@${username} already has this task`, + responses.duplicateTask + ); } - if ( - tasks[username].todos.find( - (t) => t.text.toLowerCase() === task.toLowerCase() - ) - ) { - return { - status: 1, - body: { - "error message": `@${username} already has this task`, - error: responses.duplicateTask, - }, - }; + if (taskIsInvalid) { + return createErrorResponse( + `@${username} empty task or reserved keyword used`, + responses.noTaskContent + ); } - // if task is whitespace or empty - if (!task || !task.trim()) { - return { - status: 2, - body: { - "error message": `@${username} empty task`, - error: responses.noTaskContent, - }, - }; + if (userHasReachedTaskLimit) { + return createErrorResponse( + `@${username} has reached the limit of ${settings.limit} tasks`, + responses.noTaskAdded + ); } - // if task is "all", return error - if (task.toLowerCase() === "all") { - return { - status: 2, - body: { - "error message": `@${username} all is a reserved keyword`, - error: responses.noTaskContent, - }, - }; + if (taskHasSeparators) { + return createErrorResponse( + `@${username} cannot add multiple tasks with now`, + responses.noTaskContent + ); } - // if task has commas - if (taskSeparator.some((char) => task.includes(char))) { - // return an error because you can't add multiple tasks with now - return { - status: 2, - body: { - "error message": `@${username} cannot add multiple tasks with now`, - error: responses.noTaskContent, - }, - }; - } - - // set all tasks to unfocused - for (const task of tasks[username].todos) { + tasks[username].todos.forEach((task) => { task.focus = false; - } + }); tasks[username].todos.push({ text: task, done: false, focus: true }); taskListMemory.totalTaskCount++; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); - + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -936,44 +939,35 @@ function nowTask(username, userColor, task) { } /** - * Focuses a specified task for a given user. If the task is successfully focused, the function returns an object with a status of 200 and the focused task. If the task is not focused due to the user having no tasks, the task being invalid input, or the task already being focused, the function returns an object with a status indicating the error and an error message. + * This function focuses a specified task for a given user. If the task is successfully focused, the function returns an object with a status of 200 and the focused task. If the task is not focused due to the user having no tasks, the task being invalid input, or the task already being focused, the function returns an object with a status indicating the error and an error message. * * @param {string} username - The name of the user whose task is to be focused. * @param {string} task - The task to be focused. * @returns {Object} An object with a status and body. The status is 200 if the task is successfully focused, and the body contains the focused task. If the task is not focused, the status indicates the error and the body contains an error message. */ -function focusTask(username, task) { - const tasks = JSON.parse(localStorage.tasks); +async function focusTask(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: responses.noTask, - }, - }; + return createErrorResponse( + `@${username} has no tasks`, + responses.noTask + ); } const incompleteTasks = tasks[username].todos.filter((t) => !t.done); if (incompleteTasks.length === 0) { - return { - status: 0, - body: { - "error message": `@${username} has no incomplete tasks`, - error: responses.noTask, - }, - }; + return createErrorResponse( + `@${username} has no incomplete tasks`, + responses.noTask + ); } if (taskSeparator.some((separator) => task.includes(separator))) { - return { - status: 1, - body: { - "error message": `@${username} need to specify ONLY ONE task`, - error: responses.onlyOneFocus, - }, - }; + return createErrorResponse( + `@${username} need to specify ONLY ONE task`, + responses.onlyOneFocus + ); } let index = tasks[username].todos.findIndex( @@ -986,33 +980,24 @@ function focusTask(username, task) { } else if (incompleteTasks.length === 1) { index = tasks[username].todos.findIndex((t) => !t.done); } else { - return { - status: 1, - body: { - "error message": `@${username} invalid input`, - error: responses.specifyFocusTask, - }, - }; + return createErrorResponse( + `@${username} invalid input`, + responses.specifyFocusTask + ); } if (index < 0 || index > tasks[username].todos.length - 1) { - return { - status: 1, - body: { - "error message": `@${username} invalid input`, - error: responses.specifyFocusTask, - }, - }; + return createErrorResponse( + `@${username} invalid input`, + responses.specifyFocusTask + ); } } else { if (tasks[username].todos[index].done) { - return { - status: 1, - body: { - "error message": `@${username} task is already completed`, - error: responses.specifyFocusTask, - }, - }; + return createErrorResponse( + `@${username} task is already completed`, + responses.specifyFocusTask + ); } } @@ -1020,35 +1005,21 @@ function focusTask(username, task) { // if task is already focused, return 1 if (tasks[username].todos[index].focus) { - return { - status: 1, - body: { - "error message": `@${username} task is already focused`, - error: responses.alreadyFocusedTask, - }, - }; - } - - // if task is already completed, return 2 - if (tasks[username].todos[index].done) { - return { - status: 2, - body: { - "error message": `@${username} task is already completed`, - error: responses.specifyFocusTask, - }, - }; + return createErrorResponse( + `@${username} task is already focused`, + responses.alreadyFocusedTask + ); } // set all tasks to unfocused - for (const task of tasks[username].todos) { + tasks[username].todos.forEach((task) => { task.focus = false; - } + }); // set task to focused tasks[username].todos[index].focus = true; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); @@ -1063,41 +1034,35 @@ function focusTask(username, task) { } /** - * Unfocuses all tasks for a given user. If the tasks are successfully unfocused, the function returns an object with a status of 200. If the tasks are not unfocused due to the user having no tasks or no tasks being focused, the function returns an object with a status indicating the error and an error message. + * This function unfocuses all tasks for a given user. If the tasks are successfully unfocused, the function returns an object with a status of 200. If the tasks are not unfocused due to the user having no tasks or no tasks being focused, 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 unfocused. * @returns {Object} An object with a status and body. The status is 200 if the tasks are successfully unfocused. If the tasks are not unfocused, the status indicates the error and the body contains an error message. */ -function unfocusTask(username) { - const tasks = JSON.parse(localStorage.tasks); +async function unfocusTask(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: responses.noTask, - }, - }; + return createErrorResponse( + `@${username} has no tasks`, + responses.noTask + ); } // if no tasks are focused, return 1 if (!tasks[username].todos.find((t) => t.focus)) { - return { - status: 1, - body: { - "error message": `@${username} no tasks are focused`, - error: responses.noFocusedTask, - }, - }; + return createErrorResponse( + `@${username} no tasks are focused`, + responses.noFocusedTask + ); } // set all tasks to unfocused - for (const task of tasks[username].todos) { + tasks[username].todos.forEach((task) => { task.focus = false; - } + }); - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); @@ -1115,8 +1080,8 @@ function unfocusTask(username) { * @param {string} task - The task to be removed. * @returns {Object} An object with a status and body. The status is 200 if the task is successfully removed, and the body contains the removed task. If the task is not removed, the status indicates the error and the body contains an error message. */ -function removeTask(username, task) { - const tasks = JSON.parse(localStorage.tasks); +async function removeTask(username, task) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username] || tasks[username].todos.length === 0) { return { status: 0, @@ -1201,7 +1166,7 @@ function removeTask(username, task) { tasks[username].todos.splice(index, 1); } - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -1247,7 +1212,7 @@ function removeTask(username, task) { tasks[username].todos.splice(index, 1); taskListMemory.totalTaskCount--; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); renderTaskListToDOM(); return { status: 200, @@ -1267,8 +1232,8 @@ function removeTask(username, task) { * * @returns {Object} - An object containing the status of the operation and a body with either the old and new task or an error message. */ -function nextTask(username, task) { - const tasks = JSON.parse(localStorage.tasks); +async function nextTask(username, task) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username] || tasks[username].todos.length === 0) { return { status: 0, @@ -1315,7 +1280,7 @@ function nextTask(username, task) { tasks[username].todos.push({ text: task, done: false, focus: false }); taskListMemory.totalTaskCount++; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); return { status: 200, @@ -1327,8 +1292,8 @@ function nextTask(username, task) { } } -function nextTask(username, task) { - const tasks = JSON.parse(localStorage.tasks); +async function nextTask(username, task) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username] || tasks[username].todos.length === 0) { return { status: 0, @@ -1375,7 +1340,7 @@ function nextTask(username, task) { tasks[username].todos.push({ text: task, done: false, focus: false }); taskListMemory.totalTaskCount++; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); return { status: 200, @@ -1407,8 +1372,8 @@ function isInt(value) { * @param {string} username - The name of the user whose completed tasks are to be cleared. * @returns {Object} An object with a status and body. The status is 200 if the tasks are successfully cleared. If the tasks are not cleared, the status indicates the error and the body contains an error message. */ -function clearOwnDoneTasks(username) { - const tasks = JSON.parse(localStorage.tasks); +async function clearOwnDoneTasks(username) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username] || tasks[username].todos.length === 0) { return { @@ -1425,7 +1390,7 @@ function clearOwnDoneTasks(username) { // replace user's task with incomplete tasks tasks[username].todos = incompleteUserTasks; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -1442,8 +1407,8 @@ function clearOwnDoneTasks(username) { * @param {string} task - The task to be marked as done. * @returns {Object} An object with a status and body. The status is 200 if the task is successfully marked as done, and the body contains the marked task. If the task is not marked as done, the status indicates the error and the body contains an error message. */ -function markTaskDone(username, task) { - const tasks = JSON.parse(localStorage.tasks); +async function markTaskDone(username, task) { + const tasks = await DBHandler.get("tasks"); // user does not have any tasks if (!tasks[username] || tasks[username].todos.length === 0) { @@ -1481,8 +1446,6 @@ function markTaskDone(username, task) { // check if there's a comma in the task (multiple tasks) if (taskSeparator.some((separator) => task.includes(separator))) { - // let tasksToMarkDone = task.split(",").map((t) => t.trim()); - let tasksToMarkDone = []; let char = taskSeparator.find((element) => task.includes(element)); @@ -1543,7 +1506,7 @@ function markTaskDone(username, task) { }; } - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -1614,7 +1577,7 @@ function markTaskDone(username, task) { tasks[username].todos[index].done = true; addDoneCount(username, 1); - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -1635,8 +1598,8 @@ function markTaskDone(username, task) { * @param {string} username - The name of the user whose tasks are to be marked as done. * @returns {Object} An object with a status and body. The status is 200 if the tasks are successfully marked as done. If the tasks are not marked as done, the status indicates the error and the body contains an error message. */ -function markAllTasksAsDone(username) { - const tasks = JSON.parse(localStorage.tasks); +async function markAllTasksAsDone(username) { + const tasks = await DBHandler.get("tasks"); // user does not have any tasks if (!tasks[username] || tasks[username].todos.length === 0) { @@ -1656,7 +1619,7 @@ function markAllTasksAsDone(username) { task.done = true; } - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -1670,8 +1633,8 @@ function markAllTasksAsDone(username) { // 1: invalid input // 2: task is already incomplete // 200: succcess -function markTaskUndone(username, task) { - const tasks = JSON.parse(localStorage.tasks); +async function markTaskUndone(username, task) { + const tasks = await DBHandler.get("tasks"); // user does not have any tasks if (!tasks[username] || tasks[username].todos.length === 0) { @@ -1744,7 +1707,7 @@ function markTaskUndone(username, task) { }; } - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -1789,7 +1752,7 @@ function markTaskUndone(username, task) { tasks[username].todos[index].done = false; addDoneCount(username, -1); - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); @@ -1805,7 +1768,7 @@ function markTaskUndone(username, task) { tasks[username].todos[index].done = false; addDoneCount(username, -1); - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); @@ -1830,8 +1793,8 @@ function markTaskUndone(username, task) { * @param {string} message - The message containing the task index and the new task. * @returns {Object} An object with a status and body. The status is 200 if the task is successfully edited, and the body contains the original task and the new task. If the task is not edited, the status indicates the error and the body contains an error message. */ -function editTask(username, message) { - const tasks = JSON.parse(localStorage.tasks); +async function editTask(username, message) { + const tasks = await DBHandler.get("tasks"); let noSpecifiedIndex = false; if (!tasks[username] || tasks[username].todos.length === 0) { @@ -1898,7 +1861,7 @@ function editTask(username, message) { tasks[username].todos[index].text = newTask; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); } @@ -1918,8 +1881,8 @@ function editTask(username, message) { * @param {string} username - 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. */ -function checkTasks(username) { - const tasks = JSON.parse(localStorage.tasks); +async function checkTasks(username) { + const tasks = await DBHandler.get("tasks"); // Go through keys of tasks, find match of lowercased username username = Object.keys(tasks).find( @@ -1981,8 +1944,8 @@ function checkTasks(username) { } // 0: user has no tasks -function listTasks(username) { - const tasks = JSON.parse(localStorage.tasks); +async function listTasks(username) { + const tasks = await DBHandler.get("tasks"); if (!tasks[username]) { return { status: 0, @@ -2034,8 +1997,8 @@ function listTasks(username) { * @param {string} username - The name of the user whose tasks are to be cleared. * @returns {Object} An object with a status and body. The status is 200 if the tasks are successfully cleared. If the tasks are not cleared, the status indicates the error and the body contains an error message. */ -function clearUserTasks(username) { - const tasks = JSON.parse(localStorage.tasks); +async function clearUserTasks(username) { + const tasks = await DBHandler.get("tasks"); // find username where lowercase matches username username = Object.keys(tasks).find( (user) => user.toLowerCase() === username.toLowerCase() @@ -2053,7 +2016,7 @@ function clearUserTasks(username) { tasks[username].todos = []; - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); if (!scrolling) { renderTaskListToDOM(); @@ -2069,13 +2032,13 @@ function clearUserTasks(username) { * * @returns {Object} An object with a status. The status is 200 if the tasks are successfully cleared. */ -function clearAllDoneTasks() { - const tasks = JSON.parse(localStorage.tasks); +async function clearAllDoneTasks() { + const tasks = await DBHandler.get("tasks"); for (const user in tasks) { tasks[user].todos = tasks[user].todos.filter((t) => !t.done); } cancelAnimation(); - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); renderTaskListToDOM(); return { @@ -2083,8 +2046,8 @@ function clearAllDoneTasks() { }; } -function clearAllTasks() { - localStorage.setItem(`tasks`, "{}"); +async function clearAllTasks() { + await DBHandler.set("tasks", {}); cancelAnimation(); renderTaskListToDOM(); @@ -2111,9 +2074,9 @@ function clearAll() { * @param {string[]} streamerUsernames - 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). */ -function clearAllExceptStreamer(streamerUsername) { +async function clearAllExceptStreamer(streamerUsername) { // clear all tasks except for broadcasters - const tasks = JSON.parse(localStorage.tasks); + const tasks = await DBHandler.get("tasks"); // streamerusernames lowercased @@ -2123,7 +2086,7 @@ function clearAllExceptStreamer(streamerUsername) { } } - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + await DBHandler.set("tasks", tasks); clearAllDoneTasks(); @@ -2141,8 +2104,8 @@ const taskListMemory = { totalTaskCount: 0, }; -function renderTaskListToDOM() { - const tasks = JSON.parse(localStorage.tasks); +async function renderTaskListToDOM() { + const tasks = await DBHandler.get("tasks"); const taskContainers = document.querySelectorAll(".task-container"); @@ -2216,7 +2179,9 @@ function renderTaskListToDOM() { taskListMemory.doneTaskCount = completedTasksCount; taskListMemory.totalTaskCount = totalTaskCount; }); - localStorage.setItem(`tasks`, JSON.stringify(tasks)); + + await DBHandler.set("tasks", tasks); + checkToAnimate(); } @@ -2335,12 +2300,16 @@ function cancelAnimation() { scrolling = false; } -(function () { - setupDB(); - if (settings.testTasks) { - resetDB(); - tests(); - } - importStyles(); - renderTaskListToDOM(); -})(); +DBHandler.open() + .then(() => { + setupDB(); + if (settings.testTasks) { + resetDB(); + tests(); + } + importStyles(); + renderTaskListToDOM(); + }) + .catch((error) => { + console.error("Error opening database:", error); + });