Compare commits

...

33 commits
0.9.15 ... main

Author SHA1 Message Date
John Mavrick
a3e378f219
Merge pull request #37 from Ferrhat/main
Update packages
2024-04-05 14:54:18 -07:00
Ferrhat
c63f039b11 Update packages 2024-01-12 00:23:48 +01:00
ransurf
3c7d2099bd
Merge pull request #31 from Ethanil/main
added all-Tab
2022-04-20 03:09:30 -07:00
Ethanil
44893508dc
added all-Tab 2022-04-13 16:36:56 +02:00
SuperChamp234
64d045bbc5 upped version to 1.0.1 2022-01-15 12:27:25 +05:30
SuperChamp234
fc6506525c Fixed Indent issue 2022-01-15 12:26:29 +05:30
SuperChamp234
b80d2f8dc1 Bug: Indent CSS bug 2022-01-15 11:50:27 +05:30
SuperChamp234
67463a648d Merge branch 'main' of https://github.com/SuperChamp234/habitica-sync 2022-01-15 11:26:57 +05:30
Zain
b2c93a4747
Merge pull request #25 from SuperChamp234/dev
Update README.md
2022-01-15 11:25:51 +05:30
ransurf
3d09f20d84
Update README.md 2022-01-14 23:53:19 -06:00
SuperChamp234
eadd8d9f8c fixed messages 2022-01-15 11:23:00 +05:30
Zain
e7b4572d2e
Merge pull request #24 from SuperChamp234/dev
Version 1.0.0 update
2022-01-15 09:51:24 +05:30
Zain
350ab38135
Merge branch 'main' into dev 2022-01-15 09:51:09 +05:30
SuperChamp234
615f3e8a78 Bumped up the version no(1.0.0) 2022-01-15 09:47:49 +05:30
SuperChamp234
c1851c2fc6 Minor error fixes 2022-01-15 09:45:50 +05:30
SuperChamp234
82780ba589 CSS changes for buttons, fixed numbers in stats 2022-01-15 09:23:35 +05:30
SuperChamp234
e4d3c78a9f Small CSS changes to cron button 2022-01-15 09:00:21 +05:30
ransurf
932562ab2e fix flexbox issue for empty tabs 2022-01-14 15:53:03 -08:00
SuperChamp234
22616cbed9 Commit to dev branch includes
- Due date on Todos
- Dailies segragation to not include non-due Dailies
- Checklist-items are now working
- Made a completely new wrapper for markdown+emojis, can render emojis in description as well as task name
2022-01-14 17:55:47 +05:30
SuperChamp234
88fdde519d Added a wrapper Markdown Rendered to render BOTH Emojis and Mardown as HTML 2022-01-14 14:39:21 +05:30
ransurf
916236db5d Implement subtasks view and settings option,
clean up CSS
2022-01-13 12:47:36 -08:00
ransurf
e31d103dd7
Update README.md 2021-11-21 02:15:53 -06:00
ransurf
0ac05ef6d3
Update README.md 2021-11-21 02:14:41 -06:00
Zain
6865d4f080
Fixed merge conflict error made by Ran lol 2021-11-21 13:18:07 +05:30
ransurf
4878ed8aa8
Semi-update readme
visuals are outdated
2021-11-21 01:47:32 -06:00
ransurf
dfc4e55cb5 Merge branch 'dev' of https://github.com/SuperChamp234/habitica-sync into dev 2021-11-20 23:40:02 -08:00
ransurf
6384c98181 Added "Show Task Description" Setting 2021-11-20 23:39:09 -08:00
Zain
54e6000eb8
Fixed empty space at the bottom, fixed warning at main.ts 2021-11-21 12:47:21 +05:30
Zain
d10a2ca747
Fixed cron and minor CSS tweaks 2021-11-21 12:17:46 +05:30
Zain
8deee0ecc6
Merge pull request #17 from kkzzhizhou/main
Show task notes and add rewards panel. UI Overhaul. Credit to @kkzzhizhou!
2021-11-21 11:43:37 +05:30
kkzzhizhou
e45182547f add markdown render 2021-11-20 17:11:13 +08:00
kkzzhizhou
9b15bb1236 add Rewardview Panel 2021-11-20 13:49:24 +08:00
kkzzhizhou
bc97bcfc3f add rewards panels and change styles 2021-11-18 14:16:20 +08:00
28 changed files with 736 additions and 213 deletions

3
.gitignore vendored
View file

@ -6,6 +6,9 @@
node_modules node_modules
package-lock.json package-lock.json
# yarn
yarn.lock
*.js.map *.js.map

0
.hotreload Normal file
View file

View file

@ -9,26 +9,38 @@ The plugin's view is enabled by clicking on the "Open Habitica Pane" option in t
To sync your Habitica account, go to the settings page of the plugin and enter your user ID and API token credentials. To sync your Habitica account, go to the settings page of the plugin and enter your user ID and API token credentials.
## Features ## Features
### Pane View ### Pane View
#### View HP, XP, todos, dailies, and habits #### View stats (HP, XP, coins)
[![Image from Gyazo](https://i.gyazo.com/4266d01941e71fef41819ea8a6b6592e.png)](https://gyazo.com/4266d01941e71fef41819ea8a6b6592e) #### Views
[![Image from Gyazo](https://i.gyazo.com/697a58b8e7ffd3df86a2944b6abbaa92.png)](https://gyazo.com/697a58b8e7ffd3df86a2944b6abbaa92) Task Information:
- Tab for active/completed tasks - Title, description, subtasks
- Markdown and emoji support
#### Check off tasks/dailies in the view, modify habit counters (+/-) Tabs:
[![Image from Gyazo](https://i.gyazo.com/5759e12bc5267711c5e03485a6d72c2f.gif)](https://gyazo.com/5759e12bc5267711c5e03485a6d72c2f) - To Do's
- Can uncheck completed habits/todos as shown above - Active/Completed
- Dailies
- Due/Not Due/Completed
- [![Image from Gyazo](https://i.gyazo.com/1966b17f954dcffa954922570e860a06.png)](https://gyazo.com/1966b17f954dcffa954922570e860a06)
- Habits
- [![Image from Gyazo](https://i.gyazo.com/280494e620fc91548838d5b29a62652b.png)](https://gyazo.com/280494e620fc91548838d5b29a62652b)
- Rewards
#### Interactivity
- Check off tasks/dailies in the view
- Can uncheck completed habits/todos
- [![Image from Gyazo](https://i.gyazo.com/efb858cd9d54f9d9df936da1bd5858ed.gif)](https://gyazo.com/efb858cd9d54f9d9df936da1bd5858ed)
- modify habit counters (+/-)
## Settings ### Settings
The following two inputs help fetch your user data to be displayed in the Obsidian view:
- **Habitica User ID:** You can find this by clicking on the "User" icon in the top right of the Habitica webapp, "Settings", then "API" - **Habitica User ID:** You can find this by clicking on the "User" icon in the top right of the Habitica webapp, "Settings", then "API"
- **Habitica Token API:** You can find this by clicking on the "User" icon in the top right of the Habitica webapp, "Settings", then "API" - **Habitica Token API:** You can find this by clicking on the "User" icon in the top right of the Habitica webapp, "Settings", then "API"
- **Show Task Descriptions:** Toggles whether description/notes for tasks will be shown or not
The following two inputs help fetch your user data to be displayed in the Obsidian view. - **Show Subtasks:** Toggles whether subtasks for to do's/dailies will be shown or not
## Roadmap ## Roadmap
We will be implementing additional features including:
- Add/delete/customize tasks and habits
- View/claim rewards
*Feel free to support us and donate!* *Feel free to support us and donate!*
<a href='https://ko-fi.com/leonardandran' target='_blank'><img height='35' style='border:0px;height:46px;' src='https://az743702.vo.msecnd.net/cdn/kofi3.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /> <a href='https://ko-fi.com/leonardandran' target='_blank'><img height='35' style='border:0px;height:46px;' src='https://az743702.vo.msecnd.net/cdn/kofi3.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' />

View file

@ -1,7 +1,7 @@
{ {
"id": "obsidian-habitica-integration", "id": "obsidian-habitica-integration",
"name": "Habitica Sync", "name": "Habitica Sync",
"version": "0.9.15", "version": "1.0.2",
"minAppVersion": "0.9.12", "minAppVersion": "0.9.12",
"description": "This plugin helps integrate Habitica user tasks and stats into Obsidian", "description": "This plugin helps integrate Habitica user tasks and stats into Obsidian",
"author": "Leoh and Ran", "author": "Leoh and Ran",

View file

@ -1,37 +1,51 @@
{ {
"name": "obsidian-habitica-integration", "name": "obsidian-habitica-integration",
"version": "0.12.0", "version": "1.0.0",
"description": "This plugin allows for Habitica integration into Obsidian", "description": "This plugin allows for Habitica integration into Obsidian",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"dev": "rollup --config rollup.config.js -w", "dev": "rollup --config rollup.config.mjs -w",
"build": "rollup --config rollup.config.js --environment BUILD:production" "build": "rollup --config rollup.config.mjs --environment BUILD:production",
"dev2": "obsidian-plugin dev src/main.ts"
}, },
"keywords": [], "keywords": [],
"author": "Leonard and Ran", "author": "Leonard and Ran",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^11.2.1", "@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-typescript": "^8.2.1", "@rollup/plugin-node-resolve": "^15.2.3",
"@types/node": "^14.14.37", "@rollup/plugin-typescript": "^11.1.6",
"@types/react": "^17.0.27", "@types/markdown-it": "^13.0.7",
"@types/react-dom": "^17.0.9", "@types/markdown-it-emoji": "^2.0.4",
"@types/react-tabs": "^2.3.3", "@types/node": "^20.11.0",
"css-loader": "^6.4.0", "@types/node-emoji": "^1.8.2",
"extract-text-webpack-plugin": "^2.1.2", "@types/react": "^18.2.47",
"obsidian": "^0.12.0", "@types/react-dom": "^18.2.18",
"rollup": "^2.32.1", "@types/react-tabs": "^5.0.4",
"style-loader": "^3.3.0", "@types/twemoji": "^13.1.1",
"tslib": "^2.2.0", "css-loader": "^6.9.0",
"typescript": "^4.2.4" "mini-css-extract-plugin": "^2.7.7",
"obsidian": "^1.4.11",
"obsidian-plugin-cli": "^0.0.5",
"rollup": "^4.9.4",
"style-loader": "^3.3.4",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"webpack": "^5.89.0"
}, },
"dependencies": { "dependencies": {
"node": "^16.10.0", "markdown-it": "^14.0.0",
"node-fetch": "^3.0.0", "markdown-it-emoji": "^2.0.2",
"react": "^17.0.2", "moment": "^2.30.1",
"react-dom": "^17.0.2", "node": "^21.2.0",
"react-emoji-render": "^1.2.4", "node-emoji": "^2.1.3",
"react-tabs": "^3.2.2" "node-fetch": "^3.3.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-emoji-render": "^2.0.1",
"react-markdown": "^9.0.1",
"react-tabs": "^6.0.2",
"twemoji": "^14.0.2"
} }
} }

View file

@ -1,6 +1,7 @@
import typescript from '@rollup/plugin-typescript'; import typescript from '@rollup/plugin-typescript';
import {nodeResolve} from '@rollup/plugin-node-resolve'; import {nodeResolve} from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs'; import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
const isProd = (process.env.BUILD === 'production'); const isProd = (process.env.BUILD === 'production');
@ -26,5 +27,6 @@ export default {
typescript(), typescript(),
nodeResolve({browser: true}), nodeResolve({browser: true}),
commonjs(), commonjs(),
json(),
] ]
}; };

View file

@ -5,23 +5,30 @@ import { HabiticaSyncView, VIEW_TYPE} from "./view"
interface HabiticaSyncSettings { interface HabiticaSyncSettings {
userID: string userID: string
apiToken: string apiToken: string
showTaskDescription: boolean
showSubTasks: boolean
dueDateFormat: string
} }
const DEFAULT_SETTINGS: Partial<HabiticaSyncSettings> = { const DEFAULT_SETTINGS: Partial<HabiticaSyncSettings> = {
userID: "", userID: "",
apiToken: "" apiToken: "",
showTaskDescription: true,
showSubTasks: true,
dueDateFormat: "DD-MM-YYYY"
} }
export default class HabiticaSync extends Plugin { export default class HabiticaSync extends Plugin {
settings: HabiticaSyncSettings; settings: HabiticaSyncSettings;
view: HabiticaSyncView; view: HabiticaSyncView;
async onload() { async onload() {
console.log("load plugin: habitica-sync")
await this.loadSettings(); await this.loadSettings();
this.addSettingTab(new HabiticaSyncSettingsTab(this.app, this)); this.addSettingTab(new HabiticaSyncSettingsTab(this.app, this));
this.registerView( this.registerView(
VIEW_TYPE, VIEW_TYPE,
(leaf) => (new HabiticaSyncView(leaf, this)) (leaf) => (new HabiticaSyncView(leaf, this))
); );
this.addRibbonIcon("popup-open", "Open Habitica Pane", () => { //activate view this.addRibbonIcon("popup-open", "Open Habitica Pane", () => {
this.activateView(); this.activateView();
}); });
this.addCommand({ this.addCommand({
@ -32,6 +39,7 @@ export default class HabiticaSync extends Plugin {
this.activateView(); this.activateView();
} }
}); });
} }
async loadSettings() { async loadSettings() {
this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()) this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData())
@ -39,6 +47,7 @@ export default class HabiticaSync extends Plugin {
async saveSettings() { async saveSettings() {
await this.saveData(this.settings); await this.saveData(this.settings);
} }
async onunload() { async onunload() {
await this.view.onClose(); await this.view.onClose();

View file

@ -1,5 +1,6 @@
import HabiticaSync from "./main"; import HabiticaSync from "./main";
import { App, PluginSettingTab, Setting } from "obsidian"; import { App, PluginSettingTab, Setting } from "obsidian";
import moment from "moment";
export class HabiticaSyncSettingsTab extends PluginSettingTab { export class HabiticaSyncSettingsTab extends PluginSettingTab {
plugin: HabiticaSync; plugin: HabiticaSync;
@ -38,5 +39,42 @@ export class HabiticaSyncSettingsTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
); );
new Setting(containerEl)
.setName("Show Task Descriptions")
.setDesc("Updates require pane re-opening")
.addToggle(cb => {
cb
.setValue(this.plugin.settings.showTaskDescription)
.onChange(async (isEnable) => {
this.plugin.settings.showTaskDescription = isEnable;
await this.plugin.saveSettings();
})
});
new Setting(containerEl)
.setName("Show Sub-Tasks")
.setDesc("Updates require pane re-opening")
.addToggle(cb => {
cb
.setValue(this.plugin.settings.showSubTasks)
.onChange(async (isEnable) => {
this.plugin.settings.showSubTasks = isEnable;
await this.plugin.saveSettings();
})
});
new Setting(containerEl)
.setName("Due Date Format")
.setDesc("Update requires pane re-opening, check moment.js docs for formatting. Current Format: " + moment().format(this.plugin.settings.dueDateFormat))
.addText((text) =>
text
.setPlaceholder("DD-MM-YYYY")
.setValue(this.plugin.settings.dueDateFormat)
.onChange(async (value) => {
this.plugin.settings.dueDateFormat = value;
await this.plugin.saveSettings();
})
);
} }
} }

View file

@ -31,7 +31,6 @@ export class HabiticaSyncView extends ItemView {
this.containerEl.children[1] this.containerEl.children[1]
) )
} }
async onClose(){ async onClose(){
ReactDOM.unmountComponentAtNode(this.containerEl.children[1]); ReactDOM.unmountComponentAtNode(this.containerEl.children[1]);
} }

View file

@ -1,10 +1,11 @@
import * as React from "react"; import * as React from "react";
import { Notice } from "obsidian"; import { Notice } from "obsidian";
import { getStats, scoreTask, makeCronReq } from "./habiticaAPI" import { getStats, scoreTask, makeCronReq, costReward, scoreChecklistItem } from "./habiticaAPI"
import Statsview from "./Components/Statsview" import Statsview from "./Components/Statsview"
import Taskview from "./Components/Taskview" import Taskview from "./Components/Taskview"
import ReactDOM from "react-dom";
class App extends React.Component<any,any> { class App extends React.Component<any, any> {
private _username = ""; private _username = "";
public get username() { public get username() {
return this._username; return this._username;
@ -33,6 +34,7 @@ class App extends React.Component<any,any> {
stats: { stats: {
hp: 0, hp: 0,
lvl: 0, lvl: 0,
gold: 0,
}, },
lastCron: "", lastCron: "",
}, },
@ -43,6 +45,8 @@ class App extends React.Component<any,any> {
this.handleChangeTodos = this.handleChangeTodos.bind(this); this.handleChangeTodos = this.handleChangeTodos.bind(this);
this.handleChangeDailys = this.handleChangeDailys.bind(this); this.handleChangeDailys = this.handleChangeDailys.bind(this);
this.handleChangeHabits = this.handleChangeHabits.bind(this); this.handleChangeHabits = this.handleChangeHabits.bind(this);
this.handleChangeRewards = this.handleChangeRewards.bind(this);
this.handleChangeChecklistItem = this.handleChangeChecklistItem.bind(this);
this.runCron = this.runCron.bind(this); this.runCron = this.runCron.bind(this);
} }
@ -50,10 +54,10 @@ class App extends React.Component<any,any> {
let cronDate = new Date(lastCron); let cronDate = new Date(lastCron);
let now = new Date(); let now = new Date();
if (cronDate.getDate() != now.getDate() || (cronDate.getMonth() != now.getMonth() || cronDate.getFullYear() != now.getFullYear())) { if (cronDate.getDate() != now.getDate() || (cronDate.getMonth() != now.getMonth() || cronDate.getFullYear() != now.getFullYear())) {
return( return (
<div className="cron"> <div className="cron">
<div id="cronMessage"> Welcome back! Please check your tasks for the last day and hit continue to get your daily rewards. </div> <div id="cronMessage"> Welcome back! Please check your tasks for the last day and hit continue to get your daily rewards.</div>
<button onClick={this.runCron}>Continue</button> <button id="cronButton" onClick={this.runCron}>Continue</button>
</div> </div>
); );
} }
@ -97,11 +101,11 @@ class App extends React.Component<any,any> {
this.reloadData() this.reloadData()
} }
async sendScore(id:string , score: string, message: string){ async sendScore(id: string, score: string, message: string) {
try { try {
let response = await scoreTask(this.username, this.credentials, id, score); let response = await scoreTask(this.username, this.credentials, id, score);
let result = await response.json(); let result = await response.json();
if(result.success === true){ if (result.success === true) {
new Notice(message); new Notice(message);
this.reloadData(); this.reloadData();
} else { } else {
@ -114,60 +118,107 @@ class App extends React.Component<any,any> {
} }
} }
handleChangeTodos(event: any){ async sendReward(id: string, score: string, message: string) {
this.state.tasks.todos.forEach((element: any) => { try {
if(element.id == event.target.id){ let response = await costReward(this.username, this.credentials, id, score);
if(!element.completed){ let result = await response.json();
this.sendScore(event.target.id,"up", "Checked!") if (result.success === true) {
new Notice(message);
this.reloadData();
} else { } else {
this.sendScore(event.target.id,"down", "Un-Checked!") new Notice("Resyncing, please try again");
this.reloadData();
} }
} } catch (e) {
}) console.log(e);
} new Notice("API Error: Please check credentials")
handleChangeDailys(event: any){
this.state.tasks.dailys.forEach((element: any) => {
if(element.id == event.target.id){
if(element.id == event.target.id){
if(!element.completed){
this.sendScore(event.target.id,"up", "Checked!")
} else {
this.sendScore(event.target.id,"down", "Un-Checked!")
}
}
}
})
}
handleChangeHabits(event: any){
const target_id = event.target.id.slice(4)
if(event.target.id.slice(0,4) == "plus"){
this.state.tasks.habits.forEach((element: any) => {
if(element.id == target_id){
this.sendScore(target_id,"up", "Plus!")
}
})
}
else {
this.state.tasks.habits.forEach((element: any) => {
if(element.id == target_id){
this.sendScore(target_id,"down", "Minus :(")
}
})
} }
} }
render(){ handleChangeTodos(event: any) {
this.state.tasks.todos.forEach((element: any) => {
if (element.id == event.target.id) {
if (!element.completed) {
this.sendScore(event.target.id, "up", "Checked!")
} else {
this.sendScore(event.target.id, "down", "Un-Checked!")
}
}
})
}
handleChangeDailys(event: any) {
this.state.tasks.dailys.forEach((element: any) => {
if (element.id == event.target.id) {
if (element.id == event.target.id) {
if (!element.completed) {
this.sendScore(event.target.id, "up", "Checked!")
} else {
this.sendScore(event.target.id, "down", "Un-Checked!")
}
}
}
})
}
handleChangeHabits(event: any) {
const target_id = event.target.id.slice(4)
if (event.target.id.slice(0, 4) == "plus") {
this.state.tasks.habits.forEach((element: any) => {
if (element.id == target_id) {
this.sendScore(target_id, "up", "Plus!")
}
})
}
else {
this.state.tasks.habits.forEach((element: any) => {
if (element.id == target_id) {
this.sendScore(target_id, "down", "Minus :(")
}
})
}
}
handleChangeRewards(event: any) {
const target_id = event.target.id
this.state.tasks.rewards.forEach((element: any) => {
if (element.id == event.target.id) {
if (element.id == target_id) {
this.sendReward(target_id, "down", "Redeemed!")
}
}
})
}
async handleChangeChecklistItem(event: any){
let parentID = event.target.parentNode.parentNode.parentNode.getAttribute("id")
let targetID = event.target.id
console.log(parentID+ " , " + targetID)
try{
let response = await scoreChecklistItem(this.username, this.credentials, targetID, parentID);
let result = await response.json();
if (result.success === true) {
new Notice("Checked!");
this.reloadData();
} else {
new Notice("Resyncing, please try again");
this.reloadData();
}
} catch (e) {
console.log(e);
new Notice("API Error: Please check credentials")
}
}
render() {
let content = this.CheckCron(this.state.user_data.lastCron); let content = this.CheckCron(this.state.user_data.lastCron);
if(this.state.error) if (this.state.error)
return(<div className="loading">Loading....</div>) return (<div className="loading">Loading....</div>)
else if(!this.state.isLoaded) else if (!this.state.isLoaded)
return <div className="loading">Loading....</div> return <div className="loading">Loading....</div>
else { else {
return (<div className="plugin-root"> return (<div className="plugin-root">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Statsview user_data={this.state.user_data} />
<Taskview data={this.state.tasks} handleChangeTodos={this.handleChangeTodos} handleChangeDailys={this.handleChangeDailys} handleChangeHabits={this.handleChangeHabits}/>
{content} {content}
<Statsview className ="stats-view" user_data={this.state.user_data} />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Taskview data={this.state.tasks} handleChangeTodos={this.handleChangeTodos} settings = {this.props.plugin.settings} handleChangeDailys={this.handleChangeDailys} handleChangeHabits={this.handleChangeHabits} handleChangeRewards={this.handleChangeRewards} handleChangeChecklistItem={this.handleChangeChecklistItem}/>
</div> </div>
); );
} }

View file

@ -3,9 +3,13 @@ import * as React from 'react';
export default function Index(props: any) { export default function Index(props: any) {
return( return(
<div className="stats"> <div className="stats">
<div id="profile-name">{props.user_data.profile.name}</div> {/* <div id="profile-name">{props.user_data.profile.name}</div> */}
<div className = "substats" id="hp"><i className="material-icons">favorite</i>HP: {(props.user_data.stats.hp).toPrecision(3)}</div> <div className = "substats" id="hp">HP: {numberWithCommas((props.user_data.stats.hp).toFixed(0))}</div>
<div className = "substats" id="lvl"><i className="material-icons">star</i>LVL: {props.user_data.stats.lvl}</div> <div className = "substats" id="lvl">LEVEL: {props.user_data.stats.lvl}</div>
<div className = "substats" id="gold">GOLD: {numberWithCommas(props.user_data.stats.gp.toFixed(2))}</div>
</div> </div>
); );
} }
function numberWithCommas(x: any) {
return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",");
}

View file

@ -1,11 +1,20 @@
import Emoji from "react-emoji-render";
import * as React from "react"; import * as React from "react";
import DailySubTasks from "./DailySubTasks";
import renderMarkdown from "../markdownRender";
function DailyItem(props: any) { function DailyItem(props: any) {
var text_html = renderMarkdown(props.daily_text);
var note_html = renderMarkdown(props.daily_notes);
return ( return (
<div className="todo-item" id={props.id}> <div className="todo-item" id={props.id}>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/> <input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed} />
<p><Emoji text={props.daily_text}></Emoji></p> <div>
<p><span dangerouslySetInnerHTML={{__html: text_html}}></span></p>
<div className="description" dangerouslySetInnerHTML={{__html: note_html}}></div>
{/* {console.log(props.checklist)} */}
<DailySubTasks key={props.daily_subtasks.id} subtasks={props.daily_subtasks} onChangeChecklistItem={props.onChangeChecklistItem}></DailySubTasks>
</div>
</div> </div>
) )
} }

View file

@ -0,0 +1,22 @@
import * as React from "react";
import renderMarkdown from "../markdownRender";
function DailySubTasks(props: any) {
if (props.subtasks) {
const subtasks = props.subtasks.map((subtask: any) => {
let subtask_text = renderMarkdown(subtask.text);
return (
<div className="subtask" id={subtask.id} key={subtask.id} >
<input id={subtask.id} type="checkbox" className="checkbox-checklist" onChange={props.onChangeChecklistItem} checked={subtask.completed} />
<p id={subtask.id}><span dangerouslySetInnerHTML={{__html: subtask_text}}></span></p>
</div>
)
});
return subtasks
}
else {
return <div></div>
}
}
export default DailySubTasks

View file

@ -3,23 +3,89 @@ import DailyItem from "./DailyItem"
import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
export default function Index(props: any){ export default function Index(props: any){
if(props.dailys == undefined) { if(props.dailys == undefined) {
return <div id="classDisplay">No Dailies Present</div> return <div id="classDisplay">No Dailies Present</div>
} }
else { else {
const notDueDailies = props.dailys.map((daily: any) => {
if (!daily.isDue) {
let daily_notes = '';
let daily_subtasks = '';
if (props.settings.showTaskDescription) {
daily_notes = daily.notes;
}
if (props.settings.showSubTasks) {
daily_subtasks = daily.checklist;
}
return <DailyItem key={daily.id} id={daily.id} daily_text={daily.text}
daily_notes={daily_notes} daily_subtasks={daily_subtasks}
onChange={props.onChange} completed={daily.completed} onChangeChecklistItem={props.onChangeChecklistItem}/>
}
})
const incompleteDailies = props.dailys.map((daily: any) => { const incompleteDailies = props.dailys.map((daily: any) => {
if(!daily.completed) if (!daily.completed&&daily.isDue) {
return <DailyItem key={daily.id} id={daily.id}daily_text={daily.text} onChange={props.onChange} completed={daily.completed}/> let daily_notes = '';
let daily_subtasks = '';
if (props.settings.showTaskDescription) {
daily_notes = daily.notes;
}
if (props.settings.showSubTasks) {
daily_subtasks = daily.checklist;
}
return <DailyItem key={daily.id} id={daily.id} daily_text={daily.text}
daily_notes={daily_notes} daily_subtasks={daily_subtasks}
onChange={props.onChange} completed={daily.completed} onChangeChecklistItem={props.onChangeChecklistItem}/>
}
}) })
const completedDailies = props.dailys.map((daily: any) => { const completedDailies = props.dailys.map((daily: any) => {
if(daily.completed) // if(daily.completed)
return <DailyItem key={daily.id} id={daily.id}daily_text={daily.text} onChange={props.onChange} completed={daily.completed}/> // return <DailyItem key={daily.id} id={daily.id} daily_text={daily.text} daily_notes={daily.notes} onChange={props.onChange} completed={daily.completed}/>
if (daily.completed) {
let daily_notes = '';
let daily_subtasks = '';
if (props.settings.showTaskDescription) {
daily_notes = daily.notes;
}
if (props.settings.showSubTasks) {
daily_subtasks = daily.checklist;
}
return <DailyItem key={daily.id} id={daily.id} daily_text={daily.text}
daily_notes={daily_notes} daily_subtasks={daily_subtasks}
onChange={props.onChange} completed={daily.completed} onChangeChecklistItem={props.onChangeChecklistItem}/>
}
}) })
const allDailies = props.dailys.map((daily: any) => {
// if(daily.completed)
// return <DailyItem key={daily.id} id={daily.id} daily_text={daily.text} daily_notes={daily.notes} onChange={props.onChange} completed={daily.completed}/>
let daily_notes = '';
let daily_subtasks = '';
if (props.settings.showTaskDescription) {
daily_notes = daily.notes;
}
if (props.settings.showSubTasks) {
daily_subtasks = daily.checklist;
}
return <DailyItem key={daily.id} id={daily.id} daily_text={daily.text}
daily_notes={daily_notes} daily_subtasks={daily_subtasks}
onChange={props.onChange} completed={daily.completed} onChangeChecklistItem={props.onChangeChecklistItem}/>
})
const display = <div id="classDisplay"> const display = <div id="classDisplay">
<Tabs> <Tabs>
<TabList> <TabList>
<Tab>Active</Tab> <Tab>Active</Tab>
<Tab>Completed</Tab> <Tab>Completed</Tab>
<Tab>Not Due</Tab>
<Tab>All</Tab>
</TabList> </TabList>
<TabPanel> <TabPanel>
<ul>{incompleteDailies}</ul> <ul>{incompleteDailies}</ul>
@ -27,10 +93,15 @@ export default function Index(props: any){
<TabPanel> <TabPanel>
<ul>{completedDailies}</ul> <ul>{completedDailies}</ul>
</TabPanel> </TabPanel>
<TabPanel>
<ul>{notDueDailies}</ul>
</TabPanel>
<TabPanel>
<ul>{allDailies}</ul>
</TabPanel>
</Tabs> </Tabs>
</div> </div>
return(display); return(display);
} }
} }

View file

@ -1,17 +1,24 @@
import Emoji from "react-emoji-render";
import * as React from "react"; import * as React from "react";
import renderMarkdown from "../markdownRender";
function HabitItem(props: any) { function HabitItem(props: any) {
let habit_text = renderMarkdown(props.habit_text);
let habit_notes = renderMarkdown(props.habit_notes);
return ( return (
<div className="habit-item" id={props.id}> <div className="habit-item" id={props.id}>
<button className="habit-plus" id={"plus"+props.id} onClick={props.onChange}> <div className="habit-button-grp">
<button className="habit-button" id={"plus" + props.id} onClick={props.onChange}>
+{props.upCount} +{props.upCount}
</button> </button>
<p className="habit-text"><Emoji text = {props.habit_text}></Emoji></p> <button className="habit-button" id={"mins" + props.id} onClick={props.onChange}>
<button className="habit-minus" id={"mins"+props.id} onClick={props.onChange}>
-{props.downCount} -{props.downCount}
</button> </button>
</div> </div>
<div>
<p className="habit-text"><span dangerouslySetInnerHTML={{__html: habit_text}}></span></p>
<div className="description" dangerouslySetInnerHTML={{__html: habit_notes}}></div>
</div>
</div>
) )
} }

View file

@ -9,7 +9,11 @@ export default function Index(props: any){
} }
else { else {
const allHabits = props.habits.map((habit: any) => { const allHabits = props.habits.map((habit: any) => {
if (props.settings.showTaskDescription) {
return <HabitItem key={habit.id} id={habit.id} habit_text={habit.text} habit_notes={habit.notes} upCount={habit.counterUp} downCount={habit.counterDown} onChange={props.onChange}/>
} else {
return <HabitItem key={habit.id} id={habit.id} habit_text={habit.text} upCount={habit.counterUp} downCount={habit.counterDown} onChange={props.onChange}/> return <HabitItem key={habit.id} id={habit.id} habit_text={habit.text} upCount={habit.counterUp} downCount={habit.counterDown} onChange={props.onChange}/>
}
}) })
const display = <div id="classDisplay"> const display = <div id="classDisplay">
<ul>{allHabits}</ul> <ul>{allHabits}</ul>

View file

@ -0,0 +1,21 @@
import * as React from "react";
import renderMarkdown from "../markdownRender";
function RewardItem(props: any) {
let reward_text = renderMarkdown(props.reward_text);
let reward_notes = renderMarkdown(props.reward_notes);
return (
<div className="habit-item" id={props.id}>
<div className="habit-button-grp">
<button className="habit-button" id={props.id} onClick={props.onChange}>-{props.reward_value}</button>
</div>
<div>
<p className="habit-text"><span dangerouslySetInnerHTML={{__html: reward_text}}></span></p>
<div className="description" dangerouslySetInnerHTML={{__html: reward_notes}}></div>
</div>
</div>
)
}
export default RewardItem

View file

@ -0,0 +1,25 @@
import * as React from "react";
import RewardItem from "./RewardItem"
export default function Index(props: any){
if(props.rewards == undefined) {
return (<div id="classDisplay">
No Rewards present.
</div>)
}
else {
const allRewards = props.rewards.map((reward: any) => {
if (props.settings.showTaskDescription) {
return <RewardItem key={reward.id} id={reward.id} reward_text={reward.text} reward_notes={reward.notes} reward_value={reward.value} onChange={props.onChange}/>
} else {
return <RewardItem key={reward.id} id={reward.id} reward_text={reward.text} reward_value={reward.value} onChange={props.onChange}/>
}
})
const display = <div id="classDisplay">
<ul>{allRewards}</ul>
</div>
return(display);
}
}

View file

@ -1,13 +0,0 @@
import { Emoji } from 'react-emoji-render';
import * as React from "react";
function TodoItem(props: any) {
return (
<div className="todo-item" id={props.id}>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/>
<Emoji text = {props.todo_text}></Emoji>
</div>
)
}
export default TodoItem

View file

@ -1,11 +1,21 @@
import Emoji from "react-emoji-render";
import * as React from "react"; import * as React from "react";
import TodoSubTasks from "./TodoSubTasks";
import renderMarkdown from "../markdownRender"
import moment from "moment";
function TodoItem(props: any) { function TodoItem(props: any) {
var dueDate = (props.dueDate==null)?"":("Due Date:"+(moment(props.dueDate).format(props.dueDateFormat)));
var text_html = renderMarkdown(props.todo_text);
var note_html = renderMarkdown(props.todo_notes);
return ( return (
<div className="todo-item" id={props.id}> <div className="todo-item" id={props.id}>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/> <input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/>
<p><Emoji text ={props.todo_text}></Emoji></p> <div>
<p><span dangerouslySetInnerHTML={{__html: text_html}}></span></p>
<div className="description" dangerouslySetInnerHTML={{__html: note_html}}></div>
<TodoSubTasks todoID={props.id} subtasks={props.todo_subtasks} onChange={props.onChangeChecklistItem}></TodoSubTasks>
<div className="due-date">{dueDate}</div>
</div>
</div> </div>
) )
} }

View file

@ -0,0 +1,21 @@
import * as React from "react";
import renderMarkdown from "../markdownRender";
function TodoSubTasks(props: any) {
if (props.subtasks) {
const subtasks = props.subtasks.map((subtask: any) => {
let subtask_text = renderMarkdown(subtask.text);
return (
<div className="subtask" id={subtask.id} key={subtask.id}>
<input type="checkbox" className="checkbox" onChange={props.onChange} checked={subtask.completed} id={subtask.id}/>
<p id={subtask.id}><span dangerouslySetInnerHTML={{__html: subtask_text}}></span></p>
</div>
)
});
return subtasks
}
else {
return <div></div>
}
}
export default TodoSubTasks

View file

@ -8,12 +8,26 @@ export default function Index(props: any){
} }
else { else {
const incompleteTodos = props.todos.map((todo: any) => { const incompleteTodos = props.todos.map((todo: any) => {
if(!todo.completed)
return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} onChange={props.onChange} completed={todo.completed}/> if(!todo.completed) {
let todo_notes = '';
let todo_subtasks = '';
if (props.settings.showTaskDescription) {
todo_notes = todo.notes;
}
if (props.settings.showSubTasks) {
todo_subtasks = todo.checklist;
}
return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text}
todo_notes={todo_notes} todo_subtasks={todo_subtasks}
onChange={props.onChange} onChangeChecklistItem={props.onChangeChecklistItem} completed={todo.completed} dueDate={todo.date} dueDateFormat={props.settings.dueDateFormat}/>
}
}) })
const completedTodos = props.todos.map((todo: any) => { const completedTodos = props.todos.map((todo: any) => {
if(todo.completed) if(todo.completed)
return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} onChange={props.onChange} completed={todo.completed}/> return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} todo_notes={todo.notes} onChange={props.onChange} completed={todo.completed}/>
}) })
const display = <div id="classDisplay"> const display = <div id="classDisplay">
<Tabs> <Tabs>
@ -22,10 +36,10 @@ export default function Index(props: any){
<Tab>Completed</Tab> <Tab>Completed</Tab>
</TabList> </TabList>
<TabPanel> <TabPanel>
<ul>{incompleteTodos}</ul> <ul className="todolist-indent">{incompleteTodos}</ul>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<ul>{completedTodos}</ul> <ul className="todolist-indent">{completedTodos}</ul>
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</div> </div>

View file

@ -1 +0,0 @@
import * as React from "react"

View file

@ -2,33 +2,41 @@ import * as React from "react";
import Dailiesview from "./Dailiesview" import Dailiesview from "./Dailiesview"
import Habitsview from "./Habitsview" import Habitsview from "./Habitsview"
import Todoview from "./Todoview" import Todoview from "./Todoview"
import Rewardview from "./Rewardview"
import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
export default function Index(props: any){ export default function Index(props: any){
const display = <div className="task-view"> const display = <div className="task-view">
<Tabs> <Tabs>
<TabList> <TabList>
<Tab >
<span className="material-icons md-24">task_alt</span>
</Tab>
<Tab> <Tab>
<span className="material-icons md-24">today</span> <span className="material-icons md-24">today</span>
</Tab> </Tab>
<Tab >
<span className="material-icons md-24">add_chart</span>
</Tab>
<Tab> <Tab>
<span className="material-icons md-24">add_circle_outline</span> <span className="material-icons md-24">assignment_turned_in</span>
</Tab>
<Tab>
<span className="material-icons md-24">account_balance</span>
</Tab> </Tab>
</TabList> </TabList>
<TabPanel> <TabPanel>
<Habitsview habits={props.data.habits} onChange={props.handleChangeHabits}/> <Dailiesview dailys={props.data.dailys} settings = {props.settings} onChange={props.handleChangeDailys} onChangeChecklistItem={props.handleChangeChecklistItem}/>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<Dailiesview dailys={props.data.dailys} onChange={props.handleChangeDailys} /> <Habitsview habits={props.data.habits} settings = {props.settings} onChange={props.handleChangeHabits}/>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<Todoview todos={props.data.todos} onChange={props.handleChangeTodos} /> <Todoview todos={props.data.todos} settings = {props.settings} onChange={props.handleChangeTodos} onChangeChecklistItem={props.handleChangeChecklistItem}/>
</TabPanel>
<TabPanel>
<Rewardview rewards={props.data.rewards} settings = {props.settings} onChange={props.handleChangeRewards} />
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</div> </div>
return(display); return(display);
} //yes }

View file

@ -0,0 +1,21 @@
import MarkdownIt from "markdown-it";
import markdownitEmoji from "markdown-it-emoji"
import twemoji from "twemoji";
export default function renderMarkdown(markdown: string) {
//check if markdown is empty or not a string
if (markdown === "" || markdown === undefined) {
return "";
}
const md = new MarkdownIt({
html: true,
breaks: true,
linkify: true,
typographer: true
});
md.use(markdownitEmoji);
md.renderer.rules.emoji = function(token, idx) {
return twemoji.parse(token[idx].content);
};
return md.render(markdown);
}

View file

@ -40,3 +40,31 @@ export async function makeCronReq(username: string, credentials: string){
}) })
return(response) return(response)
} }
export async function costReward(username: string, credentials: string, taskID: string, direction: string) {
const url = "https://habitica.com/api/v4/tasks/".concat(taskID).concat("/score/").concat(direction)
const response = fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync",
"x-api-user": username,
"x-api-key": credentials,
}
})
return(response)
}
export async function scoreChecklistItem(username: string, credentials: string, checklistItemID: string, taskID: string) {
const url = "https://habitica.com/api/v3/tasks/".concat(taskID).concat("/checklist/").concat(checklistItemID).concat("/score")
const response = fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync",
"x-api-user": username,
"x-api-key": credentials,
}
})
return(response)
}

View file

@ -1,3 +1,9 @@
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@1,300&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Open Sans:ital,wght@0,400;1,100&family=Roboto&display=swap');
.add-task-input {
display: flex;
}
#profile-name { #profile-name {
font-size: x-large; font-size: x-large;
@ -5,79 +11,149 @@
padding-bottom: 3%; padding-bottom: 3%;
} }
.stats { .stats {
padding-bottom: 6px; width: 95%;
display: flex;
justify-content: space-between;
font-weight: bold;
padding-bottom: 5px;
}
.stats-view {
border-bottom: 1px;
}
.modify-todo {
align-self: center;
}
.delete-todo {
align-self: center;
} }
.todo-item { .todo-item {
display: flex; display: grid;
justify-content: flex-start; grid-template-columns: 1fr 30fr 1fr 1fr;
align-items: center; justify-content: left;
padding: 0px 0px 0; align-items: flex-start;
width: 80%; padding-top: 5px;
padding-bottom: 5px;
width: 100%;
border-bottom: 1px solid #cecece; border-bottom: 1px solid #cecece;
font-family: Roboto, sans-serif; font-family: Roboto, sans-serif;
font-weight: normal; font-weight: bold;
font-size: 16px; font-size: 16px;
padding-left: 0;
}
.description {
font-family: Open Sans, sans-serif;
font-weight: 100;
}
.description > ul {
list-style-type: none;
}
p {
margin: 0;
} }
.habit-text { .habit-text {
text-align: center !important; text-align: left !important;
padding: 0px; font-weight: bold;
width: 50%; padding-top: 5px;
width: 80%;
margin-right: 20px; margin-right: 20px;
} }
.habit-plus { .habit-button {
/* background-color: #fff; */ background-color: var(--interactive-accent);
border: none; border: none;
color: white; /* color: black; */
padding: 7px 10px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
font-size: 16px; font-size: 16px;
display: block;
width: 100%;
color: var(--text-on-accent);
} }
/* habit-button on hover css selector */
.habit-minus { .habit-button:hover {
/* background-color: #fff; */ color: var(--text-on-accent);
/* border-radius: 50%; */ background-color: var(--interactive-accent-hover);
border: none;
color: white;
padding: 7px 10px;
text-align: center;
text-decoration: none;
font-size: 16px;
} }
.habit-item { .habit-item {
display: flex; display: flex;
/* justify-content: center; */ grid-template-columns: 60px 1fr;
align-content: space-between;
align-items: center;
padding: 0px 0px 0;
width: 100%; width: 100%;
gap: 5px;
border-bottom: 1px solid #cecece; border-bottom: 1px solid #cecece;
font-family: Roboto, sans-serif; font-family: Open Sans, sans-serif;
font-weight: 50%; font-weight: 100%;
font-size: 16px; font-size: 16px;
padding-top: 5px;
padding-bottom: 5px;
}
.habit-button-grp {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-content: stretch;
height: 100%;
} }
input[type=checkbox] { input[type=checkbox] {
margin-right: 10px; margin-right: 10px;
margin-top: 5px;
align-self: start;
}
.todo-content {
align-self: center;
}
.submit-button {
/* padding: 5px 5px; */
font-size: 15px;
border: 1px solid #aaa;
/* white-space: nowrap; */
/* margin: 10px; */
margin: 0;
} }
input[type=checkbox]:focus { input[type=checkbox]:focus {
outline: 0; outline: 0;
} }
::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
.plugin-root { .plugin-root {
min-width: 260px; min-width: 260px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
#classDisplay {
height: 100%;
width: 100%;
}
.view-content {
margin-bottom: 0;
} }
.substats { .substats {
font-size: medium; font-size: medium;
} }
ul {
margin: 0;
}
/* react-tabs internal file :wink: */ /* react-tabs internal file :wink: */
.material-icons { .material-icons {
font-size: 12px !important; font-size: 12px !important;
@ -94,12 +170,12 @@ input[type=checkbox]:focus {
.react-tabs { .react-tabs {
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
height: 100%;
} }
.react-tabs__tab-list { .react-tabs__tab-list {
border-bottom: 1px solid #aaa; border-bottom: 1px solid #aaa;
margin: 0 0 10px; margin: 0 0 5px;
padding: 0; padding: 0;
} }
@ -144,6 +220,14 @@ input[type=checkbox]:focus {
.react-tabs__tab-panel { .react-tabs__tab-panel {
display: none; display: none;
left: 0px;
height: 88%;
/* overflow: scroll; */
}
.task-panel {
overflow: scroll;
height: 100%;
} }
.react-tabs__tab-panel--selected { .react-tabs__tab-panel--selected {
@ -159,9 +243,45 @@ ul li:not(.task-list-item)::before {
font-weight: bold; font-weight: bold;
text-shadow: 0 0 0.5em transparent; text-shadow: 0 0 0.5em transparent;
} }
body > div.app-container.is-left-sidedock-collapsed.is-right-sidedock-collapsed > div.horizontal-main-container > div > div.workspace-split.mod-horizontal.mod-right-split > div.workspace-tabs > div.workspace-leaf > div > div.view-content > div > div.cron > button {
.task-operation {
align-self: center;
margin: 0;
padding: 0;
}
.edit-item {
display: flex;
flex-direction: column;
width: 100%;
}
.edit-button {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.task-submit {
display: grid;
grid-template-columns: 10fr 5fr;
}
.add-task-input {
display: block;
width: 100%;
}
.task-input-box {
margin-right: 10px;
}
button {
margin: auto; margin: auto;
margin-bottom: 5px; margin-bottom: 5px;
white-space: nowrap;
padding: 5px 5px;
margin-right: 0;
} }
.cron { .cron {
display: inline-grid; display: inline-grid;
@ -171,7 +291,6 @@ body > div.app-container.is-left-sidedock-collapsed.is-right-sidedock-collapsed
margin-left: 10%; margin-left: 10%;
margin-right: 10%; margin-right: 10%;
margin-bottom: 10px; margin-bottom: 10px;
background-color: var(--background-secondary-alt);
border-radius: 10px; border-radius: 10px;
} }
#cronMessage { #cronMessage {
@ -179,3 +298,26 @@ body > div.app-container.is-left-sidedock-collapsed.is-right-sidedock-collapsed
margin-bottom: 10px; margin-bottom: 10px;
color: var(--text-normal) color: var(--text-normal)
} }
#cronButton {
margin: auto;
margin-bottom: 5px;
white-space: nowrap;
padding: 5px 5px;
background-color: var(--interactive-accent);
margin-right: auto;
}
.subtask {
display: flex;
flex-direction: row;
font-weight: normal;
font-family: Roboto, sans-serif;
justify-content: flex-start;
}
.emoji {
height: 1em;
}
.description>ul {
list-style-type: disc;
margin-left: 10% !important;
}

View file

@ -9,6 +9,8 @@
"noImplicitAny": true, "noImplicitAny": true,
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"lib": [ "lib": [
"dom", "dom",
"es5", "es5",