shows all unresolved links (links to non existing pages), optionally with the files containing them
Basic query contributed via Discord and Discord
List non existing links¶
Basic¶
As DQL:
TABLE WITHOUT ID key AS "unresolved link", rows.file.link AS "referencing file"
FROM "10 Example Data"
FLATTEN file.outlinks as outlinks
WHERE !(outlinks.file) AND !(contains(meta(outlinks).path, "/"))
GROUP BY outlinks
As dataviewjs: (for a more similar result like the DQL solution, see variant "List the files the unresolved link is contained in")
const res = Object.values(app.metadataCache.unresolvedLinks)
.flatMap(unresolved => Object.keys(unresolved))
.sort();
const resSet = new Set(res).values();
dv.list(resSet);
Variants¶
List the files the unresolved link is contained in¶
See Basic -> DQL for a pure DQL version of this result
const unresolvedLinksMap = app.metadataCache.unresolvedLinks
const res = {}
for (let page in unresolvedLinksMap) {
const unresolved = Object.keys(unresolvedLinksMap[page]);
if (unresolved.length === 0) continue;
for (let link of unresolved) {
if (!res[link]) res[link] = {link, usages: []}
res[link].usages.push(dv.fileLink(page))
}
}
dv.table(["Unresolved Link", "Contained in"], Object.values(res).map(l => [dv.fileLink(l.link), l.usages]));
Sort the results by count of references¶
This version sorts the unresolved links by the count on how much they appear in other files. It also shows a direct file link to create the file and condenses the references for easier viewing.
const unresolvedLinksMap = app.metadataCache.unresolvedLinks
const res = {}
for (let page in unresolvedLinksMap) {
const unresolved = Object.keys(unresolvedLinksMap[page]);
if (unresolved.length === 0) {
continue;
}
for (let link of unresolved) {
if (!res[link]) {
res[link] = {link, usages: []}
}
res[link].usages.push(dv.fileLink(page))
}
}
const rows = Object.values(res)
.map(l => [dv.fileLink(l.link), l.usages.join(' · '), l.usages.length])
.sort((a, b) => a[2] > b[2] ? -1 : 1)
dv.table(
["Unresolved Link", "Contained in", "Count"],
rows
);
List only unresolved links from a specific folder meeting specific criteria¶
As DQL¶
TABLE WITHOUT ID key AS "unresolved link", rows.file.link AS "referencing file"
FROM "10 Example Data"
FLATTEN file.outlinks as outlinks
WHERE startswith(meta(outlinks).path, "B") AND length(meta(outlinks).path) > 3
WHERE !(outlinks.file) AND !(contains(meta(outlinks).path, "/"))
GROUP BY outlinks
As DataviewJS¶
Limitation when filtering unresolved links in DataviewJS
The second filter only works limited. When a file contains two unresolved links, i.e. "Fernando" and "Bob" and you're filtering after "Bob", you'll still end up with "Fernando" in the result, too - because they are both referenced from the same file and Bob's availability will keep the file and all its unresolved links in the filtered set.
In order to fix that, it'd be necessary to filter out the unresolvedLinks array and map it back to the object that'll be processed.
let result = {};
function process(referingFile, unresolvedLinks) {
Object.keys(unresolvedLinks).forEach(function (link) {
link = dv.fileLink(link);
if (!result[link]) result[link] = [];
result[link].push(dv.fileLink(referingFile));
});
}
Object.entries(dv.app.metadataCache.unresolvedLinks)
.filter(([referingFile]) => {
return referingFile.startsWith("10 Example Data/dailys")
})
.filter(([_, unresolvedLinks]) => {
return Object.keys(unresolvedLinks)
.filter(link => link.startsWith("B") && link.length > 3)
.length
})
.forEach(([referingFile, unresolvedLink]) => process(referingFile, unresolvedLink));
dv.table(
["Non existing notes", "Linked from"],
Object.entries(result).map(([unresolvedLink, referingFiles]) => [unresolvedLink, referingFiles.join(" • ")])
);