Compare commits

...

7 commits

Author SHA1 Message Date
liyunze
d8df9b2ca1 update no chat version 2024-07-05 21:53:23 +10:00
liyunze
72da262759 version 2.0 2024-05-25 19:02:08 +08:00
liyunze
de75b3cd4c added usernameFontsize and doneTaskFontColor 2023-11-23 21:04:38 +08:00
liyunze
7c9869e967 updated with focus task 2023-11-22 22:14:50 +08:00
liyunze
7fc656680e Removed unnecessary configs 2023-11-14 17:27:48 +08:00
liyunze
52bbde40bd Added local storage event listener 2023-11-12 13:36:12 +08:00
liyunze
efcc8000f8 First commit, might not be stable 2023-11-12 13:24:00 +08:00
7 changed files with 459 additions and 1331 deletions

View file

@ -1,350 +1,110 @@
const configs = (function () { const configs = (function () {
"use strict"; "use strict";
// settings
const enableLimit = true; // true or false
const limit = 5; // integer
// animation
const scrollSpeed = 40; // milliseconds
const gapBetweenScrolls = 0; // px
// STYLES
// task list
const taskListBackgroundColor = "#000000"; // hex only
const taskListBackgroundOpacity = 0; // 0.0 - 1.0
const taskListBorderWidth = "0px"; // px
const taskListBorderColor = "#ffffff"; // hex or name
const taskListBorderRadius = "5px"; // px
const taskListHorizontalPadding = "0px"; // px
const taskListVerticalPadding = "0px"; // px
// header
const headerHorizontalPadding = "10px"; // px
const headerVerticalPadding = "10px"; // px
const headerBackgroundColor = "#000000"; // hex only
const headerBackgroundOpacity = 0.8; // 0.0 - 1.0
const headerBorderWidth = "2px"; // px
const headerBorderColor = "#ffffff"; // hex or name
const headerBorderRadius = "5px"; // px
const headerFontFamily = "Fredoka"; // font name
const headerGoogleFont = true; // true: use google font, false: use system font
const headerFontSize = "25px"; // px
const headerFontWeight = "bold"; // normal or bold or number
const headerFontColor = "#ffffff"; // hex or name
// body
const bodyBackgroundColor = "#000000"; // hex only
const bodyBackgroundOpacity = 0; // 0.0 - 1.0
const bodyBorderWidth = "0px"; // px
const bodyBorderColor = "#fff"; // hex or name
const bodyBorderRadius = "5px"; // px
// task
const lineHeight = 1.5; // number
const usernameFontWeight = "bold"; // normal or bold or number
const usernameColor = "#ffc0cb"; // hex or name or "" for twitch user color
const taskWidth = "100%";
const taskBackgroundColor = "#000000"; // hex only
const taskBackgroundOpacity = 0.7; // 0.0 - 1.0
const taskFontFamily = "Poppins"; // font name
const taskGoogleFont = true; // true: use google font, false: use system font
const taskFontSize = "20px"; // px
const taskFontColor = "#ffffff"; // hex or name
const taskBorderColor = "#000000"; // hex or name
const taskBorderWidth = "0px"; // px
const taskBorderRadius = "5px"; // px
const taskMarginBottom = "5px"; // px
const taskHorizontalPadding = "20px"; // px
const taskVerticalPadding = "10px"; // px
// Add task commands - please add commands in the exact format
const addTaskCommands = [
"!taska",
"!taskadd",
"!atask",
"!addtask",
"!task",
"!add",
"!todo",
];
// Edit task commands - please add commands in the exact format
const editTaskCommands = [
"!taske",
"!taskedit",
"!etask",
"!edittask",
"!edit",
];
// Delete task commands - please add commands in the exact format
const deleteTaskCommands = [
"!taskd",
"!taskdel",
"!taskdelete",
"!deltask",
"!deletetask",
"!taskr",
"!taskremove",
"!rtask",
"!removetask",
"!remove",
"!delete",
];
// Finish task commands - please add commands in the exact format
const finishTaskCommands = [
"!taskf",
"!taskfinish",
"!ftask",
"!finishtask",
"!taskd",
"!taskdone",
"!donetask",
"!dtask",
"!finish",
"!done",
"!finished",
];
// Unfinish task commands - please add commands in the exact format
const unfinishTaskCommands = [
"!tasku",
"!taskunfinish",
"!utask",
"!unfinishtask",
"!taskud",
"!taskundone",
"!undonetask",
"!undone",
"!unfinish",
"!unfinished",
];
// Check task commands - please add commands in the exact format
const checkCommands = [
"!taskc",
"!taskcheck",
"!ctask",
"!checktask",
"!mytask",
"!check",
"!mytasks",
];
// List tasks commands - please add commands in the exact format
const listCommands = ["!taskl", "!tasklist", "!listtasks", "!list"];
// Clear my done commands - please add commands in the exact format
const clearMyDoneCommands = ["!clearmydone"];
// Help commands - please add commands in the exact format
const helpCommands = [
"!taskh",
"!taskhelp",
"!htask",
"!helptask",
"!tasks",
"!help",
];
// Admin clear all except streamer - please add commands following the exact format
const adminClearNotStreamerCommands = [
"!clearnotstreamer",
"!aclearnotstreamer",
"!adminclearnotstreamer",
"!clearns",
];
// Admin delete - please add commands following the exact format
const adminDeleteCommands = [
"!taskadel",
"!adel",
"!adelete",
"!admindelete",
];
// Admin clear done - please add commands following the exact format
const adminClearDoneCommands = [
"!acleardone",
"!admincleardone",
"!cleardone",
];
const adminClearAllCommands = [
"!clearall",
"!allclear",
"!adminclearall",
"!adminallclear",
"!aclearall",
"!aclear",
];
// Responses
const taskAdded = 'The task(s) "{task}" has been added, {user}!';
const noTaskAdded =
"Looks like you already hit the limit of incomplete tasks, {user}";
const noTaskContent = "Try using !task the-task-you-are-working-on {user}";
const taskDeleted =
'Task(s) "{task}" has been deleted successfully, {user}';
const specifyTaskIndex = "Try specifying the index of the task {user}";
const duplicateTask =
"Looks like you already have the task '{message}' up there {user}!";
const clearTasksExceptBroadcaster =
"All tasks have been cleared except for the streamer's, {user}!";
const adminDeleteTasks = "All of {mentioned}'s tasks have been deleted";
const taskFinished = 'Good job on finishing the task "{task}", {user}!';
const allTasksFinished = "Good job on finishing all your tasks, {user}!";
const taskUnfinished =
'Task(s) "{task}" has been unmarked as done, {user}!';
const taskEdited =
"Task '{originalTask}' has been edited to '{task}' successfully, {user}";
const noTaskEdit = "Try doing !edit [index] [new task] {user}";
const noTask = "Looks like you don't have a task up there {user}";
const noTaskA = "Looks like there is no task from that user there {user}";
const notMod = "Permission denied, {user}; Mods only";
const clearedAll = "All tasks have been cleared!";
const clearedDone = "All completed tasks have been cleared!";
const clearedMyDone = "All your completed tasks have been cleared!";
const specifyUser = "Try specifying a user to delete their tasks {user}";
const 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)
const 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",
};
const titles = [
"!taskhelp",
"!task",
"!edit",
"!remove",
"!done",
"!botcred",
];
const settings = { const settings = {
enableLimit, streamer: "RythonDev",
limit, testTasks: false, // true or false
headerGoogleFont, headerGoogleFont: true, // true: use google font, false: use system font
taskGoogleFont, taskGoogleFont: true, // true: use google font, false: use system font
}; showStreamersTasksOnly: false, // true or false
const styles = {
taskListBackgroundColor,
taskListBackgroundOpacity,
taskListBorderWidth,
taskListBorderColor,
taskListBorderRadius,
taskListHorizontalPadding,
taskListVerticalPadding,
headerHorizontalPadding,
headerVerticalPadding,
headerBackgroundColor,
headerBackgroundOpacity,
headerBorderWidth,
headerBorderColor,
headerBorderRadius,
headerFontSize,
headerFontWeight,
headerFontFamily,
headerFontColor,
bodyBackgroundColor,
bodyBackgroundOpacity,
bodyBorderWidth,
bodyBorderColor,
bodyBorderRadius,
taskWidth,
lineHeight,
usernameFontWeight,
usernameColor,
taskBackgroundColor,
taskBackgroundOpacity,
taskFontFamily,
taskFontSize,
taskFontColor,
taskBorderColor,
taskBorderWidth,
taskBorderRadius,
taskMarginBottom,
taskHorizontalPadding,
taskVerticalPadding,
}; };
const animation = { const animation = {
scrollSpeed, scrollSpeed: 40, // milliseconds
gapBetweenScrolls, gapBetweenScrolls: 0, // px
titles, titles: [
"!taskhelp",
"!task",
"!edit",
"!remove",
"!done",
"!undone",
"!botcred",
"!count",
"!points",
"!boardcount",
],
}; };
const commands = { const styles = {
addTaskCommands, // task list
editTaskCommands, taskListBackgroundColor: "#000000", // hex only
deleteTaskCommands, taskListBackgroundOpacity: 0, // 0.0 - 1.0
finishTaskCommands,
unfinishTaskCommands,
helpCommands,
checkCommands,
listCommands,
clearMyDoneCommands,
adminDeleteCommands,
adminClearNotStreamerCommands,
adminClearDoneCommands,
adminClearAllCommands,
additionalCommands,
};
const responses = { taskListBorderWidth: "0px", // px
taskAdded, taskListBorderColor: "#ffffff", // hex or name
noTaskAdded, taskListBorderRadius: "0px", // px
noTaskContent,
taskDeleted, taskListHorizontalPadding: "0px", // px
specifyTaskIndex, taskListVerticalPadding: "0px", // px
duplicateTask,
taskFinished, // header
allTasksFinished, headerHorizontalPadding: "10px", // px
taskUnfinished, headerVerticalPadding: "10px", // px
taskEdited,
noTaskEdit, headerBackgroundColor: "#fff", // hex only
noTask, headerBackgroundOpacity: 1, // 0.0 - 1.0
noTaskA,
notMod, headerBorderWidth: "0px", // px
help, headerBorderColor: "#ffffff", // hex or name
clearTasksExceptBroadcaster, headerBorderRadius: "3px", // px
adminDeleteTasks,
clearedMyDone, headerFontFamily: "Fredoka", // font name
clearedAll,
clearedDone, headerFontSize: "25px", // px
specifyUser, headerFontWeight: "bold", // normal or bold or number
headerFontColor: "#000", // hex or name
// 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: "pink", // hex or name or "" for twitch user color
usernameFontSize: "20px", // px
taskWidth: "100%",
taskBackgroundColor: "#000", // hex only
taskBackgroundOpacity: 0, // 0.0 - 1.0
taskFontFamily: "Poppins", // font name
taskFontSize: "20px", // 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
}; };
return { return {
settings, settings,
styles, styles,
animation, animation,
commands,
responses,
}; };
})(); })();

View file

@ -1,5 +0,0 @@
https://id.twitch.tv/oauth2/authorize
?client_id=CLIENT_ID_HERE
&redirect_uri=http://localhost
&response_type=token
&scope=channel:moderate+chat:edit+chat:read+channel:manage:broadcast+user:edit:broadcast+channel:read:redemptions+user:read:email

View file

@ -1,44 +1,31 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
<script src="https://cdn.jsdelivr.net/npm/comfy.js@latest/dist/comfy.min.js"></script>
<head> <script src="./configs.js"></script>
<meta charset="UTF-8" /> <script defer src="scripts/defaultConfigs.js"></script>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <script defer src="scripts/taskList.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
<script src="https://cdn.jsdelivr.net/npm/comfy.js@latest/dist/comfy.min.js"></script>
<script src="./auth.js"></script> <link rel="stylesheet" type="text/css" href="styles/taskList.css" />
<script src="./configs.js"></script>
<script defer src="scripts/index.js"></script>
<script defer src="scripts/taskList.js"></script>
<link rel="stylesheet" type="text/css" href="styles/taskList.css" /> <title>Task List</title>
</head>
<title>Task List</title> <body>
</head> <div id="main-container">
<div class="header">
<body> <div class="title">!taskhelp</div>
<div id="main-container"> <div class="task-count">0/0</div>
<div class="header"> </div>
<div class="title">!taskhelp</div> <div class="task-wrapper">
<div class="task-count">0/0</div> <div class="task-container primary"></div>
</div> <div class="task-container secondary"></div>
<div class="task-wrapper">
<div class="task-container primary">
<!--
<div class="task">
<div class="username">RyanPython</div>
<ol class="user-tasks">
<li class="todo">complete website</li>
<li class="todo">work on overlay</li>
</ol>
</div>
-->
</div> </div>
<div class="task-container secondary"></div>
</div> </div>
</div> </body>
</body>
</html> </html>

110
scripts/defaultConfigs.js Normal file
View file

@ -0,0 +1,110 @@
const defaultConfigs = (function () {
"use strict";
const settings = {
streamer: "RythonDev",
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
showStreamersTasksOnly: true, // true or false
};
const animation = {
scrollSpeed: 40, // milliseconds
gapBetweenScrolls: 0, // px
titles: [
"!taskhelp",
"!task",
"!edit",
"!remove",
"!done",
"!undone",
"!botcred",
"!count",
"!points",
"!boardcount",
],
};
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: "10px", // px
headerBackgroundColor: "#fff", // hex only
headerBackgroundOpacity: 1, // 0.0 - 1.0
headerBorderWidth: "0px", // px
headerBorderColor: "#ffffff", // hex or name
headerBorderRadius: "3px", // px
headerFontFamily: "Fredoka", // font name
headerFontSize: "25px", // px
headerFontWeight: "bold", // normal or bold or number
headerFontColor: "#000", // hex or name
// 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: "pink", // hex or name or "" for twitch user color
usernameFontSize: "20px", // px
taskWidth: "100%",
taskBackgroundColor: "#000", // hex only
taskBackgroundOpacity: 0, // 0.0 - 1.0
taskFontFamily: "Poppins", // font name
taskFontSize: "20px", // 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
};
return {
settings,
styles,
animation,
};
})();

View file

@ -1,225 +0,0 @@
const commands = configs.commands;
const responses = configs.responses;
let params = {};
function respond(template, params) {
ComfyJS.Say(
template
.replace("{user}", `@${params.user}`)
.replace("{message}", params.message)
.replace("{mentioned}", `@${params.mentioned}`)
.replace("{task}", params.task)
.replace("{originalTask}", params.originalTask)
);
}
function isMod(flags) {
return flags.broadcaster || flags.mod;
}
ComfyJS.onCommand = (user, command, message, flags, extra) => {
command = `!${command.toLowerCase()}`;
params = {
user: user,
message: message,
mentioned: "",
task: "",
originalTask: "",
};
if (commands.addTaskCommands.includes(command)) {
let addStatus = addTask(user, extra.userColor, message);
if (addStatus === 0) {
// limit has reached
respond(responses.noTaskAdded, params);
} else if (addStatus === 1) {
// duplicate task
respond(responses.duplicateTask, params);
} else if (addStatus === 2) {
// task has no content
respond(responses.noTaskContent, params);
} else {
let addResponse = responses.taskAdded;
params.task = addStatus.task;
if (addStatus.tasksFailedToAdd !== "") {
addResponse += ` | Failed to add task(s): "${addStatus.tasksFailedToAdd}"`;
}
// task added
respond(addResponse, params);
}
} else if (commands.editTaskCommands.includes(command)) {
let editStatus = editTask(user, message);
if (editStatus === 0) {
// no task
respond(responses.noTask, params);
} else if (editStatus === 1) {
// invalid input
respond(responses.noTaskEdit, params);
} else {
// task edited
params.task = editStatus[1];
params.originalTask = editStatus[0];
respond(responses.taskEdited, params);
}
} else if (commands.deleteTaskCommands.includes(command)) {
let removeStatus = removeTask(user, message);
if (removeStatus === 0) {
// no task
respond(responses.noTask, params);
} else if (removeStatus === 1) {
// invalid input
respond(responses.specifyTaskIndex, params);
} else {
// task deleted
let deletedResponse = responses.taskDeleted;
params.task = removeStatus.removedTasks;
if (removeStatus.failedTasks !== "") {
deletedResponse += ` | Failed to delete task(s): "${removeStatus.failedTasks}"`;
}
respond(deletedResponse, params);
}
} else if (commands.finishTaskCommands.includes(command)) {
if (message === "all") {
let finishAllStatus = markAllTasksAsDone(user);
if (finishAllStatus === 0) {
// user has no tasks
respond(responses.noTask, params);
} else {
// all tasks finished
let finishedResponse = responses.allTasksFinished;
respond(finishedResponse, params);
}
return;
}
let finishStatus = markTaskDone(user, message);
if (finishStatus === 0) {
// user has no tasks
respond(responses.noTask, params);
} else if (finishStatus === 1) {
// invalid input
respond(responses.specifyTaskIndex, params);
} else {
// task finished
let finishedResponse = responses.taskFinished;
params.task = finishStatus.markedTasks;
if (finishStatus.failedTasks !== "") {
finishedResponse += ` | Failed to finish task(s): "${finishStatus.failedTasks}"`;
}
respond(finishedResponse, params);
}
} else if (commands.unfinishTaskCommands.includes(command)) {
let unfinishStatus = markTaskUndone(user, message);
if (unfinishStatus === 0) {
// user has no tasks
respond(responses.noTask, params);
} else if (unfinishStatus === 1) {
// invalid input
respond(responses.specifyTaskIndex, params);
} else {
// task unfinished
let unfinishedResponse = responses.taskUnfinished;
params.task = unfinishStatus.markedTasks;
if (unfinishStatus.failedTasks !== "") {
unfinishedResponse += ` | Failed to unfinish task(s): "${unfinishStatus.failedTasks}"`;
}
respond(unfinishedResponse, params);
}
} else if (commands.checkCommands.includes(command)) {
if (message === "") {
let response = checkTasks(user);
if (response === 0) {
return respond(responses.noTask, params);
}
return ComfyJS.Say(response);
} else {
let mentioned = message.replace("@", "");
let response = checkTasks(mentioned);
if (response === 0) {
// no tasks
return respond(responses.noTaskA, params);
}
return ComfyJS.Say(response);
}
} else if (commands.adminDeleteCommands.includes(command)) {
if (!isMod(flags)) {
respond(responses.notMod, params);
return;
}
let mentioned = message.replace("@", "");
if (mentioned === "") {
respond(responses.specifyUser, params);
return;
}
clearUserTasks(mentioned);
params.mentioned = mentioned;
respond(responses.adminDeleteTasks, params);
return mentioned;
} else if (commands.adminClearDoneCommands.includes(command)) {
if (!isMod(flags)) {
respond(responses.notMod, params);
return;
} else {
clearAllDoneTasks();
respond(responses.clearedDone, params);
}
} else if (commands.adminClearAllCommands.includes(command)) {
if (!isMod(flags)) {
respond(responses.notMod, params);
return;
} else {
clearAllTasks();
respond(responses.clearedAll, params);
}
} else if (commands.clearMyDoneCommands.includes(command)) {
let response = clearOwnDoneTasks(user);
if (response === 0) {
// no tasks
respond(responses.noTask, params);
return;
}
respond(responses.clearedMyDone, params);
} else if (commands.adminClearNotStreamerCommands.includes(command)) {
clearAllExceptStreamer(auth.channel);
respond(responses.clearTasksExceptBroadcaster, params);
} else if (commands.helpCommands.includes(command)) {
respond(responses.help, params);
} else if (commands.listCommands.includes(command)) {
let response = listTasks(user);
if (response === 0) {
respond(responses.noTask, params);
return;
}
ComfyJS.Say(response);
} else if (commands.additionalCommands[command]) {
respond(commands.additionalCommands[command], params);
} else {
// command not found
}
};
ComfyJS.Init(auth.username, `oauth:${auth.oauth}`, [auth.channel]);

View file

@ -2,13 +2,119 @@
DB structure: DB structure:
- tasks - tasks
- username: - [username]:
- todos (array) - todos (array)
- [{text, done}, {text, done}] - [{text, done, focus}, {text, done, focus}]
- done
- userColor - userColor
- counts
- users
- [username]:
- completeCount
- points
- totalCompleteCount
- taskmaster
- users
- [username]:
- taskMasterCompleteCount
- startDate
*/ */
var taskListMemory = {
doneTaskCount: 0,
totalTaskCount: 0,
};
function getSetting(setting) {
return configs.settings[setting] ?? defaultConfigs.settings[setting];
}
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);
};
});
},
};
const settings = configs.settings; const settings = configs.settings;
const styles = configs.styles; const styles = configs.styles;
const scrollSpeed = configs.animation.scrollSpeed; const scrollSpeed = configs.animation.scrollSpeed;
@ -136,739 +242,122 @@ function importStyles() {
}, 8000); }, 8000);
} }
function resetDB() { async function renderTaskListToDOM() {
localStorage.clear(); const tasks = await DBHandler.get("tasks");
setupDB();
}
function setupDB() {
if (!localStorage.tasks) {
localStorage.setItem(`tasks`, "{}");
}
}
// 0: limit has reached
// 1: duplicate task
// 2: invalid input
// string (single or multiple tasks): success
function addTask(username, userColor, task) {
const tasks = JSON.parse(localStorage.tasks);
if (!tasks[username]) {
tasks[username] = {
todos: [],
done: [],
userColor: userColor,
};
}
if (
incompleteTasksCount(username) >= settings.limit &&
settings.enableLimit
) {
return 0;
}
if (
tasks[username].todos.find(
(t) => t.text.toLowerCase() === task.toLowerCase()
)
) {
return 1;
}
// if task is whitespace or empty
if (!task || !task.trim()) {
return 2;
}
let tasksFailedToAdd = [];
// if task has commas
if (task.includes(",")) {
let tasksToAdd = task.split(",").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) {
tasks[username].todos.push({ text: t, done: false });
}
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return {
task: tasksToAdd.join('", "'),
tasksFailedToAdd: tasksFailedToAdd.join('", "') || "",
};
}
tasks[username].todos.push({ text: task, done: false });
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return {
task: task,
tasksFailedToAdd: "",
};
}
// 0: user has no tasks
// 1: invalid input
// removed task: success
function removeTask(username, task) {
const tasks = JSON.parse(localStorage.tasks);
if (!tasks[username] || tasks[username].todos.length === 0) {
return 0;
}
const incompleteTasks = tasks[username].todos.filter((t) => !t.done);
// match regex: integers separated by space e.g. '1 2 3 4'
if (task.match(/^(\d+ )*\d+$/)) {
// insert commas between integers
task = task.replace(/(\d+)/g, "$1,");
// remove trailing comma
task = task.slice(0, -1);
}
let removedTaskIndex = [];
let tasksRemoved = [];
let tasksFailedToRemove = [];
// check if there's a comma in the task (multiple tasks)
if (task.includes(",")) {
let tasksToRemove = task.split(",").map((t) => t.trim());
for (const t of tasksToRemove) {
let index = tasks[username].todos.findIndex(
(task) => task.text.toLowerCase() === t.toLowerCase()
);
if (index === -1) {
if (isInt(t)) {
index = parseInt(t) - 1; // ACTUAL INDEX
} else if (incompleteTasks.length === 1) {
index = tasks[username].todos.findIndex((t) => !t.done);
tasksRemoved.push(tasks[username].todos[index].text);
removedTaskIndex.push(index);
} else {
tasksFailedToRemove.push(t);
continue;
}
if (index < 0 || index > tasks[username].todos.length - 1) {
tasksFailedToRemove.push(t);
continue;
} else {
tasksRemoved.push(tasks[username].todos[index].text);
removedTaskIndex.push(index);
}
} else {
tasksRemoved.push(tasks[username].todos[index].text);
removedTaskIndex.push(index);
}
}
if (tasksRemoved.length === 0) {
return 1;
}
// sort removedTaskIndex in descending order
removedTaskIndex.sort((a, b) => b - a);
// remove tasks from tasks array
for (const index of removedTaskIndex) {
tasks[username].todos.splice(index, 1);
}
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return {
removedTasks: tasksRemoved.join('", "'),
failedTasks: tasksFailedToRemove.join('", "'),
};
} else {
let index = tasks[username].todos.findIndex(
(t) => t.text.toLowerCase() === task.toLowerCase()
);
if (index === -1) {
if (isInt(task)) {
index = parseInt(task) - 1; // ACTUAL INDEX
} else if (incompleteTasks.length === 1) {
index = tasks[username].todos.findIndex((t) => !t.done);
} else {
return 1;
}
if (index < 0 || index > tasks[username].todos.length - 1) {
return 1;
}
}
task = tasks[username].todos[index].text;
tasks[username].todos.splice(index, 1);
localStorage.setItem(`tasks`, JSON.stringify(tasks));
renderTaskListToDOM();
return {
removedTasks: task,
failedTasks: "",
};
}
}
// function to determine if string is integer
function isInt(value) {
return (
!isNaN(value) &&
parseInt(Number(value)) == value &&
!isNaN(parseInt(value, 10))
);
}
// 0: user has no tasks
// 1: success
function clearOwnDoneTasks(username) {
const tasks = JSON.parse(localStorage.tasks);
if (!tasks[username] || tasks[username].todos.length === 0) {
return 0;
}
let incompleteUserTasks = tasks[username].todos.filter((t) => !t.done);
// replace user's task with incomplete tasks
tasks[username].todos = incompleteUserTasks;
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return 1;
}
// 0: user has no tasks
// 1: invalid input
// task: succcess
function markTaskDone(username, task) {
const tasks = JSON.parse(localStorage.tasks);
// user does not have any tasks
if (!tasks[username] || tasks[username].todos.length === 0) {
return 0;
}
const incompleteTasks = tasks[username].todos.filter((t) => !t.done);
// match regex: integers separated by space e.g. '1 2 3 4'
if (task.match(/^(\d+ )*\d+$/)) {
// insert commas between integers
task = task.replace(/(\d+)/g, "$1,");
// remove trailing comma
task = task.slice(0, -1);
}
let tasksMarkedComplete = [];
let tasksFailedToComplete = [];
// check if there's a comma in the task (multiple tasks)
if (task.includes(",")) {
let tasksToMarkDone = task.split(",").map((t) => t.trim());
for (const t of tasksToMarkDone) {
let index = tasks[username].todos.findIndex(
(task) => task.text.toLowerCase() === t.toLowerCase()
);
if (index === -1) {
if (isInt(t)) {
index = parseInt(t) - 1; // ACTUAL INDEX
} else if (incompleteTasks.length === 1) {
index = tasks[username].todos.findIndex((t) => !t.done);
tasksMarkedComplete.push(tasks[username].todos[index].text);
} else {
tasksFailedToComplete.push(t);
continue;
}
if (index < 0 || index > tasks[username].todos.length - 1) {
tasksFailedToComplete.push(t);
continue;
} else {
tasksMarkedComplete.push(tasks[username].todos[index].text);
}
} else {
tasksMarkedComplete.push(tasks[username].todos[index].text);
}
tasks[username].todos[index].done = true;
}
if (tasksMarkedComplete.length === 0) {
return 1;
}
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return {
markedTasks: tasksMarkedComplete.join('", "'),
failedTasks: tasksFailedToComplete.join('", "'),
};
} else {
let index = tasks[username].todos.findIndex(
(t) => t.text.toLowerCase() === task.toLowerCase()
);
if (index === -1) {
if (isInt(task)) {
index = parseInt(task) - 1; // ACTUAL INDEX
} else if (incompleteTasks.length === 1) {
index = tasks[username].todos.findIndex((t) => !t.done);
} else {
return 1;
}
if (index < 0 || index > tasks[username].todos.length - 1) {
return 1;
}
}
task = tasks[username].todos[index].text;
tasks[username].todos[index].done = true;
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return {
markedTasks: task,
failedTasks: "",
};
}
}
function markAllTasksAsDone(username) {
const tasks = JSON.parse(localStorage.tasks);
// user does not have any tasks
if (!tasks[username] || tasks[username].todos.length === 0) {
return 0;
}
for (const task of tasks[username].todos) {
task.done = true;
}
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return 1;
}
function markTaskUndone(username, task) {
const tasks = JSON.parse(localStorage.tasks);
// user does not have any tasks
if (!tasks[username] || tasks[username].todos.length === 0) {
return 0;
}
const completedTasks = tasks[username].todos.filter((t) => t.done);
// match regex: integers separated by space e.g. '1 2 3 4'
if (task.match(/^(\d+ )*\d+$/)) {
// insert commas between integers
task = task.replace(/(\d+)/g, "$1,");
// remove trailing comma
task = task.slice(0, -1);
}
let tasksMarkedUndone = [];
let tasksFailedToMarkUndone = [];
// check if there's a comma in the task (multiple tasks)
if (task.includes(",")) {
let tasksToMarkUndone = task.split(",").map((t) => t.trim());
for (const t of tasksToMarkUndone) {
let index = tasks[username].todos.findIndex(
(task) => task.text.toLowerCase() === t.toLowerCase()
);
if (index === -1) {
// user input isn't the task name
if (isInt(t)) {
index = parseInt(t) - 1; // ACTUAL INDEX
} else {
tasksFailedToMarkUndone.push(t);
continue;
}
if (index < 0 || index > tasks[username].todos.length - 1) {
tasksFailedToMarkUndone.push(t);
continue;
} else {
tasksMarkedUndone.push(tasks[username].todos[index].text);
}
} else {
tasksMarkedUndone.push(tasks[username].todos[index].text);
}
tasks[username].todos[index].done = false;
}
if (tasksMarkedUndone.length === 0) {
return 1;
}
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return {
markedTasks: tasksMarkedUndone.join('", "'),
failedTasks: tasksFailedToMarkUndone.join('", "'),
};
} else {
let index = tasks[username].todos.findIndex(
(t) => t.text.toLowerCase() === task.toLowerCase()
);
if (index === -1) {
if (isInt(task)) {
index = parseInt(task) - 1; // ACTUAL INDEX
} else if (completedTasks.length === 1) {
index = tasks[username].todos.findIndex((t) => t.done);
} else {
return 1;
}
if (index < 0 || index > tasks[username].todos.length - 1) {
return 1;
}
tasks[username].todos[index].done = false;
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return {
markedTasks: tasks[username].todos[index].text,
failedTasks: "",
};
}
}
}
// 0: user has no tasks
// 1: invalid input
// [original task, new task]: succcess
function editTask(username, message) {
const tasks = JSON.parse(localStorage.tasks);
let noSpecifiedIndex = false;
if (!tasks[username] || tasks[username].todos.length === 0) {
return 0;
}
// example 'message': 1 new task
let index = parseInt(message.split(" ")[0]) - 1; // ACTUAL INDEX
let incompleteTaskCount = tasks[username].todos.filter(
(t) => !t.done
).length;
if (incompleteTaskCount === 1 && index !== 0) {
// find index of incomplete task
index = tasks[username].todos.findIndex((t) => !t.done);
noSpecifiedIndex = true;
}
if (isNaN(index)) {
return 1;
}
let newTask = message.split(" ").slice(1).join(" ");
if (noSpecifiedIndex) {
newTask = message;
}
// if newTask is empty or whitespace, return 1
if (!newTask || !newTask.trim()) {
return 1;
}
if (index < 0 || index > tasks[username].todos.length - 1) {
return 1;
}
let originalTask = tasks[username].todos[index].text;
tasks[username].todos[index].text = newTask;
localStorage.setItem(`tasks`, JSON.stringify(tasks));
if (!scrolling) {
renderTaskListToDOM();
}
return [originalTask, newTask];
}
// 0: user has no tasks
function checkTasks(username) {
const tasks = JSON.parse(localStorage.tasks);
// Go through keys of tasks, find match of lowercased username
username = Object.keys(tasks).find(
(user) => user.toLowerCase() === username.toLowerCase()
);
if (!tasks[username]) {
return 0;
}
// filter completed tasks
const incompleteTasks = tasks[username].todos.filter((t) => !t.done);
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 = `@${username} incomplete tasks (${incompleteTasks.length}) : `;
let taskIndex;
for (let i = 0; i < incompleteTasks.length; i++) {
// get index of task by task name
taskIndex =
tasks[username].todos.findIndex(
(t) =>
t.text.toLowerCase() ===
incompleteTasks[i].text.toLowerCase()
) + 1;
reply += `${taskIndex}. ${incompleteTasks[i].text} | `;
}
reply = reply.slice(0, -3);
// format completed tasks into string: 1. task 1 | 2. task 2 | 3. task 3...
// reply += `\n|| completed tasks (${completedTasks.length}) : `;
// for (let j = 0; j < completedTasks.length; j++) {
// taskIndex =
// tasks[username].todos.findIndex(
// (t) =>
// t.text.toLowerCase() ===
// completedTasks[j].text.toLowerCase()
// ) + 1;
// reply += `${taskIndex}. ${completedTasks[j].text} | `;
// }
// reply = reply.slice(0, -3);
return reply;
}
// 0: user has no tasks
function listTasks(username) {
const tasks = JSON.parse(localStorage.tasks);
if (!tasks[username]) {
return 0;
}
// filter completed tasks
const incompleteTasks = tasks[username].todos.filter((t) => !t.done);
if (incompleteTasks.length === 0) {
return 0;
}
// format incomplete tasks into string: 1. task 1 | 2. task 2 | 3. task 3...
let reply = `@${username} `;
let taskIndex;
for (let i = 0; i < incompleteTasks.length; i++) {
// get index of task by task name
taskIndex =
tasks[username].todos.findIndex(
(t) =>
t.text.toLowerCase() ===
incompleteTasks[i].text.toLowerCase()
) + 1;
reply += `${incompleteTasks[i].text}, `;
}
reply = reply.slice(0, -2);
return reply;
}
function incompleteTasksCount(username) {
const tasks = JSON.parse(localStorage.tasks);
if (!tasks[username]) {
return 0;
}
return tasks[username].todos.filter((t) => !t.done).length;
}
// 0: user has no tasks
function clearUserTasks(username) {
const tasks = JSON.parse(localStorage.tasks);
// find username where lowercase matches username
username = Object.keys(tasks).find(
(user) => user.toLowerCase() === username.toLowerCase()
);
if (!username) {
return 0;
}
tasks[username].todos = [];
localStorage.setItem(`tasks`, JSON.stringify(tasks));
}
function clearAllDoneTasks() {
const tasks = JSON.parse(localStorage.tasks);
for (const user in tasks) {
tasks[user].todos = tasks[user].todos.filter((t) => !t.done);
}
cancelAnimation();
checkToAnimate();
localStorage.setItem(`tasks`, JSON.stringify(tasks));
renderTaskListToDOM();
}
function clearAllTasks() {
resetDB();
cancelAnimation();
checkToAnimate();
renderTaskListToDOM();
}
function clearAllExceptStreamer(streamer) {
// clear all tasks except for broadcasters
const tasks = JSON.parse(localStorage.tasks);
for (const user in tasks) {
if (user.toLowerCase() !== streamer.toLowerCase()) {
tasks[user].todos = [];
}
}
localStorage.setItem(`tasks`, JSON.stringify(tasks));
clearAllDoneTasks();
cancelAnimation();
checkToAnimate();
renderTaskListToDOM();
}
function renderTaskListToDOM() {
const tasks = JSON.parse(localStorage.tasks);
const taskContainers = document.querySelectorAll(".task-container"); const taskContainers = document.querySelectorAll(".task-container");
taskContainers.forEach((taskList) => { taskContainers.forEach((taskList) => {
taskList.innerHTML = ""; taskList.innerHTML = "";
let totalTaskCount = 0; let totalTaskCount = 0;
let completedTasksCount = 0; let completedTasksCount = 0;
for (const user in tasks) {
const userTasks = tasks[user]; if (getSetting("showStreamersTasksOnly")) {
if (userTasks.todos.length === 0) { const streamer = getSetting("streamer");
continue; if (tasks[streamer]) {
({ totalTaskCount, completedTasksCount } = handleTasks(
tasks,
streamer,
taskList,
totalTaskCount,
completedTasksCount
));
} }
} else {
const userColor = userTasks.userColor; for (const user in tasks) {
if (user.toLowerCase() === "id" || !tasks[user].todos) continue;
const taskDiv = document.createElement("div"); ({ totalTaskCount, completedTasksCount } = handleTasks(
taskDiv.classList.add("task"); tasks,
taskList.appendChild(taskDiv); user,
taskList,
const usernameDiv = document.createElement("div"); totalTaskCount,
usernameDiv.classList.add("username"); completedTasksCount
usernameDiv.innerText = user; ));
if (styles.usernameColor === "") {
usernameDiv.style.color = userColor;
} else {
usernameDiv.style.color = styles.usernameColor;
}
taskDiv.appendChild(usernameDiv);
const olListDiv = document.createElement("ol");
olListDiv.classList.add("user-tasks");
taskDiv.appendChild(olListDiv);
for (const task of userTasks.todos) {
const taskElement = document.createElement("li");
taskElement.classList.add("todo");
totalTaskCount++;
if (task.done) {
taskElement.classList.add("done");
completedTasksCount++;
}
taskElement.innerText = task.text;
olListDiv.appendChild(taskElement);
} }
} }
document.querySelector( updateTaskCount(totalTaskCount, completedTasksCount);
".task-count"
).innerText = `${completedTasksCount}/${totalTaskCount}`;
}); });
checkToAnimate();
await DBHandler.set("tasks", tasks);
await checkToAnimate();
}
function handleTasks(
tasks,
user,
taskList,
totalTaskCount,
completedTasksCount
) {
const userTasks = tasks[user];
if (userTasks.todos.length === 0) {
delete tasks[user];
} else {
const taskDiv = createTaskDiv(userTasks, user);
taskList.appendChild(taskDiv);
for (const task of userTasks.todos) {
const taskElement = createTaskElement(task);
taskDiv.querySelector(".user-tasks").appendChild(taskElement);
totalTaskCount++;
if (task.done) completedTasksCount++;
}
}
return { totalTaskCount, completedTasksCount };
}
function createTaskDiv(userTasks, username) {
const taskDiv = document.createElement("div");
taskDiv.classList.add("task");
const usernameDiv = document.createElement("div");
usernameDiv.classList.add("username");
usernameDiv.innerText = username;
usernameDiv.style.color = styles.usernameColor || userTasks.userColor;
taskDiv.appendChild(usernameDiv);
const olListDiv = document.createElement("ol");
olListDiv.classList.add("user-tasks");
taskDiv.appendChild(olListDiv);
return taskDiv;
}
function createTaskElement(task) {
const taskElement = document.createElement("li");
taskElement.classList.add("todo");
taskElement.innerText = task.text;
if (task.done) taskElement.classList.add("done");
else if (task.focus) taskElement.classList.add("focus");
return taskElement;
}
function updateTaskCount(totalTaskCount, completedTasksCount) {
if (taskListMemory.doneTaskCount > completedTasksCount) {
completedTasksCount = taskListMemory.doneTaskCount;
totalTaskCount = taskListMemory.totalTaskCount;
}
if (totalTaskCount < completedTasksCount) {
totalTaskCount = completedTasksCount;
}
document.querySelector(
".task-count"
).innerText = `${completedTasksCount}/${totalTaskCount}`;
taskListMemory.doneTaskCount = completedTasksCount;
taskListMemory.totalTaskCount = totalTaskCount;
} }
function sleep(ms) { function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
// unit tests
async function tests1() {
let listOfStreamers = [
`cloudydayzzz`,
`berryspace`,
`MohFocus`,
`xeno_hiraeth`,
`euphie___`,
`unknownnie`,
`theyolotato`,
// `charliosaurus`,
// `jutstreams`,
// `mikewhatwhere`,
// `studypaws`,
// `pcc_lanezzz`,
// `workwithjandj`,
// `studylena`,
];
for (let i = 0; i < listOfStreamers.length; i++) {
addTask(
listOfStreamers[i],
"#ffc0cb",
`task 1, task 2, task 3, task 4, task 5`
);
await sleep(1000);
markTaskDone(listOfStreamers[i], `1, 2, 3, 4, 5`);
await sleep(1000);
}
}
async function checkToAnimate() { async function checkToAnimate() {
// compare task container height and task wrapper height // compare task container height and task wrapper height
@ -940,10 +429,10 @@ function addAnimationListeners() {
} }
} }
function animationFinished() { async function animationFinished() {
scrolling = false; scrolling = false;
renderTaskListToDOM(); await renderTaskListToDOM();
checkToAnimate(); await checkToAnimate();
} }
function cancelAnimation() { function cancelAnimation() {
@ -954,11 +443,17 @@ function cancelAnimation() {
secondaryAnimation.cancel(); secondaryAnimation.cancel();
} }
scrolling = false; scrolling = false;
console.log("animation cancelled");
} }
(function () { DBHandler.open()
setupDB(); .then(async () => {
importStyles(); importStyles();
renderTaskListToDOM(); await renderTaskListToDOM();
})();
setInterval(async () => {
await renderTaskListToDOM();
}, 5000);
})
.catch((error) => {
console.error("Error opening database:", error);
});

View file

@ -43,10 +43,6 @@
box-sizing: border-box; box-sizing: border-box;
} }
body {
background-color: gray;
}
#main-container { #main-container {
width: 100%; width: 100%;
height: 100vh; height: 100vh;
@ -109,6 +105,7 @@ body {
} }
#main-container .task-wrapper .task-container .task .username { #main-container .task-wrapper .task-container .task .username {
font-weight: var(--username-font-weight); font-weight: var(--username-font-weight);
font-size: var(--username-font-size);
} }
#main-container .task-wrapper .task-container .task .user-tasks { #main-container .task-wrapper .task-container .task .user-tasks {
list-style-position: inside; list-style-position: inside;
@ -120,4 +117,13 @@ body {
} }
#main-container .task-wrapper .task-container .task .user-tasks .done { #main-container .task-wrapper .task-container .task .user-tasks .done {
text-decoration: line-through; text-decoration: line-through;
color: var(--done-task-font-color);
}
#main-container .task-wrapper .task-container .task .user-tasks .focus {
background: var(--focus-task-background-color);
color: var(--focus-task-font-color);
padding: var(--focus-task-vertical-padding) var(--focus-task-horizontal-padding);
border-radius: var(--focus-task-border-radius);
width: -moz-fit-content;
width: fit-content;
}/*# sourceMappingURL=taskList.css.map */ }/*# sourceMappingURL=taskList.css.map */