organised js

This commit is contained in:
liyunze 2026-08-07 12:20:16 +10:00
parent 6efe723361
commit 9b9e301076
16 changed files with 1201 additions and 1315 deletions

View file

@ -9,12 +9,21 @@
<script src="./auth.js"></script> <script src="./auth.js"></script>
<script src="./configs.js"></script> <script src="./configs.js"></script>
<script defer src="./scripts/defaultConfigs.js"></script> <script defer src="./scripts/core/defaultConfigs.js"></script>
<script defer src="./scripts/constants.js"></script> <script defer src="./scripts/core/constants.js"></script>
<script defer src="./scripts/functions.js"></script> <script defer src="./scripts/core/functions.js"></script>
<script defer src="./scripts/processCommand.js"></script> <script defer src="./scripts/core/db.js"></script>
<script defer src="./scripts/index.js"></script> <script defer src="./scripts/core/utils.js"></script>
<script defer src="./scripts/taskList.js"></script> <script defer src="./scripts/ui/styles.js"></script>
<script defer src="./scripts/tasks/taskMaster.js"></script>
<script defer src="./scripts/tasks/counts.js"></script>
<script defer src="./scripts/ui/renderer.js"></script>
<script defer src="./scripts/tasks/taskCleanup.js"></script>
<script defer src="./scripts/tasks/taskCore.js"></script>
<script defer src="./scripts/testing/tests.js"></script>
<script defer src="./scripts/chat/processCommand.js"></script>
<script defer src="./scripts/chat/index.js"></script>
<script defer src="./scripts/ui/taskList.js"></script>
<link rel="stylesheet" type="text/css" href="./styles/taskList.css" /> <link rel="stylesheet" type="text/css" href="./styles/taskList.css" />
<link <link

View file

@ -1,6 +1,4 @@
const commands = configs.commands; const commands = configs.commands;
const openQuote = getSetting("openQuote");
const closeQuote = getSetting("closeQuote");
async function processCommand(user, command, message, flags, extra) { async function processCommand(user, command, message, flags, extra) {
params = { params = {
@ -124,7 +122,6 @@ async function processCommand(user, command, message, flags, extra) {
return; return;
} }
// task unfinished
let unfinishedResponse = getResponse("taskUnfinished"); let unfinishedResponse = getResponse("taskUnfinished");
params.task = `${openQuote}${unfinishRequest.body.markedTasks}${closeQuote}`; params.task = `${openQuote}${unfinishRequest.body.markedTasks}${closeQuote}`;
@ -143,7 +140,6 @@ async function processCommand(user, command, message, flags, extra) {
return; return;
} }
// task next
let nextResponse = getResponse("taskNext"); let nextResponse = getResponse("taskNext");
params.oldTask = `${openQuote}${nextRequest.body.oldTask}${closeQuote}`; params.oldTask = `${openQuote}${nextRequest.body.oldTask}${closeQuote}`;
@ -158,7 +154,6 @@ async function processCommand(user, command, message, flags, extra) {
return; return;
} }
// task logged
let loggedResponse = await getResponse("taskLogged"); let loggedResponse = await getResponse("taskLogged");
params.task = `${openQuote}${logRequest.body.task}${closeQuote}`; params.task = `${openQuote}${logRequest.body.task}${closeQuote}`;
@ -179,7 +174,6 @@ async function processCommand(user, command, message, flags, extra) {
return; return;
} }
// task focused
let focusedResponse = getResponse("nowTask"); let focusedResponse = getResponse("nowTask");
params.task = `${openQuote}${focusRequest.body.focusedTask}${closeQuote}`; params.task = `${openQuote}${focusRequest.body.focusedTask}${closeQuote}`;
@ -193,7 +187,6 @@ async function processCommand(user, command, message, flags, extra) {
return; return;
} }
// task focused
let focusedResponse = getResponse("focusedTask"); let focusedResponse = getResponse("focusedTask");
params.task = `${openQuote}${focusedRequest.body.focusedTask}${closeQuote}`; params.task = `${openQuote}${focusedRequest.body.focusedTask}${closeQuote}`;
@ -207,7 +200,6 @@ async function processCommand(user, command, message, flags, extra) {
return; return;
} }
// task unfocused
let unfocusedResponse = getResponse("clearFocused"); let unfocusedResponse = getResponse("clearFocused");
respond(unfocusedResponse, params, extra.id); respond(unfocusedResponse, params, extra.id);
@ -220,15 +212,12 @@ async function processCommand(user, command, message, flags, extra) {
if (message === "") { if (message === "") {
let checkRequest = await checkTasks(user, completed); let checkRequest = await checkTasks(user, completed);
if (checkRequest.status !== 200) { if (checkRequest.status !== 200) {
// no tasks
respond(checkRequest.body.error, params, extra.id); respond(checkRequest.body.error, params, extra.id);
return; return;
} }
// array of filtered tasks
let filteredTasks = checkRequest.body.reply.split(" | "); let filteredTasks = checkRequest.body.reply.split(" | ");
// respond every 10 tasks
for (let i = 0; i < filteredTasks.length; i += 10) { for (let i = 0; i < filteredTasks.length; i += 10) {
let tasks = filteredTasks.slice(i, i + 10); let tasks = filteredTasks.slice(i, i + 10);
params.tasks = tasks params.tasks = tasks
@ -251,17 +240,14 @@ async function processCommand(user, command, message, flags, extra) {
let checkRequest = await checkTasks(mentioned, completed); let checkRequest = await checkTasks(mentioned, completed);
// array of incomplete tasks
let filteredTasks = checkRequest.body.reply.split(" | "); let filteredTasks = checkRequest.body.reply.split(" | ");
if (checkRequest.status !== 200) { if (checkRequest.status !== 200) {
// no tasks
respond(getResponse("noTaskA"), params, extra.id); respond(getResponse("noTaskA"), params, extra.id);
return; return;
} }
// respond every 10 tasks
for (let i = 0; i < filteredTasks.length; i += 10) { for (let i = 0; i < filteredTasks.length; i += 10) {
let tasks = filteredTasks.slice(i, i + 10); let tasks = filteredTasks.slice(i, i + 10);
params.tasks = tasks params.tasks = tasks
@ -293,7 +279,6 @@ async function processCommand(user, command, message, flags, extra) {
params.mentioned = mentioned; params.mentioned = mentioned;
if (clearUserTaskResponse.status === 0) { if (clearUserTaskResponse.status === 0) {
// no tasks
respond(getResponse("noTaskA"), params, extra.id); respond(getResponse("noTaskA"), params, extra.id);
return; return;
} }
@ -322,7 +307,6 @@ async function processCommand(user, command, message, flags, extra) {
let clearOwnDoneResponse = await clearOwnDoneTasks(user); let clearOwnDoneResponse = await clearOwnDoneTasks(user);
if (clearOwnDoneResponse.status !== 200) { if (clearOwnDoneResponse.status !== 200) {
// no tasks
respond(getResponse("noTask"), params, extra.id); respond(getResponse("noTask"), params, extra.id);
return; return;
} }
@ -352,7 +336,6 @@ async function processCommand(user, command, message, flags, extra) {
let listTaskResponse = await listTasks(user); let listTaskResponse = await listTasks(user);
if (listTaskResponse.status !== 200) { if (listTaskResponse.status !== 200) {
// no tasks
respond(listTaskResponse.body.error, params, extra.id); respond(listTaskResponse.body.error, params, extra.id);
return; return;
} }
@ -367,26 +350,20 @@ async function processCommand(user, command, message, flags, extra) {
let args = message.split(" "); let args = message.split(" ");
let anotherUser = true; let anotherUser = true;
// separate separator from mentioned user
if (args[0].length === 1) { if (args[0].length === 1) {
// args[0] is separator
separator = args[0]; separator = args[0];
args.shift(); args.shift();
} else if (args[1] && args[1].length === 1) { } else if (args[1] && args[1].length === 1) {
// args[1] is separator
separator = args[1]; separator = args[1];
args.splice(1, 1); args.splice(1, 1);
} else { } else {
// default, no separator specified
} }
let mentioned = args.join(" ").replace("@", ""); let mentioned = args.join(" ").replace("@", "");
if (args.length === 0) { if (args.length === 0) {
// no user specified
mentioned = user; mentioned = user;
anotherUser = false; anotherUser = false;
// noTaskA
} }
if (mentioned.toLowerCase() === "id") { if (mentioned.toLowerCase() === "id") {
@ -397,7 +374,6 @@ async function processCommand(user, command, message, flags, extra) {
let listTaskResponse = await listTasks(mentioned, separator); let listTaskResponse = await listTasks(mentioned, separator);
if (listTaskResponse.status !== 200 && anotherUser) { if (listTaskResponse.status !== 200 && anotherUser) {
// no tasks
respond(getResponse("noTaskA"), params, extra.id); respond(getResponse("noTaskA"), params, extra.id);
return; return;
} else if (listTaskResponse.status !== 200 && !anotherUser) { } else if (listTaskResponse.status !== 200 && !anotherUser) {
@ -612,7 +588,6 @@ async function processCommand(user, command, message, flags, extra) {
return; return;
} }
// check if message is a number using regex
if (!/^\d+$/.test(message)) { if (!/^\d+$/.test(message)) {
respond(getResponse("invalidNumber"), params, extra.id); respond(getResponse("invalidNumber"), params, extra.id);
return; return;

165
scripts/core/db.js Normal file
View file

@ -0,0 +1,165 @@
/*
DB structure:
- tasks
- [username]:
- todos (array)
- [{text, done, focus}, {text, done, focus}]
- userColor
- counts
- users
- [username]:
- completeCount
- points
- totalCompleteCount
- taskmaster
- users
- [username]:
- taskMasterCompleteCount
- startDate
*/
let taskListMemory = {
doneTaskCount: 0,
totalTaskCount: 0,
};
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() {
let keys = Object.keys(localStorage);
for (let key of keys) {
let value = JSON.parse(localStorage.getItem(key));
await DBHandler.set(key, value);
}
console.log("Transfer complete");
await renderTaskListToDOM();
}
function clearLocalStorage() {
localStorage.removeItem("tasks");
localStorage.removeItem("counts");
}
async function resetDB() {
await DBHandler.clear();
await setupDB();
}
async function setupDB() {
const keys = ["tasks", "counts", "taskmaster"];
const defaultValues = [
{},
{ users: {} },
{
users: {},
startDate: new Date(),
taskMasterCompleteCount: 0,
},
];
for (let i = 0; i < keys.length; i++) {
let value = await DBHandler.get(keys[i]);
if (!value) {
value = defaultValues[i];
}
await DBHandler.set(keys[i], value);
}
return;
}
function clearMemory() {
taskListMemory = {
doneTaskCount: 0,
totalTaskCount: 0,
};
}

68
scripts/core/utils.js Normal file
View file

@ -0,0 +1,68 @@
function isInt(value) {
return (
!isNaN(value) &&
parseInt(Number(value)) === value &&
!isNaN(parseInt(value, 10))
);
}
function createErrorResponse(errorMessage, errorType, status = 0) {
return {
status: status,
body: {
"error message": errorMessage,
error: errorType,
},
};
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Converts a camelCase string to a CSS variable format.
*
* @param {string} name - The camelCase string to convert.
* @returns {string} The converted string in CSS variable format.
*/
function convertToCSSVar(name) {
let cssVar = name.replace(/([A-Z])/g, "-$1").toLowerCase();
return `--${cssVar}`;
}
/**
* Converts a hexadecimal color value to its RGB equivalent.
*
* @param {string} hex - The hexadecimal color value. Can be 3 or 6 digits, with or without a leading '#'.
* @returns {string} The RGB color value as a string in the format 'r, g, b'.
*/
function hexToRgb(hex) {
// remove # if present
if (hex[0] === "#") {
hex = hex.slice(1);
}
let r = 0,
g = 0,
b = 0;
if (hex.length == 3) {
// 3 digits
r = "0x" + hex[0] + hex[0];
g = "0x" + hex[1] + hex[1];
b = "0x" + hex[2] + hex[2];
} else if (hex.length == 6) {
// 6 digits
r = "0x" + hex[0] + hex[1];
g = "0x" + hex[2] + hex[3];
b = "0x" + hex[4] + hex[5];
}
// integer value of rgb
r = +r;
g = +g;
b = +b;
return `${r}, ${g}, ${b}`;
}

402
scripts/tasks/counts.js Normal file
View file

@ -0,0 +1,402 @@
/**
* Counts the number of incomplete tasks for a given user.
*
* @param {string} username - The name of the user whose tasks are to be counted.
* @returns {number} The number of incomplete tasks for the user.
*/
async function incompleteTasksCount(username) {
const tasks = await DBHandler.get("tasks");
if (!tasks[username]) {
return 0;
}
return tasks[username].todos.filter((t) => !t.done).length;
}
/**
* Counts the number of completed tasks for a given user.
*
* @param {string} username - The name of the user whose tasks are to be counted.
* @returns {number} The number of completed tasks for the user.
*/
async function completedTasksCount(username) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
return 0;
}
return counts.users[username.toLowerCase()].completeCount;
}
/**
* Gets the total number of tasks for a given user.
*
* @param {string} username - The name of the user whose tasks are to be counted.
* @returns {number} The total number of tasks for the user.
*/
async function getUserTotalTaskCount(username) {
return (
(await incompleteTasksCount(username)) +
(await completedTasksCount(username))
);
}
/**
* Calculates the total points for a given user based on the number of completed tasks and the points per task setting.
*
* @param {string} username - The name of the user whose points are to be calculated.
* @returns {number} The total points for the user.
*/
async function calculatePoints(username) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
return 0;
}
return (
counts.users[username.toLowerCase()].completeCount *
getSetting("pointsPerTask")
);
}
/**
* Retrieves the points of a specific user from local storage.
*
* @param {string} username - The name of the user.
* @returns {number} The points of the user. If the user does not exist, returns 0.
*/
async function getUserPoints(username) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
return 0;
}
return counts.users[username.toLowerCase()].points;
}
/**
* Adds a specified value to the completed task count for a given user and the total completed task count.
*
* @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.
*/
async function addDoneCount(username, value) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
counts.users[username.toLowerCase()] = {
completeCount: 0,
};
}
if (!counts.totalCompleteCount) {
counts.totalCompleteCount = 0;
}
counts.users[username.toLowerCase()].completeCount += value;
counts.totalCompleteCount += value;
taskListMemory.doneTaskCount += value;
// check if user has points
if (!counts.users[username.toLowerCase()].points) {
counts.users[username.toLowerCase()].points = 0;
}
// add to points
counts.users[username.toLowerCase()].points +=
value * getSetting("pointsPerTask");
// add to taskmaster
addCountToTaskMasterUser(username, value);
await DBHandler.set("counts", counts);
}
/**
* Sets the completed task count for a given user.
*
* @param {string} username
* @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.
*/
async function setUserCompleteCount(username, value) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
counts.users[username.toLowerCase()] = {
completeCount: 0,
};
}
counts.users[username.toLowerCase()].completeCount = parseInt(value);
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: `Successfully set ${username}'s complete count to ${value}`,
},
};
}
/**
* Sets the total count of completed tasks in local storage.
*
* @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.
*/
async function setTotalCompleteCount(value) {
const counts = await DBHandler.get("counts");
counts.totalCompleteCount = parseInt(value);
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: `Successfully set total complete count to ${value}`,
},
};
}
/**
* Adds a specified number of points to a user's total in local storage.
*
* @param {string} username - The name of the user.
* @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.
*/
async function addPoints(username, value) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
counts.users[username.toLowerCase()] = {
points: 0,
};
}
if (!counts.users[username.toLowerCase()].points) {
counts.users[username.toLowerCase()].points = 0;
}
counts.users[username.toLowerCase()].points += value;
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: `Successfully added ${value} points to ${username}`,
},
};
}
/**
* Sets the points for a specific user in local storage.
*
* @param {string} username - The name of the user.
* @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.
*/
async function setUserPoints(username, pointsCount) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
counts.users[username.toLowerCase()] = {
points: 0,
};
}
counts.users[username.toLowerCase()].points = pointsCount;
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: `Successfully set ${username}'s points to ${pointsCount}`,
},
};
}
/**
* 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 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.
*/
async function reducePoints(username, value) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
counts.users[username.toLowerCase()] = {
points: 0,
};
}
if (!counts.users[username.toLowerCase()].points) {
counts.users[username.toLowerCase()].points = 0;
}
counts.users[username.toLowerCase()].points -= parseInt(value);
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: `Successfully reduced ${username}'s points by ${value}`,
},
};
}
/**
* Retrieves the total count of tasks from the board.
*
* @returns {number} The total count of tasks. If no tasks are found, returns 0.
*/
async function getBoardTotalTaskCount() {
const counts = await DBHandler.get("counts");
if (!counts.totalCompleteCount) {
return 0;
}
return counts.totalCompleteCount;
}
/**
* Sets the task count for a specific user in local storage and returns a success message.
*
* @param {string} username - The name of the user.
* @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.
*/
async function setUserTaskCount(username, value) {
const counts = await DBHandler.get("counts");
if (!counts.users[username.toLowerCase()]) {
counts.users[username.toLowerCase()] = {
completeCount: 0,
};
}
if (!counts.users[username.toLowerCase()].completeCount) {
counts.users[username.toLowerCase()].completeCount = 0;
}
counts.users[username.toLowerCase()].completeCount = parseInt(value);
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: `Successfully set ${username}'s task count to ${value}`,
},
};
}
/**
* Synchronizes the points of each user with their task count in local storage. After the synchronization, the function returns an object with a status of 200 and a success message.
*
* @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.
*/
async function syncPointsToCount() {
const counts = await DBHandler.get("counts");
for (const user in counts.users) {
counts.users[user].points = calculatePoints(user);
}
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: "Successfully synced counts to points",
},
};
}
/**
* Gets the leaderboard of users based on task count.
*
* @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.
*/
async function leaderboardTaskCount(limit) {
const counts = await DBHandler.get("counts");
let leaderboardArray = [];
for (const user in counts.users) {
let userTaskCount = await completedTasksCount(user);
leaderboardArray.push({
username: user,
taskCount: userTaskCount,
});
}
leaderboardArray.sort((a, b) => b.taskCount - a.taskCount);
// shorten to limit
leaderboardArray = leaderboardArray.slice(0, limit);
let leaderboardstring = "";
leaderboardArray.map((user, index) => {
leaderboardstring += `${index + 1}. @${user.username} : ${
user.taskCount
} | `;
});
leaderboardstring = leaderboardstring.slice(0, -3);
return {
status: 200,
body: {
leaderboard: leaderboardstring,
},
};
}
/**
* Resets the total count of completed tasks in the board to zero.
*/
async function resetBoardCount() {
const counts = await DBHandler.get("counts");
counts.totalCompleteCount = 0;
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: "Successfully reset board count",
},
};
}
/**
* Resets the task count for all users.
*/
async function resetUsersCount() {
const counts = await DBHandler.get("counts");
counts.users = {};
await DBHandler.set("counts", counts);
return {
status: 200,
body: {
message: "Successfully reset users count",
},
};
}

View file

@ -0,0 +1,140 @@
/**
* Clears all completed tasks for a given user. If the tasks are successfully cleared, the function returns an object with a status of 200. If the tasks are not cleared due to the user having no tasks, the function returns an object with a status indicating the error and an error message.
*
* @param {string} username - The name of the user whose 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.
*/
async function clearOwnDoneTasks(username) {
const tasks = await DBHandler.get("tasks");
if (!tasks[username] || tasks[username].todos.length === 0) {
return createErrorResponse(
`@${username} has no tasks`,
getResponse("noTask")
);
}
let incompleteUserTasks = tasks[username].todos.filter((t) => !t.done);
// replace user's task with incomplete tasks
tasks[username].todos = incompleteUserTasks;
await DBHandler.set("tasks", tasks);
if (!scrolling) {
await renderTaskListToDOM();
}
return {
status: 200,
};
}
/**
* Clears all tasks for a given user. If the tasks are successfully cleared, the function returns an object with a status of 200.
* If the tasks are not cleared due to the user having no tasks, the function returns an object with a status indicating the error and an error message.
*
* @param {string} username - The name of the user whose tasks are to be 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.
*/
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()
);
if (!username) {
return {
status: 0,
body: {
"error message": `@${username} has no tasks`,
error: getResponse("noTask"),
},
};
}
tasks[username].todos = [];
await DBHandler.set("tasks", tasks);
if (!scrolling) {
await renderTaskListToDOM();
}
return {
status: 200,
};
}
/**
* Clears all completed tasks for all users. If the tasks are successfully cleared, the function returns an object with a status of 200. If there are no tasks to clear, the function still returns an object with a status of 200 as it's considered a successful operation.
*
* @returns {Object} An object with a status. The status is 200 if the tasks are successfully cleared.
*/
async function clearAllDoneTasks() {
const tasks = await DBHandler.get("tasks");
for (const user in tasks) {
if (!tasks[user].todos) continue;
if (user.toLowerCase() === "id") continue;
tasks[user].todos = tasks[user].todos.filter((t) => !t.done);
}
cancelAnimation();
await DBHandler.set("tasks", tasks);
await renderTaskListToDOM();
return {
status: 200,
};
}
async function clearAllTasks() {
await DBHandler.set("tasks", {});
await setupDB();
cancelAnimation();
await renderTaskListToDOM();
return {
status: 200,
};
}
async function clearAll() {
await resetDB();
cancelAnimation();
checkToAnimate();
await renderTaskListToDOM();
return {
status: 200,
};
}
/**
* Clears all tasks from the local storage except for those belonging to specified streamer usernames.
* After clearing, it also clears all done tasks, cancels any ongoing animation and re-renders the task list to the DOM.
*
* @param {string[]} streamerUsername - An array of streamer usernames whose tasks should not be cleared.
* @returns {Object} An object with a status property indicating the success of the operation (200 for success).
*/
async function clearAllExceptStreamer(streamerUsername) {
const tasks = await DBHandler.get("tasks");
for (const user in tasks) {
if (user.toLowerCase() === "id") continue;
if (user.toLowerCase() !== streamerUsername.toLowerCase()) {
tasks[user].todos = [];
}
}
await DBHandler.set("tasks", tasks);
await clearAllDoneTasks();
cancelAnimation();
await renderTaskListToDOM();
return {
status: 200,
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
/**
* adds a count to user for 'taskmaster'
*
* @param {string} username
*/
async function addCountToTaskMasterUser(username, count) {
const taskmaster = await DBHandler.get("taskmaster");
if (!taskmaster.users[username.toLowerCase()]) {
taskmaster.users[username.toLowerCase()] = {
taskMasterCompleteCount: 0,
};
}
taskmaster.users[username.toLowerCase()].taskMasterCompleteCount +=
parseInt(count);
await DBHandler.set("taskmaster", taskmaster);
}
/**
* gets the count of a user from 'taskmaster'
*
* @param {string} username
* @returns {number} the count of the user
*/
async function getUserTaskMasterCount(username) {
const taskmaster = await DBHandler.get("taskmaster");
if (!taskmaster.users[username.toLowerCase()]) {
return 0;
}
return taskmaster.users[username.toLowerCase()].taskMasterCompleteCount;
}
async function getTaskMasterChampion() {
const taskmaster = await DBHandler.get("taskmaster");
if (!taskmaster.users) {
return null;
}
let champion = {
username: "",
count: 0,
};
for (const user in taskmaster.users) {
if (
user.toLowerCase() !== auth.channel.toLowerCase() &&
taskmaster.users[user].taskMasterCompleteCount > champion.count
) {
champion.username = user;
champion.count = taskmaster.users[user].taskMasterCompleteCount;
}
}
if (champion.count === 0) {
return null;
}
return champion;
}
async function resetTaskMaster() {
await DBHandler.set("taskmaster", {
users: {},
startDate: new Date(),
totalCompleteCount: 0,
});
}

27
scripts/testing/tests.js Normal file
View file

@ -0,0 +1,27 @@
function randomTasks(num) {
let tasks = [];
for (let i = 0; i < num; i++) {
tasks.push(`task ${i + 1}`);
}
return tasks.join(", ");
}
async function tests() {
let listOfStreamers = [
`followRythonDev1`,
`followRythonDev2`,
`followRythonDev3`,
`followRythonDev4`,
`followRythonDev5`,
`followRythonDev6`,
`followRythonDev7`,
];
for (let i = 0; i < listOfStreamers.length; i++) {
let randomNum = Math.floor(Math.random() * 10) + 1;
addTask(listOfStreamers[i], "#ffc0cb", randomTasks(randomNum));
await sleep(1000);
}
}

180
scripts/ui/renderer.js Normal file
View file

@ -0,0 +1,180 @@
async function renderTaskListToDOM() {
const tasks = await DBHandler.get("tasks");
const taskContainers = document.querySelectorAll(".task-container");
let hasTasks = false;
taskContainers.forEach((taskList) => {
taskList.innerHTML = "";
let totalTaskCount = 0;
let completedTasksCount = 0;
for (const user in tasks) {
if (user.toLowerCase() === "id") continue;
if (!tasks[user].todos) continue;
const userTasks = tasks[user];
if (userTasks.todos.length === 0) {
// remove user from tasks
delete tasks[user];
continue;
}
const userColor = userTasks.userColor;
const taskDiv = document.createElement("div");
taskDiv.classList.add("task");
taskList.appendChild(taskDiv);
const usernameDiv = document.createElement("div");
usernameDiv.classList.add("username");
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++;
} else if (task.focus) {
taskElement.classList.add("focus");
}
const taskContent = document.createElement("div");
taskContent.classList.add("content");
taskContent.innerText = task.text;
taskElement.appendChild(taskContent);
olListDiv.appendChild(taskElement);
hasTasks = true;
}
}
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;
});
if (getSetting("hideWhenNoTasks") && !hasTasks) {
document.querySelector("#main-container").style.opacity = "0";
visible = false;
} else if (getSetting("hideWhenNoTasks") && hasTasks && !visible) {
document.querySelector("#main-container").style.opacity = "1";
visible = true;
}
await DBHandler.set("tasks", tasks);
checkToAnimate();
}
async function checkToAnimate() {
let taskContainer = document.querySelector(".task-container");
let taskContainerHeight = taskContainer.scrollHeight;
let taskWrapper = document.querySelector(".task-wrapper");
let taskWrapperHeight = taskWrapper.clientHeight;
let secondary = document.querySelector(".secondary");
if (taskContainerHeight > taskWrapperHeight && !scrolling) {
if (!scrolling) {
secondary.style.display = "flex";
let finalHeight =
taskContainerHeight + configs.animation.gapBetweenScrolls;
let keyframes = [
{ transform: `translateY(0)` },
{ transform: `translateY(-${finalHeight}px)` },
];
let scrollingSpeed = (finalHeight / scrollSpeed) * 1000;
let options = {
duration: scrollingSpeed,
iterations: 1,
easing: "linear",
};
primaryAnimation = document
.querySelector(".primary")
.animate(keyframes, options);
secondaryAnimation = document
.querySelector(".secondary")
.animate(keyframes, options);
primaryAnimation.play();
secondaryAnimation.play();
scrolling = true;
addAnimationListeners();
}
} else if (!scrolling) {
secondary.style.display = "none";
if (primaryAnimation) {
primaryAnimation.cancel();
}
if (secondaryAnimation) {
secondaryAnimation.cancel();
}
scrolling = false;
}
}
function addAnimationListeners() {
if (primaryAnimation) {
primaryAnimation.addEventListener("finish", animationFinished);
primaryAnimation.addEventListener("cancel", animationFinished);
}
}
async function animationFinished() {
scrolling = false;
await renderTaskListToDOM();
checkToAnimate();
}
function cancelAnimation() {
if (primaryAnimation) {
primaryAnimation.cancel();
}
if (secondaryAnimation) {
secondaryAnimation.cancel();
}
scrolling = false;
}

94
scripts/ui/styles.js Normal file
View file

@ -0,0 +1,94 @@
function loadGoogleFont(font) {
WebFont.load({
google: {
families: [font],
},
});
}
/**
* import styles from configs
*/
function importStyles() {
const styles = configs.styles;
// fonts
if (getSetting("headerGoogleFont")) {
loadGoogleFont(styles.headerFontFamily);
}
if (getSetting("taskGoogleFont")) {
loadGoogleFont(styles.taskFontFamily);
}
document.documentElement.style.setProperty(
"--gap-between-scrolls",
`${configs.animation.gapBetweenScrolls}px` ?? "0px"
);
const stylesToImport = Object.keys(styles).filter((style) => {
return !style.includes("Background");
});
stylesToImport.forEach((style) => {
document.documentElement.style.setProperty(
convertToCSSVar(style),
styles[style]
);
});
let backgroundStyles = Object.keys(styles).filter((style) => {
return style.includes("Background");
});
// use regex to filter out after "Background"
backgroundStyles = backgroundStyles.map((style) => {
return style.replace(/Background.*/, "");
});
// loop through backgroundstyles
backgroundStyles.forEach((style) => {
// get background color and opacity
let backgroundColor = styles[`${style}BackgroundColor`];
let backgroundOpacity = styles[`${style}BackgroundOpacity`];
let cssStyle = convertToCSSVar(style);
// set background color
document.documentElement.style.setProperty(
`${cssStyle}-background-color`,
`rgba(${hexToRgb(backgroundColor)}, ${backgroundOpacity})`
);
});
if (!getSetting("displayTaskCount")) {
document.querySelector(".task-count").style.display = "none";
}
let currentTitle = 0;
// interval the task title
setInterval(async () => {
let taskTitle = document.querySelector(".title");
// cycle through a list of titles
let titles = configs.animation.titles;
// if current title is the last title, set it to the first title
if (currentTitle === titles.length - 1) {
currentTitle = 0;
} else {
currentTitle++;
}
// on change title, add fade animation
taskTitle.classList.add("fade");
await sleep(500);
// set new title
taskTitle.innerText = titles[currentTitle];
await sleep(100);
// remove fade animation
taskTitle.classList.remove("fade");
}, 8000);
}

23
scripts/ui/taskList.js Normal file
View file

@ -0,0 +1,23 @@
const styles = configs.styles;
const scrollSpeed = configs.animation.scrollSpeed;
let openQuote = getSetting("openQuote");
let closeQuote = getSetting("closeQuote");
let visible = false;
let scrolling = false;
let primaryAnimation, secondaryAnimation;
const taskSeparator = getSetting("taskSeparator");
DBHandler.open()
.then(async () => {
await setupDB();
importStyles();
if (getSetting("testTasks")) {
await resetDB();
await tests();
}
await renderTaskListToDOM();
})
.catch((error) => {
console.error("Error opening database:", error);
});