DataviewJS Snippet Showcase

what’s your problem with it?

```dataviewjs
const table = document.createElement("table");
const rows = ["foo", "bar", "asdf", "bla"].map(text => {
	const tr = document.createElement("tr");
	tr.textContent = text;
	return tr;
});
table.append(...rows);
dv.container.append(table);
```

this appears to be working just fine.

Same error here for other snippets that I’m using. Usually it happens for a short time and then disappears…

Putting the code from @gilmar and @Moonbase59 (thank you both!) together for easier future reference:

/*
    previous/next note by date for Daily Notes
    Also works for other files having a `date:` YAML entry.
    MCH 2021-06-14
    https://forum.obsidian.md/t/dataviewjs-snippet-showcase/17847/21
*/
var none = '(none)';
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");
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]);
3 Likes

Thanks moonbase and gilmar!
I had to make some changes to get the code to work with my daily note template that’s based on dannb’s daily note template.
I changed the search to be based on tags, and filtered out the template so there aren’t links to it.
I also changed the code to get dates based on the YAML created tag that I am using.
Finally I made the variable names more descriptive.

/*
    previous/next note by date for Daily Notes
    Also works for other files having a `date:` YAML entry.
    MCH 2021-06-14
    https://forum.obsidian.md/t/dataviewjs-snippet-showcase/17847/21
*/
var noNotePlaceholder = '(none)'; 

var notes = dv.pages("#daily").where(note => note.created).map(note => [note.file.name, note.created]).sort(note => note[1]).filter(note => note[1] != "<% tp.file.creation_date() %>");

var currentNoteDate = dv.current().created ? dv.current().created : dv.date("now").toFormat("yyyy-MM-dd");
var dateFormat = 'YYYY-MM-DD';
var formattedCurrentDate = '(' + moment(currentNoteDate).format(dateFormat) + ')';

var navigationLinks = [];
var currentNote = notes.find(notes => notes[1] == currentNoteDate);

var nextNote = notes.find(notes => notes[1] > currentNoteDate);

var previousNote = undefined;
notes.forEach(function (notes) {
    if (notes[1] < currentNoteDate) {
        previousNote = notes;
    }
});

navigationLinks.push(previousNote ? '[[' + previousNote[0] + ']]' : noNotePlaceholder);
navigationLinks.push(currentNote ? currentNote[0] : formattedCurrentDate);
navigationLinks.push(nextNote ? '[[' + nextNote[0] + ']]' : noNotePlaceholder);

dv.paragraph(navigationLinks[0] + ' ← ' + navigationLinks[1] + ' → ' + navigationLinks[2]);
1 Like

Hi Moonbase,
Just wanted to see if this was still your most current iteration of this project or if you had made improvements not mentioned here. I love this and am working to adopt my own adaption of it in my notes for my travels.

Thank you so much for sharing!

I spent some time earlier this week tweaking the location-based JS that @Moonbase59 created. I was having numerous issues with formatting of locations in notes, probably due to ongoing enhancements by the Obsidian team on how front-matter properties are managed.

The core issue for me was storing locations in notes as a tuple / list of lat/lng pairs. To make things easier I moved to storing latitude and longitude as their own properties. This has a side effect of making this Dataview work better with the Geocoding properties plugin.

I removed the “driving distance” function because it was just a scale of the crow-flies distance and didn’t seem all that useful to me. I also refactored the filtering / querying so that it only needs to call the getDistance function once per note.

// Nearby Family Members, Friends and Places
// 2021-05-15 - Matthias C. Hormann (Moonbase59)
// 2025-01-02 - Chris Brooks updates

// set parameters (to be supplied via YAML frontmatter, eventually)
// DV 0.3.3 interprets "500000 m" as a Luxon duration,
// so we have to put nearby: '"500000 m"' and remove the ".
let nearby = dv.current().nearby.replace(/['"]+/g, '');
let unit = nearby.split(' ')[1];
let radius = nearby.split(' ')[0];
let origin = [dv.current().lat, dv.current().lng];

// search term as used in dv.pages()
// use "#tags" or '"folder/subfolder"' for a folder
let searchterm = dv.current().searchterm;

//======================================================================

function getDistance(origin, destination, unit='m') {
    // return distance in selected unit (m,km,yd,mi)
    var factor = 1.0;
    switch (unit) {
      case 'm':
        factor = 1.0;
        break;
      case 'km':
        factor = 1000.0;
        break;
      case 'yd':
        factor = 0.9144;
        break;
      case 'mi':
        factor = 1609.344;
        break;
      default:
        factor = 1.0;
        console.warn("getDistance: Invalid unit '%s', using 'm'. Valid units are: m,km,yd,mi.",unit);
    }

    var lon1 = toRadian(origin[1]),
        lat1 = toRadian(origin[0]),
        lon2 = toRadian(destination[1]),
        lat2 = toRadian(destination[0]);
    var deltaLat = lat2 - lat1;
    var deltaLon = lon2 - lon1;

    var a = Math.pow(Math.sin(deltaLat/2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(deltaLon/2), 2);
    var c = 2 * Math.asin(Math.sqrt(a));
    var EARTH_RADIUS = 6371000; // 6,371 km in metres
    return c * EARTH_RADIUS / factor;
}

function toRadian(degree) {
    return degree*Math.PI/180;
}

//======================================================================
// Show what we will display.
dv.paragraph("List shows nearby places (within " + radius + " " + unit + " driving distance).");
dv.paragraph("<br><br>");

// get the pages
let pages = dv.pages(searchterm)
  .filter(p => {
    if (!p.lat || !p.lng) return false;
    p.distance = getDistance(origin, [p.lat, p.lng], unit);
    return p.distance > 0.0 && p.distance <= radius;
  }).sort(p => p.distance);

// create table
dv.table(["Name", "Tags", "Last Played", "Distance"],
  pages.map(p => [
    // The name
    p.file.link,
    // tags (show '–' if none defined)
    (p.file.etags ? p.file.etags.join(' ') : '–'),
    p.played,
    // straight-line distance
    /*
    getDistance(origin, p.location, unit)
      .toLocaleString(moment.locale(), {maximumFractionDigits: 1}) + " " + unit,
    */
    // predicted driving distance and Google Maps Route Planner Link
    (p.distance
      .toLocaleString(moment.locale(), {maximumFractionDigits: 1}) + " " + unit)
      .link('https://www.google.com/maps/dir/?api=1&dir_action=navigate&destination=' + p.lat + ',' + p.lng),
  ])
);

1 Like

Oh wow! Thank you for sharing!
Regarding “driving distance” I believe @Moonbase59 was using statistical data on the average driving distance in relation to distance as the crow flies. I imagine this can vary greately for a country like the US.

Thank you. This is going to be lovely to get working in my notes :slight_smile:

The driving distance calc didn’t help me much because I live near water most of the year and many locations that are 5 miles away might be 30 minutes of driving. If I eventually find a good web API for calculating that I’ll add it back in.

1 Like

URLs list:
Make a list of all the URLs in the Vault as:
Note name
Header
URL

let searchTerm = /(https?:\/\/)/i;  // Could be any word or REGexp like "http" or "cool"
dv.paragraph("# Lines containing 'http' or 'https:");
dv.pages().file.forEach(file => {
    let filePath = file.path;
    let note = app.vault.getAbstractFileByPath(filePath);

    // Read the content of the note
    app.vault.cachedRead(note).then(content => {
        let lines = content.split("\n");
        let currentHeader = null;
        let output = [];
        
        // Process lines
        lines.forEach(line => {
            line = line.trim(); // Trim whitespace
            if (line.startsWith("#")) {
                //currentHeader = line; // Update current header
                currentHeader = line.replace(/^#+\s*/, '');
                }
            } else if (searchTerm.test(line)) { // Use .test() to check for matches
                if (!output.length) {
                    output.push(`**${file.name}**`); // Add note name only once
                }
                if (currentHeader) {
                    output.push(`\t<span style="color: #FFA347;">${currentHeader}</span>`); // Add header
                    currentHeader = null; // Avoid duplicate headers
                }
                output.push(`\t\t${line}`); // Add line with link
            }
        });

        // Display the output if links were found
        if (output.length > 0) {
            dv.paragraph(output.join("\n"));
        }
    });
})
3 Likes

I mark things which I want to study by [[Study]] for the Note or by Task - [?] for something inside the Note.
To get a table of all material related to Study I use the following script:

const currentNotePath = dv.current().file.path;
const currentNoteName = dv.current().file.name;


let tasksByNote = {};
let notesReferencingCurrent = [];
let tableData = [];


const notes = dv.pages()
    .where(p => p.file.outlinks.some(link => link.path === currentNotePath))
    .map(p => [p.file.name, p.file.link, p.file.ctime.toFormat("yyyy-MM-dd")]);


dv.pages()
    .where(p => p.file.tasks) // Фильтруем только файлы с задачами
    .forEach(p => {
        if (p.file.tasks) {
            p.file.tasks
                .filter(t => t.status === "?") // Только задачи со статусом "?"
                .forEach(t => {
                    if (!tasksByNote[p.file.name]) {
                        tasksByNote[p.file.name] = [];
                    }
                    tasksByNote[p.file.name].push({
                        text: t.text,
                        created: t.created ? t.created.toFormat("yyyy-MM-dd") : p.file.ctime.toFormat("yyyy-MM-dd") // Дата создания задачи или заметки
                    });
                });
        }
    });


let allNotes = new Set();

notes.forEach(note => {
    allNotes.add(note[0]);
});


for (let note in tasksByNote) {
    allNotes.add(note);
}


let sortedNotes = Array.from(allNotes).sort();


sortedNotes.forEach(note => {
    // Добавляем заметку
    tableData.push([`[[${note}]]`, "", tasksByNote[note] ? tasksByNote[note][0].created : notes.find(n => n[0] === note)?.[2] || ""]);

    // Если у заметки есть задачи, добавляем их
    if (tasksByNote[note]) {
        tasksByNote[note].forEach((task, index) => {
            if (index > 0) { // Первая задача уже добавлена вместе с заметкой
                tableData.push(["", `- [?] ${task.text}`, task.created]);
            } else {
                tableData[tableData.length - 1][1] = `- [?] ${task.text}`; // Обновляем задачу в строке заметки
            }
        });
    }
});


dv.table(
    ["Note", "Task", "Date"],
    tableData
);
2 Likes

Hierarchy list of links to Note
We have Note.md we search for all the notes referring to Note, then we search for notes referring to notes which referring to Note etc as per settings (default 6)
Especially useful for beginners to check the structure of the vault

const target = "ToDo"; // Start Note
const pages = dv.pages();
const maxDepth = 5; // Tree max depth

// Функция для нормализации пути (убираем расширение .md и делаем регистронезависимым)
function normalizePath(path) {
    return path.replace(/\.md$/, "").toLowerCase(); // Убираем .md и приводим к нижнему регистру
}
// Находим целевую заметку по её имени (без учета пути)
const targetFile = pages.find(page => normalizePath(page.file.name) === normalizePath(target));
if (!targetFile) {
    dv.paragraph(`Заметка "${target}" не найдена.`);
    throw new Error(`Заметка "${target}" не найдена.`);
}
// Полный путь к целевой заметке
const targetPath = normalizePath(targetFile.file.path);
// Функция для рекурсивного построения дерева с ограничением глубины
function buildTree(note, level = 0, visited = new Set(), depth = 0) {
    if (visited.has(note) || depth > maxDepth) return ""; // Избегаем зацикливания и ограничиваем глубину
    visited.add(note);
    let result = "  ".repeat(level) + "- [[" + note.split("/").pop() + "]]\n"; // Выводим только имя файла
    // Находим все заметки, которые ссылаются на текущую
    const backlinks = pages.filter(p =>
        p.file.outlinks.some(link => normalizePath(link.path) === note)
    );
    // Рекурсивно обрабатываем каждую связь, увеличивая глубину
    backlinks.forEach(link => {
        result += buildTree(normalizePath(link.file.path), level + 1, visited, depth + 1);
    });
    return result;
}
// Строим дерево, начиная с целевой заметки
let result = buildTree(targetPath, 0, new Set(), 0);
// Если результат пустой, выводим сообщение
if (!result.trim()) {
    dv.paragraph(`Нет заметок, ссылающихся на "${target}".`);
} else {
    dv.paragraph(result);
}

1 Like

Here’s my take on an outline from headings across multiple files… If pages have “intro” content in them, that will be displayed before the outline for each file. The outline itself is rebuilt as a multi-level list with some extra line breaks to aid with visual separation.

for (let page of dv.pages('"Course/The Gospel"').sort(p => p.file.name, 'asc')) {

    // display the page name as main header
    dv.header(1, page.file.name);

    const content = await dv.io.load(page.file.path);
    const lines = content.split(/\r?\n/);

    // each page contains an "intro" section and some headings
    var intro = [];
    var outline = [];

    for (let line of lines) {
        const match = line.match(/^(#+)\s+(.*)/);

        // this line contains a heading; add to the outline
        if (match) {
            const head = match[1];
            const text = match[2];
            const indent = "  ".repeat(head.length - 1);
            const link = dv.sectionLink(page.file.path, text, false, text)
            outline.push(indent + '- ' + link + '<br><br>');

		// if there is no outline, we are still in the "intro" section
        } else if (outline.length == 0) {
            intro.push(line);
	    }
    }

    dv.paragraph(intro.join('\n'));
    dv.paragraph('---')
    dv.paragraph(outline.join('\n'));
}
1 Like

Hi. Thank you for the code. It kind of worked for me. I´m trying to generate a quotes DB. As I said, the query is working but the output is a little bit off. Is it possible to remove the vertical line? Thank you.

My Dashboard - Homepage. Its a section for favorite notes:
Mark Note with [[Dashboard]] for favorites and [[DashboardFAV]] for Most important Notes.
Script will put Important notes to the first column and then all marked Notes one by one approx equally column by column. You can choose column quantity and Mode - each note link in cell or All note links in Column. Just try.
You can change Callout at the end of the code as per MD

// Флаг для выбора режима вывода
const useSingleCell = true; // true для режима <br>, false для режима "по ячейкам"
const columns = 6; // Количество колонок для Dashboard (всего колонок будет columns + 1, первая — для FAV)

// Получаем все заметки, которые ссылаются на [[DashboardFAV]]
const favPages = dv.pages('[[DashboardFAV]]')
    .sort(p => p.file.name) // Сортируем по алфавиту
    .map(p => `[[${p.file.name}]]`); // Преобразуем в массив ссылок

// Получаем все заметки, которые ссылаются на [[Dashboard]]
const dashPages = dv.pages('[[Dashboard]]')
    .sort(p => p.file.name) // Сортируем по алфавиту
    .map(p => `[[${p.file.name}]]`); // Преобразуем в массив ссылок

// Вычисляем количество строк
const rows = Math.ceil(dashPages.length / columns);

// Группируем элементы из dashPages по колонкам
let columnData = Array.from({ length: columns }, () => []);
for (let i = 0; i < dashPages.length; i++) {
    const colIndex = Math.floor(i / rows); // Определяем индекс колонки
    columnData[colIndex].push(dashPages[i]);
}

// Создаем заголовки таблицы
let table = "| MAIN |"; // Начинаем с заголовка для FAV
for (let j = 0; j < columns; j++) {
    table += `  |`; // Добавляем заголовки для остальных колонок
}
table += "\n";

// Добавляем разделитель для заголовков
table += "| --- |";
for (let j = 0; j < columns; j++) {
    table += ` --- |`;
}
table += "\n";

if (useSingleCell) {
    // Режим: Все ссылки в одной ячейке через <br>
    table += `| ${favPages.join("<br>")} |`; // Все FAV в одной ячейке через <br>
    for (let col of columnData) {
        table += ` ${col.join("<br>")} |`; // Все элементы колонки в одной ячейке через <br>
    }
    table += "\n";
} else {
    // Режим: Каждая ссылка в отдельной ячейке
    for (let i = 0; i < rows; i++) {
        let row = `| ${favPages[i] || ""} |`; // Добавляем элемент из favPages или оставляем пустым
        for (let j = 0; j < columns; j++) {
            row += ` ${columnData[j][i] || ""} |`; // Добавляем элементы из соответствующей колонки
        }
        table += row + "\n"; // Добавляем строку к таблице
    }
}

// Обертываем результат в callout
const result = `> [!example] Favorites\n> ${table}`;

// Выводим результат
dv.paragraph(result);