Skip to content

Commit

Permalink
typescript rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
adamhl8 committed Dec 18, 2023
1 parent c46c1a0 commit 7fc43f9
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 57 deletions.
22 changes: 22 additions & 0 deletions .eslintrc.cjs
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",
},
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
6 changes: 0 additions & 6 deletions .prettierrc

This file was deleted.

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Adam
Copyright (c) 2023 adamhl8

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Issues/PRs are welcome and encouraged.
![](https://user-images.githubusercontent.com/1844269/177612045-5409aff4-c569-419c-8314-1554ee206091.png)

- It automatically creates table headers based on unique task names (no duplicates).
- **This means you dont need to define any specific Dataview annotations. Just normal tasks.**
- If a task didnt exist previously (or no longer exists), youll see a “➖” for its status. Otherwise, ✔" for complete and “❌” for incomplete.
- **This means you don't need to define any specific Dataview annotations. Just normal tasks.**
- If a task didn't exist previously (or no longer exists), you'll see a "➖" for its status. Otherwise, "✔" for complete and "❌" for incomplete.

## Setup

Expand Down
6 changes: 6 additions & 0 deletions build.sh
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
Binary file added bun.lockb
Binary file not shown.
110 changes: 62 additions & 48 deletions dataviewjs.js
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()
25 changes: 25 additions & 0 deletions package.json
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"
}
}
6 changes: 6 additions & 0 deletions prettier.config.js
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"],
}
101 changes: 101 additions & 0 deletions src/index.ts
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()
18 changes: 18 additions & 0 deletions tsconfig.json
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"]
}
}

0 comments on commit 7fc43f9

Please sign in to comment.