first commit
This commit is contained in:
commit
dae6c59363
13 changed files with 1637 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
.vscode/*
|
||||||
|
styles/taskList.css.map
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 RyanPython
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
207
README.md
Normal file
207
README.md
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
# Multi-Task widget Infinity (PRIVATE)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
It's basically the same as the original "Multi-Task widget" but with infinite scroll! (more aesthetic, hehe)
|
||||||
|
|
||||||
|
## Logs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
I'll just update you guys on the Discord
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- directory -->
|
||||||
|
|
||||||
|
## Content
|
||||||
|
|
||||||
|
- [Multi-Task widget Infinity (PRIVATE)](#multi-task-widget-infinity-private)
|
||||||
|
- [Logs](#logs)
|
||||||
|
- [Content](#content)
|
||||||
|
- [Commands](#commands)
|
||||||
|
- [Moderators only](#moderators-only)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Method 1 (fastest, easiest way)](#method-1-fastest-easiest-way)
|
||||||
|
- [Method 2 (better if you're using an alt account as a bot)](#method-2-better-if-youre-using-an-alt-account-as-a-bot)
|
||||||
|
- [Customization settings](#customization-settings)
|
||||||
|
- [settings](#settings)
|
||||||
|
- [task list styles](#task-list-styles)
|
||||||
|
- [header](#header)
|
||||||
|
- [body](#body)
|
||||||
|
- [task](#task)
|
||||||
|
- [Credits](#credits)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### Moderators only
|
||||||
|
|
||||||
|
- !clear - Clear all tasks
|
||||||
|
- !cleardone - Clear all done tasks
|
||||||
|
- !adel @user - Remove all tasks from a user
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Method 1 (fastest, easiest way)
|
||||||
|
|
||||||
|
1. Install the zip or clone the repository
|
||||||
|
|
||||||
|
2. Go to https://twitchapps.com/tmi/ to create an oauth token (it acts as a password)
|
||||||
|
|
||||||
|
3. Setup authentication in `auth.js`
|
||||||
|
|
||||||
|
4. Setup `Browser Source` in OBS studio or other streaming software with the following settings:
|
||||||
|
|
||||||
|
- Local File: `checked`
|
||||||
|
- Local File: `index.html`
|
||||||
|
|
||||||
|
### Method 2 (better if you're using an alt account as a bot)
|
||||||
|
|
||||||
|
1. Install the zip or clone the repository
|
||||||
|
|
||||||
|
2. Create a Twitch application [here](https://dev.twitch.tv/console/apps) \(Log in with your alternate Twitch account if you wish to use a different account as a bot account\)
|
||||||
|
|
||||||
|
3. Copy the `Client ID` from the application
|
||||||
|
|
||||||
|
4. Create token using `get_token.txt`. Replace `CLIENT_ID` with the `Client ID` from the application
|
||||||
|
|
||||||
|
5. Setup authentication in `auth.js`
|
||||||
|
|
||||||
|
6. Setup `Browser Source` in OBS studio or other streaming software with the following settings:
|
||||||
|
|
||||||
|
- Local File: `checked`
|
||||||
|
- Local File: `index.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customization settings
|
||||||
|
|
||||||
|
Edit `configs.js` to edit the style of the task list
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### settings
|
||||||
|
|
||||||
|
`showDoneTasks`:
|
||||||
|
|
||||||
|
**true**: show the done tasks
|
||||||
|
|
||||||
|
**false**: hide (and remove) the done tasks
|
||||||
|
|
||||||
|
`enableLimit`:
|
||||||
|
|
||||||
|
**true**: limit the number of incomplete tasks stored in the list
|
||||||
|
|
||||||
|
**false**: unlimited incomplete tasks
|
||||||
|
|
||||||
|
`limit`: The number of incomplete tasks to store in the list (integer)
|
||||||
|
|
||||||
|
`OneScrollPerMS`: Per MS, how many pixels to scroll (integer)
|
||||||
|
|
||||||
|
`pauseBetweenScrolls`: How many MS to pause between each end of the scroll (integer)
|
||||||
|
|
||||||
|
### task list styles
|
||||||
|
|
||||||
|
`taskListWidth`: width of the task list (px)
|
||||||
|
|
||||||
|
`taskListHeight`: height of the task list (px)
|
||||||
|
|
||||||
|
`taskListBackgroundColor`: background color of the task list (hex only)
|
||||||
|
|
||||||
|
`taskListBackgroundOpacity`: opacity of the task list background (0.0 - 1.0)
|
||||||
|
|
||||||
|
`taskListBorderWidth`: width of the task list border (px)
|
||||||
|
|
||||||
|
`taskListBorderColor`: color of the task list border (hex or name)
|
||||||
|
|
||||||
|
`taskListBorderRadius`: how round the task list border should be (px)
|
||||||
|
|
||||||
|
`taskListHorizontalPadding`: horizontal padding of the task list (px)
|
||||||
|
|
||||||
|
`taskListVerticalPadding`: vertical padding of the task list (px)
|
||||||
|
|
||||||
|
### header
|
||||||
|
|
||||||
|
`headerBackgroundColor`: background color of the header (hex only)
|
||||||
|
|
||||||
|
`headerBackgroundOpacity`: opacity of the header background (0.0 - 1.0)
|
||||||
|
|
||||||
|
`headerBorderWidth`: width of the header border (px)
|
||||||
|
|
||||||
|
`headerBorderColor`: color of the header border (hex or name)
|
||||||
|
|
||||||
|
`headerBorderRadius`: how round the header border should be (px)
|
||||||
|
|
||||||
|
`headerFontFamily`: font family of the header (font name)
|
||||||
|
|
||||||
|
`headerGoogleFont`: whether to use Google font for the header (true or false)
|
||||||
|
|
||||||
|
`headerFontSize`: font size of the header (px)
|
||||||
|
|
||||||
|
`headerFontWeight`: font weight of the header (normal, bold, or number)
|
||||||
|
|
||||||
|
`headerFontColor`: font color of the header (hex or name)
|
||||||
|
|
||||||
|
### body
|
||||||
|
|
||||||
|
`bodyBackgroundColor`: background color of the body (hex only)
|
||||||
|
|
||||||
|
`bodyBackgroundOpacity`: opacity of the body background (0.0 - 1.0)
|
||||||
|
|
||||||
|
`bodyBorderWidth`: width of the body border (px)
|
||||||
|
|
||||||
|
`bodyBorderColor`: color of the body border (hex or name)
|
||||||
|
|
||||||
|
`bodyBorderRadius`: how round the body border should be (px)
|
||||||
|
|
||||||
|
`bodyHorizontalPadding`: horizontal padding of the body (px)
|
||||||
|
|
||||||
|
`bodyVerticalPadding`: vertical padding of the body (px)
|
||||||
|
|
||||||
|
### task
|
||||||
|
|
||||||
|
`lineHeight`: line height of the task (number)
|
||||||
|
|
||||||
|
`usernameFontWeight`: font weight of the username (normal, bold, or number)
|
||||||
|
|
||||||
|
`usernameColor`: color of the username (hex or name or "" for Twitch user color)
|
||||||
|
|
||||||
|
`taskBackgroundColor`: background color of the task (hex only)
|
||||||
|
|
||||||
|
`taskBackgroundOpacity`: opacity of the task background (0.0 - 1.0)
|
||||||
|
|
||||||
|
`taskFontFamily`: font family of the task (font name)
|
||||||
|
|
||||||
|
`taskGoogleFont`: whether to use Google font for the task (true or false)
|
||||||
|
|
||||||
|
`taskFontSize`: font size of the task (px)
|
||||||
|
|
||||||
|
`taskFontColor`: font color of the task (hex or name)
|
||||||
|
|
||||||
|
`taskBorderColor`: color of the task border (hex or name)
|
||||||
|
|
||||||
|
`taskBorderWidth`: width of the task border (px)
|
||||||
|
|
||||||
|
`taskBorderRadius`: how round the task border should be (px)
|
||||||
|
|
||||||
|
`taskMarginBottom`: bottom margin of the task (px)
|
||||||
|
|
||||||
|
`taskHorizontalPadding`: horizontal padding of the task (px)
|
||||||
|
|
||||||
|
`taskVerticalPadding`: vertical padding of the task (px)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
**Author:** [**@RyanPython**](https://twitch.tv/RyanPython)
|
||||||
|
|
||||||
|
**Special thanks to:**
|
||||||
|
|
||||||
|
- [**@Instafluff**](https://twitch.tv/Instafluff) \(for the Comfy JS library\)
|
||||||
12
auth.js
Normal file
12
auth.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
const auth = (function () {
|
||||||
|
// Authentication and channels - required
|
||||||
|
const channel = ""; // your channel
|
||||||
|
const username = ""; // bot account
|
||||||
|
const oauth = ""; // should be oauth:xxxxxxxxxxxx from the bot account DO NOT INCLUDE oauth:
|
||||||
|
|
||||||
|
return {
|
||||||
|
channel,
|
||||||
|
username,
|
||||||
|
oauth,
|
||||||
|
};
|
||||||
|
})();
|
||||||
313
configs.js
Normal file
313
configs.js
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
const configs = (function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// settings
|
||||||
|
const enableLimit = true; // true or false
|
||||||
|
const limit = 20; // integer
|
||||||
|
|
||||||
|
// animation
|
||||||
|
const scrollSpeed = 30; // milliseconds (DO NOT PUT BELOW 5, COMPUTER MIGHT CRASH) the lower the value, the faster the scroll
|
||||||
|
const pauseBetweenScrolls = 2000; // milliseconds
|
||||||
|
const gapBetweenScrolls = "100px"; // px
|
||||||
|
|
||||||
|
// STYLES
|
||||||
|
// task list
|
||||||
|
const taskListWidth = "400px"; // px
|
||||||
|
const taskListHeight = "300px"; // px
|
||||||
|
|
||||||
|
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 = "98%";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
// 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",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check task commands - please add commands in the exact format
|
||||||
|
const checkCommands = [
|
||||||
|
"!taskc",
|
||||||
|
"!taskcheck",
|
||||||
|
"!ctask",
|
||||||
|
"!checktask",
|
||||||
|
"!mytask",
|
||||||
|
"!check",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Help commands - please add commands in the exact format
|
||||||
|
const helpCommands = [
|
||||||
|
"!taskh",
|
||||||
|
"!taskhelp",
|
||||||
|
"!htask",
|
||||||
|
"!helptask",
|
||||||
|
"!tasks",
|
||||||
|
"!help",
|
||||||
|
];
|
||||||
|
|
||||||
|
// 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",
|
||||||
|
"!clear",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Responses
|
||||||
|
const taskAdded = 'The task "{message}" 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 '{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 adminDeleteTasks = "All of {mentioned}'s tasks have been deleted";
|
||||||
|
const taskFinished = "Good job on finishing the task '{task}', {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 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/ryanpython",
|
||||||
|
};
|
||||||
|
|
||||||
|
const titles = [
|
||||||
|
"!taskhelp",
|
||||||
|
"!task",
|
||||||
|
"!edit",
|
||||||
|
"!remove",
|
||||||
|
"!done",
|
||||||
|
"!botcred",
|
||||||
|
];
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
enableLimit,
|
||||||
|
limit,
|
||||||
|
headerGoogleFont,
|
||||||
|
taskGoogleFont,
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
taskListWidth,
|
||||||
|
taskListHeight,
|
||||||
|
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 = {
|
||||||
|
scrollSpeed,
|
||||||
|
pauseBetweenScrolls,
|
||||||
|
gapBetweenScrolls,
|
||||||
|
titles,
|
||||||
|
};
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
addTaskCommands,
|
||||||
|
editTaskCommands,
|
||||||
|
deleteTaskCommands,
|
||||||
|
finishTaskCommands,
|
||||||
|
helpCommands,
|
||||||
|
checkCommands,
|
||||||
|
adminDeleteCommands,
|
||||||
|
adminClearDoneCommands,
|
||||||
|
adminClearAllCommands,
|
||||||
|
additionalCommands,
|
||||||
|
};
|
||||||
|
|
||||||
|
const responses = {
|
||||||
|
taskAdded,
|
||||||
|
noTaskAdded,
|
||||||
|
noTaskContent,
|
||||||
|
taskDeleted,
|
||||||
|
specifyTaskIndex,
|
||||||
|
duplicateTask,
|
||||||
|
taskFinished,
|
||||||
|
taskEdited,
|
||||||
|
noTaskEdit,
|
||||||
|
noTask,
|
||||||
|
noTaskA,
|
||||||
|
notMod,
|
||||||
|
help,
|
||||||
|
adminDeleteTasks,
|
||||||
|
clearedAll,
|
||||||
|
clearedDone,
|
||||||
|
specifyUser,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
settings,
|
||||||
|
styles,
|
||||||
|
animation,
|
||||||
|
commands,
|
||||||
|
responses,
|
||||||
|
};
|
||||||
|
})();
|
||||||
5
get_token.txt
Normal file
5
get_token.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
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
|
||||||
BIN
images/labels.png
Normal file
BIN
images/labels.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 263 KiB |
BIN
images/preview.png
Normal file
BIN
images/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 356 KiB |
44
index.html
Normal file
44
index.html
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<script src="./auth.js"></script>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="main-container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">!taskhelp</div>
|
||||||
|
<div class="task-count">0/0</div>
|
||||||
|
</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 class="task-container secondary"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
147
scripts/index.js
Normal file
147
scripts/index.js
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
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 if (addStatus === 3) {
|
||||||
|
// task added
|
||||||
|
respond(responses.taskAdded, 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
|
||||||
|
params.task = removeStatus;
|
||||||
|
respond(responses.taskDeleted, params);
|
||||||
|
}
|
||||||
|
} else if (commands.finishTaskCommands.includes(command)) {
|
||||||
|
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
|
||||||
|
params.task = finishStatus;
|
||||||
|
respond(responses.taskFinished, 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.helpCommands.includes(command)) {
|
||||||
|
respond(responses.help, params);
|
||||||
|
} else if (commands.additionalCommands[command]) {
|
||||||
|
respond(commands.additionalCommands[command], params);
|
||||||
|
} else {
|
||||||
|
// command not found
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ComfyJS.Init(auth.username, `oauth:${auth.oauth}`, [auth.channel]);
|
||||||
592
scripts/taskList.js
Normal file
592
scripts/taskList.js
Normal file
|
|
@ -0,0 +1,592 @@
|
||||||
|
/*
|
||||||
|
DB structure:
|
||||||
|
|
||||||
|
- tasks
|
||||||
|
- username:
|
||||||
|
- todos (array)
|
||||||
|
- [{text, done}, {text, done}]
|
||||||
|
- done
|
||||||
|
- userColor
|
||||||
|
*/
|
||||||
|
|
||||||
|
const settings = configs.settings;
|
||||||
|
const styles = configs.styles;
|
||||||
|
const scrollSpeed = configs.animation.scrollSpeed;
|
||||||
|
let scrolling = false;
|
||||||
|
|
||||||
|
function loadGoogleFont(font) {
|
||||||
|
WebFont.load({
|
||||||
|
google: {
|
||||||
|
families: [font],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert taskListBorderColor to task-list-border-color
|
||||||
|
function convertToCSSVar(name) {
|
||||||
|
let cssVar = name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||||||
|
return `--${cssVar}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hex to rgb that accepts 3 or 6 digits
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
// interger value of rgb
|
||||||
|
r = +r;
|
||||||
|
g = +g;
|
||||||
|
b = +b;
|
||||||
|
|
||||||
|
return `${r}, ${g}, ${b}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// import styles from configs
|
||||||
|
function importStyles() {
|
||||||
|
const styles = configs.styles;
|
||||||
|
|
||||||
|
// fonts
|
||||||
|
if (configs.settings.headerGoogleFont) {
|
||||||
|
loadGoogleFont(styles.headerFontFamily);
|
||||||
|
}
|
||||||
|
if (configs.settings.taskGoogleFont) {
|
||||||
|
loadGoogleFont(styles.taskFontFamily);
|
||||||
|
}
|
||||||
|
|
||||||
|
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})`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// interval the task title
|
||||||
|
setInterval(async () => {
|
||||||
|
let taskTitle = document.querySelector(".title");
|
||||||
|
let taskTitleText = taskTitle.innerText;
|
||||||
|
|
||||||
|
// cycle through a list of titles
|
||||||
|
let titles = configs.animation.titles;
|
||||||
|
|
||||||
|
// get current title
|
||||||
|
let currentTitle = titles.indexOf(taskTitleText);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDB() {
|
||||||
|
localStorage.clear();
|
||||||
|
setupDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupDB() {
|
||||||
|
if (!localStorage.tasks) {
|
||||||
|
localStorage.setItem(`tasks`, "{}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0: limit has reached
|
||||||
|
// 1: duplicate task
|
||||||
|
// 2: invalid input
|
||||||
|
// 3: 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks[username].todos.push({ text: task, done: false });
|
||||||
|
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||||
|
renderTaskListToDOM();
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
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 (tasks[username].todos.length === 1) {
|
||||||
|
index = 0;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let removedTask = tasks[username].todos[index].text;
|
||||||
|
|
||||||
|
tasks[username].todos.splice(index, 1);
|
||||||
|
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||||
|
renderTaskListToDOM();
|
||||||
|
|
||||||
|
return removedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: invalid input
|
||||||
|
// task: succcess
|
||||||
|
function markTaskDone(username, task) {
|
||||||
|
const tasks = JSON.parse(localStorage.tasks);
|
||||||
|
|
||||||
|
if (!tasks[username] || tasks[username].todos.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let index = tasks[username].todos.findIndex(
|
||||||
|
(t) => t.text.toLowerCase() === task.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
// filter by t.done is false
|
||||||
|
let incompleteTaskCount = tasks[username].todos.filter(
|
||||||
|
(t) => !t.done
|
||||||
|
).length;
|
||||||
|
if (incompleteTaskCount === 1) {
|
||||||
|
index = tasks[username].todos.findIndex((t) => !t.done);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
if (isInt(task)) {
|
||||||
|
index = parseInt(task) - 1; // ACTUAL INDEX
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0 || index > tasks[username].todos.length - 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let completedTask = tasks[username].todos[index].text;
|
||||||
|
|
||||||
|
tasks[username].todos[index].done = true;
|
||||||
|
|
||||||
|
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||||
|
renderTaskListToDOM();
|
||||||
|
|
||||||
|
return completedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
renderTaskListToDOM();
|
||||||
|
|
||||||
|
return [originalTask, newTask];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0: user has no tasks
|
||||||
|
function checkTasks(username) {
|
||||||
|
const tasks = JSON.parse(localStorage.tasks);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function incompleteTasksCount(username) {
|
||||||
|
const tasks = JSON.parse(localStorage.tasks);
|
||||||
|
if (!tasks[username]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let count = 0;
|
||||||
|
for (const task of tasks[username].todos) {
|
||||||
|
if (!task.done) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearUserTasks(username) {
|
||||||
|
const tasks = JSON.parse(localStorage.tasks);
|
||||||
|
if (!tasks[username]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tasks[username].todos = [];
|
||||||
|
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||||
|
renderTaskListToDOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAllDoneTasks() {
|
||||||
|
const tasks = JSON.parse(localStorage.tasks);
|
||||||
|
for (const user in tasks) {
|
||||||
|
tasks[user].todos = tasks[user].todos.filter((t) => !t.done);
|
||||||
|
}
|
||||||
|
localStorage.setItem(`tasks`, JSON.stringify(tasks));
|
||||||
|
renderTaskListToDOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAllTasks() {
|
||||||
|
resetDB();
|
||||||
|
renderTaskListToDOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
function smutOnly(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();
|
||||||
|
renderTaskListToDOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTaskListToDOM() {
|
||||||
|
const tasks = JSON.parse(localStorage.tasks);
|
||||||
|
|
||||||
|
// console.log(tasks);
|
||||||
|
|
||||||
|
const taskContainers = document.querySelectorAll(".task-container");
|
||||||
|
taskContainers.forEach((taskList) => {
|
||||||
|
taskList.innerHTML = "";
|
||||||
|
|
||||||
|
let totalTaskCount = 0;
|
||||||
|
let completedTasksCount = 0;
|
||||||
|
for (const user in tasks) {
|
||||||
|
const userTasks = tasks[user];
|
||||||
|
if (userTasks.todos.length === 0) {
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
taskElement.innerText = task.text;
|
||||||
|
olListDiv.appendChild(taskElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector(
|
||||||
|
".task-count"
|
||||||
|
).innerText = `${completedTasksCount}/${totalTaskCount}`;
|
||||||
|
});
|
||||||
|
checkToAnimate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// unit tests
|
||||||
|
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 ${i + 1}`);
|
||||||
|
addTask(listOfStreamers[i], "#ffc0cb", `another task ${i + 1}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tests2() {
|
||||||
|
smutOnly("theyolotato");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkToAnimate() {
|
||||||
|
// compare task container height and task wrapper height
|
||||||
|
|
||||||
|
let taskContainer = document.querySelector(".task-container");
|
||||||
|
let taskContainerHeight = taskContainer.scrollHeight;
|
||||||
|
|
||||||
|
let taskWrapper = document.querySelector(".task-wrapper");
|
||||||
|
let taskWrapperHeight = taskWrapper.clientHeight;
|
||||||
|
|
||||||
|
console.log(taskContainerHeight > taskWrapperHeight);
|
||||||
|
|
||||||
|
// scroll task wrapper up and down once
|
||||||
|
if (taskContainerHeight > taskWrapperHeight) {
|
||||||
|
if (!scrolling) {
|
||||||
|
document.querySelector(".secondary").style.display = "flex";
|
||||||
|
|
||||||
|
let finalHeight = taskContainerHeight + 100;
|
||||||
|
|
||||||
|
let primaryKeyFrames = [
|
||||||
|
{ transform: `translateY(0)` },
|
||||||
|
{ transform: `translateY(-${finalHeight}px)` },
|
||||||
|
];
|
||||||
|
|
||||||
|
let secondaryKeyFrames = [
|
||||||
|
{ transform: `translateY(${finalHeight}px)` },
|
||||||
|
{ transform: `translateY(0)` },
|
||||||
|
];
|
||||||
|
|
||||||
|
let scrollingSpeed = (finalHeight / scrollSpeed) * 1000;
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
duration: scrollingSpeed,
|
||||||
|
iterations: 1,
|
||||||
|
easing: "linear",
|
||||||
|
};
|
||||||
|
|
||||||
|
let primaryAnimation = document
|
||||||
|
.querySelector(".primary")
|
||||||
|
.animate(primaryKeyFrames, options);
|
||||||
|
|
||||||
|
let secondaryAnimation = document
|
||||||
|
.querySelector(".secondary")
|
||||||
|
.animate(secondaryKeyFrames, options);
|
||||||
|
|
||||||
|
primaryAnimation.play();
|
||||||
|
secondaryAnimation.play();
|
||||||
|
|
||||||
|
scrolling = true;
|
||||||
|
|
||||||
|
await sleep(scrollingSpeed);
|
||||||
|
|
||||||
|
scrolling = false;
|
||||||
|
checkToAnimate();
|
||||||
|
}
|
||||||
|
} else if (!scrolling) {
|
||||||
|
document.querySelector(".secondary").style.display = "none";
|
||||||
|
|
||||||
|
// cancel animations
|
||||||
|
document
|
||||||
|
.querySelector(".task-container")
|
||||||
|
.getAnimations()
|
||||||
|
.forEach((a) => {
|
||||||
|
a.cancel();
|
||||||
|
});
|
||||||
|
scrolling = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
setupDB();
|
||||||
|
importStyles();
|
||||||
|
renderTaskListToDOM();
|
||||||
|
})();
|
||||||
123
styles/taskList.css
Normal file
123
styles/taskList.css
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
:root {
|
||||||
|
--task-list-width: 400px;
|
||||||
|
--task-list-height: 300px;
|
||||||
|
--task-list-background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
--task-list-border-width: 2px;
|
||||||
|
--task-list-border-color: white;
|
||||||
|
--task-list-border-radius: 5px;
|
||||||
|
--task-list-horizontal-padding: 0px;
|
||||||
|
--task-list-vertical-padding: 0px;
|
||||||
|
--header-horizontal-padding: 10px;
|
||||||
|
--header-vertical-padding: 10px;
|
||||||
|
--header-background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
--header-border-width: 2px;
|
||||||
|
--header-border-color: white;
|
||||||
|
--header-border-radius: 5px;
|
||||||
|
--header-font-size: 20px;
|
||||||
|
--header-font-family: sans-serif;
|
||||||
|
--header-font-color: white;
|
||||||
|
--header-font-weight: bold;
|
||||||
|
--body-background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
--body-border-width: 5px;
|
||||||
|
--body-border-color: white;
|
||||||
|
--body-border-radius: 5px;
|
||||||
|
--line-height: 1.5;
|
||||||
|
--username-font-weight: bold;
|
||||||
|
--task-background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
--task-font-family: sans-serif;
|
||||||
|
--task-font-size: 25px;
|
||||||
|
--task-font-color: white;
|
||||||
|
--task-border-color: black;
|
||||||
|
--task-border-width: 0px;
|
||||||
|
--task-border-radius: 5px;
|
||||||
|
--task-horizontal-padding: 0px;
|
||||||
|
--task-vertical-padding: 0px;
|
||||||
|
--task-margin-bottom: 5px;
|
||||||
|
--task-horizontal-padding: 5px;
|
||||||
|
--task-vertical-padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-container {
|
||||||
|
width: var(--task-list-width);
|
||||||
|
height: var(--task-list-height);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: var(--task-list-vertical-padding) var(--task-list-horizontal-padding);
|
||||||
|
background-color: var(--task-list-background-color);
|
||||||
|
border: solid var(--task-list-border-width) var(--task-list-border-color);
|
||||||
|
border-radius: var(--task-list-border-radius);
|
||||||
|
}
|
||||||
|
#main-container .header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--header-vertical-padding) var(--header-horizontal-padding);
|
||||||
|
border: solid var(--header-border-width) var(--header-border-color);
|
||||||
|
border-radius: var(--header-border-radius);
|
||||||
|
color: var(--header-font-color);
|
||||||
|
background: var(--header-background-color);
|
||||||
|
font-family: var(--header-font-family);
|
||||||
|
font-size: var(--header-font-size);
|
||||||
|
font-weight: var(--header-font-weight);
|
||||||
|
}
|
||||||
|
#main-container .header .title {
|
||||||
|
transition: opacity 0.5s ease;
|
||||||
|
}
|
||||||
|
#main-container .header .fade {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
#main-container .task-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
background: var(--body-background-color);
|
||||||
|
color: var(--task-font-color);
|
||||||
|
font-family: var(--task-font-family);
|
||||||
|
font-size: var(--task-font-size);
|
||||||
|
line-height: var(--line-height);
|
||||||
|
border: var(--body-border-width) solid var(--body-border-color);
|
||||||
|
border-radius: var(--body-border-radius);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#main-container .task-wrapper .task-container {
|
||||||
|
position: absolute;
|
||||||
|
width: inherit;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
#main-container .task-wrapper .task-container .task {
|
||||||
|
padding: var(--task-vertical-padding) var(--task-horizontal-padding);
|
||||||
|
background: var(--task-background-color);
|
||||||
|
margin-bottom: var(--task-margin-bottom);
|
||||||
|
width: var(--task-width);
|
||||||
|
border: solid var(--task-border-width) var(--task-border-color);
|
||||||
|
border-radius: var(--task-border-radius);
|
||||||
|
}
|
||||||
|
#main-container .task-wrapper .task-container .task .username {
|
||||||
|
font-weight: var(--username-font-weight);
|
||||||
|
}
|
||||||
|
#main-container .task-wrapper .task-container .task .user-tasks {
|
||||||
|
list-style-position: inside;
|
||||||
|
}
|
||||||
|
#main-container .task-wrapper .task-container .task .user-tasks .todo {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
#main-container .task-wrapper .task-container .task .user-tasks .done {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}/*# sourceMappingURL=taskList.css.map */
|
||||||
171
styles/taskList.scss
Normal file
171
styles/taskList.scss
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
:root {
|
||||||
|
--task-list-width: 400px;
|
||||||
|
--task-list-height: 300px;
|
||||||
|
|
||||||
|
--task-list-background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
|
||||||
|
--task-list-border-width: 2px;
|
||||||
|
--task-list-border-color: white;
|
||||||
|
--task-list-border-radius: 5px;
|
||||||
|
|
||||||
|
--task-list-horizontal-padding: 0px;
|
||||||
|
--task-list-vertical-padding: 0px;
|
||||||
|
|
||||||
|
--header-horizontal-padding: 10px;
|
||||||
|
--header-vertical-padding: 10px;
|
||||||
|
|
||||||
|
--header-background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
|
||||||
|
--header-border-width: 2px;
|
||||||
|
--header-border-color: white;
|
||||||
|
--header-border-radius: 5px;
|
||||||
|
|
||||||
|
--header-font-size: 20px;
|
||||||
|
--header-font-family: sans-serif;
|
||||||
|
--header-font-color: white;
|
||||||
|
--header-font-weight: bold;
|
||||||
|
|
||||||
|
--body-background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
|
||||||
|
--body-border-width: 5px;
|
||||||
|
--body-border-color: white;
|
||||||
|
--body-border-radius: 5px;
|
||||||
|
|
||||||
|
--line-height: 1.5;
|
||||||
|
--username-font-weight: bold;
|
||||||
|
|
||||||
|
--task-background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
|
||||||
|
--task-font-family: sans-serif;
|
||||||
|
--task-font-size: 25px;
|
||||||
|
--task-font-color: white;
|
||||||
|
|
||||||
|
--task-border-color: black;
|
||||||
|
--task-border-width: 0px;
|
||||||
|
--task-border-radius: 5px;
|
||||||
|
|
||||||
|
--task-horizontal-padding: 0px;
|
||||||
|
--task-vertical-padding: 0px;
|
||||||
|
|
||||||
|
--task-margin-bottom: 5px;
|
||||||
|
--task-horizontal-padding: 5px;
|
||||||
|
--task-vertical-padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-container {
|
||||||
|
width: var(--task-list-width);
|
||||||
|
height: var(--task-list-height);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
padding: var(--task-list-vertical-padding)
|
||||||
|
var(--task-list-horizontal-padding);
|
||||||
|
|
||||||
|
background-color: var(--task-list-background-color);
|
||||||
|
|
||||||
|
border: solid var(--task-list-border-width) var(--task-list-border-color);
|
||||||
|
border-radius: var(--task-list-border-radius);
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
padding: var(--header-vertical-padding) var(--header-horizontal-padding);
|
||||||
|
|
||||||
|
border: solid var(--header-border-width) var(--header-border-color);
|
||||||
|
border-radius: var(--header-border-radius);
|
||||||
|
|
||||||
|
color: var(--header-font-color);
|
||||||
|
background: var(--header-background-color);
|
||||||
|
|
||||||
|
font-family: var(--header-font-family);
|
||||||
|
font-size: var(--header-font-size);
|
||||||
|
font-weight: var(--header-font-weight);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
transition: opacity 0.5s ease;
|
||||||
|
}
|
||||||
|
.fade {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
background: var(--body-background-color);
|
||||||
|
|
||||||
|
color: var(--task-font-color);
|
||||||
|
font-family: var(--task-font-family);
|
||||||
|
font-size: var(--task-font-size);
|
||||||
|
|
||||||
|
line-height: var(--line-height);
|
||||||
|
|
||||||
|
border: var(--body-border-width) solid var(--body-border-color);
|
||||||
|
border-radius: var(--body-border-radius);
|
||||||
|
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.task-container {
|
||||||
|
position: absolute;
|
||||||
|
width: inherit;
|
||||||
|
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
.task {
|
||||||
|
padding: var(--task-vertical-padding)
|
||||||
|
var(--task-horizontal-padding);
|
||||||
|
background: var(--task-background-color);
|
||||||
|
margin-bottom: var(--task-margin-bottom);
|
||||||
|
|
||||||
|
width: var(--task-width);
|
||||||
|
|
||||||
|
border: solid var(--task-border-width) var(--task-border-color);
|
||||||
|
border-radius: var(--task-border-radius);
|
||||||
|
|
||||||
|
.username {
|
||||||
|
font-weight: var(--username-font-weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-tasks {
|
||||||
|
// put list inner
|
||||||
|
list-style-position: inside;
|
||||||
|
|
||||||
|
.todo {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue