Skip to content

Instantly share code, notes, and snippets.

@johanfriis
Last active December 26, 2023 21:19
Show Gist options
  • Save johanfriis/468cc06326fec23c70dc4a3127874461 to your computer and use it in GitHub Desktop.
Save johanfriis/468cc06326fec23c70dc4a3127874461 to your computer and use it in GitHub Desktop.
A DataView View that implements almost the same rendering for lists that we have for tasks, and adds pagination. This version has tasks removed from output to drastically simplify the view.
if (!input?.query) {
dv.paragraph("You must pass a `query` to the view");
return;
}
const pagesWithLists = await dv.tryQuery(input.query);
const HEADER_SIZE = input?.headerSize ?? 2;
/**
* We only want to render a certain amount of list elements at a time, to cut
* down on render time. We `batch` our pages into groups of pages up till the
* BATCH_LIMIT. Every batch can run over the batch limit on a page basis, but
* that will start a new batch.
*/
const BATCH_LIMIT = input?.limit ?? 150;
let batches = [];
let listCount = 0;
let batch = [];
for (let [page, list] of pagesWithLists.values) {
batch.push([page, list]);
listCount += list.length;
if (listCount > BATCH_LIMIT) {
batches.push(batch);
batch = [];
listCount = 0;
}
}
batches.push(batch);
/**
* Loop over a batch of pages and render them. Add an event listener to the
* wrapper that handles all clicks on list items it contains.
*/
const container = dv.container.createEl("div");
function renderPages(pages) {
const listItemCache = new Map();
container.addEventListener("click", listClickHandler(listItemCache));
for (let [page, list] of pages) {
dv.header(HEADER_SIZE, page, { container });
const wrapper = container.createEl("div", {
cls: "dataview result-group",
});
renderListHtml(list, wrapper, listItemCache);
}
}
let currentBatch = 0;
function loadNextBatch() {
const nextBatch = currentBatch + 1;
renderPages(batches[nextBatch - 1]);
currentBatch = nextBatch;
}
loadNextBatch();
const loadMore = dv.container.createEl("a", { text: "Load More" });
loadMore.addEventListener("click", (evt) => {
evt.stopPropagation();
loadNextBatch();
});
/**
* This click handler finds the current element in the cache and creates
* a selectionState that will highlight that item upon navigating to it.
*/
function listClickHandler(cache) {
return (evt) => {
// Skip this event if a link was pressed.
const { target } = evt;
if (target != null && target != undefined && target.tagName === "A") {
return;
}
evt.stopPropagation();
/**
* Gather selection state information from the closest parent `li` that was
* clicked. We use this to move to the right spot in the target file.
*/
const parent = target.closest(`li`);
const listId = parent.dataset.listId;
const item = cache.get(listId);
const selectionState = {
eState: {
cursor: {
from: { line: item.line, ch: item.position.start.col },
to: {
line: item.line + item.lineCount - 1,
ch: item.position.end.col,
},
},
line: item.line,
},
};
// MacOS interprets the Command key as Meta.
dv.app.workspace.openLinkText(
item.link.toFile().obsidianLink(),
item.path,
evt.ctrlKey || (evt.metaKey && Platform.isMacOS),
selectionState
);
};
}
/**
* Recursively render tasks
*/
function renderListHtml(input, parent, cache, depth = 0) {
if (dv.value.isArray(input)) {
const ul = parent.createEl("ul", { cls: "contains-task-list" });
for (let listItem of input) {
if (listItem?.position?.start?.col === depth) {
renderListHtml(listItem, ul, cache, depth);
}
}
} else if (dv.value.isObject(input)) {
if (input.task) {
return;
}
const listId = input.path + ":" + input.line;
cache.set(listId, input);
const attr = {
"data-list-id": listId,
};
const li = dv.el("li", input.text, {
container: parent,
cls: "dataview",
attr,
});
li.addClasses(["task-list-basic-item"]);
if (input?.children?.length > 0) {
renderListHtml(input.children, li, cache, depth + 1);
}
}
}
@gapmiss
Copy link

gapmiss commented Nov 18, 2022

This is great; perfect for the project I am working on. Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment