diff --git a/.gitignore b/.gitignore index de7c3d1..e6fab1b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,17 +6,12 @@ node_modules package-lock.json -# yarn -yarn.lock - - +# build +main.js *.js.map # obsidian data.json -#build_files -main.js - #vscode .vscode \ No newline at end of file diff --git a/.hotreload b/.hotreload deleted file mode 100644 index e69de29..0000000 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 46563a3..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Zain Siddavatam and John Mavrick Reyes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 43497e5..9e4fe9c 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,57 @@ -# Habitica Sync in Obsidian -This plugin for Obsidian incorporates a view to display and interact with the task management app Habitica. +## Obsidian Sample Plugin -Please open issues for any bugs/functionality requests :) +This is a sample plugin for Obsidian (https://obsidian.md). -## Usage -The plugin's view is enabled by clicking on the "Open Habitica Pane" option in the side ribbon (default hotkey is `Ctrl+Shift+H`). +This project uses Typescript to provide type checking and documentation. +The repo depends on the latest plugin API (obsidian.d.ts) in Typescript Definition format, which contains TSDoc comments describing what it does. -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 +**Note:** The Obsidian API is still in early alpha and is subject to change at any time! -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 (+/-) +This sample plugin demonstrates some of the basic functionality the plugin API can do. +- Changes the default font color to red using `styles.css`. +- Adds a ribbon icon, which shows a Notice when clicked. +- Adds a command "Open Sample Modal" which opens a Modal. +- Adds a plugin setting tab to the settings page. +- Registers a global click event and output 'click' to the console. +- Registers a global interval which logs 'setInterval' to the console. -### Settings +### First time developing plugins? -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 -- **Show Subtasks:** Toggles whether subtasks for to do's/dailies will be shown or not +Quick starting guide for new plugin devs: -## Roadmap +- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it). +- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder. +- Install NodeJS, then run `npm i` in the command line under your repo folder. +- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`. +- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`. +- Reload Obsidian to load the new version of your plugin. +- Enable plugin in settings window. +- For updates to the Obsidian API run `npm update` in the command line under your repo folder. -*Feel free to support us and donate!* +### Releasing new releases -Buy Me a Coffee at ko-fi.com +- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release. +- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible. +- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases +- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. +- Publish the release. +### Adding your plugin to the community plugin list + +- Publish an initial version. +- Make sure you have a `README.md` file in the root of your repo. +- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin. + +### How to use + +- Clone this repo. +- `npm i` or `yarn` to install dependencies +- `npm run dev` to start compilation in watch mode. + +### Manually installing the plugin + +- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`. + +### API Documentation + +See https://github.com/obsidianmd/obsidian-api diff --git a/ReactView.tsx b/ReactView.tsx new file mode 100644 index 0000000..e924dac --- /dev/null +++ b/ReactView.tsx @@ -0,0 +1,6 @@ +import * as React from "react"; +import App from "./view/App"; + +export const ReactView = () => { + return ; +}; \ No newline at end of file diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..f701dd2 --- /dev/null +++ b/main.ts @@ -0,0 +1,52 @@ +import { Plugin } from "obsidian"; +import { ExampleSettingsTab } from "./settings"; +import { ExampleView, VIEW_TYPE_EXAMPLE} from "./view" + +interface ExamplePluginSettings { + dateFormat: string +} +const DEFAULT_SETTINGS: Partial = { + dateFormat: "YYYY-MM-DD" +} +export default class ExamplePlugin extends Plugin { + settings: ExamplePluginSettings; + view: ExampleView; + + async onload() { + await this.loadSettings(); + this.addSettingTab(new ExampleSettingsTab(this.app, this)); + this.registerView( + VIEW_TYPE_EXAMPLE, + (leaf) => (this.view = new ExampleView(leaf)) + ); + this.addRibbonIcon("dice", "Activate view", () => { //activate view + this.activateView(); + }); + } + async loadSettings() { + this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()) + } + async saveSettings() { + await this.saveData(this.settings); + } + async onunload() { + await this.view.onClose(); + + this.app.workspace + .getLeavesOfType(VIEW_TYPE_EXAMPLE) + .forEach((leaf) => leaf.detach()); + } + async activateView() { + this.app.workspace.detachLeavesOfType(VIEW_TYPE_EXAMPLE); + + await this.app.workspace.getRightLeaf(false).setViewState({ + type: VIEW_TYPE_EXAMPLE, + active: true, + }); + + this.app.workspace.revealLeaf( + this.app.workspace.getLeavesOfType(VIEW_TYPE_EXAMPLE)[0] + ); + } + +} diff --git a/manifest.json b/manifest.json index 2ae5bfe..4915397 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,10 @@ { - "id": "obsidian-habitica-integration", - "name": "Habitica Sync", - "version": "1.0.2", + "id": "test-plugin", + "name": "Test Plugin", + "version": "0.0.1", "minAppVersion": "0.9.12", - "description": "This plugin helps integrate Habitica user tasks and stats into Obsidian", - "author": "Leoh and Ran", + "description": "This is a sample plugin for Obsidian. This plugin demonstrates some of the capabilities of the Obsidian API.", + "author": "Leoh", "authorUrl": "", - "isDesktopOnly": false, - "js": "main.js" + "isDesktopOnly": false } diff --git a/package.json b/package.json index 37371e6..27eb3d7 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,34 @@ { - "name": "obsidian-habitica-integration", - "version": "1.0.0", - "description": "This plugin allows for Habitica integration into Obsidian", + "name": "test-plugin", + "version": "0.12.0", + "description": "This is a sample plugin for Obsidian (https://obsidian.md)", "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", + "author": "", "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", + "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" } } diff --git a/rollup.config.mjs b/rollup.config.js similarity index 89% rename from rollup.config.mjs rename to rollup.config.js index 1551460..dd4d041 100644 --- a/rollup.config.mjs +++ b/rollup.config.js @@ -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: 'main.ts', + output: { + dir: '.', + sourcemap: 'inline', + sourcemapExcludeSources: isProd, + format: 'cjs', + exports: 'default', + banner, + }, + external: ['obsidian'], + plugins: [ + typescript(), + nodeResolve({browser: true}), + commonjs(), + ] }; \ No newline at end of file diff --git a/settings.ts b/settings.ts new file mode 100644 index 0000000..8d40205 --- /dev/null +++ b/settings.ts @@ -0,0 +1,30 @@ +import ExamplePlugin from "main"; +import { App, PluginSettingTab, Setting } from "obsidian"; + +export class ExampleSettingsTab extends PluginSettingTab { + plugin: ExamplePlugin; + + constructor(app: App, plugin: ExamplePlugin) { + super(app, plugin) + this.plugin = plugin + } + + display(): void { + let { containerEl } = this; + containerEl.empty(); + + new Setting(containerEl) + .setName("Date format") + .setDesc("Default date format") + .addText((text) => + + text + .setPlaceholder("MMMM dd, yyyy") + .setValue(this.plugin.settings.dateFormat) + .onChange(async (value) => { + this.plugin.settings.dateFormat = value; + await this.plugin.saveSettings(); + }) + ); + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index b8eaff4..0000000 --- a/src/main.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Plugin } from "obsidian"; -import { HabiticaSyncSettingsTab } from "./settings"; -import { HabiticaSyncView, VIEW_TYPE} from "./view" - -interface HabiticaSyncSettings { - userID: string - apiToken: string - showTaskDescription: boolean - showSubTasks: boolean - dueDateFormat: string -} -const DEFAULT_SETTINGS: Partial = { - userID: "", - 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", () => { - this.activateView(); - }); - this.addCommand({ - id: "habitica-view-open", - name: "Open Pane", - hotkeys: [{ modifiers: ["Mod", "Shift"], key: "h"}], - callback: () => { - this.activateView(); - } - }); - - } - async loadSettings() { - this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()) - } - async saveSettings() { - await this.saveData(this.settings); - } - - async onunload() { - await this.view.onClose(); - - this.app.workspace - .getLeavesOfType(VIEW_TYPE) - .forEach((leaf) => leaf.detach()); - } - 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 deleted file mode 100644 index 974fe44..0000000 --- a/src/settings.ts +++ /dev/null @@ -1,80 +0,0 @@ -import HabiticaSync from "./main"; -import { App, PluginSettingTab, Setting } from "obsidian"; -import moment from "moment"; - -export class HabiticaSyncSettingsTab extends PluginSettingTab { - plugin: HabiticaSync; - - constructor(app: App, plugin: HabiticaSync) { - super(app, plugin) - this.plugin = plugin - } - - display(): void { - let { containerEl } = this; - containerEl.empty(); - - new Setting(containerEl) - .setName("Habitica User ID") - .setDesc("Can be found in Settings > API") - .addText((text) => - text - .setPlaceholder("User ID") - .setValue(this.plugin.settings.userID) - .onChange(async (value) => { - this.plugin.settings.userID = value; - await this.plugin.saveSettings(); - }) - ); - - new Setting(containerEl) - .setName("Habitica API Token") - .setDesc("Can be found in Settings > API") - .addText((text) => - text - .setPlaceholder("API Token") - .setValue(this.plugin.settings.apiToken) - .onChange(async (value) => { - this.plugin.settings.apiToken = value; - 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 deleted file mode 100644 index be66720..0000000 --- a/src/view.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { ItemView,WorkspaceLeaf } from "obsidian"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import App from "./view/App" -import HabiticaSync from "./main"; - - -export const VIEW_TYPE = "example-view" - -export class HabiticaSyncView extends ItemView { - plugin: HabiticaSync; - constructor(leaf: WorkspaceLeaf, plugin: HabiticaSync) { - super(leaf) - this.plugin = plugin - } - - getViewType() { - return VIEW_TYPE - } - - getDisplayText() { - return "Habitica Pane" - } - getIcon(): string { - return "popup-open" - } - - async onOpen() { - ReactDOM.render( - , - this.containerEl.children[1] - ) - } - 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 deleted file mode 100644 index 1406d23..0000000 --- a/src/view/App.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import * as React from "react"; -import { Notice } from "obsidian"; -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 { - 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) - this.username = this.props.plugin.settings.userID - this.credentials = this.props.plugin.settings.apiToken - this.state = { - needCron: false, - isLoaded: false, - user_data: { - profile: { - name: "", - }, - 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); - - } - 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() { - 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) { - 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") - } - } - - 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!") - } else { - this.sendScore(event.target.id, "down", "Un-Checked!") - } - } - }) - } - handleChangeDailys(event: any) { - this.state.tasks.dailys.forEach((element: any) => { - if (element.id == event.target.id) { - if (element.id == event.target.id) { - if (!element.completed) { - this.sendScore(event.target.id, "up", "Checked!") - } else { - this.sendScore(event.target.id, "down", "Un-Checked!") - } - } - } - }) - } - handleChangeHabits(event: any) { - const target_id = event.target.id.slice(4) - if (event.target.id.slice(0, 4) == "plus") { - this.state.tasks.habits.forEach((element: any) => { - if (element.id == target_id) { - this.sendScore(target_id, "up", "Plus!") - } - }) - } - else { - this.state.tasks.habits.forEach((element: any) => { - if (element.id == target_id) { - this.sendScore(target_id, "down", "Minus :(") - } - }) - } - } - handleChangeRewards(event: any) { - const target_id = event.target.id - this.state.tasks.rewards.forEach((element: any) => { - if (element.id == event.target.id) { - if (element.id == target_id) { - this.sendReward(target_id, "down", "Redeemed!") - } - } - }) - } - async handleChangeChecklistItem(event: any){ - let parentID = event.target.parentNode.parentNode.parentNode.getAttribute("id") - let targetID = event.target.id - console.log(parentID+ " , " + targetID) - try{ - let response = await scoreChecklistItem(this.username, this.credentials, targetID, parentID); - let result = await response.json(); - if (result.success === true) { - new Notice("Checked!"); - this.reloadData(); - } else { - new Notice("Resyncing, please try again"); - this.reloadData(); - } - } catch (e) { - console.log(e); - new Notice("API Error: Please check credentials") - } - } - - render() { - let content = this.CheckCron(this.state.user_data.lastCron); - if (this.state.error) - return (
Loading....
) - else if (!this.state.isLoaded) - return
Loading....
- else { - return (
- {content} - - - - -
- ); - } - } -} -export default App \ No newline at end of file diff --git a/src/view/Components/Statsview/index.css b/src/view/Components/Statsview/index.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/view/Components/Statsview/index.tsx b/src/view/Components/Statsview/index.tsx deleted file mode 100644 index 506cae4..0000000 --- a/src/view/Components/Statsview/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; - -export default function Index(props: any) { - return( -
- {/*
{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)} */} - -
- - - ) -} - -export default DailyItem \ No newline at end of file diff --git a/src/view/Components/Taskview/Dailiesview/DailySubTasks.tsx b/src/view/Components/Taskview/Dailiesview/DailySubTasks.tsx deleted file mode 100644 index 09e4c99..0000000 --- a/src/view/Components/Taskview/Dailiesview/DailySubTasks.tsx +++ /dev/null @@ -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 ( -
- -

-
- ) - }); - 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 deleted file mode 100644 index db5944b..0000000 --- a/src/view/Components/Taskview/Dailiesview/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import * as React from "react"; -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&&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) { - 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}
-
- -
    {completedDailies}
-
- -
    {notDueDailies}
-
- -
    {allDailies}
-
-
-
- return(display); - } - -} diff --git a/src/view/Components/Taskview/Habitsview/HabitItem.tsx b/src/view/Components/Taskview/Habitsview/HabitItem.tsx deleted file mode 100644 index 37d672c..0000000 --- a/src/view/Components/Taskview/Habitsview/HabitItem.tsx +++ /dev/null @@ -1,25 +0,0 @@ -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 ( -
-
- - -
-
-

-
-
-
- ) -} - -export default HabitItem \ No newline at end of file diff --git a/src/view/Components/Taskview/Habitsview/index.tsx b/src/view/Components/Taskview/Habitsview/index.tsx deleted file mode 100644 index 1d0107c..0000000 --- a/src/view/Components/Taskview/Habitsview/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from "react"; -import HabitItem from "./HabitItem" - -export default function Index(props: any){ - if(props.habits == undefined) { - return (
- No habits present. -
) - } - else { - const allHabits = props.habits.map((habit: any) => { - if (props.settings.showTaskDescription) { - return - } else { - return - } - }) - const display =
-
    {allHabits}
-
- - return(display); - } -} - diff --git a/src/view/Components/Taskview/Rewardview/RewardItem.tsx b/src/view/Components/Taskview/Rewardview/RewardItem.tsx deleted file mode 100644 index 5f331e2..0000000 --- a/src/view/Components/Taskview/Rewardview/RewardItem.tsx +++ /dev/null @@ -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 ( -
-
- -
-
-

-
-
- -
- ) -} - -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 deleted file mode 100644 index 4ddc21a..0000000 --- a/src/view/Components/Taskview/Rewardview/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -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/Todoview/TodoItem.tsx b/src/view/Components/Taskview/Todoview/TodoItem.tsx deleted file mode 100644 index 7c1653b..0000000 --- a/src/view/Components/Taskview/Todoview/TodoItem.tsx +++ /dev/null @@ -1,23 +0,0 @@ -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}
-
-
- ) -} - -export default TodoItem \ No newline at end of file diff --git a/src/view/Components/Taskview/Todoview/TodoSubTasks.tsx b/src/view/Components/Taskview/Todoview/TodoSubTasks.tsx deleted file mode 100644 index 92ab97a..0000000 --- a/src/view/Components/Taskview/Todoview/TodoSubTasks.tsx +++ /dev/null @@ -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 ( -
- -

-
- ) - }); - 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 deleted file mode 100644 index 035b52e..0000000 --- a/src/view/Components/Taskview/Todoview/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from "react"; -import TodoItem from "./TodoItem" -import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; - -export default function Index(props: any){ - if(props.todos == undefined) { - return
No Todos present.
- } - 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 - } - - }) - const completedTodos = props.todos.map((todo: any) => { - if(todo.completed) - return - }) - const display =
- - - Active - Completed - - -
    {incompleteTodos}
-
- -
    {completedTodos}
-
-
-
- - - return(display); - } -} \ No newline at end of file diff --git a/src/view/Components/Taskview/index.tsx b/src/view/Components/Taskview/index.tsx deleted file mode 100644 index 06d2874..0000000 --- a/src/view/Components/Taskview/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -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 =
- - - - - today - - - add_chart - - - assignment_turned_in - - - account_balance - - - - - - - - - - - - - - - -
- return(display); -} - diff --git a/src/view/Components/Taskview/markdownRender.ts b/src/view/Components/Taskview/markdownRender.ts deleted file mode 100644 index e09aa1f..0000000 --- a/src/view/Components/Taskview/markdownRender.ts +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/src/view/habiticaAPI.ts b/src/view/habiticaAPI.ts deleted file mode 100644 index eae3ab8..0000000 --- a/src/view/habiticaAPI.ts +++ /dev/null @@ -1,70 +0,0 @@ -// import fetch from "node-fetch"; - -export async function getStats(username: string, credentials: string){ - const url = "https://habitica.com/export/userdata.json" - const response = await fetch(url, { - method: 'GET', - headers: { - "Content-Type": "application/json", - "x-client": "278e719e-5f9c-43b1-9dba-8b73343dc062-HabiticaSync", - "x-api-user": username, - "x-api-key": credentials, - }, - }) - return (await response) -} - -export async function scoreTask(username: string, credentials: string, taskID: string, direction: string) { - const url = "https://habitica.com/api/v3/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 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, - } - }) - return(response) -} \ No newline at end of file diff --git a/styles.css b/styles.css index 90c9ec1..7312bb1 100644 --- a/styles.css +++ b/styles.css @@ -1,323 +1,36 @@ -@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; -} +/* Empty. change later */ + +.book { + border: 1px solid var(--background-modifier-border); + padding: 10px; + } + + .book__title { + font-weight: 600; + } + + .book__author { + color: var(--text-muted); + } + +.todo-item { + display: flex; + justify-content: flex-start; + align-items: center; + padding: 30px 20px 0; + width: 70%; + border-bottom: 1px solid #cecece; + font-family: Roboto, sans-serif; + font-weight: 100; + font-size: 15px; + color: #ffffff; +} + +input[type=checkbox] { + margin-right: 10px; + font-size: 30px; +} + +input[type=checkbox]:focus { + outline: 0; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c6bbfcb..4968d9a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,14 +3,13 @@ "baseUrl": ".", "inlineSourceMap": true, "inlineSources": true, - "module": "ESNext", - "target": "es6", + "module": "esnext", "jsx": "react", + "target": "es2017", + "allowJs": true, "noImplicitAny": true, "moduleResolution": "node", "importHelpers": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, "lib": [ "dom", "es5", @@ -21,4 +20,4 @@ "include": [ "**/*.ts" ] -} \ No newline at end of file +} diff --git a/view.ts b/view.ts new file mode 100644 index 0000000..d54ef70 --- /dev/null +++ b/view.ts @@ -0,0 +1,32 @@ +import { ItemView,WorkspaceLeaf } from "obsidian"; +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { ReactView } from "./ReactView"; + + +export const VIEW_TYPE_EXAMPLE = "example-view" + +export class ExampleView extends ItemView { + constructor(leaf: WorkspaceLeaf) { + super(leaf) + } + + getViewType() { + return VIEW_TYPE_EXAMPLE + } + + getDisplayText() { + return "Example View" + } + + async onOpen() { + ReactDOM.render( + React.createElement(ReactView), + this.containerEl.children[1] + ) + } + + async onClose(){ + ReactDOM.unmountComponentAtNode(this.containerEl.children[1]); + } +} \ No newline at end of file diff --git a/view/App.tsx b/view/App.tsx new file mode 100644 index 0000000..a723428 --- /dev/null +++ b/view/App.tsx @@ -0,0 +1,54 @@ +import * as React from "react"; +import { getTasks } from "./habiticaAPI" +import TodoItem from "./TodoItem" + + +const username = "5b70c4ea-ed91-4fc4-8231-edd1984ec02c" +const credentials = "ccbeec3b-fe55-4952-a2fa-023d0fbbab85" + +class App extends React.Component { + constructor(props: any) { + super(props) + this.state = { + isLoaded: false, + tasks: "" + } + } + componentDidMount() { + getTasks(username, credentials) + .then(res => res.json()) + .then( + result => { + console.log(result.data) + this.setState({ + isLoaded: true, + tasks: result.data + }) + }, + (error) => { + this.setState({ + isLoaded: true, + error + }) + } + ) + + } + render(){ + const { error, isLoaded, tasks } = this.state; + if (error) { + return
Error: {error.message}
; + } else if (!isLoaded) { + return
Loading...
; + } else { + const listItems = tasks.map((tasks: any) => + + + ); + return ( +
    {listItems}
+ ); + } + } +} +export default App \ No newline at end of file diff --git a/view/TodoItem.tsx b/view/TodoItem.tsx new file mode 100644 index 0000000..84a5415 --- /dev/null +++ b/view/TodoItem.tsx @@ -0,0 +1,12 @@ + import * as React from "react"; + +function TodoItem(props: any) { + return ( +
+ +

{props.task.text}

+
+ ) +} + +export default TodoItem \ No newline at end of file diff --git a/view/habiticaAPI.ts b/view/habiticaAPI.ts new file mode 100644 index 0000000..39b9eaf --- /dev/null +++ b/view/habiticaAPI.ts @@ -0,0 +1,16 @@ +// import fetch from "node-fetch"; + + +export async function getTasks(username: string, credentials: string){ + const url = "https://habitica.com/api/v3/tasks/user?type=todos" + const response = fetch(url, { + method: 'GET', + headers: { + "Content-Type": "application/json", + "x-client": username.concat("-testAPI"), + "x-api-user": username, + "x-api-key": credentials, + }, + }) + return (response) +}