diff --git a/.gitignore b/.gitignore index f36d0a2..de7c3d1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ node_modules package-lock.json +# yarn +yarn.lock + *.js.map diff --git a/.hotreload b/.hotreload new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index ce90528..43497e5 100644 --- a/README.md +++ b/README.md @@ -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. ## Features ### Pane View -#### View HP, XP, todos, dailies, and habits -[![Image from Gyazo](https://i.gyazo.com/4266d01941e71fef41819ea8a6b6592e.png)](https://gyazo.com/4266d01941e71fef41819ea8a6b6592e) -[![Image from Gyazo](https://i.gyazo.com/697a58b8e7ffd3df86a2944b6abbaa92.png)](https://gyazo.com/697a58b8e7ffd3df86a2944b6abbaa92) -- Tab for active/completed tasks +#### View stats (HP, XP, coins) +#### Views +Task Information: +- Title, description, subtasks + - Markdown and emoji support -#### Check off tasks/dailies in the view, modify habit counters (+/-) -[![Image from Gyazo](https://i.gyazo.com/5759e12bc5267711c5e03485a6d72c2f.gif)](https://gyazo.com/5759e12bc5267711c5e03485a6d72c2f) -- Can uncheck completed habits/todos as shown above +Tabs: +- To Do's + - 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 Token API:** You can find this by clicking on the "User" icon in the top right of the Habitica webapp, "Settings", then "API" - -The following two inputs help fetch your user data to be displayed in the Obsidian view. +- **Show Task Descriptions:** Toggles whether description/notes for tasks will be shown or not +- **Show Subtasks:** Toggles whether subtasks for to do's/dailies will be shown or not ## Roadmap -We will be implementing additional features including: -- Add/delete/customize tasks and habits -- View/claim rewards *Feel free to support us and donate!* Buy Me a Coffee at ko-fi.com + diff --git a/manifest.json b/manifest.json index bfa0b32..2ae5bfe 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-habitica-integration", "name": "Habitica Sync", - "version": "0.9.6", + "version": "1.0.2", "minAppVersion": "0.9.12", "description": "This plugin helps integrate Habitica user tasks and stats into Obsidian", "author": "Leoh and Ran", diff --git a/package.json b/package.json index 4acb414..37371e6 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,51 @@ { "name": "obsidian-habitica-integration", - "version": "0.12.0", + "version": "1.0.0", "description": "This plugin allows for Habitica integration into Obsidian", "main": "main.js", "scripts": { - "dev": "rollup --config rollup.config.js -w", - "build": "rollup --config rollup.config.js --environment BUILD:production" + "dev": "rollup --config rollup.config.mjs -w", + "build": "rollup --config rollup.config.mjs --environment BUILD:production", + "dev2": "obsidian-plugin dev src/main.ts" }, "keywords": [], "author": "Leonard and Ran", "license": "MIT", "devDependencies": { - "@rollup/plugin-commonjs": "^18.0.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-typescript": "^8.2.1", - "@types/node": "^14.14.37", - "@types/react": "^17.0.27", - "@types/react-dom": "^17.0.9", - "@types/react-tabs": "^2.3.3", - "css-loader": "^6.4.0", - "extract-text-webpack-plugin": "^2.1.2", - "obsidian": "^0.12.0", - "rollup": "^2.32.1", - "style-loader": "^3.3.0", - "tslib": "^2.2.0", - "typescript": "^4.2.4" + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-typescript": "^11.1.6", + "@types/markdown-it": "^13.0.7", + "@types/markdown-it-emoji": "^2.0.4", + "@types/node": "^20.11.0", + "@types/node-emoji": "^1.8.2", + "@types/react": "^18.2.47", + "@types/react-dom": "^18.2.18", + "@types/react-tabs": "^5.0.4", + "@types/twemoji": "^13.1.1", + "css-loader": "^6.9.0", + "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": { - "node": "^16.10.0", - "node-fetch": "^3.0.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-tabs": "^3.2.2" + "markdown-it": "^14.0.0", + "markdown-it-emoji": "^2.0.2", + "moment": "^2.30.1", + "node": "^21.2.0", + "node-emoji": "^2.1.3", + "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" } } diff --git a/rollup.config.js b/rollup.config.mjs similarity index 92% rename from rollup.config.js rename to rollup.config.mjs index 8e28e8d..1551460 100644 --- a/rollup.config.js +++ b/rollup.config.mjs @@ -1,30 +1,32 @@ -import typescript from '@rollup/plugin-typescript'; -import {nodeResolve} from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; - -const isProd = (process.env.BUILD === 'production'); - -const banner = -`/* -THIS IS A GENERATED/BUNDLED FILE BY ROLLUP -if you want to view the source visit the plugins github repository -*/ -`; - -export default { - input: 'src/main.ts', - output: { - dir: '.', - sourcemap: 'inline', - sourcemapExcludeSources: isProd, - format: 'cjs', - exports: 'default', - banner, - }, - external: ['obsidian'], - plugins: [ - typescript(), - nodeResolve({browser: true}), - commonjs(), - ] +import typescript from '@rollup/plugin-typescript'; +import {nodeResolve} from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; + +const isProd = (process.env.BUILD === 'production'); + +const banner = +`/* +THIS IS A GENERATED/BUNDLED FILE BY ROLLUP +if you want to view the source visit the plugins github repository +*/ +`; + +export default { + input: 'src/main.ts', + output: { + dir: '.', + sourcemap: 'inline', + sourcemapExcludeSources: isProd, + format: 'cjs', + exports: 'default', + banner, + }, + external: ['obsidian'], + plugins: [ + typescript(), + nodeResolve({browser: true}), + commonjs(), + json(), + ] }; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index bdf0312..b8eaff4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,23 +5,30 @@ import { HabiticaSyncView, VIEW_TYPE} from "./view" interface HabiticaSyncSettings { userID: string apiToken: string + showTaskDescription: boolean + showSubTasks: boolean + dueDateFormat: string } const DEFAULT_SETTINGS: Partial = { userID: "", - apiToken: "" + apiToken: "", + showTaskDescription: true, + showSubTasks: true, + dueDateFormat: "DD-MM-YYYY" } export default class HabiticaSync extends Plugin { settings: HabiticaSyncSettings; view: HabiticaSyncView; async onload() { + console.log("load plugin: habitica-sync") await this.loadSettings(); this.addSettingTab(new HabiticaSyncSettingsTab(this.app, this)); this.registerView( VIEW_TYPE, (leaf) => (new HabiticaSyncView(leaf, this)) ); - this.addRibbonIcon("popup-open", "Open Habitica Pane", () => { //activate view + this.addRibbonIcon("popup-open", "Open Habitica Pane", () => { this.activateView(); }); this.addCommand({ @@ -32,6 +39,7 @@ export default class HabiticaSync extends Plugin { this.activateView(); } }); + } async loadSettings() { this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()) @@ -39,6 +47,7 @@ export default class HabiticaSync extends Plugin { async saveSettings() { await this.saveData(this.settings); } + async onunload() { await this.view.onClose(); @@ -48,15 +57,15 @@ export default class HabiticaSync extends Plugin { } async activateView() { this.app.workspace.detachLeavesOfType(VIEW_TYPE); - + await this.app.workspace.getRightLeaf(false).setViewState({ type: VIEW_TYPE, active: true, }); - + this.app.workspace.revealLeaf( this.app.workspace.getLeavesOfType(VIEW_TYPE)[0] ); } - + } diff --git a/src/settings.ts b/src/settings.ts index bd298e5..974fe44 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,5 +1,6 @@ import HabiticaSync from "./main"; import { App, PluginSettingTab, Setting } from "obsidian"; +import moment from "moment"; export class HabiticaSyncSettingsTab extends PluginSettingTab { plugin: HabiticaSync; @@ -38,5 +39,42 @@ export class HabiticaSyncSettingsTab extends PluginSettingTab { 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(); + }) + ); + } } \ No newline at end of file diff --git a/src/view.tsx b/src/view.tsx index e73dbb7..be66720 100644 --- a/src/view.tsx +++ b/src/view.tsx @@ -31,8 +31,7 @@ export class HabiticaSyncView extends ItemView { this.containerEl.children[1] ) } - - async onClose(){ + async onClose(){ ReactDOM.unmountComponentAtNode(this.containerEl.children[1]); } } \ No newline at end of file diff --git a/src/view/App.tsx b/src/view/App.tsx index fd8bdff..1406d23 100644 --- a/src/view/App.tsx +++ b/src/view/App.tsx @@ -1,18 +1,31 @@ import * as React from "react"; import { Notice } from "obsidian"; -import { getStats, scoreTask } from "./habiticaAPI" +import { getStats, scoreTask, makeCronReq, costReward, scoreChecklistItem } from "./habiticaAPI" import Statsview from "./Components/Statsview" import Taskview from "./Components/Taskview" +import ReactDOM from "react-dom"; -let username = "" -let credentials = "" - -class App extends React.Component { +class App extends React.Component { + private _username = ""; + public get username() { + return this._username; + } + public set username(value) { + this._username = value; + } + private _credentials = ""; + public get credentials() { + return this._credentials; + } + public set credentials(value) { + this._credentials = value; + } constructor(props: any) { super(props) - username = this.props.plugin.settings.userID - credentials = this.props.plugin.settings.apiToken + this.username = this.props.plugin.settings.userID + this.credentials = this.props.plugin.settings.apiToken this.state = { + needCron: false, isLoaded: false, user_data: { profile: { @@ -21,119 +34,192 @@ class App extends React.Component { stats: { hp: 0, lvl: 0, - } + gold: 0, + }, + lastCron: "", }, todos: [], dailys: [], habits: [], } - this.handleChangeTodos = this.handleChangeTodos.bind(this) - this.handleChangeDailys = this.handleChangeDailys.bind(this) - this.handleChangeHabits = this.handleChangeHabits.bind(this) + this.handleChangeTodos = this.handleChangeTodos.bind(this); + this.handleChangeDailys = this.handleChangeDailys.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); - - } - sendNotice(message: string){ - new Notice(message) + } + CheckCron(lastCron: string) { + let cronDate = new Date(lastCron); + let now = new Date(); + if (cronDate.getDate() != now.getDate() || (cronDate.getMonth() != now.getMonth() || cronDate.getFullYear() != now.getFullYear())) { + return ( +
+
Welcome back! Please check your tasks for the last day and hit continue to get your daily rewards.
+ +
+ ); + } + else { + return null + }; + } + async runCron() { + console.log("running cron"); + try { + let response = await makeCronReq(this.username, this.credentials); + this.setState({ + needCron: false, + }) + } catch (error) { + console.log(error); + new Notice("There was an error running the cron. Please try again later."); + } + this.reloadData(); } async reloadData() { - const result = (await getStats(username, credentials)).json() - result.then( - result => { - if(result.success === false){ - this.sendNotice("Login Failed, Please check credentials and try again!") - } else { - this.setState({ - isLoaded: true, - user_data: result, - tasks: result.tasks, - }) - } - }, - (error) => { - this.setState({ - isLoaded: true, - error - }) + try { + let response = await getStats(this.username, this.credentials); + let result = await response.json(); + if (result.success === false) { + new Notice('Login Failed, Please check credentials and try again!'); } - ) + else { + this.setState({ + isLoaded: true, + user_data: result, + tasks: result.tasks, + }); + } + } catch (e) { + console.log(e); + new Notice("API Error: Please check credentials") + } } componentDidMount() { this.reloadData() } - - async sendScore(id:string , score: string, message: string){ - const result = (await scoreTask(username, credentials, id, score)).json() - result.then( - result => { - if(result.success) { - this.sendNotice(message) - this.reloadData() - } else { - this.sendNotice("Resyncing, please try again") - this.reloadData() - } - }, - (error) => { - this.sendNotice("API Error: Please Check crendentials and try again") - console.log(error) + + async sendScore(id: string, score: string, message: string) { + try { + let response = await scoreTask(this.username, this.credentials, id, score); + let result = await response.json(); + if (result.success === true) { + new Notice(message); + this.reloadData(); + } else { + new Notice("Resyncing, please try again"); + this.reloadData(); } - ) + } catch (e) { + console.log(e); + new Notice("API Error: Please check credentials") + } } - handleChangeTodos(event: any){ + async sendReward(id: string, score: string, message: string) { + try { + let response = await costReward(this.username, this.credentials, id, score); + let result = await response.json(); + if (result.success === true) { + new Notice(message); + this.reloadData(); + } else { + new Notice("Resyncing, please try again"); + this.reloadData(); + } + } catch (e) { + console.log(e); + new Notice("API Error: Please check credentials") + } + } + + 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!") + 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!") + this.sendScore(event.target.id, "down", "Un-Checked!") } } }) } - handleChangeDailys(event: any){ + 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!") + 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!") + this.sendScore(event.target.id, "down", "Un-Checked!") } } } }) } - handleChangeHabits(event: any){ + handleChangeHabits(event: any) { const target_id = event.target.id.slice(4) - if(event.target.id.slice(0,4) == "plus"){ + 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!") + 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 :(") + 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(){ - if(this.state.error) - return(
Loading....
) - else if(!this.state.isLoaded) + render() { + let content = this.CheckCron(this.state.user_data.lastCron); + if (this.state.error) + return (
Loading....
) + else if (!this.state.isLoaded) return
Loading....
else { return (
+ {content} + - - -
+ + + ); } } diff --git a/src/view/Components/Statsview/index.tsx b/src/view/Components/Statsview/index.tsx index f15d886..506cae4 100644 --- a/src/view/Components/Statsview/index.tsx +++ b/src/view/Components/Statsview/index.tsx @@ -3,9 +3,13 @@ import * as React from 'react'; export default function Index(props: any) { return(
-
{props.user_data.profile.name}
-
favoriteHP: {(props.user_data.stats.hp).toPrecision(3)}
-
starLVL: {props.user_data.stats.lvl}
+ {/*
{props.user_data.profile.name}
*/} +
HP: {numberWithCommas((props.user_data.stats.hp).toFixed(0))}
+
LEVEL: {props.user_data.stats.lvl}
+
GOLD: {numberWithCommas(props.user_data.stats.gp.toFixed(2))}
); +} +function numberWithCommas(x: any) { + return x.toString().replace(/\B(? - -

{props.daily_text}

+ +
+

+
+ {/* {console.log(props.checklist)} */} + +
+ ) } diff --git a/src/view/Components/Taskview/Dailiesview/DailySubTasks.tsx b/src/view/Components/Taskview/Dailiesview/DailySubTasks.tsx new file mode 100644 index 0000000..09e4c99 --- /dev/null +++ b/src/view/Components/Taskview/Dailiesview/DailySubTasks.tsx @@ -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 ( +
+ +

+
+ ) + }); + return subtasks + } + else { + return
+ } +} +export default DailySubTasks \ No newline at end of file diff --git a/src/view/Components/Taskview/Dailiesview/index.tsx b/src/view/Components/Taskview/Dailiesview/index.tsx index 0d0f192..db5944b 100644 --- a/src/view/Components/Taskview/Dailiesview/index.tsx +++ b/src/view/Components/Taskview/Dailiesview/index.tsx @@ -3,23 +3,89 @@ import DailyItem from "./DailyItem" import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; export default function Index(props: any){ + if(props.dailys == undefined) { return
No Dailies Present
} 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 + } + }) + const incompleteDailies = props.dailys.map((daily: any) => { - if(!daily.completed) - return - }) + if (!daily.completed&&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 + } + }) const completedDailies = props.dailys.map((daily: any) => { - if(daily.completed) - return + // if(daily.completed) + // return + 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 + } }) + + const allDailies = props.dailys.map((daily: any) => { + // if(daily.completed) + // return + let daily_notes = ''; + let daily_subtasks = ''; + if (props.settings.showTaskDescription) { + daily_notes = daily.notes; + } + + if (props.settings.showSubTasks) { + daily_subtasks = daily.checklist; + } + return + }) + const display =
Active Completed + Not Due + All
    {incompleteDailies}
@@ -27,10 +93,15 @@ export default function Index(props: any){
    {completedDailies}
+ +
    {notDueDailies}
+
+ +
    {allDailies}
+
return(display); } } - diff --git a/src/view/Components/Taskview/Habitsview/HabitItem.tsx b/src/view/Components/Taskview/Habitsview/HabitItem.tsx index faff665..37d672c 100644 --- a/src/view/Components/Taskview/Habitsview/HabitItem.tsx +++ b/src/view/Components/Taskview/Habitsview/HabitItem.tsx @@ -1,15 +1,23 @@ - import * as React from "react"; +import * as React from "react"; +import renderMarkdown from "../markdownRender"; function HabitItem(props: any) { + let habit_text = renderMarkdown(props.habit_text); + let habit_notes = renderMarkdown(props.habit_notes); return (
- -

{props.habit_text}

- +
+ + +
+
+

+
+
) } diff --git a/src/view/Components/Taskview/Habitsview/index.tsx b/src/view/Components/Taskview/Habitsview/index.tsx index dc322bb..1d0107c 100644 --- a/src/view/Components/Taskview/Habitsview/index.tsx +++ b/src/view/Components/Taskview/Habitsview/index.tsx @@ -9,7 +9,11 @@ export default function Index(props: any){ } else { const allHabits = props.habits.map((habit: any) => { - return + if (props.settings.showTaskDescription) { + return + } else { + return + } }) const display =
    {allHabits}
diff --git a/src/view/Components/Taskview/Rewardview/RewardItem.tsx b/src/view/Components/Taskview/Rewardview/RewardItem.tsx new file mode 100644 index 0000000..5f331e2 --- /dev/null +++ b/src/view/Components/Taskview/Rewardview/RewardItem.tsx @@ -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 ( +
+
+ +
+
+

+
+
+ +
+ ) +} + +export default RewardItem \ No newline at end of file diff --git a/src/view/Components/Taskview/Rewardview/index.tsx b/src/view/Components/Taskview/Rewardview/index.tsx new file mode 100644 index 0000000..4ddc21a --- /dev/null +++ b/src/view/Components/Taskview/Rewardview/index.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import RewardItem from "./RewardItem" + +export default function Index(props: any){ + if(props.rewards == undefined) { + return (
+ No Rewards present. +
) + } + else { + const allRewards = props.rewards.map((reward: any) => { + if (props.settings.showTaskDescription) { + return + } else { + return + } + }) + const display =
+
    {allRewards}
+
+ + return(display); + } +} + diff --git a/src/view/Components/Taskview/TodoItem.tsx b/src/view/Components/Taskview/TodoItem.tsx deleted file mode 100644 index 4adc70f..0000000 --- a/src/view/Components/Taskview/TodoItem.tsx +++ /dev/null @@ -1,12 +0,0 @@ - import * as React from "react"; - -function TodoItem(props: any) { - return ( -
- -

{props.todo_text}

-
- ) -} - -export default TodoItem \ No newline at end of file diff --git a/src/view/Components/Taskview/Todoview/TodoItem.tsx b/src/view/Components/Taskview/Todoview/TodoItem.tsx index 4adc70f..7c1653b 100644 --- a/src/view/Components/Taskview/Todoview/TodoItem.tsx +++ b/src/view/Components/Taskview/Todoview/TodoItem.tsx @@ -1,10 +1,21 @@ - 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) { + 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 (
-

{props.todo_text}

+
+

+
+ +
{dueDate}
+
) } diff --git a/src/view/Components/Taskview/Todoview/TodoSubTasks.tsx b/src/view/Components/Taskview/Todoview/TodoSubTasks.tsx new file mode 100644 index 0000000..92ab97a --- /dev/null +++ b/src/view/Components/Taskview/Todoview/TodoSubTasks.tsx @@ -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 ( +
+ +

+
+ ) + }); + return subtasks + } + else { + return
+ } +} +export default TodoSubTasks \ No newline at end of file diff --git a/src/view/Components/Taskview/Todoview/index.tsx b/src/view/Components/Taskview/Todoview/index.tsx index 7ce2edf..035b52e 100644 --- a/src/view/Components/Taskview/Todoview/index.tsx +++ b/src/view/Components/Taskview/Todoview/index.tsx @@ -8,12 +8,26 @@ export default function Index(props: any){ } else { const incompleteTodos = props.todos.map((todo: any) => { - if(!todo.completed) - return + + 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 + } + }) const completedTodos = props.todos.map((todo: any) => { if(todo.completed) - return + return }) const display =
@@ -22,10 +36,10 @@ export default function Index(props: any){ Completed -
    {incompleteTodos}
+
    {incompleteTodos}
-
    {completedTodos}
+
    {completedTodos}
diff --git a/src/view/Components/Taskview/Todoview/tabs.ts b/src/view/Components/Taskview/Todoview/tabs.ts deleted file mode 100644 index 55153e7..0000000 --- a/src/view/Components/Taskview/Todoview/tabs.ts +++ /dev/null @@ -1 +0,0 @@ -import * as React from "react" diff --git a/src/view/Components/Taskview/index.tsx b/src/view/Components/Taskview/index.tsx index 9546979..06d2874 100644 --- a/src/view/Components/Taskview/index.tsx +++ b/src/view/Components/Taskview/index.tsx @@ -2,33 +2,41 @@ import * as React from "react"; import Dailiesview from "./Dailiesview" import Habitsview from "./Habitsview" import Todoview from "./Todoview" +import Rewardview from "./Rewardview" import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; export default function Index(props: any){ const display =
- - task_alt - + today + + add_chart + - add_circle_outline + assignment_turned_in + + + account_balance - + - + - + + + +
return(display); -} //yes +} diff --git a/src/view/Components/Taskview/markdownRender.ts b/src/view/Components/Taskview/markdownRender.ts new file mode 100644 index 0000000..e09aa1f --- /dev/null +++ b/src/view/Components/Taskview/markdownRender.ts @@ -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); +} \ No newline at end of file diff --git a/src/view/habiticaAPI.ts b/src/view/habiticaAPI.ts index 36f2b25..eae3ab8 100644 --- a/src/view/habiticaAPI.ts +++ b/src/view/habiticaAPI.ts @@ -6,7 +6,7 @@ export async function getStats(username: string, credentials: string){ method: 'GET', headers: { "Content-Type": "application/json", - "x-client": username.concat("-testAPI"), + "x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync", "x-api-user": username, "x-api-key": credentials, }, @@ -20,7 +20,48 @@ export async function scoreTask(username: string, credentials: string, taskID: s method: 'POST', headers: { "Content-Type": "application/json", - "x-client": username.concat("-testAPI"), + "x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync", + "x-api-user": username, + "x-api-key": credentials, + } + }) + return(response) +} +export async function makeCronReq(username: string, credentials: string){ + const url = "https://habitica.com/api/v3/cron"; + 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 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, } diff --git a/styles.css b/styles.css index 1f12342..90c9ec1 100644 --- a/styles.css +++ b/styles.css @@ -1,161 +1,323 @@ - -#profile-name { - font-size: x-large; - font-weight: bold; - padding-bottom: 3%; -} -.stats { - padding-bottom: 6px; -} - -.todo-item { - display: flex; - justify-content: flex-start; - align-items: center; - padding: 0px 0px 0; - width: 80%; - border-bottom: 1px solid #cecece; - font-family: Roboto, sans-serif; - font-weight: normal; - font-size: 16px; -} - -.habit-text { - text-align: center !important; - padding: 0px; - width: 80px; - margin-right: 20px; -} - -.habit-plus { - /* background-color: #fff; */ - border: none; - color: white; - padding: 7px 10px; - text-align: center; - text-decoration: none; - font-size: 16px; -} - -.habit-minus { - /* background-color: #fff; */ - /* border-radius: 50%; */ - border: none; - color: white; - padding: 7px 10px; - text-align: center; - text-decoration: none; - font-size: 16px; -} - -.habit-item { - display: flex; - /* justify-content: center; */ - align-content: space-between; - align-items: center; - padding: 0px 0px 0; - width: 100%; - border-bottom: 1px solid #cecece; - font-family: Roboto, sans-serif; - font-weight: 50%; - font-size: 16px; - -} - -input[type=checkbox] { - margin-right: 10px; -} - -input[type=checkbox]:focus { - outline: 0; -} - -.plugin-root { - min-width: 260px; -} - -.substats { - font-size: medium; -} - -/* react-tabs internal file :wink: */ -.material-icons { - font-size: 12px !important; - padding-top: 1px; - padding-right: 2px; - -} - -.material-icons.md-18 { font-size: 18px !important; } -.material-icons.md-24 { font-size: 24px !important; } -.material-icons.md-32 { font-size: 32px !important; } -.material-icons.md-48 { font-size: 48px !important; } - - -.react-tabs { - -webkit-tap-highlight-color: transparent; - -} - -.react-tabs__tab-list { - border-bottom: 1px solid #aaa; - margin: 0 0 10px; - padding: 0; -} - -.react-tabs__tab { - display: inline-block; - border: 1px solid transparent; - border-bottom: none; - bottom: -1px; - position: relative; - list-style: none; - padding: 1% 2%; - cursor: pointer; - font-size: medium; -} - -.react-tabs__tab--selected { - background: var(--interactive-accent); - color: white; - border-radius: 5px 5px 0 0; -} - -.react-tabs__tab--disabled { - color: GrayText; - cursor: default; -} - -.react-tabs__tab:focus { - box-shadow: 0 0 5px hsl(208, 99%, 50%); - border-color: hsl(208, 99%, 50%); - outline: none; -} - -.react-tabs__tab:focus:after { - content: ""; - position: absolute; - height: 5px; - left: -4px; - right: -4px; - bottom: -5px; - background: #fff; -} - -.react-tabs__tab-panel { - display: none; -} - -.react-tabs__tab-panel--selected { - display: block; -} -ul li:not(.task-list-item)::before { - content: "•"; - color: transparent; - display: inline-block; - width: 1em; - margin-left: -1em; - padding: 0; - font-weight: bold; - text-shadow: 0 0 0.5em transparent; -} \ No newline at end of file +@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 { + font-size: x-large; + font-weight: bold; + padding-bottom: 3%; +} +.stats { + 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 { + display: grid; + grid-template-columns: 1fr 30fr 1fr 1fr; + justify-content: left; + align-items: flex-start; + padding-top: 5px; + padding-bottom: 5px; + width: 100%; + border-bottom: 1px solid #cecece; + font-family: Roboto, sans-serif; + font-weight: bold; + 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 { + text-align: left !important; + font-weight: bold; + padding-top: 5px; + width: 80%; + margin-right: 20px; +} + +.habit-button { + background-color: var(--interactive-accent); + border: none; + /* color: black; */ + text-align: center; + text-decoration: none; + font-size: 16px; + display: block; + width: 100%; + color: var(--text-on-accent); +} +/* habit-button on hover css selector */ +.habit-button:hover { + color: var(--text-on-accent); + background-color: var(--interactive-accent-hover); +} + +.habit-item { + display: flex; + grid-template-columns: 60px 1fr; + width: 100%; + gap: 5px; + border-bottom: 1px solid #cecece; + font-family: Open Sans, sans-serif; + font-weight: 100%; + 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] { + 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 { + outline: 0; +} + +::-webkit-scrollbar { + display: none; /* Chrome Safari */ +} + +.plugin-root { + 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 { + font-size: medium; +} + +ul { + margin: 0; +} + +/* react-tabs internal file :wink: */ +.material-icons { + font-size: 12px !important; + padding-top: 1px; + padding-right: 2px; + +} + +.material-icons.md-18 { font-size: 18px !important; } +.material-icons.md-24 { font-size: 24px !important; } +.material-icons.md-32 { font-size: 32px !important; } +.material-icons.md-48 { font-size: 48px !important; } + + +.react-tabs { + -webkit-tap-highlight-color: transparent; + height: 100%; +} + +.react-tabs__tab-list { + border-bottom: 1px solid #aaa; + margin: 0 0 5px; + padding: 0; +} + +.react-tabs__tab { + display: inline-block; + border: 1px solid transparent; + border-bottom: none; + bottom: -1px; + position: relative; + list-style: none; + padding: 1% 2%; + cursor: pointer; + font-size: medium; +} + +.react-tabs__tab--selected { + background: var(--interactive-accent); + color: white; + border-radius: 5px 5px 0 0; +} + +.react-tabs__tab--disabled { + color: GrayText; + cursor: default; +} + +.react-tabs__tab:focus { + box-shadow: 0 0 5px hsl(208, 99%, 50%); + border-color: hsl(208, 99%, 50%); + outline: none; +} + +.react-tabs__tab:focus:after { + content: ""; + position: absolute; + height: 5px; + left: -4px; + right: -4px; + bottom: -5px; + background: #fff; +} + +.react-tabs__tab-panel { + display: none; + left: 0px; + height: 88%; + /* overflow: scroll; */ +} + +.task-panel { + overflow: scroll; + height: 100%; +} + +.react-tabs__tab-panel--selected { + display: block; +} +ul li:not(.task-list-item)::before { + content: "•"; + color: transparent; + display: inline-block; + width: 1em; + margin-left: -1em; + padding: 0; + font-weight: bold; + text-shadow: 0 0 0.5em transparent; +} + +.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-bottom: 5px; + white-space: nowrap; + padding: 5px 5px; + margin-right: 0; +} +.cron { + display: inline-grid; + justify-content: center; + text-align: center;; + margin-top: 5px; + margin-left: 10%; + margin-right: 10%; + margin-bottom: 10px; + border-radius: 10px; +} +#cronMessage { + margin: 20px; + margin-bottom: 10px; + 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; +} diff --git a/tsconfig.json b/tsconfig.json index 48f3cf9..c6bbfcb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,8 @@ "noImplicitAny": true, "moduleResolution": "node", "importHelpers": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, "lib": [ "dom", "es5",