DataviewJS Snippet Showcase

REAL Daily Notes previous/next navigation

Sometimes I have large gaps between my Daily Notes, but still like the idea of having a “previous Daily Note / next Daily Note” navigator at the top.

Now this doesn’t work without actually traversing the file tree, and what’s better for that than DataviewJS?

I’m a little hesitant of putting such an amount of code into every Daily Note, so we might have to wait for common includes. Nevertheless, here is the code for those who wish to play around with it. It’s quite robust (doesn’t break when there is no previous/today’s/next note, and also works for notes with names different from YYYY-MM-DD).

It’s also great to use in a folder with weekly or monthly notes, because it “travels” through all files in “this” folder (and its subfolders) that have either

  • a Dataview-recognizable date in their name, or
  • a date: (ISO format) field in their YAML.

If no note with today’s date can be found, it’ll display a “date hint” in parentheses in the middle, plus the previous and next notes links (based on today’s date). The “date hint” is shown in the format set in the Daily Notes plugin’s settings, or ‘YYYY-MM-DD’ if the plugin is disabled or no date format has been set.

Code

```dataviewjs
/*
    previous/next note by date for Daily Notes
    Also works for other files having a `date:` YAML entry.
    MCH 2021-06-14
*/
var none = '(none)';
var p = dv.pages('"' + dv.current().file.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() : luxon.DateTime.now().toISODate();
// Obsidian uses moment.js; Luxon’s format strings differ!
var format = app['internalPlugins']['plugins']['daily-notes']['instance']['options']['format'] || 'YYYY-MM-DD';
var current = '(' + moment(t).format(format) + ')';
var nav = [];
var today = p.find(p => p[1] == t);
var next = p.find(p => p[1] > t);
var prev = undefined;
p.forEach(function (p, i) {
    if (p[1] < t) {
        prev = p;
    }
});
nav.push(prev ? '[[' + prev[0] + ']]' : none);
//nav.push(today ? today[0] : none);
nav.push(today ? today[0] : current);
nav.push(next ? '[[' + next[0] + ']]' : none);

//dv.list(nav);
//dv.paragraph(nav.join(" · "));
dv.paragraph(nav[0] + ' ← ' + nav[1] + ' → ' + nav[2]);
```

I’ve left in some commented-out other ways for displaying. You can of course also leave out today’s note in the middle.

Suggestions for optimizing the JS without breaking functionality welcome.

Screenshots

Auswahl_015

Auswahl_016

Auswahl_017

Changelog:

  • 2016-06-14
    • initial version
  • 2016-06-14
    • If it’s unable to pick up a note name for today, will now show a hint at “where” we are in the middle, based on the day format set in the Daily Notes plugin settings.

      Auswahl_018
      Standard date format “YYYY-MM-DD”

      Auswahl_019
      Unusual date format “ww’ DD-MMM (ddd) Y” used by @den

39 Likes

Hi - I’m trying to have dataviewjs show me any open Kanban tasks that are due today or overdue. It’s beyond my javascript abilities… does anyone have a suggestion on how to change this code to check for any incomplete tasks that are undated or are <= 2021-06-14 ?

dv.taskList(dv.pages("[[2021-06-14]]").file.tasks
	.where (t => !t.completed) 
	.where (t => t.text.includes("2021-06-14")))

Here’s a code block that finds any tasks which include a date of the form YYYY-MM-DD and are overdue (i.e. have a date earlier than now). Yes my code is verbose :slight_smile:

function overdue(t) {
  let dValidate = moment(t.text, 'YYYY-MM-DD', true);
  let d = moment(t.text, 'YYYY-MM-DD');
  let containsValidDate = dValidate._pf.unusedTokens.length==0 ;
  let isOverdue = d.diff(moment()) <= 0;
  return (containsValidDate && isOverdue);
}

dv.taskList(dv.pages("").file.tasks
	.where (t => overdue(t))
	.where (t => !t.completed))
2 Likes

Hi! Im trying to have a daview table in my meeting note with the last 3 meetings from the date of the meeting I’m taking notes…can anybody help me please…Thanks in advance

1 Like

HI! really nice! im having hard times figuring out what to chance in here for tasks unscheduled and coming in the future…can you advice? Thanks!!!

Here you go. If you just want upcoming, then use the commented-out return statement instead

function filter(t) {
  let dValidate = moment(t.text, 'YYYY-MM-DD', true);
  let d = moment(t.text, 'YYYY-MM-DD');
  let unscheduled = !dValidate._pf.unusedTokens.length==0 ;
  let upcoming = (!unscheduled) && moment().diff(d) < 0;
  return (unscheduled || upcoming)
  // return ( upcoming );
}

dv.taskList(dv.pages("").file.tasks
	.where (t => filter(t))
	.where (t => !t.completed))
1 Like

Hey, it may just be that I don’t have access to the repo but that link is giving me a 404. Any chance you have a better one?

1 Like

Perfect! thanks so much!!!

Sorry. I moved some things around. Here is a new link: dataviewjs.wordCount · GitHub. Additionally, I made some large updates to the snippet so check out this new one: GitHub - torantine/obsidian-snippets

Hey all,

Looking for some help here. I currently have three types of notes:

  1. Subject
  2. Topic
  3. Excerpt

Each excerpt has a dataview field called Topic that contains a link to a Topic note.

Each Topic note has a dataview field called Subject that contains a link to a Subject note.

What I want is to have two queries on each Subject note. Query 1 would be to show all Topic notes that are linked to the Subject. Query 2 would be all Excerpt notes that are linked to any of the Topic notes from Query 1.

Anyone have any idea how I can accomplish this? Thanks!

First question:
Where/how you create that links? Inline Fields or yaml frontmatter field?
If in YAML frontmatter, then these links works only as a value in one specific field; if in inline field, then these links working in two ways: associated to the field (as yaml) or as an “Implicit Field”, i.e., as a metadata that Obsidian capture as “Outlinks” or “Backlinks” (depends of the direction) and listed in right pane.
Depending on this, the queries can be build in different ways.

Second question
Your link structure seems to work in this (inverted) way:
SUBJECT ← TOPIC ← EXCERPT
Not in top-bottom way:
SUBJECT → TOPIC → EXCERPT

Thanks so much for responding!

The fields will be created to allow Dataview to be utilized. For example, I may make a note capturing a quote I read, which would look like the following:

----

source: [[ForSelfExamination]]
author: [[SorenKierkegaard]]
primaryTopic: [[faithAndWorks]]

----

 If it is to be works...fine, but then I must also ask for the legitimate yield I have coming from my works, so that they are meritorious. If it is to be grace--fine, but then I must also ask to be free from works-otherwise it surely is not grace. If it is to be works and nevertheless grace, that is indeed foolishness." Yes, that is indeed foolishness; that would also be true [[Lutheranism]]; that would indeed be Christianity. Christianity's requirement is this: your life should express works as strenuously as possible; then one thing more is required- that you humble yourself and confess: But my being saved is nevertheless grace. The error of the [[MiddleAges]], meritoriousness, was abhorred..`

I have the primaryTopic field to designate the main topic of the note. I then will use inline links in the actual text to capture supporting topics. As displayed above, faithAndWorks is the primary topic of this note, but Lutheranism is a supporting topic mentioned as well.

So, I then want to be able to go to the faithAndWorks note and create a query that lists all the notes that have faithAndWorks in the primaryTopic field. This is easy enough, and I know how to do this.

Here is where I’m stuck at: On each primaryTopic note, I have a field called Subject that designates what the primaryTopic falls under. For example, the faithAndWorks note would have Christianity as its Subject.

When I go to the Christianity note, I want to be able to create a query (or queries) that lists all the notes that have a primaryTopic field populated with a primaryTopic falling under Christianity. So if I went to the Christianity note, it would list the above example note, since its primaryTopic (faithAndWorks) has Christianity in its Subject field.

Let me know if I’ve thoroughly confused you or not and I will try again!

Well, I’m not an expert in Dataview (and a ‘zero’ in DataviewJS).
Maybe an expert in dataviewjs can help you in better way than me.
I understand your ‘logic’ and make all sense. The solution would be more obvious (for me) if your system was build in reversed way: from Subject → Topics → Notes.
But I accepted your ‘challenge’ and forced me to think in reverse way (as your links system). After some gray matter consumed and some white smoke, I found one possibility:

## Christianity

```dataview
TABLE primaryTopic as Topics, sort(rows.file.link) as Notes
WHERE primaryTopic.Subject = [[Christianity]]
GROUP BY primaryTopic
SORT primaryTopic ASC
```
  • this is the query to create in Christianity note
  • primaryTopic is the field you defined in your notes;
  • Subject is the field you defined in Topic files;
  • Topics is the name for the topics column (you can choose what you want)
  • Notes is the name for the notes column (you can choose what you want)

I’m not sure about this but you can try.

Would you mind elaborating on the way that is more obvious to you (Subject—>Topics—>Notes)? Interested to get your thoughts on it, as your way may indeed make more sense and if so, I can then implement it in my vault :slight_smile:

Well, I think maybe I expressed myself in a not very clear way (a problem for a portoghese trying to write in english). I said “the (dataview) solution would be more obvious” for me, not your system. Because I’m unable to play with dataviewjs, my restriction to simple queries implies a way of thinking with some limitations.
For dataview is more easy to call values in cascade (top-down)

Taking your example, it’s more easy to call a value in “Subject” file, then the others comes in sequence. From one file to multiple ones: Subject → Topics → Notes

But your system take things more complicated to the query because it works in reverse way: Notes → Topics → Subject. (It’s easy to visualize the ‘problem’: in the query proposed in my last reply, the table follow the intended final order - subject / topics / notes - which is contrary to the links system implemented). With your links system it’s necessary to call in first place all the notes… but we don’t want all the notes. Then it’s necessary to filter these notes with a value in a field (“Subject”) that is not placed in these notes but in the next level (in the topics files).

In conclusion: your system is fine but the tool (or me) has some limitations! :slight_smile:

4 Likes

Obsidian Command and Hotkey List (new version)

In continuation of the older post, here is a new version of the Obsidian Command List.

Features:

  • Commands sorted by assigned hotkey
  • Commands sorted by internal Command ID
  • Command names shown in the language Obsidian is currently set to—ideal for multi-lingual people who switch often, and for users of the Buttons, Obsidian Leaflet, Command URI and Advanced Obsidian URI plugins.
  • Now shows the current hotkey settings, not just the defaults.
  • Modified hotkeys (different from the default setting) are now shown bolded.

Here’s what it looks like:

Complete note showing both versions:

Just copy-paste into a new note and try it out:

---
date: 2021-06-26
author: Matthias C. Hormann a.k.a. Moonbase59
tags: [obsidian, commands, hotkeys]
---

# Obsidian Command List

This shows the currently active commands and defined hotkeys. Command _names_ are localized to whatever language you’ve set Obsidian to use (the same as used in the Command Palette).

Check the code to see how I’ve done this using _Dataview JS_.

### Commands sorted by assigned hotkey

```dataviewjs

const getNestedObject = (nestedObj, pathArr) => {
    return pathArr.reduce((obj, key) =>
        (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}

function hilite(keys, how) {
    // need to check if existing key combo is overridden by undefining it
    if (keys && keys[1][0] !== undefined) {
        return how + keys.flat(2).join('+').replace('Mod', 'Ctrl') + how;
    } else {
        return how + '–' + how;
    }
}

function getHotkey(arr, highlight=true) {
    let hi = highlight ? '**' : '';
    let defkeys = arr.hotkeys ? [[getNestedObject(arr.hotkeys, [0, 'modifiers'])],
    [getNestedObject(arr.hotkeys, [0, 'key'])]] : undefined;
    let ck = app.hotkeyManager.customKeys[arr.id];
    var hotkeys = ck ? [[getNestedObject(ck, [0, 'modifiers'])], [getNestedObject(ck, [0, 'key'])]] : undefined;
    return hotkeys ? hilite(hotkeys, hi) : hilite(defkeys, '');
}

let cmds = dv.array(Object.entries(app.commands.commands))
    .where(v => getHotkey(v[1]) != '–')
    .sort(v => v[1].id, 'asc')
    .sort(v => getHotkey(v[1], false), 'asc');

dv.paragraph(cmds.length + " commands with assigned hotkeys; " +
    "non-default hotkeys <strong>bolded</strong>.<br><br>");

dv.table(["Command ID", "Name in current locale", "Hotkeys"],
  cmds.map(v => [
    v[1].id,
    v[1].name,
    getHotkey(v[1]),
    ])
  );
```

### Commands sorted by Command ID

```dataviewjs

const getNestedObject = (nestedObj, pathArr) => {
    return pathArr.reduce((obj, key) =>
        (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}

function hilite(keys, how) {
    // need to check if existing key combo is overridden by undefining it
    if (keys && keys[1][0] !== undefined) {
        return how + keys.flat(2).join('+').replace('Mod', 'Ctrl') + how;
    } else {
        return how + '–' + how;
    }
}

function getHotkey(arr, highlight=true) {
    let hi = highlight ? '**' : '';
    let defkeys = arr.hotkeys ? [[getNestedObject(arr.hotkeys, [0, 'modifiers'])],
    [getNestedObject(arr.hotkeys, [0, 'key'])]] : undefined;
    let ck = app.hotkeyManager.customKeys[arr.id];
    var hotkeys = ck ? [[getNestedObject(ck, [0, 'modifiers'])], [getNestedObject(ck, [0, 'key'])]] : undefined;
    return hotkeys ? hilite(hotkeys, hi) : hilite(defkeys, '');
}

let cmds = dv.array(Object.entries(app.commands.commands))
    .sort(v => v[1].id, 'asc');

dv.paragraph(cmds.length + " commands currently enabled; " +
    "non-default hotkeys <strong>bolded</strong>.<br><br>");

dv.table(["Command ID", "Name in current locale", "Hotkeys"],
  cmds.map(v => [
    v[1].id,
    v[1].name,
    getHotkey(v[1]),
    ])
  );
```

Enjoy.

56 Likes

Works perfectly on Mac OS High Sierra so should be good for all macs I reckon

1 Like

Previous/Next Daily Note

I would suggest similiar approach for those who want to add some custom styles to their links:

<div class="breadcrumbs-wrapper">
	<a class="internal-link prev-daily elegant-btn"></a>
	<a class="internal-link next-daily elegant-btn"></a>
</div>



```dataviewjs
const defaultFormat = 'YYYY-MM-DD';
const format = app['internalPlugins']['plugins']['daily-notes']['instance']['options']['format'] || defaultFormat;

const dailyNotesRegexp = new RegExp(format.replace(/\w/g, '\\d')) || defaultDailyPageRegexp; 

function isDailyNote(page) {
	const path = page.file.path;
	if (!path.startsWith(dv.current().file.folder)) return false;
	
	const filename = page.file.name;
	return filename.match(dailyNotesRegexp)
}


//filter dailyPages and sort it
const dailyPagesSorted = dv.pages()
	.filter(isDailyNote)
		.sort(page => page.file.name)


const curPageInd = dailyPagesSorted
	.findIndex((page, ind) => (page.file.path === this.currentFilePath));

	

const prevPage = curPageInd === 0 ? null : dailyPagesSorted[curPageInd - 1].file;


const nextPage = curPageInd === (dailyPagesSorted.length - 1) ? null : dailyPagesSorted[curPageInd + 1].file;
	
const options = [
	{
		selector: 'a.prev-daily',
		path: prevPage?.path,
		text: prevPage !== null ? `< ${prevPage.name}` : ''
	},
	{
		selector: 'a.next-daily',
		path: nextPage?.path,
		text: nextPage !== null ? `${nextPage.name} >` : ''
	},
];

//use interval to catch moment when links are loaded, btw document.onload not working here
const checkInterval = setInterval(() => {
	options.forEach(({selector, path, text}) => {
		const aEl = document.querySelector(selector);
		
		if (!path || !aEl) return;
		
		//once created links, clear interval
		clearInterval(checkInterval)

		aEl.innerText = text;
		aEl.href = path;
		aEl.classList.add('ready');
		//sometimes Obsidian marks link as unresolved for some reason
		aEl.classList.remove('is-unresolved');
	})
}, 100)


const styles = `
.breadcrumbs-wrapper {
	display: flex; 
	justify-content: space-between;
	margin-top: 50px;
}

.ready.elegant-btn {
	color: black;
	text-decoration: none;
	font-size: 14px;
	padding: 4px 22px;
	border: 2px solid black;
	display: flex;
	justify-content: center;
	background-color: transparent;
	box-shadow: 11px 11px 0px 0px black;
	transition: all 0.2s ease;
}

.ready.elegant-btn:hover {
	box-shadow: 0px 0px 0px 0px black;
}
`

var styleSheet = document.createElement("style")
styleSheet.type = "text/css"
styleSheet.innerText = styles
document.head.appendChild(styleSheet)

``` 

that would result something like this:

or like this:

If you wish, you can customize buttons appearence and behaviour on hover just as you can only imagine with custom css&html. You can find some ideas on customization here: https://codepen.io/giana/pen/BZaGyP

I’d ike to mention one advantage of using html links vs dataview-rendered links: you can segregate JS code and HTML template, so that on the top of your page would be a small HTML boilerplate, while the bulky JS boilerplate would be in the bottom of your page.

Any suggestions on js optimizations are welcome.

Side note: Obsidian doesn’t render css pseudo-elements and svg elements.

7 Likes

So - maybe it’s the margaritas… but I’m not sure where to start. I just want to have a code block that returns a list of all files (not just .md files!) that are in the same directory as the current note (i.e. the note that contains the code block). I suspect I’ll need to use the Obsidian API but it seems impenetrable. Extra credit is a code block that traverses subdirectories. Any pointers appreciated!

Something like my “-Inbox TOC” maybe?

# Inbox TOC

This is the Table of Contents of your Inbox.
**The Inbox should be empty at the end of a workday!**

The list is sorted by last modified time, newest at top.

<!--
The following might slow down performance, since it checks _all_ files in the vault.
Then again, it’ll always work for files in this folder, regardless of location and name.
-->

```dataview
table file.ctime as Created, file.mtime as "Last modified"
where file.name != this.file.name and contains(file.path, this.file.folder)
sort file.mtime descending
```

The only thing that’s not completely implemented in Dataview is the “all file types”.

4 Likes