Skip to content

Instantly share code, notes, and snippets.

@ebullient
Last active August 14, 2024 21:31
Show Gist options
  • Save ebullient/41d2e9159e32e6a16510836c33d9df0f to your computer and use it in GitHub Desktop.
Save ebullient/41d2e9159e32e6a16510836c33d9df0f to your computer and use it in GitHub Desktop.
Method for creating/updating daily/weekly/monthly templates in Obsidian.md

Scripts

CustomJS files:

  • create.js - file creation
  • dated.js - date-based file manipulation (daily, weekly, monthly, yearly)
  • project.js - random functions for project files (crosses with dataview)
  • task-cleanup.js - finds all dated tasks from the previous month and un-tasks them

Templates

All templates rely on the Templater plugin + CustomJS

  • new-note.md - slugified file name + alias + folder prompting
  • daily.md - daily plan (for use with Day Planner OG)
  • weekly.md - weekly plan (where most actual planning is)
  • weekly-leftovers.md - collects tasks from previous week
  • monthly.md - monthly goals/reflection
  • yearly.md - not shown, but this is a simple file w/ months as headings

Other

Bonus is I was able to create a small test project to verify the output of dated.js, as it has no other dependencies aside from moment.js. The test project contains:

  • package.json sets up a simple node project (such that npm install works)
  • main.js runs the test: node main.js
class Create {
headerPush = ['Section', 'Log item', 'Tasks item'];
itemPush = ['Log item', 'Tasks item'];
dated = /^.*?(\d{4}-\d{2}-\d{2}).*$/;
constructor() {
this.app = window.customJS.app;
}
lowerKebab = (name) => {
return name
.replace(/([a-z])([A-Z])/g, '$1-$2') // separate on camelCase
.replace(/[\s_]+/g, '-') // replace all spaces and low dash
.replace(/[^0-9a-zA-Z_\-]/g, '') // strip other things
.toLowerCase(); // convert to lower case
}
chooseFolder = async (tp, folder) => {
if (folder === "/") {
folder = "";
}
const folders = Object.entries(app.vault.adapter.files)
.filter(x => x[1].type === "folder")
.filter(x => !x[0].startsWith("assets"))
.filter(x => x[0].startsWith(folder))
.map(x => x[0]);
folders.sort();
folders.unshift('--');
const choice = await tp.system.suggester(folders, folders);
if (choice) {
if (choice == '--') {
return await tp.system.prompt("Enter folder path");
}
return choice;
}
warn("No choice selected. Using 'athenaeum'");
return 'athenaeum';
}
createConversation = async (tp) => {
let result = "";
const day = moment(tp.file.title).format("YYYY-MM-DD");
const files = Object.entries(app.vault.adapter.files)
.filter(x => x[1].type === "file")
.filter(x => x[0].startsWith('chronicles/conversations'))
.map(x => x[0]);
files.sort();
const choice = await tp.system.suggester(files, files);
if (choice) {
const file = tp.file.find_tfile(choice);
const fileCache = this.app.metadataCache.getFileCache(file);
let title = fileCache.frontmatter && fileCache.frontmatter.aliases
? fileCache.frontmatter.aliases[0]
: file.basename;
result = `\n- [**${title}**](${file.path}#${day})\n`;
result += ` ![${day}](${file.path}#${day})\n`;
const headings = fileCache.headings
.filter(x => x.level == 2);
if (!headings || headings.length == 0) {
await this.app.vault.process(file, (content) => {
return content + `\n\n## ${day}\n`;
});
} else if (headings[0].heading != day) {
await this.app.vault.process(file, (content) => {
const split = content.split("\n");
split.splice(headings[0].position.start.line, 0, `## ${day}\n\n`);
return split.join("\n");
});
}
}
return result;
}
findLine = async (tp, obsidian) => {
let line = undefined;
const split = tp.file.content.split("\n");
const file = tp.file.find_tfile(tp.file.title);
const fileCache = this.app.metadataCache.getFileCache(file);
const title = fileCache.frontmatter && fileCache.frontmatter.aliases
? fileCache.frontmatter.aliases[0]
: file.basename;
const view = this.app.workspace.getActiveViewOfType(window.customJS.obsidian.MarkdownView);
if (view) {
const cursor = view.editor.getCursor("from").line;
line = split[cursor];
}
let heading = undefined;
let text = undefined;
if (line && line.match(/^\s*- .*/)) {
text = line.replace(/^\s*- (?:\[.\] )?(.*)/, "$1").trim();
} else {
if (!line || !line.startsWith('#')) {
const headings = fileCache.headings
.filter(x => x.level == 2);
line = split[headings[0]?.position.start.line];
}
heading = line.replace(/#+ /, "").trim();
}
return {
title,
path: tp.file.path(true),
heading,
text
}
}
listFiles = () => {
return this.app.vault.getMarkdownFiles()
.map(x => x.path);
}
chooseFile = async (tp) => {
const files = this.listFiles();
return await tp.system.suggester(files, files);
}
addToSection = async (tp, choice, addThis, section = "Log") => {
const file = tp.file.find_tfile(choice);
const fileCache = this.app.metadataCache.getCache(choice);
const headings = fileCache.headings
.filter(x => x.level >= 2)
.filter(x => x.heading.contains(section));
if (headings[0]) {
await this.app.vault.process(file, (content) => {
const split = content.split("\n");
split.splice(headings[0].position.start.line + 1, 0, addThis);
return split.join("\n");
});
}
}
pushText = async (tp) => {
const choice = await this.chooseFile(tp);
if (choice) {
const { title, heading, path, text } = await this.findLine(tp);
if (heading) {
await this.doPushHeader(tp, choice, title, heading, path);
} else {
await this.doPushText(tp, choice, title, path, text);
}
}
}
doPushHeader = async (tp, choice, title, heading, path) => {
const type = await tp.system.suggester(this.headerPush, this.headerPush);
const date = heading.replace(/^.*?(\d{4}-\d{2}-\d{2}).*$/, `$1`);
const interesting = heading.replace(/\s*\d{4}-\d{2}-\d{2}\s*/, '');
const pretty = path.contains("conversations") ? `**${title}**` : `_${title}_`;
const linkText = path == choice ? '⤴' : `${pretty}`;
const lineText = interesting ? `: ${interesting}` : '';
console.log("PUSH HEADER", `"${path}"`, `"${title}"`, `"${heading}"`, `"${date}"`, `"${interesting}"`);
const anchor = heading
.replace(/\s+/g, ' ')
.replace(/:/g, '')
.replace(/ /g, '%20');
switch (type) {
case 'Section': {
let addThis = `## ${heading} ${title}\n`;
addThis += `![invisible-embed](${path}#${anchor})\n\n`;
const file = tp.file.find_tfile(choice);
const fileCache = this.app.metadataCache.getCache(choice);
const headings = fileCache.headings
.filter(x => x.level == 2);
await this.app.vault.process(file, (content) => {
const split = content.split("\n");
if (headings && headings[0]) {
split.splice(headings[0].position.start.line, 0, addThis);
} else {
split.push("");
split.push(addThis);
}
return split.join("\n");
});
break;
}
case 'Tasks item': { // Tasks section
const addThis = `- [ ] [${linkText}](${path}#${anchor})${lineText}\n`;
this.addToSection(tp, choice, addThis, 'Tasks');
break;
}
default: { // Log section
const isDaily = choice.match(this.dated);
const isWeekly = choice.endsWith("_week.md");
const task = !isDaily || isWeekly ? '[x] ' : '';
const completed = task ? ` (${date})` : '';
const addThis = `- ${task}[${linkText}](${path}#${anchor})${lineText}${completed}`;
this.addToSection(tp, choice, addThis);
break;
}
}
}
doPushText = async (tp, choice, title, path, text) => {
const type = await tp.system.suggester(this.itemPush, this.itemPush);
const fromDaily = path.match(this.dated);
const isDaily = choice.match(this.dated);
const lineDate = text.match(this.dated);
const pretty = path.contains("conversations") ? `**${title}**` : `_${title}_`;
const date = lineDate
? lineDate[1]
: (fromDaily ? fromDaily[1] : window.moment().format('YYYY-MM-DD'));
const from = isDaily ? '' : `[${pretty}](${path}): `;
console.log("PUSH TEXT", `"${path}"`, `"${text}"`, `"${date}"`, `"${from}"`);
switch (type) {
case 'Tasks item': { // Tasks section
const addThis = `- [ ] ${text}${from}\n`;
this.addToSection(tp, choice, addThis, 'Tasks');
break;
}
default: { // Log section
const isWeekly = choice.endsWith("_week.md");
const task = !isDaily || isWeekly ? '[x] ' : '';
const completed = task && !lineDate ? ` (${date})` : '';
const addThis = `- ${task}${from}${text}${completed}`;
this.addToSection(tp, choice, addThis);
break;
}
}
}
linkedToPage = (dv) => {
const pages = this.pagesForTag(dv, "[[]]");
return pages.map(k => this.pageToLink(k));
}
/**
* Find pages (excluding this one) that have the specified tag
* See queryTagPages for further constraints based on current scope
* @param {*} pages Dataview result, array of pages
* @returns Array of pages sorted by file name, then by first path segment (grouped by folder root)
*/
pagesForTag = (dv, tag) => {
const scope = dv.current().file;
return dv.pages(tag)
.where(p => p.file.path != scope.path)
.sort(p => this.sortByName(p), 'asc');
}
pageToLink = (k) => {
return `[${k.file.aliases[0] ? k.file.aliases[0] : k.file.name}](/${k.file.path})`;
}
sortByName = (p) => {
if (p.file.frontmatter && p.file.frontmatter.sort) {
return p.file.frontmatter.sort + p.file.name;
}
return p.file.name;
}
}

<%* const { Dated } = window.customJS; const result = Dated.daily(tp.file.title); const today = result.dates.day.isoWeekday(); -%><% result.header %> Goals for today

  • .

<%* if (1 <= today && today <= 5 ) { -%> %% agenda %%

Day Planner

%%

  • 🎉 Focused for the entire time block
  • 🎠 Got a little distracted %%

Morning

  • 07:30 Reflection
  • 08:00 Caitlin to the bus
  • 08:45 Planning
  • 09:00 Start : GH Notifications / Email
  • 09:45 BREAK / chat
  • 10:00 Start :
  • 10:45 BREAK / Sudo
  • 11:00 Start :
  • 11:45 Lunch

After Lunch

  • 12:30 Meditation
  • 12:45 BREAK / chat
  • 13:00 Start :
  • 13:45 BREAK / chat

Afternoon

  • 14:00 Start :
  • 14:45 BREAK / chat
  • 15:00 Start :
  • 15:45 BREAK / chat
  • 16:00 Start :
  • 16:45 BREAK / chat

Wrap up

  • 17:00 Preview tomorrow's Agenda
  • 17:30 Reflection
  • 18:00 END

<%* } else { -%> %% %% <%* } -%>

Log

%% done %%

class Dated {
constructor() { // Constructor
this.tyiwStartWeek = 40;
this.birthdayFile = app.vault.adapter.basePath + "/assets/birthdays.json";
this.parseDate = (filename) => {
const titledate = filename.replace("_week", '');
const day = moment(titledate);
const dayOfWeek = day.isoWeekday();
let theMonday = moment(day).day(1);
let nextWorkDay = moment(day).add(1, 'd');
let nextWorkDayName = 'tomorrow';
if (dayOfWeek === 0 || dayOfWeek === 7) {
theMonday = moment(day).add(-1, 'week').day(1);
} else if (dayOfWeek > 4) {
nextWorkDay = moment(theMonday).add(1, 'week');
nextWorkDayName = 'Monday';
}
return {
day: day,
nextWorkDay: nextWorkDay,
nextWorkDayName: nextWorkDayName,
lastMonday: moment(theMonday).add(-1, 'week'),
monday: theMonday,
nextMonday: moment(theMonday).add(1, 'week')
}
}
this.dailyFile = (start, target) => {
return target.format("YYYY-MM-DD[.md]");
}
this.dayOfWeekFile = (monday, dayOfWeek) => {
return this.dailyFile(monday, moment(monday).day(dayOfWeek));
}
this.weeklyFile = (start, monday) => {
return monday.format("YYYY-MM-DD[_week.md]");
}
this.monthlyFile = (start, target) => {
return target.format("YYYY-MM[_month.md]");
}
this.yearlyFile = (start, target) => {
return target.format("YYYY[_year.md]");
}
this.tyiwFile = (target) => {
return target.format("[tyiw-journal]-YYYY-MM-DD[.md]");
}
this.daily = (filename) => {
const dates = this.parseDate(filename);
const header = '# My Day\n'
+ dates.day.format("dddd, MMMM DD, YYYY")
+ ' .... [' + dates.nextWorkDayName + '](' + this.dailyFile(dates.day, dates.nextWorkDay) + ') \n'
+ 'Week of [' + dates.monday.format("MMMM DD") + '](' + this.weeklyFile(dates.day, dates.monday) + ') \n';
return {
dates: dates,
header: header
}
}
this.tyiwWeeklyJournal = (filename) => {
const dates = this.parseDate(filename.replace('tyiw-journal-', ''));
let week = dates.monday.week();
if (week >= this.tyiwStartWeek) {
week = week - this.tyiwStartWeek + 1;
} else {
week = (52 - this.tyiwStartWeek) + week + 1;
}
return {
tags: "'me/tyiw'",
week: week,
weekOf: dates.monday.format("MMMM DD, YYYY"),
dates: dates,
prefix: dates.monday.format("tyiw-journal-YYYY-MM-DD")
}
}
this.weekly = (filename) => {
const dates = this.parseDate(filename);
const thisMonthFile = this.monthlyFile(dates.monday, dates.monday);
const lastWeekFile = this.weeklyFile(dates.monday, dates.lastMonday);
const lastMonthFile = this.monthlyFile(dates.monday, dates.lastMonday);
let monthlyReflection = '';
let upcoming = `> ![Upcoming](chronicles/${dates.monday.format("YYYY")}_year.md#${dates.monday.format("MMMM")})\n`
+ `> ![invisible-embed](chronicles/${dates.monday.format("YYYY")}_year.md#^todoist)`;
var header = `# Week of ${dates.monday.format("MMM D")}\n`
+ `[< ${dates.lastMonday.format("MMM D")}](${lastWeekFile}) --`
+ ` [Mo](${this.dayOfWeekFile(dates.monday, 1)})`
+ ` [Tu](${this.dayOfWeekFile(dates.monday, 2)})`
+ ` [We](${this.dayOfWeekFile(dates.monday, 3)})`
+ ` [Th](${this.dayOfWeekFile(dates.monday, 4)})`
+ ` [Fr](${this.dayOfWeekFile(dates.monday, 5)})`
+ ` -- [${dates.nextMonday.format("MMM D")} >](${this.weeklyFile(dates.monday, dates.nextMonday)}) \n`
+ `Goals for [${dates.monday.format("MMMM")}](${thisMonthFile})`;
if (dates.monday.month() !== dates.nextMonday.month()) {
header += `, [${dates.nextMonday.format("MMMM")}](${this.monthlyFile(dates.monday, dates.nextMonday)})`;
upcoming += `\n> ![Upcoming](${dates.nextMonday.format("YYYY")}_year.md#${dates.nextMonday.format("MMMM")})`
monthlyReflection =
`- [ ] [Reflect on last month](${lastMonthFile})\n`
+ `- [ ] [Goals for this month](${this.monthlyFile(dates.monday, dates.nextMonday)})`;
} else if (dates.monday.month() !== dates.lastMonday.month()) {
monthlyReflection =
`- [ ] [Reflect on last month](${lastMonthFile})\n`
+ `- [ ] [Goals for this month](${thisMonthFile})`;
}
const log =
`### Log ${this.dayOfWeekFile(dates.monday, 1)}\n`
+ `![invisible-embed](${this.dayOfWeekFile(dates.monday, 1)}#Log)\n\n`
+ `### Log ${this.dayOfWeekFile(dates.monday, 2)}\n`
+ `![invisible-embed](${this.dayOfWeekFile(dates.monday, 2)}#Log)\n\n`
+ `### Log ${this.dayOfWeekFile(dates.monday, 3)}\n`
+ `![invisible-embed](${this.dayOfWeekFile(dates.monday, 3)}#Log)\n\n`
+ `### Log ${this.dayOfWeekFile(dates.monday, 4)}\n`
+ `![invisible-embed](${this.dayOfWeekFile(dates.monday, 4)}#Log)\n\n`
+ `### Log ${this.dayOfWeekFile(dates.monday, 5)}\n`
+ `![invisible-embed](${this.dayOfWeekFile(dates.monday, 5)}#Log)\n\n`
+ `### Log ${this.dayOfWeekFile(dates.monday, 6)}\n`
+ `![invisible-embed](${this.dayOfWeekFile(dates.monday, 6)}#Log)\n\n`
+ `### Log ${this.dayOfWeekFile(dates.monday, 7)}\n`
+ `![invisible-embed](${this.dayOfWeekFile(dates.monday, 7)}#Log)\n\n`;
const dataview =
`dataviewjs
const { Project } = window.customJS;
const tasks = Project.demesneQuestTasks(dv)
.where(t => Project.completedThisWeek(dv.current(), t));
dv.list(Project.tasksToPageList(tasks, dv));`
const activity =
`> [!activity]
> \`\`\`dataviewjs
> const { Activity } = window.customJS;
> const today = DateTime.fromISO("${dates.monday.format("YYYY-MM-DD")}");
> Activity.barChart(today, dv, this.container, DateTime);
> \`\`\``;
return {
dates: dates,
header: header,
log: log,
dataview: dataview,
activity: activity,
monthName: `${dates.monday.format("MMMM")}`,
monthlyReflection: monthlyReflection,
weeklyReflection: `${lastMonthFile}#${dates.lastMonday.format("YYYY-MM-DD")}`,
upcoming: upcoming,
lastWeekFile: lastWeekFile
}
}
this.monthlyDates = (fileName) => {
const dateString = fileName.replace('.md', '').replace('_month', '-01');
const date = moment(dateString);
const lastMonth = moment(date).add(-1, 'month');
const nextMonth = moment(date).add(1, 'month');
let firstMonday = moment(date).startOf('month').day("Monday");
if (firstMonday.date() > 7) {
// We might be at the end of the previous month. So
// find the next Monday.. the first Monday *in* the month
firstMonday.add(7, 'd');
}
return {
monthYear: date.format("MMMM YYYY"),
month: date.format("MMMM"),
year: date.format("YYYY"),
lastMonth: lastMonth.format("MMMM"),
lastMonthFile: this.monthlyFile(date, lastMonth),
nextMonth: nextMonth.format("MMMM"),
nextMonthFile: this.monthlyFile(date, nextMonth),
firstMonday: firstMonday
}
}
this.monthly = (filename) => {
const dates = this.monthlyDates(filename);
const header = `# Goals for ${dates.monthYear}\n`
+ `[< ${dates.lastMonth}](${dates.lastMonthFile}) -- [${dates.nextMonth} >](${dates.nextMonthFile})`;
return {
dates: dates,
yearEmbed: `![${dates.month}](${this.yearlyFile(dates.firstMonday, dates.firstMonday)}#${dates.month})`,
header: header
}
}
this.yearly = (filename) => {
const dateString = filename.replace('.md', '').replace('_year', '-01-01');
const date = moment(dateString);
const year = date.format("YYYY");
const lastYear = moment(date).add(-1, 'year');
const lastYearFile = this.yearlyFile(date, lastYear);
const nextYear = moment(date).add(1, 'year');
const nextYearFile = this.yearlyFile(date, nextYear);
const header = `# Overview of ${year}\n`
+ `[< ${lastYear.format("YYYY")}](${lastYearFile}) -- [${nextYear.format("YYYY")} >](${nextYearFile})`;
const birthdays = {};
const contents = app.vault.adapter.fs.readFileSync(this.birthdayFile, "utf8");
const dates = JSON.parse(contents);
for (const [MM, value] of Object.entries(dates)) {
let list = '';
value.forEach(v => {
if (v.year) {
const diff = year - v.year;
list += `- ${v.date}: ${v.text} (${diff})\n`;
} else {
list += `- ${v.date}: ${v.text}\n`;
}
});
birthdays[MM] = list;
}
return {
year,
header,
birthdays
}
}
this.monthOfYear = (year, i) => {
let month = moment([year, i, 1]);
return {
month: month.format("MMMM"),
monthFile: this.monthlyFile(month, month)
}
}
this.filterLeftoverTasks = (line) => {
return line.includes('- [ ] ')
|| line.includes('- [>] ')
|| line.includes('- [/] ')
|| line.includes('- [R] ');
}
}
}
const moment = require('moment');
const fs = require('fs');
const vm = require('vm');
const assert = require('assert');
global.dated = {};
global.moment = moment;
try {
const data = fs.readFileSync('customjs/dated.js', 'utf8');
const script = new vm.Script(data + '; dated = new Dated();');
script.runInThisContext();
let dates = global.dated.parseDate('2021-08-01'); // sun
assert.equal(dates.nextWorkDayName, 'tomorrow');
assert.equal(dates.nextWorkDay.format('YYYY-MM-DD'), '2021-08-02');
assert.equal(dates.lastMonday.format('YYYY-MM-DD'), '2021-07-19');
assert.equal(dates.monday.format('YYYY-MM-DD'), '2021-07-26');
assert.equal(dates.nextMonday.format('YYYY-MM-DD'), '2021-08-02');
// ...
} catch (err) {
console.error(err);
process.exit(1);
}

<%* const { Dated } = window.customJS; const result = Dated.monthly(tp.file.title); await tp.file.move(/chronicles/${tp.file.title}); -%><% result.header %>

%% What are your goals for this month? What practical actions will you be taking to achieve them? S = Specific (What EXACTLY do you want to accomplish?)
M = Measurable (How will you measure success?)
A = Attainable (Is it within your reach?)
R = Resonant (Do you feel driven to accomplish it?)
T = Thrilling (Thrilling?) %%

  • Focus: %% one thing to focus on this month %%
  • Habit: %% one habit to focus on this month %%

<% result.yearEmbed %>

🤓 Weekly review

<%* const monday = result.dates.firstMonday; var month = monday.month(); while(month === monday.month()) { var weekStart = monday.format("YYYY-MM-DD"); var weekFile = Dated.weeklyFile(monday, monday); var tyiwFile = Dated.tyiwFile(monday); %>

<% weekStart %>

🎉 Big wins

🎯 How far did I get on my goals?

👩‍🎓 What worked? What didn't?

✨ How should I tweak my strategy next week?

<%* monday.add(7,'d'); } -%>

Reflection

🎉 This month's wins

  1. .
  2. .
  3. .

🙌 Insights gained

  1. .
  2. .
  3. .
Error in user YAML: (<unknown>): mapping values are not allowed in this context at line 6 column 22
---
<%* const { Create } = window.customJS;
const title = await tp.system.prompt("Enter Name"); 
const lower = Create.lowerKebab(title); 
const current = tp.file.folder(true);
const folder = await Create.chooseFolder(tp, current);
console.log("pre-move: %o, %o, %o", title, lower, folder);
await tp.file.move(`/${folder}/${lower}`);

tR += `aliases: ["${title}"]`;
%>
---

<% title %>

{
"name": "mystuff",
"version": "0.1",
"description": "Test scripts",
"private": true,
"main": "main.js",
"keywords": [],
"dependencies": {
"moment": "^2.29.1"
}
}
class Project {
constructor() { // Constructor
// Update metaedit plugin as well
this.urgent = ['yes', 'no'];
this.important = ['yes', 'no'];
this.priorityVisual = ['x', '1️⃣❣️🏃', '2️⃣❣️', '3️⃣🏃', '4️⃣'];
this.status = ['brainstorming', 'active', 'ongoing', 'blocked', 'inactive', 'complete'];
this.statusVisual = {
'active': '🆗',
'blocked': '⏰',
'brainstorming': '🧠',
'ongoing': '🔄',
'inactive': '⏸',
'complete': '✅'
}
this.role = ['owner', 'collaborator', 'observer'];
this.roleVisual = {
'owner': '🖐',
'collaborator': '🤝',
'observer': '👀',
}
}
chooseUrgent = async (tp) => {
return await tp.system.suggester(['urgent', 'not urgent'], this.urgent);
}
chooseImportant = async (tp) => {
return await tp.system.suggester(['important', 'not important'], this.important);
}
chooseStatus = async (tp) => {
return await tp.system.suggester(this.status, this.status);
}
chooseRole = async (tp) => {
return await tp.system.suggester(this.role, this.role);
}
priority = (p) => {
if (p.important == 'yes') {
return p.urgent == 'yes' ? 1 : 2;
}
return p.urgent == 'yes' ? 3 : 4;
}
sortProjects = (p1, p2) => {
return this.testPriority(p1, p2,
() => this.test(p1, p2, this.status, 'status',
() => this.test(p1, p2, this.role, 'role',
() => this.testName(p1, p2))));
}
test = (p1, p2, values, field, fallback) => {
let test1 = values.indexOf(p1[field]);
let test2 = values.indexOf(p2[field]);
if (test1 == test2) {
return fallback();
}
return test1 - test2;
}
testPriority = (p1, p2, fallback) => {
let test1 = this.priority(p1);
let test2 = this.priority(p2)
// console.log('priority %o(%o|%o) <--> %o(%o|%o)',
// test1, p1.important, p1.urgent,
// test2, p2.important, p2.urgent);
if (test1 == test2) {
return fallback();
}
return test1 - test2;
}
testName = (p1, p2) => {
//console.log('comparing file name: %o, %o', p1.file.name, p2.file.name)
return p1.file.name.localeCompare(p2.file.name);
}
sortTasks = (t1, t2) => {
if (t1.completed == t2.completed) {
const api = window.app.plugins.plugins.dataview.api;
return this.sort(api.page(t1.path), api.page(t2.path));
}
return t1.completed - t2.completed;
}
filter = (pages) => {
return pages
.where(p => p.type == "project" || p.type == "quest")
.sort(p => p, 'asc', this.sortProjects)
.map(k => [
`[${k.file.aliases[0] ? k.file.aliases[0] : k.file.name}](${k.file.path})`,
this.priorityVisual[this.priority(k)],
this.statusVisual[k.status],
this.roleVisual[k.role]
]);
}
filterArea = (pages) => {
return pages
.where(p => p.type == "area")
.sort(p => p, 'asc', this.sortProjects)
.map(k => [
`[${k.file.aliases[0] ? k.file.aliases[0] : k.file.name}](${k.file.path})`,
// this.priorityVisual[this.priority(k)],
// this.statusVisual[k.status],
this.roleVisual[k.role]
]);
}
completedThisWeek = (page, task) => {
const taskMatch = task.text.match(/\((\d{4}-\d{2}-\d{2})\)/);
if (task.completed && taskMatch) {
const monday = moment(page.file.name.replace("_week", ''));
return moment(taskMatch[1]).isBetween(moment(monday).add(-1, 'd'), moment(monday).add(7, 'd'));
}
return false;
}
tasksToPageList = (tasks, dv) => {
return tasks.map(t => {
let k = dv.page(t.path);
return `*[${k.file.aliases[0] ? k.file.aliases[0] : k.file.name}](${k.file.path})*: ${t.text}`;
});
}
otherTasks = (dv) => {
return dv.pages('!"chronicles" and !"assets" and !"Ω-archives"')
.where(p => p.type != "project" && p.type != "area" && p.type != "quest").file.tasks;
}
demesneQuestPages = (dv) => {
return dv.pages('"demesne" or "quests"')
.where(p => p.type == "project" || p.type == "area" || p.type == "quest");
}
demesneQuestTasks = (dv) => {
return this.demesneQuestPages(dv).file.tasks;
}
folderPages = (dv) => {
return this.folderPagesQuery(dv, '');
}
folderPagesQuery = (dv, query) => {
const folderQuery = query ? `"${dv.current().file.folder}" ${query}` : `"${dv.current().file.folder}"`;
return dv.pages(folderQuery);
}
folderIndex = (dv, w = (p) => true) => {
const pg = dv.current().file;
const pages = this.folderPages(dv, '')
.where(p => p.file.path != pg.path)
.where(w);
this.index(dv, pg, pages);
}
areaIndex = (dv, areaQuery, w = (p) => true) => {
const pg = dv.current().file;
const pages = this.folderPagesQuery(dv, areaQuery)
.where(p => p.file.path != pg.path)
.where(w);
this.index(dv, pg, pages);
}
index = (dv, pg, pages) => {
const groups = pages
.map(p => {
let title = (p.file.aliases[0] ? p.file.aliases[0] : p.file.name);
let link = `[${title}](/${p.file.path})`;
let folder = p.file.folder;
return [folder, link, title, p.file.name];
})
.groupBy(p => p[0]);
groups.forEach(g => {
if (g.key != pg.folder) {
dv.header(3, g.key);
}
const sortKey = g.key.substring(g.key.lastIndexOf('/') + 1);
dv.list(g.rows
.sort(r => r, 'asc', (r1, r2) => {
if(r1[3] === sortKey) {
return -1;
} else if (r2[3] === sortKey) {
return 1;
}
return r1[2].localeCompare(r2[2]);
})
.map(r => r[1] + (r[3] === sortKey ? " (index)" : "")));
})
}
relatedAreasSection = (dv, pg, query) => {
const areaElements = this.filterArea(this.folderPagesQuery(dv, query)
.where(p => p.file.path != pg.path));
const projElements = this.filter(this.folderPagesQuery(dv, query));
if (areaElements.length > 0 || projElements.length > 0) {
dv.el("h2", "Related Areas");
dv.table(["Name", "Role"], areaElements)
dv.table(["Name", "Priority", "Status", "Role"], projElements)
}
}
pagesForTag = (dv, tag) => {
const scope = dv.current().file;
const pages = dv.pages(tag);
return pages
.where(p => p.file.path != scope.path)
.sort(p => p.file.name, 'asc')
.sort(p => p.file.path.substring(0, p.file.path.indexOf('/')), 'asc');
}
pageToListItem = (k) => {
return `<small>(${k.file.path.substring(0, k.file.path.indexOf('/'))})</small> [${k.file.aliases[0] ? k.file.aliases[0] : k.file.name}](/${k.file.path})`;
}
linkedToPage = (dv) => {
const pages = this.pagesForTag(dv, "[[]]");
return pages.map(k => this.pageToListItem(k));
}
}
class TaskCleanup {
anyTaskMark = new RegExp(/^([\s>]*- )(\[(?:x|-)\]) (.*) \((\d{4}-\d{2}-\d{2})\)\s*$/);
app = window.customJS.app;
dailyNote = /^(\d{4}-\d{2}-\d{2}).md$/;
done = new RegExp(/^[\s>]*- (✔️|〰️) .*$/);
list = new RegExp(/^[\s>]*- .*$/);
async invoke() {
console.log("Cleaning up old tasks");
let monthMoment = moment().startOf('month');
console.log("Cleaning up tasks before", monthMoment.format("(YYYY-MM-"));
// Map each file to the result of a cached read
const promises = this.app.vault.getMarkdownFiles()
.map((file) => {
if (file.name.match(this.dailyNote) || file.path.startsWith("assets")) {
return () => true;
}
const fileCache = this.app.metadataCache.getFileCache(file);
if (!fileCache.headings) {
return () => true;
}
const logHeading = fileCache.headings.find(x => x.heading.endsWith("Log"));
if (logHeading) {
return this.updateTasks(file, logHeading, monthMoment);
}
return () => true;
});
await Promise.all(promises);
}
updateTasks = async (file, logHeading, monthMoment) => {
await this.app.vault.process(file, (source) => {
const split = source.split("\n");
let i = logHeading.position.start.line + 1;
for (i; i < split.length; i++) {
if (split[i].startsWith("#") || split[i] == "---") {
break;
}
if (split[i].match(this.list)) {
if (!!split[i].match(this.done)) {
break;
}
const taskMatch = this.anyTaskMark.exec(split[i]);
if (taskMatch) {
const mark = taskMatch[2];
const completed = moment(taskMatch[4]);
if (completed.isBefore(monthMoment)) {
if (mark == "[x]") {
split[i] = `${taskMatch[1]} ✔️ ${taskMatch[3]} (${taskMatch[4]})`;
} else {
split[i] = `${taskMatch[1]} 〰️ ~~${taskMatch[3]} (${taskMatch[4]})~~`;
}
}
}
}
}
return split.join("\n");
});
}
}

<%* const { Dated } = window.customJS; const result = Dated.weekly(tp.file.title);

var incompleteTasks = '';
const lastWeek = await tp.file.find_tfile(result.lastWeekFile);
if(lastWeek) { 
    const content = await app.vault.cachedRead(lastWeek); 
    incompleteTasks = content.split('\n')
                .filter(Dated.filterLeftoverTasks)
                .join('\n'); 
}
if(incompleteTasks) {%>

Leftovers <% incompleteTasks %> <%*}%>

<%* const { Dated } = window.customJS; const result = Dated.weekly(tp.file.title); const monday = result.dates.monday.format("YYYY-MM-DD"); -%> <% result.activity %> <% result.header %> %%

  • Reflect on last week
  • Review percolator (priority, next actions, etc)
  • File any Inbox items from last week <%* if(result.monthlyReflection) {-%> <% result.monthlyReflection %> <%*}-%> %%

Goals / Focus

Habit:
Goal for the week:
I am excited about:

Priorities:

  1. .
  2. .
  3. .

[!tldr] Upcoming <% result.upcoming %>

Tasks

Commonhaus

Red Hat

Other

  • Run qk to update git repos (anduin, erebor)
  • updates on erebor
  • updates on anduin
  • updates on esgaroth
  • check on moria
  • updates on wyrmling
  • Check "missing"
  • water plants

<% tp.file.include(tp.file.find_tfile("assets/templates/weekly-leftovers.md")) %>


Project items completed this week:


<% result.log %>

<%* const { Dated } = window.customJS; const year = await tp.system.prompt("Enter Year"); await tp.file.move(journal/${year}_year); -%>

Overview of <% year %>

%% Quick notes about a given month. Allow lookahead for important deadlines in the future %%

<%* for (let i = 0; i < 12; i++) { const result = Dated.yearly(year, i); -%>

<% result.month %>

<%* } -%>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment