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 395678d..85ab4b5 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ To sync your Habitica account, go to the settings page of the plugin and enter y 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" +- **Show Task Descriptions:** Toggles whether description/notes for tasks will be shown or not +- **Due date format:** Format in which the due date is displayed +- **Show Checklist items:** Toggles the checklist items for a task. ## Roadmap @@ -33,3 +36,4 @@ We will be implementing additional features including: *Feel free to support us and donate!* Buy Me a Coffee at ko-fi.com + diff --git a/manifest.json b/manifest.json index c9b0afa..19dbe5b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-habitica-integration", "name": "Habitica Sync", - "version": "0.9.15", + "version": "1.0.0", "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 344ca09..80f9f65 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,50 @@ { "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" + "build": "rollup --config rollup.config.js --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-json": "^4.1.0", "@rollup/plugin-node-resolve": "^11.2.1", "@rollup/plugin-typescript": "^8.2.1", + "@types/markdown-it": "^12.2.3", + "@types/markdown-it-emoji": "^2.0.2", "@types/node": "^14.14.37", + "@types/node-emoji": "^1.8.1", "@types/react": "^17.0.27", "@types/react-dom": "^17.0.9", "@types/react-tabs": "^2.3.3", + "@types/twemoji": "^12.1.2", "css-loader": "^6.4.0", "extract-text-webpack-plugin": "^2.1.2", "obsidian": "^0.12.0", + "obsidian-plugin-cli": "^0.4.3", "rollup": "^2.32.1", "style-loader": "^3.3.0", "tslib": "^2.2.0", "typescript": "^4.2.4" }, "dependencies": { + "markdown-it": "^12.3.2", + "markdown-it-emoji": "^2.0.0", + "moment": "^2.29.1", "node": "^16.10.0", + "node-emoji": "^1.11.0", "node-fetch": "^3.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-emoji-render": "^1.2.4", - "react-tabs": "^3.2.2" + "react-markdown": "^7.1.0", + "react-tabs": "^3.2.2", + "twemoji": "^13.1.0" } } diff --git a/rollup.config.js b/rollup.config.js index 8e28e8d..6da9767 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,6 +1,7 @@ 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'); @@ -26,5 +27,6 @@ export default { typescript(), nodeResolve({browser: true}), commonjs(), + json(), ] }; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index bdf0312..13cae87 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,9 +47,10 @@ export default class HabiticaSync extends Plugin { async saveSettings() { await this.saveData(this.settings); } + async onunload() { await this.view.onClose(); - + this.app.workspace .getLeavesOfType(VIEW_TYPE) .forEach((leaf) => leaf.detach()); 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 aaf4cc0..9ae5826 100644 --- a/src/view/App.tsx +++ b/src/view/App.tsx @@ -1,10 +1,11 @@ import * as React from "react"; import { Notice } from "obsidian"; -import { getStats, scoreTask, makeCronReq } 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"; -class App extends React.Component { +class App extends React.Component { private _username = ""; public get username() { return this._username; @@ -33,6 +34,7 @@ class App extends React.Component { stats: { hp: 0, lvl: 0, + gold: 0, }, lastCron: "", }, @@ -43,6 +45,8 @@ class App extends React.Component { 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); } @@ -50,10 +54,10 @@ class App extends React.Component { let cronDate = new Date(lastCron); let now = new Date(); if (cronDate.getDate() != now.getDate() || (cronDate.getMonth() != now.getMonth() || cronDate.getFullYear() != now.getFullYear())) { - return( + return (
-
Welcome back! Please check your tasks for the last day and hit continue to get your daily rewards.
- +
Welcome back! Please check your tasks for the last day and hit continue to get your daily rewards.
+
); } @@ -73,102 +77,149 @@ class App extends React.Component { new Notice("There was an error running the cron. Please try again later."); } this.reloadData(); - } + } async reloadData() { 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) { + 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){ + + 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){ + 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) { + } 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", "Cost!") + } + } + }) + } + 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(){ + render() { let content = this.CheckCron(this.state.user_data.lastCron); - if(this.state.error) - return(
Loading....
) - else if(!this.state.isLoaded) + 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(? - -

+ +
+

+
+ {/* {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..9785888 100644 --- a/src/view/Components/Taskview/Dailiesview/index.tsx +++ b/src/view/Components/Taskview/Dailiesview/index.tsx @@ -3,23 +3,71 @@ 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 display =
Active Completed + Not Due
    {incompleteDailies}
@@ -27,10 +75,12 @@ export default function Index(props: any){
    {completedDailies}
+ +
    {notDueDailies}
+
return(display); } } - diff --git a/src/view/Components/Taskview/Habitsview/HabitItem.tsx b/src/view/Components/Taskview/Habitsview/HabitItem.tsx index fa397cc..37d672c 100644 --- a/src/view/Components/Taskview/Habitsview/HabitItem.tsx +++ b/src/view/Components/Taskview/Habitsview/HabitItem.tsx @@ -1,16 +1,23 @@ -import Emoji from "react-emoji-render"; 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 (
- -

- +
+ + +
+
+

+
+
) } 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 c02e230..0000000 --- a/src/view/Components/Taskview/TodoItem.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Emoji } from 'react-emoji-render'; -import * as React from "react"; - -function TodoItem(props: any) { - return ( -
- - -
- ) -} - -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 2766dbe..7c1653b 100644 --- a/src/view/Components/Taskview/Todoview/TodoItem.tsx +++ b/src/view/Components/Taskview/Todoview/TodoItem.tsx @@ -1,11 +1,21 @@ -import Emoji from "react-emoji-render"; 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 (
-

+
+

+
+ +
{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..a050528 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 =
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 142a923..eae3ab8 100644 --- a/src/view/habiticaAPI.ts +++ b/src/view/habiticaAPI.ts @@ -39,4 +39,32 @@ export async function makeCronReq(username: string, credentials: string){ } }) 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) } \ No newline at end of file diff --git a/styles.css b/styles.css index b31b4a2..64be137 100644 --- a/styles.css +++ b/styles.css @@ -1,3 +1,13 @@ +@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'); + +* { + padding: 0; +} + +.add-task-input { + display: flex; +} #profile-name { font-size: x-large; @@ -5,79 +15,148 @@ padding-bottom: 3%; } .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 { - 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; + 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; +} + +.description { + font-family: Open Sans, sans-serif; + font-weight: 100; +} +.description > ul { + list-style-type: none; +} +p { + margin: 0; } .habit-text { - text-align: center !important; - padding: 0px; - width: 50%; + text-align: left !important; + font-weight: bold; + padding-top: 5px; + width: 80%; margin-right: 20px; } -.habit-plus { - /* background-color: #fff; */ +.habit-button { + background-color: var(--interactive-accent); border: none; - color: white; - padding: 7px 10px; + /* color: black; */ text-align: center; text-decoration: none; font-size: 16px; + display: block; + width: 100%; + color: var(--text-on-accent); } - -.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-button on hover css selector */ +.habit-button:hover { + color: var(--text-on-accent); + background-color: var(--interactive-accent-hover); } .habit-item { display: flex; - /* justify-content: center; */ - align-content: space-between; - align-items: center; - padding: 0px 0px 0; + grid-template-columns: 60px 1fr; width: 100%; + gap: 5px; border-bottom: 1px solid #cecece; - font-family: Roboto, sans-serif; - font-weight: 50%; + 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; @@ -94,12 +173,12 @@ input[type=checkbox]:focus { .react-tabs { -webkit-tap-highlight-color: transparent; - + height: 100%; } .react-tabs__tab-list { border-bottom: 1px solid #aaa; - margin: 0 0 10px; + margin: 0 0 5px; padding: 0; } @@ -144,6 +223,14 @@ input[type=checkbox]:focus { .react-tabs__tab-panel { display: none; + left: 0px; + height: 88%; + /* overflow: scroll; */ +} + +.task-panel { + overflow: scroll; + height: 100%; } .react-tabs__tab-panel--selected { @@ -159,9 +246,45 @@ ul li:not(.task-list-item)::before { font-weight: bold; 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-bottom: 5px; + white-space: nowrap; + padding: 5px 5px; + margin-right: 0; } .cron { display: inline-grid; @@ -171,7 +294,6 @@ body > div.app-container.is-left-sidedock-collapsed.is-right-sidedock-collapsed margin-left: 10%; margin-right: 10%; margin-bottom: 10px; - background-color: var(--background-secondary-alt); border-radius: 10px; } #cronMessage { @@ -179,3 +301,26 @@ body > div.app-container.is-left-sidedock-collapsed.is-right-sidedock-collapsed 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; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 48f3cf9..95b95d2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "noImplicitAny": true, "moduleResolution": "node", "importHelpers": true, + "allowSyntheticDefaultImports": true, "lib": [ "dom", "es5",