Hey @Moonbase59 ! Thanks a lot for sharing this code. It has been conducive to me.
I had to make a minor change to remove the Luxon dependency, which was breaking for me.
Also, as I use a different folder structure for my daily notes, I added some code to search the pages from the daily notes configured folder. The relevant lines of code I changed:
var folder = app['internalPlugins']['plugins']['daily-notes']['instance']['options']['folder'] || dv.current().file.folder
var p = dv.pages('"'+folder+'"').where(p => p.file.day).map(p => [p.file.name, p.file.day.toISODate()]).sort(p => p[1]);
var t = dv.current().file.day ? dv.current().file.day.toISODate() : dv.date("now").toFormat("yyyy-MM-dd");
Wanted a drop down menu for my dashboard so I used chat gpt to create this query and a css snippet to style it. I’m sure the query could be simplified or there is a better way to do this but it works and links to the pages AND it’s all updated automatically.
function formatDate(dateString) {
let date = new Date(dateString);
let options = { year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: true };
return date.toLocaleString("en-US", options);
}
let folderTree = new Map();
function addToFolderTree(path, content) {
let parts = path.split("/");
let currentLevel = folderTree;
for (let i = 0; i < parts.length; i++) {
let part = parts[i];
if (!currentLevel.has(part)) {
currentLevel.set(part, { folders: new Map(), files: "" });
}
if (i === parts.length - 1) {
currentLevel.get(part).files = content;
} else {
currentLevel = currentLevel.get(part).folders;
}
}
}
for (let group of dv.pages('"----01. PROJECTS"').groupBy(p => p.file.folder)) {
let folderName = group.key;
let rows = group.rows
.map(
k => `| [${k.file.name.replace(/\|/g, "\\|")}](/${k.file.path.replace(/ /g, "%20")}) | ${formatDate(k.file.ctime)} | ${formatDate(k.file.mtime)} |`
)
.join("\n");
let tableContent = `
| Name | Created | Modified |
| ---- | ------- | -------- |
${rows}
`;
addToFolderTree(folderName, tableContent);
}
function renderFolderTree(folderMap, level = 0) {
let content = "";
for (let [folder, data] of folderMap.entries()) {
let subcontent = data.folders.size > 0 ? renderFolderTree(data.folders, level + 1) : "";
let folderContent = data.files ? data.files : "";
if (level > 0) {
content += `### ${folder}\n<details><summary>Click to expand</summary>\n\n${subcontent}${folderContent}\n\n</details>\n`;
} else {
content += `${subcontent}${folderContent}`;
}
}
return content;
}
dv.header(6, renderFolderTree(folderTree));
SEARCH FOR AND DISPLAY LIST OF CALLOUTS (WITH LINKS TO NOTES) IN A SPECIFIED FOLDER WITH A SPECIFIED TAG:
I scrolled through a fair amount of this thread looking for a way to list ‘todo’ callouts with a link, but only in a specific folder with a specified tag. I could not find it, so I am posting this for posterity’s sake. Credit to this question thread which allowed me to form a base to build this.
```dataviewjs
// Replace TAG with whatever tag you wish.
const pages = dv.pages('#TAG')
//Use the regular expression to define what kind of Callout you want to check
//Replace !todo with whatever callout type you wish.
const regex = />\s\[\!todo\]\s(.+?)((\n>\s.*?)*)\n/
//check for more types of callouts by adding new const regex# where # is a number
const regex2 = />\s\[\!TODO\]\s(.+?)((\n>\s.*?)*)\n/
const rows = []
for (const page of pages) {
const file = app.vault.getAbstractFileByPath(page.file.path);
//get file path as string
var checkMe = "" + page.file.path;
// Read the file contents
const contents = await app.vault.read(file)
// Extract the summary via regex
for (const callout of contents.match(new RegExp(regex, 'sg')) || []) {
//check if the file path is in your designated file path.
//change FILEPATH to any keyword, folder name, or file path
//You can also change to exclude a file path by changing "==true" to "!=true"
if(checkMe.includes('FILEPATH') == true){
const match = callout.match(new RegExp(regex, 's'))
rows.push([match[1], page.file.link])
}
}
//duplicate the for loop for each const regex# you created
//adjust regex to the appropriate name
for (const callout of contents.match(new RegExp(regex2, 'sg')) || []) {
if(checkMe.includes('FILEPATH') == true){
const match = callout.match(new RegExp(regex2, 's'))
rows.push([match[1], page.file.link])
}
}
}
dv.table(['Term', 'Link'], rows)
```
I am still playing with this, and if I update it, I will edit this post or reply to it.
Hope this helps someone.
SEARCH FOR AND DISPLAY LIST OF CALLOUTS (WITH LINKS TO NOTES) IN A SPECIFIED FOLDER WITH A SPECIFIED TAG (UPDATE):
I went and tried to edit the post I made to include the regex Array, but I was unable to do so.
```dataviewjs
// Replace TAG with whatever tag you wish.
const pages = dv.pages('#TAG')
//Use the regular expression to define what kind of Callout you want to check
//Replace !todo with whatever callout type you wish.
//check for more types of callouts by adding new entries in the array.
//Remember: every key in the Array has to have a comma at the end, except for the last one
const regex = {
0: />\s\[\!todo\]\s(.+?)((\n>\s.*?)*)\n/,
1: />\s\[\!TODO\]\s(.+?)((\n>\s.*?)*)\n/
}
const rows = []
for (const page of pages) {
const file = app.vault.getAbstractFileByPath(page.file.path);
//get file path as string
var checkMe = "" + page.file.path;
// Read the file contents
const contents = await app.vault.read(file)
//cycle through the callout type array.
//Be sure to change the 'i<2' to be one more than your highest array key.
for(var i = 0; i < 2; i++){
// Extract the summary via regex
for (const callout of contents.match(new RegExp(regex[i], 'sg')) || []) {
//check if the file path is in your designated file path.
//change FILEPATH to any keyword, folder name, or file path
//You can also change to exclude a file path by changing "==true" to "!=true"
if(checkMe.includes('FILEPATH') == true){
const match = callout.match(new RegExp(regex[i], 's'))
rows.push([match[1], page.file.link])
}
}
}
}
dv.table(['Term', 'Link'], rows);
```
Not the OP, but the following works in a local diagnostic (see screenshot below).
Person File:
---
birthday: 2021-08-17
tags: family
---
Birthdays File
---
searchterm: '#family'
duration: 1 year
---
```dataviewjs
var start = moment().startOf('day');
var end = moment(start).add(dv.current().duration);
var dateformat = "YYYY-MM-DD";
if (dv.current().dateformat) { dateformat = dv.current().dateformat; }
// info text above table, {0}=duration, {1}=start date, {2}=end date
// parameters can be left out, or the string empty
var infotext = "Upcoming birthdays for {0} from now ({1} – {2})<br><br>";
//=============
function nextBirthday(birthday) {
// Get person’s next birthday on or after "start"
// returns a moment
// need to "unparse" because DV has already converted YAML birthday to DateTime object
// shouldn’t harm if already a string
var bday = moment(birthday.toString());
var bdayNext = moment(bday).year(start.year());
if (bdayNext.isBefore(start, 'day')) {
bdayNext.add(1, "year");
}
return bdayNext;
}
function turns(birthday) {
// Get the age in years a person will turn to on their next birthday
// need to "unparse" because DV has already converted YAML birthday to DateTime object
// shouldn’t harm if already a string
var bday = moment(birthday.toString());
return nextBirthday(birthday).diff(bday, 'years');
}
function showBirthday(birthday) {
// Determine if this birthday is in the range to be shown
// including the start date, excluding the end date
// because that comes from a duration calculation
// for use with "where", returns true or false
if (birthday) {
// need to "unparse" because DV has already converted YAML birthday to DateTime object
// shouldn’t harm if already a string
var bday = moment(birthday.toString());
var bdayNext = nextBirthday(birthday);
if (bdayNext.isBetween(start, end, 'day', '[)')) {
return true;
} else {
return false;
}
} else {
return false;
}
}
function sortByNextBirthday(a, b) {
// comparator function for "sort"
if (nextBirthday(a).isBefore(nextBirthday(b))) {
return -1;
}
if (nextBirthday(a).isAfter(nextBirthday(b))) {
return 1;
}
// they’re equal
return 0;
}
//======================================================================
dv.paragraph(infotext.format(moment.duration(dv.current().duration.toString()).humanize(), start.format(dateformat), end.format(dateformat)));
dv.table(
["Name", "Birthday", "Turns"],
dv.pages(dv.current().searchterm)
// use a function to see if this birthday is in range to be shown
.where(p => showBirthday(p.birthday))
// use a comparator function to sort by next birthday
.sort(p => p.birthday, 'asc', sortByNextBirthday)
.map(p => [
p.file.link,
p.birthday ? nextBirthday(p.birthday).format(dateformat) : '–',
turns(p.birthday)
])
);
```