Dataview plugin snippet showcase

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.

DataviewJS Query

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));

CSS



details {
  font: 16px "Open Sans", Calibri, sans-serif;
  width: 620px;
}

details > summary {
  padding: 2px 6px;
  background-color: #424741;
  border: none;
  box-shadow: 3px 3px 4px black;
  cursor: pointer;
  list-style: none;
}

details > p {
  border-radius: 0 0 10px 10px;
  background-color: #ddd;
  padding: 2px 6px;
  margin: 0;
  box-shadow: 3px 3px 4px black;
}

details:hover {
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
}

details summary::-webkit-details-marker {
  color: transparent;
  transform: scale(0.01);
}

details summary::before {
  content: '🡵';
  display: inline-block;
  font-size: 0.9em;
  margin-right: 8px;
  transform: translateY(-1px);
}

details[open] {
  background-color: var(--background-secondary);
}

details[open] summary {
  margin-bottom: 8px;
}

details[open] summary::before {
  transform: translateY(-1px) rotate(90deg);
}

details[open] summary:before {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  width: 0;
  height: 0;
  margin-left: -8px;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-bottom: 8px solid #ccc;
}

summary::marker {
  content: "";
}

details > * {
  padding-left: 12px;
  padding-right: 12px;
}

details summary:hover::before {
  content: "";
  position: absolute;
  z-index: 999999;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  animation: bg-move 2s linear infinite;
  background-size: 100% 8px;
  background-image: linear-gradient(0, rgba(255, 255, 255, 0.05) 10%, transparent 10%, transparent 50%, rgba(255, 255, 255, 0.05) 50%, rgba(255, 255, 255, 0.05) 60%, transparent 60%, transparent);
}

@keyframes bg-move {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: 0 -32px;
  }
}

.heading-collapse-indicator,
.cm-fold-indicator .collapse-indicator {
  opacity: 0 !important;
}

details summary::-webkit-details-marker {
  font-size: 0.001px;
}

details > summary {
  list-style: none;
}
6 Likes

There is a way for links from generated tables to be counted in the backlinks?
like auto moc by tag

How do you make these headers with lines separators?

Absolutely brilliant!!!

An account log using “Checking::” entries in my Daily Notes. The Related Docs column links to relevant images/docs inside my Vault or uses a message://<message_id> link to open the relevant email directly. I use a Shortcut on my iPhone, iPad, Mac, &c., to append a properly-formatted bullet point to the end of my current daily note.

The » character can be typed on a Mac or keyboarded iPhone/iPad with ALT+SHIFT+\ or by holding down the single-quote key ' on the touch keyboard. I use » and « for field and row separators for almost everything because it’s a character that I’m highly unlikely to use for any other purpose, not being French. :slight_smile:

Since I’m on a Mac, I have an AppleScript to copy a reformatted link to a selected email:

tell application "Mail"
	set _sel to get selection
	set _links to {}
	repeat with _msg in _sel
		set _messageURL to "message://%3c" & _msg's message id & "%3e"
		set end of _links to _messageURL
	end repeat
	set AppleScript's text item delimiters to return
	set the clipboard to ("[Email Receipt](" & _links & ")" as string)
end tell

Use field: Checking
Row Layout: “Y” if Cleared»Amount»Where»Location»Related Docs

TABLE WITHOUT ID
regexreplace(string(round(sum(number(rows.Checking)) *100)), "(..)$", ".$1") AS "Final Balance"
FROM "Daily Notes"
FLATTEN Checking
WHERE Checking
GROUP BY true
TABLE WITHOUT ID
regexreplace(string(round(sum(number(rows.Checking)) *100)), "(..)$", ".$1") AS "Cleared Balance"
FROM "Daily Notes"
FLATTEN Checking
WHERE split(Checking,"»")[0] = "Y"
GROUP BY true
TABLE
	split(Checking,"»")[0] AS C,
	dateformat(date(split(Checking,"»")[4]), "DDD") AS Date, 
	split(Checking,"»")[2] AS Where, 
	split(Checking,"»")[3] AS Location, 
	regexreplace(string(round(number(split(Checking, "»")[1]) *100)), "(..)$", ".$1") AS Amount,
	split(Checking,"»")[5] AS "Related Docs"
FROM "Daily Notes"
FLATTEN Checking
WHERE Checking
SORT split(Checking,"»")[4] DESC

2 Likes

Since this a showcase thread, I’d thought I show you a potential nicer way to setup such a query as that last one. This way you’ll get easier access to all the bits and pieces, and could continue to do WHERE clauses, or GROUP BY using the predefined version of your Checking.

One vital change in the script below also, is that it starts of by doing the WHERE Checking before it tries to FLATTEN and split it. This causes the query to skip all files not having that field before trying to do something with it. Note that you can have multiple WHERE clauses and so, so there is no problem adding one on the line before the SORT for example.

```dataview
TABLE
	check.C AS C,
	check.LocDate AS Date, 
	check.Where AS Where, 
	check.Location AS Location, 
	check.Amount AS Amount,
	check.Related AS "Related Docs"
FROM "Daily Notes"
WHERE Checking

FLATTEN list(split(Checking, "»")) as tmpC
FLATTEN object(
  "C", tmpC[0],
  "Amount", regexreplace(string(round(number(tmpC[1]) *100)), "(..)$", ".$1"),
  "Where", tmpC[2],
  "Location", tmpC[3],
  "LocDate", dateformat(date(tmpC[4]), "DDD"),
  "Date", date(tmpC[4]),
  "Related", tmpC[5] ) as check
  
SORT check.Date DESC
```

I think this way of doing a split is much better due to a few reasons:

  1. It only does the split once per file, instead of the seven splits you did
  2. It allows for easier access to the split part in other places of your query, like it’s a lot easier to do check.Amount instead of repeating that regex-thingy.
  3. Adding a few blank lines in the query, makes it clearer how the first part handles the display part, and main filtering of which files we’re working with. The second part transforms the data into something meaningful, and the third part (here just the sorting) could further refine which data to display

You can use file.outlinks, for example like this:

TABLE dateformat(file.mtime, "dd.MM.yyyy - HH:mm") AS "Backlinks"
FROM ""
WHERE contains(file.outlinks, this.file.link)
SORT file.mtime DESC
2 Likes

For those who have this issue in dataview.js, you can use the following snippet which converts to lowercase before sorting:
.sort((left, right) => left.name.toLowerCase() > right.name.toLowerCase() ? 1 : left.name.toLowerCase() < right.name.toLowerCase() ? -1 : 0)