Compare commits

..

No commits in common. "main" and "0.9.7" have entirely different histories.
main ... 0.9.7

28 changed files with 328 additions and 943 deletions

3
.gitignore vendored
View file

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

View file

View file

@ -9,38 +9,26 @@ 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 stats (HP, XP, coins)
#### Views
Task Information:
- Title, description, subtasks
- Markdown and emoji support
#### 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
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 (+/-)
#### 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
### Settings
The following two inputs help fetch your user data to be displayed in the Obsidian view:
## Settings
- **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
- **Show Subtasks:** Toggles whether subtasks for to do's/dailies will be shown or not
The following two inputs help fetch your user data to be displayed in the Obsidian view.
## Roadmap
We will be implementing additional features including:
- Add/delete/customize tasks and habits
- View/claim rewards
*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' />

View file

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

View file

@ -1,51 +1,36 @@
{
"name": "obsidian-habitica-integration",
"version": "1.0.0",
"version": "0.12.0",
"description": "This plugin allows for Habitica integration into Obsidian",
"main": "main.js",
"scripts": {
"dev": "rollup --config rollup.config.mjs -w",
"build": "rollup --config rollup.config.mjs --environment BUILD:production",
"dev2": "obsidian-plugin dev src/main.ts"
"dev": "rollup --config rollup.config.js -w",
"build": "rollup --config rollup.config.js --environment BUILD:production"
},
"keywords": [],
"author": "Leonard and Ran",
"license": "MIT",
"devDependencies": {
"@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"
"@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"
},
"dependencies": {
"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"
"node": "^16.10.0",
"node-fetch": "^3.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-tabs": "^3.2.2"
}
}

View file

@ -1,32 +1,30 @@
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(),
]
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(),
]
};

View file

@ -5,30 +5,23 @@ import { HabiticaSyncView, VIEW_TYPE} from "./view"
interface HabiticaSyncSettings {
userID: string
apiToken: string
showTaskDescription: boolean
showSubTasks: boolean
dueDateFormat: string
}
const DEFAULT_SETTINGS: Partial<HabiticaSyncSettings> = {
userID: "",
apiToken: "",
showTaskDescription: true,
showSubTasks: true,
dueDateFormat: "DD-MM-YYYY"
apiToken: ""
}
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", () => {
this.addRibbonIcon("popup-open", "Open Habitica Pane", () => { //activate view
this.activateView();
});
this.addCommand({
@ -39,7 +32,6 @@ export default class HabiticaSync extends Plugin {
this.activateView();
}
});
}
async loadSettings() {
this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData())
@ -47,7 +39,6 @@ export default class HabiticaSync extends Plugin {
async saveSettings() {
await this.saveData(this.settings);
}
async onunload() {
await this.view.onClose();
@ -57,15 +48,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]
);
}
}

View file

@ -1,6 +1,5 @@
import HabiticaSync from "./main";
import { App, PluginSettingTab, Setting } from "obsidian";
import moment from "moment";
export class HabiticaSyncSettingsTab extends PluginSettingTab {
plugin: HabiticaSync;
@ -39,42 +38,5 @@ 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();
})
);
}
}

View file

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

View file

@ -1,31 +1,17 @@
import * as React from "react";
import { Notice } from "obsidian";
import { getStats, scoreTask, makeCronReq, costReward, scoreChecklistItem } from "./habiticaAPI"
import { getStats, scoreTask } from "./habiticaAPI"
import Statsview from "./Components/Statsview"
import Taskview from "./Components/Taskview"
import ReactDOM from "react-dom";
class App extends React.Component<any, any> {
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;
}
class App extends React.Component<any,any> {
username = ""
credentials = ""
constructor(props: any) {
super(props)
this.username = this.props.plugin.settings.userID
this.credentials = this.props.plugin.settings.apiToken
this.state = {
needCron: false,
isLoaded: false,
user_data: {
profile: {
@ -34,192 +20,111 @@ class App extends React.Component<any, any> {
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.handleChangeRewards = this.handleChangeRewards.bind(this);
this.handleChangeChecklistItem = this.handleChangeChecklistItem.bind(this);
this.runCron = this.runCron.bind(this);
this.handleChangeTodos = this.handleChangeTodos.bind(this)
this.handleChangeDailys = this.handleChangeDailys.bind(this)
this.handleChangeHabits = this.handleChangeHabits.bind(this)
}
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 (
<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>
<button id="cronButton" onClick={this.runCron}>Continue</button>
</div>
);
}
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() {
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")
}
}
}
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) {
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() {
let content = this.CheckCron(this.state.user_data.lastCron);
if (this.state.error)
return (<div className="loading">Loading....</div>)
else if (!this.state.isLoaded)
render(){
if(this.state.error)
return(<div className="loading">Loading....</div>)
else if(!this.state.isLoaded)
return <div className="loading">Loading....</div>
else {
return (<div className="plugin-root">
{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>
<Statsview user_data={this.state.user_data} />
<Taskview data={this.state.tasks} handleChangeTodos={this.handleChangeTodos} handleChangeDailys={this.handleChangeDailys} handleChangeHabits={this.handleChangeHabits}/>
</div>
);
}
}

View file

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

View file

@ -1,20 +1,10 @@
import * as React from "react";
import DailySubTasks from "./DailySubTasks";
import renderMarkdown from "../markdownRender";
import * as React from "react";
function DailyItem(props: any) {
var text_html = renderMarkdown(props.daily_text);
var note_html = renderMarkdown(props.daily_notes);
return (
<div className="todo-item" id={props.id}>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed} />
<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>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/>
<p>{props.daily_text}</p>
</div>
)
}

View file

@ -1,22 +0,0 @@
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,89 +3,23 @@ import DailyItem from "./DailyItem"
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
export default function Index(props: any){
if(props.dailys == undefined) {
return <div id="classDisplay">No Dailies Present</div>
}
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) => {
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 <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}/>
}
})
if(!daily.completed)
return <DailyItem key={daily.id} id={daily.id}daily_text={daily.text} onChange={props.onChange} completed={daily.completed}/>
})
const completedDailies = 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}/>
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}/>
}
if(daily.completed)
return <DailyItem key={daily.id} id={daily.id}daily_text={daily.text} onChange={props.onChange} completed={daily.completed}/>
})
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">
<Tabs>
<TabList>
<Tab>Active</Tab>
<Tab>Completed</Tab>
<Tab>Not Due</Tab>
<Tab>All</Tab>
</TabList>
<TabPanel>
<ul>{incompleteDailies}</ul>
@ -93,15 +27,10 @@ export default function Index(props: any){
<TabPanel>
<ul>{completedDailies}</ul>
</TabPanel>
<TabPanel>
<ul>{notDueDailies}</ul>
</TabPanel>
<TabPanel>
<ul>{allDailies}</ul>
</TabPanel>
</Tabs>
</div>
return(display);
}
}

View file

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

View file

@ -9,11 +9,7 @@ export default function Index(props: any){
}
else {
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">
<ul>{allHabits}</ul>

View file

@ -1,21 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -0,0 +1,12 @@
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}/>
<p>{props.todo_text}</p>
</div>
)
}
export default TodoItem

View file

@ -1,21 +1,10 @@
import * as React from "react";
import TodoSubTasks from "./TodoSubTasks";
import renderMarkdown from "../markdownRender"
import moment from "moment";
import * as React from "react";
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 (
<div className="todo-item" id={props.id}>
<input type="checkbox" className="checkbox" id={props.id} onChange={props.onChange} checked={props.completed}/>
<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>
<p>{props.todo_text}</p>
</div>
)
}

View file

@ -1,21 +0,0 @@
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,26 +8,12 @@ export default function Index(props: any){
}
else {
const incompleteTodos = props.todos.map((todo: any) => {
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}/>
}
if(!todo.completed)
return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} onChange={props.onChange} completed={todo.completed}/>
})
const completedTodos = props.todos.map((todo: any) => {
if(todo.completed)
return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} todo_notes={todo.notes} onChange={props.onChange} completed={todo.completed}/>
return <TodoItem key={todo.id} id={todo.id} todo_text={todo.text} onChange={props.onChange} completed={todo.completed}/>
})
const display = <div id="classDisplay">
<Tabs>
@ -36,10 +22,10 @@ export default function Index(props: any){
<Tab>Completed</Tab>
</TabList>
<TabPanel>
<ul className="todolist-indent">{incompleteTodos}</ul>
<ul>{incompleteTodos}</ul>
</TabPanel>
<TabPanel>
<ul className="todolist-indent">{completedTodos}</ul>
<ul>{completedTodos}</ul>
</TabPanel>
</Tabs>
</div>

View file

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

View file

@ -2,41 +2,33 @@ 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 = <div className="task-view">
<Tabs>
<TabList>
<Tab >
<span className="material-icons md-24">task_alt</span>
</Tab>
<Tab>
<span className="material-icons md-24">today</span>
</Tab>
<Tab >
<span className="material-icons md-24">add_chart</span>
</Tab>
<Tab>
<span className="material-icons md-24">assignment_turned_in</span>
</Tab>
<Tab>
<span className="material-icons md-24">account_balance</span>
<span className="material-icons md-24">add_circle_outline</span>
</Tab>
</TabList>
<TabPanel>
<Dailiesview dailys={props.data.dailys} settings = {props.settings} onChange={props.handleChangeDailys} onChangeChecklistItem={props.handleChangeChecklistItem}/>
<Habitsview habits={props.data.habits} onChange={props.handleChangeHabits}/>
</TabPanel>
<TabPanel>
<Habitsview habits={props.data.habits} settings = {props.settings} onChange={props.handleChangeHabits}/>
<Dailiesview dailys={props.data.dailys} onChange={props.handleChangeDailys} />
</TabPanel>
<TabPanel>
<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} />
<Todoview todos={props.data.todos} onChange={props.handleChangeTodos} />
</TabPanel>
</Tabs>
</div>
return(display);
}
} //yes

View file

@ -1,21 +0,0 @@
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

@ -6,7 +6,7 @@ export async function getStats(username: string, credentials: string){
method: 'GET',
headers: {
"Content-Type": "application/json",
"x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync",
"x-client": username.concat("-testAPI"),
"x-api-user": username,
"x-api-key": credentials,
},
@ -20,48 +20,7 @@ export async function scoreTask(username: string, credentials: string, taskID: s
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 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-client": username.concat("-testAPI"),
"x-api-user": username,
"x-api-key": credentials,
}

View file

@ -1,323 +1,151 @@
@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;
}
#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;
}

View file

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