-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
244 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
module.exports = { | ||
root: true, | ||
parser: "@typescript-eslint/parser", | ||
parserOptions: { | ||
project: "./tsconfig.json", | ||
ecmaVersion: "latest", | ||
}, | ||
plugins: ["@typescript-eslint/eslint-plugin", "unicorn", "sonarjs"], | ||
extends: [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/strict-type-checked", | ||
"plugin:@typescript-eslint/stylistic-type-checked", | ||
"plugin:unicorn/recommended", | ||
"plugin:sonarjs/recommended", | ||
"prettier", | ||
], | ||
ignorePatterns: [".eslintrc.cjs", "prettier.config.js", "dataviewjs.js"], | ||
rules: { | ||
"@typescript-eslint/no-throw-literal": "off", | ||
"unicorn/prevent-abbreviations": "off", | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/sh | ||
|
||
bun build ./src/index.ts --outfile=dataviewjs.js | ||
bun format | ||
|
||
sed -i '1i // dataviewjs-habit-tracker | https://github.com/adamhl8/dataviewjs-habit-tracker\n// Looking to make modifications? See the TypeScript source here: https://github.com/adamhl8/dataviewjs-habit-tracker/blob/main/src/index.ts\n' dataviewjs.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,70 @@ | ||
const habits = [] // Array of objects for each page's tasks. | ||
const defaultHeaders = ['Day'] | ||
const headers = new Set(defaultHeaders) // Set of task names to be used as table headers. | ||
const rows = [] | ||
// dataviewjs-habit-tracker | https://github.com/adamhl8/dataviewjs-habit-tracker | ||
// Looking to make modifications? See the TypeScript source here: https://github.com/adamhl8/dataviewjs-habit-tracker/blob/main/src/index.ts | ||
|
||
const noteDay = dv.current().file.day | ||
if (!noteDay) | ||
throw { | ||
stack: | ||
'(If this note is your template, this error is expected.) Unable to get note\'s day. Note should be named in the "YYYY-MM-DD" format.', | ||
} | ||
|
||
const pages = dv | ||
.pages('"Daily Notes"') | ||
.where((p) => p.file.day >= noteDay.minus({ days: 7 })) // Only include previous week in table. | ||
.where((p) => p.file.day <= noteDay) // Don't include future notes. | ||
.sort((p) => p.file.day, 'desc') // Sort table by most recent day. | ||
|
||
for (const page of pages) { | ||
// Only include tasks under a header named "Habits". | ||
const pageHabits = page.file.tasks.filter((t) => t.header.subpath == 'Habits') | ||
|
||
const noteLink = page.file.link | ||
noteLink.display = page.file.day.weekdayLong // Set display name of the note link to the day of the week. | ||
const habitsObject = { noteLink } | ||
|
||
for (const habit of pageHabits) { | ||
let habitText = habit.text.split(' ✅')[0] // Remove completion text from Tasks plugin. | ||
// Remove tag text. | ||
for (const tag of habit.tags) { | ||
habitText = habitText.replace(tag, '') | ||
// src/index.ts | ||
var getPageDay = function (currentPage) { | ||
const pageDay = currentPage.file.day | ||
if (!pageDay) { | ||
throw { | ||
stack: | ||
'(If this note is your template, this error is expected.) Unable to get note\'s day. Note should be named in the "YYYY-MM-DD" format.', | ||
} | ||
habitText = habitText.trim() | ||
|
||
habitsObject[habitText] = habit.completed // Build habitsObject. Key is the task's text. Value is tasks's completion. | ||
headers.add(habitText) // Build headers set where each header is the task's text. | ||
} | ||
|
||
habits.push(habitsObject) | ||
return pageDay | ||
} | ||
|
||
for (const habitsObject of habits) { | ||
const row = [habitsObject.noteLink] // Start building row data. Fill in first value (Day) with note link. | ||
var getDailyNotesPages = function (pageDay) { | ||
return dv | ||
.pages('"Daily Notes"') | ||
.where((p) => getPageDay(p) >= pageDay.minus({ days: 7 })) | ||
.where((p) => getPageDay(p) <= pageDay) | ||
.sort((p) => p.file.day, "desc") | ||
} | ||
var getCleanHabitText = function (habit) { | ||
let habitText = habit.text.split(" \u2705")[0] ?? "" | ||
for (const tag of habit.tags) { | ||
habitText = habitText.replace(tag, "") | ||
} | ||
return habitText.trim() | ||
} | ||
var getPageHabits = function (page) { | ||
const habitTasks = page.file.tasks.filter((t) => t.section.subpath == "Habits") | ||
const habits = {} | ||
for (const habitTask of habitTasks) { | ||
const habitText = getCleanHabitText(habitTask) | ||
habits[habitText] = habitTask.completed | ||
} | ||
return habits | ||
} | ||
var createRow = function (pageData, headers) { | ||
const pageDay = getPageDay(pageData.page) | ||
const pageLink = pageData.page.file.link | ||
pageLink.display = pageDay.weekdayLong ?? "" | ||
const row = [pageLink] | ||
for (const header of headers) { | ||
if (defaultHeaders.includes(header)) continue // Don't overwrite default headers. | ||
|
||
let habitStatus = '➖' // This emoji is seen if a corresponding task doesn't exist for a header (e.g. task didn't previously exist). | ||
if (habitsObject.hasOwnProperty(header)) | ||
// If task exists, we know it must be complete or incomplete. | ||
habitStatus = habitsObject[header] ? '✔' : '❌' | ||
if (defaultHeaders.includes(header)) continue | ||
let habitStatus = "\u2796" | ||
if (Object.prototype.hasOwnProperty.call(pageData.habits, header)) | ||
habitStatus = pageData.habits[header] ? "\u2714" : "\u274C" | ||
row.push(habitStatus) | ||
} | ||
rows.push(row) | ||
return row | ||
} | ||
|
||
dv.table(headers, rows) | ||
async function main() { | ||
const currentPage = dv.current() | ||
const pageDay = getPageDay(currentPage) | ||
const pages = getDailyNotesPages(pageDay) | ||
const headers = new Set(defaultHeaders) | ||
const pageDataArray = pages.map((page) => { | ||
const habits = getPageHabits(page) | ||
for (const habit of Object.keys(habits)) headers.add(habit) | ||
return { page, habits } | ||
}) | ||
const rows = [] | ||
for (const pageData of pageDataArray) { | ||
const row = createRow(pageData, headers) | ||
rows.push(row) | ||
} | ||
await dv.table([...headers], rows) | ||
} | ||
var defaultHeaders = ["Day"] | ||
await main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "dataviewjs-habit-tracker", | ||
"type": "module", | ||
"scripts": { | ||
"build": "bun build ./src/index.ts --outfile=dataviewjs.js && bun format", | ||
"format": "prettier --write .", | ||
"lint": "tsc && eslint ./src/" | ||
}, | ||
"devDependencies": { | ||
"@types/luxon": "^3.3.7", | ||
"@typescript-eslint/eslint-plugin": "^6.14.0", | ||
"@typescript-eslint/parser": "^6.14.0", | ||
"bun-types": "latest", | ||
"eslint": "^8.56.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"eslint-plugin-sonarjs": "^0.23.0", | ||
"eslint-plugin-unicorn": "^49.0.0", | ||
"obsidian-dataview": "^0.5.64", | ||
"prettier": "^3.1.1", | ||
"prettier-plugin-organize-imports": "^3.2.4", | ||
"prettier-plugin-pkg": "^0.18.0", | ||
"prettier-plugin-sh": "^0.13.1", | ||
"typescript": "^5.3.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** @type {import("prettier").Options} */ | ||
export default { | ||
printWidth: 120, | ||
semi: false, | ||
plugins: ["prettier-plugin-organize-imports", "prettier-plugin-pkg", "prettier-plugin-sh"], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { DateTime } from "luxon" | ||
import { DataArray } from "obsidian-dataview/lib/api/data-array" | ||
import { DataviewInlineApi } from "obsidian-dataview/lib/api/inline-api" | ||
import { SMarkdownPage, STask } from "obsidian-dataview/lib/data-model/serialized/markdown" | ||
import { Link } from "obsidian-dataview/lib/data-model/value" | ||
|
||
interface ExtendedDataviewInlineApi extends DataviewInlineApi { | ||
current: () => SMarkdownPage | ||
} | ||
|
||
declare const dv: ExtendedDataviewInlineApi | ||
|
||
interface PageData { | ||
page: SMarkdownPage | ||
habits: Record<string, boolean> | ||
} | ||
|
||
const defaultHeaders = ["Day"] | ||
|
||
function getPageDay(currentPage: SMarkdownPage) { | ||
const pageDay = currentPage.file.day | ||
if (!pageDay) { | ||
throw { | ||
stack: | ||
'(If this note is your template, this error is expected.) Unable to get note\'s day. Note should be named in the "YYYY-MM-DD" format.', | ||
} | ||
} | ||
return pageDay | ||
} | ||
|
||
function getDailyNotesPages(pageDay: DateTime) { | ||
return dv | ||
.pages('"Daily Notes"') | ||
.where((p: SMarkdownPage) => getPageDay(p) >= pageDay.minus({ days: 7 })) // Only include previous week in table. | ||
.where((p: SMarkdownPage) => getPageDay(p) <= pageDay) // Don't include future notes. | ||
.sort((p: SMarkdownPage) => p.file.day, "desc") as DataArray<SMarkdownPage> // Sort table by most recent day. | ||
} | ||
|
||
function getCleanHabitText(habit: STask) { | ||
let habitText = habit.text.split(" ✅")[0] ?? "" // Remove completion text from Tasks plugin. | ||
// Remove tag text. | ||
for (const tag of habit.tags) { | ||
habitText = habitText.replace(tag, "") | ||
} | ||
return habitText.trim() | ||
} | ||
|
||
function getPageHabits(page: SMarkdownPage) { | ||
const habitTasks = page.file.tasks.filter((t: { section: Link }) => t.section.subpath == "Habits") | ||
const habits: Record<string, boolean> = {} | ||
|
||
// Build habits. Key is the task's text. Value is tasks's completion. | ||
for (const habitTask of habitTasks) { | ||
const habitText = getCleanHabitText(habitTask) | ||
habits[habitText] = habitTask.completed | ||
} | ||
|
||
return habits | ||
} | ||
|
||
function createRow(pageData: PageData, headers: Set<string>) { | ||
const pageDay = getPageDay(pageData.page) | ||
const pageLink = pageData.page.file.link as Link | ||
pageLink.display = pageDay.weekdayLong ?? "" // Set display name of the note link to the day of the week. | ||
|
||
const row: [Link | string] = [pageLink] // Start building row data. Fill in first value (Day) with note link. | ||
for (const header of headers) { | ||
if (defaultHeaders.includes(header)) continue // Don't overwrite default headers. | ||
|
||
let habitStatus = "➖" // This emoji is seen if a corresponding task doesn't exist for a header (e.g. task didn't previously exist). | ||
if (Object.prototype.hasOwnProperty.call(pageData.habits, header)) | ||
// If task exists, we know it must be complete or incomplete. | ||
habitStatus = pageData.habits[header] ? "✔" : "❌" | ||
|
||
row.push(habitStatus) | ||
} | ||
return row | ||
} | ||
|
||
async function main() { | ||
const currentPage = dv.current() | ||
const pageDay = getPageDay(currentPage) | ||
const pages = getDailyNotesPages(pageDay) | ||
|
||
const headers = new Set(defaultHeaders) // Set of task names to be used as table headers. | ||
const pageDataArray: DataArray<PageData> = pages.map((page) => { | ||
const habits = getPageHabits(page) | ||
for (const habit of Object.keys(habits)) headers.add(habit) | ||
return { page, habits } | ||
}) | ||
|
||
const rows = [] | ||
for (const pageData of pageDataArray) { | ||
const row = createRow(pageData, headers) | ||
rows.push(row) | ||
} | ||
|
||
await dv.table([...headers], rows) | ||
} | ||
|
||
await main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"include": ["./src/"], | ||
"compilerOptions": { | ||
"target": "ESNext", | ||
"module": "ESNext", | ||
"moduleResolution": "Bundler", | ||
"moduleDetection": "force", | ||
"allowImportingTsExtensions": true, | ||
"skipLibCheck": true, | ||
"noEmit": true, | ||
"isolatedModules": true, | ||
"strict": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"exactOptionalPropertyTypes": true, | ||
"noUncheckedIndexedAccess": true, | ||
"types": ["bun-types"] | ||
} | ||
} |