├── src ├── icon.woff ├── main.js ├── index.css └── App.vue ├── .gitignore ├── .prettierignore ├── vite.config.js ├── index.html ├── package.json ├── LICENSE ├── icon.svg ├── .github └── workflows │ └── publish.yml └── README.md /src/icon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c6p/logseq-habit-tracker/HEAD/src/icon.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | releases 7 | test -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | releases 7 | test -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | 4 | const config = defineConfig({ 5 | base: "", 6 | build: { 7 | minify: true, 8 | }, 9 | plugins: [vue()], 10 | }); 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logseq-habit-tracker", 3 | "version": "0.4.7", 4 | "main": "dist/index.html", 5 | "author": "c6p", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "watch": "vite build --watch", 10 | "format": "prettier --write **/*.{js,jsx,ts,tsx,vue}" 11 | }, 12 | "dependencies": { 13 | "@logseq/libs": "^0.0.17", 14 | "dayjs": "^1.11.13", 15 | "prettier": "^3.3.3", 16 | "vue": "^3.5.12", 17 | "vue-draggable-next": "^2.3.0" 18 | }, 19 | "devDependencies": { 20 | "@vitejs/plugin-vue": "^5.1.4", 21 | "vite": "5.4.10" 22 | }, 23 | "logseq": { 24 | "id": "c6p_logseq-habit-tracker", 25 | "icon": "icon.svg" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Can ALTIPARMAK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import "@logseq/libs"; 2 | import { createApp } from "vue"; 3 | import App from "./App.vue"; 4 | import "./index.css"; 5 | import Font from "./icon.woff"; 6 | 7 | function createModel() { 8 | return { 9 | show(_) { 10 | logseq.showMainUI(); 11 | }, 12 | }; 13 | } 14 | 15 | function main() { 16 | logseq.setMainUIInlineStyle({ 17 | zIndex: 11, 18 | }); 19 | 20 | logseq.provideStyle(String.raw` 21 | @font-face { 22 | font-family: 'habit-tracker'; 23 | src: url(${Font}) format('woff'); 24 | font-weight: normal; 25 | font-style: normal; 26 | font-display: block; 27 | } 28 | i.icon-habit-tracker { 29 | /* use !important to prevent issues with browser extensions that change fonts */ 30 | font-family: 'habit-tracker' !important; 31 | speak: never; 32 | font-style: normal; 33 | font-weight: normal; 34 | font-variant: normal; 35 | text-transform: none; 36 | line-height: 1; 37 | font-size: 20px; 38 | } 39 | 40 | i.icon-habit-tracker:before { 41 | content: "\e900"; 42 | } 43 | `); 44 | 45 | logseq.App.registerUIItem("toolbar", { 46 | key: "show-habits", 47 | template: ` 48 | 49 | `, 50 | }); 51 | 52 | createApp(App).mount("#app"); 53 | } 54 | 55 | // bootstrap 56 | logseq.ready(createModel()).then(main); 57 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build plugin 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10 8 | 9 | env: 10 | PLUGIN_NAME: logseq-habit-tracker 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Use Node.js 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: "20.x" # You might need to adjust this value to your own version 22 | - name: Build 23 | id: build 24 | run: | 25 | npm install 26 | npm run build 27 | mkdir ${{ env.PLUGIN_NAME }} 28 | cp LICENSE README.md package.json icon.svg ${{ env.PLUGIN_NAME }} 29 | mv dist ${{ env.PLUGIN_NAME }} 30 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 31 | ls 32 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 33 | - name: Create Release 34 | id: create_release 35 | uses: actions/create-release@v1 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | VERSION: ${{ github.ref }} 39 | with: 40 | tag_name: ${{ github.ref }} 41 | release_name: ${{ github.ref }} 42 | draft: false 43 | prerelease: false 44 | 45 | - name: Upload zip file 46 | id: upload_zip 47 | uses: actions/upload-release-asset@v1 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | with: 51 | upload_url: ${{ steps.create_release.outputs.upload_url }} 52 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 53 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 54 | asset_content_type: application/zip 55 | 56 | - name: Upload package.json 57 | id: upload_metadata 58 | uses: actions/upload-release-asset@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | upload_url: ${{ steps.create_release.outputs.upload_url }} 63 | asset_path: ./package.json 64 | asset_name: package.json 65 | asset_content_type: application/json 66 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | #app { 7 | font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 8 | min-height: 100vh; 9 | user-select: none; 10 | } 11 | 12 | #habit-wrapper { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | right: 0; 17 | bottom: 0; 18 | } 19 | #habit-tracker { 20 | position: absolute; 21 | top: 3em; 22 | right: 0; 23 | padding-right: .5em; 24 | background: var(--background); 25 | color: var(--color); 26 | box-shadow: 1px 1px 8px var(--shadow); 27 | max-width: calc(100% - 3em); 28 | } 29 | a { color: var(--color); } 30 | #settings div { display: flex; justify-content: space-between; margin-bottom: .5em; padding: 0 1em; } 31 | input[type=text] { background: var(--input); border: 1px solid var(--shadow);color: var(--color); width: 5em; text-align: center; } 32 | input.s { width: 10em } 33 | input.m { width: 20em } 34 | input.l { width: 40em } 35 | th { font-size: 70%; padding: .5em 0; } 36 | .hidden, .hidden input { width: 1em; text-align: center; } 37 | .habit { min-width: 10em; white-space: nowrap; } 38 | .weekend { background: var(--input); } 39 | .track { cursor: pointer; color: var(--color); } 40 | th.track { white-space: pre-line; } 41 | td.track { font-size: .8em; font-weight: bold; text-align: center; } 42 | .success { background: var(--green); } 43 | .failure { background: var(--red); } 44 | .period, .period input { width: 5em; text-align: center; } 45 | .streak { width: 3em; text-align: center; } 46 | .up { font-weight: bold; transform: rotate(90deg); } 47 | .down { font-weight: bold; transform: rotate(-90deg); } 48 | #toolbar { 49 | position: absolute; 50 | left: -2em; 51 | display: flex; 52 | flex-direction: column; 53 | } 54 | #toolbar > button { padding: .2em; margin-bottom: 1em; } 55 | .color-group > input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } 56 | .color-group > input[type="color"] { 57 | border: none; 58 | height: 100%; 59 | width: 3em; 60 | vertical-align: bottom; 61 | } 62 | .color-group { display: inline-block; margin-left: 1em; } 63 | div#color-groups { 64 | display: flex; 65 | justify-content: flex-start; 66 | flex-wrap: wrap; 67 | margin: 0; 68 | padding: 0; 69 | } 70 | select.color { background: var(--input); color: inherit; } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > If you like this plugin, you can [buy me a ☕ here](https://www.buymeacoffee.com/c.6p) 2 | 3 | # Logseq Habit Tracker 4 | 5 | ![image](https://user-images.githubusercontent.com/80478/141788606-44566dfe-bbf7-4272-a3ea-5a9a48eba313.png) 6 | ![image](https://user-images.githubusercontent.com/80478/141792679-3c164eab-be20-4c2f-a4fe-757676c3b4c7.png) 7 | 8 | Record your habits under `#habit` block in daily journal, then easily track your habits. 9 | 10 | Click on gear (⚙️) icon to edit settings. 11 | 12 | * **Habit marker** is a keyword to match habits: `#habit`, it was `Habits` before v0.2.0 13 | * **Habit pattern** is the [regex](https://regex101.com/) to parse habits. See [Customization](#customization) for more examples. 14 | Default format is `Habit text - 1,2,3` and you can add notes after a new line. 15 | Default: `^(TODO|DONE)?\s*(?.*?)(?:| - (?:(?\d*?) times|(?.*?)))$`, 16 | capture groups: 17 | * **habit** group is the identifier for the habit and 18 | * **int** is integer count in special format. i.e `5 times` 19 | * only required to specify a count number - else count 20 | * **count** is comma separated list of things (e.g. numbers, time) 21 | * only required to count comma-seperated list - else 1 22 | 23 | * **Ignore Pattern** is the regex to ignore habits matching the habitPattern. 24 | Default value is `TODO .*`. So while `DONE` blocks count `TODO` blocks not. 25 | * **Date [format](https://day.js.org/docs/en/display/format)**: `D.M\ndd` where `\n` is new line. 26 | * **Date [width](https://developer.mozilla.org/en-US/docs/Web/CSS/width#syntax)**: `2em` 27 | 28 | For `Frequency / Period`, period is one of `d` (day), `w` (week), `m` (month), `y` (year). 29 | 30 | * `3/d` means 3 times per day 31 | * `4/2w` means 4 times in 2 weeks 32 | 33 | By checking `At Most` you can model bad habits to avoid. 34 | 35 | ### Customization 36 | 37 | Instead of nesting under `#habit` tag you can also use it in every line and change the format to suit your needs. 38 | 39 | #### Example 1 40 | 41 | ``` 42 | 14:10 pushups #habit 43 | 14:11 #habit drink water 44 | ``` 45 | 46 | Habit Pattern: `\d{2}:\d{2} (?.*)` 47 | 48 | ##### Example 1a 49 | 50 | With optional time and notes. Thanks @someinternetguy 51 | 52 | ``` 53 | pushups #habit 54 | pushups #habit did 10 more than usual today, go me! 55 | 14:17 pushups #habit 56 | 14:17 pushups #habit yay, I remembered to work out today! 57 | ``` 58 | 59 | Habit pattern: `((\d{2}:\d{2})|())(?.*#habit(\s|\b))` 60 | 61 | #### Example 2 62 | 63 | Ignore habits with unfinished marker. See [#10](https://github.com/c6p/logseq-habit-tracker/issues/10) 64 | 65 | ``` 66 | - #habit 67 | - qwer - 1,2 68 | - qwe 69 | - qwert - X 70 | ``` 71 | 72 | Ignore pattern: `.*- X` will ignore habit `qwert` 73 | 74 | #### Example 3 75 | 76 | Do not count comma-separated items. See [#29](https://github.com/c6p/logseq-habit-tracker/issues/29) 77 | 78 | ``` 79 | - #habit 80 | - qwerty - 1,2 81 | - qwerty 82 | ``` 83 | 84 | Both will be counted as 1. Habit pattern: `^(TODO|DONE)?\s*(?.*?)(?:| - .*)$` 85 | 86 | ### Running the Plugin 87 | 88 | * `npm && npm build` in terminal to install dependencies. 89 | * `Load unpacked plugin` in Logseq Desktop client. 90 | 91 | ### Attribution 92 | 93 | * Icon by Microsoft (CC BY 4.0): 94 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 137 | 138 | 559 | --------------------------------------------------------------------------------