DataviewJS Snippet Showcase

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:

3 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

Outstanding, thanks for sharing this! Works a treat for me.

2 Likes

@Moonbase59 : that’s really helpful. One question: I’d like to format the dates with the German format “DD.MM.YYYY”. How can that be done? In DataviewJS Snippet Showcase - #6 by Moonbase59, you describe a way via frontmatter, but that seems to work only for Javascript. I searched through this forum, but didn’t find another post about that. Thanks!

You could use (for above example):

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

In this case, use the Luxon format tokens, not the normal Moment ones! (Dataview uses Luxon instead of Moment.)

Looks like this:

(Darn! Now you know I don’t always adhere to my own rules! :grin:)


N.B.: If you have many date fields that might change, the date format can also be put into the YAML frontmatter:

---
dateformat: "yyyy-MM-dd HH:mm" # Luxon format
---

and then used in the ```dataview codeblock like so:


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

Wow, thank you very much! That’s what I want to get.

But do I have to set a reference to Luxon, or something like that? When I use your code, I get an error message:

Dataview: Every row during final data extraction failed with an error; first 96:

- Unrecognized function name 'dateformat'
- Unrecognized function name 'dateformat'
- Unrecognized function name 'dateformat'

That’s beautifully formatted and I will use that for my own inbox! Thank you. However the problem remains, as you say… "The only thing that’s not completely implemented in Dataview is the “all file types”.

I use my Obsidian vault as the vault for all of my projects, writing, etc. This includes documents created in Excel, Word, CSV files, PDFs, PNG, videos, the panoply of my work-as-it-happens. While I can drag and drop those files onto a note to create a link, I’m holding out hope that there’s a way to generate a TOC that includes all file types.

@bobkitz As far as I know, the “all file types” is on @blacksmithgu’s roadmap for Dataview. I also have a lot of other files I want to display in Dataview, like GeoJSON and GPX, for instance. And images.

1 Like

@a_wue Possible you haven’t updated Dataview for a while? The dateformat function is rather new. (Luxon comes with Dataview, no worries.)

(Community plugins don’t auto-update. You have to manually “Check for updates” in Settings, and perform the update by clicking “Update all” or “Update” on selected plugins.)

That did it. Thank you!

1 Like

Have you toughs about integrating standard DATAVIEW syntax for creation of the tables, and JS only for making variable that will be used in the table?

What I’m struggling now is that I have DATAVIEW queries, but I want to add just one field that needs to be calculated by Javascript.


Also is there any way we can add javascript into file like is the Obsidian.css, and just call it? I really don’t like to have bunch of script in dataview script.

Ideally, even compatible/reachable using Templater. That’d just be the thing. But no cigar, yet.

From a standpoint of code-reuse, I’d love to be able to write a few functions and call them up in both Templater and Dataview … One can dream, right?