SkepticMystic / breadcrumbs

Add typed-links to your Obsidian notes
https://publish.obsidian.md/breadcrumbs-docs
MIT License
533 stars 36 forks source link

v4: Sort by next #509

Closed soulsynapse closed 8 months ago

soulsynapse commented 8 months ago

Edit: Not closing this but I will say I tried to write the logic for this and it's a nightmare. Any other sorting option would be sufficient.

This might already be possible in breadcrumbs, I'm not sure.

There could also be a better way to do it that is already baked in.

Is your feature request related to a problem? Please describe. Sort order is a pain to manage, but I already have next relationships. Functionally this is a linked list and can be used for sort order. The problem is that maintaining sort order and defining it in breadcrumbs is difficult.

Describe the solution you'd like For code blocks, sorting by the next relationships would be great. That way I don't have to define a numerical order or have it included in the name.

Describe alternatives you've considered Originally I wanted to do this with dataview and the breadcrumbs API. I guess sort by creation date could work and be useful.

Additional context I've been messing with the codeblocks and this occurred to me.

I set up an example vault to show this.

The files are linked by next sequentially image

They all have 1 common parent image

Ideally I could set the sort to be next and it would split out a flattened 'next' list while showing the hierarchy from down.

tsbertalan commented 8 months ago

As a similar ask, it would be nice to be able to do something like

type: tree
dir: down
depth: -1
order-by: next

(with the order-by an imagined extra feature), so that I can see the immediate children of this node, but ordered by those children's own next/prev property.

soulsynapse commented 8 months ago

Tried writing the logic for this and it's a nightmare, both in terms of resources and how things need to be assessed.

SkepticMystic commented 8 months ago

Hey, thanks for giving it a try, I appreciate the effort. I was going to take a look sometime. Perhaps there's a way to acheive this. Will keep it in mind

tsbertalan commented 8 months ago

The logic for building the tree is here, right? What are the nest objects that get mentioned there--nodes in the tree?

Once you have a particular, limited tree in hand in abstract form, but haven't yet generated an HTML representation, it seems to me that any numerical operations on that tree are nicely limited in scope. You can recurse from the root, at each point considering the small list of children X, (1a) determining the order of children at each point by building (from the next/prev metadata) a new doubly linked list Y from them and following it's pointers to its head, (1b) following the pointers the other way to reorder X, (2) then recurse into each of X.

SkepticMystic commented 8 months ago

Hey, thanks for your input. I've been thinking about this a bit, and I think a simple option is to pass the graph into the sort function, then for each node, fetch the first "next" neighbour, and set that as its sort key. It's a little inefficient to be fetching it more than once for each node. So perhaps the "nexts" can be memoised, but this should work

tsbertalan commented 8 months ago

@SkepticMystic So, you mean you sort all the nodes in the graph? It's a tree, though--there is no single chain of next across everyone; just within a set of siblings.

SkepticMystic commented 8 months ago

No, not for all nodes, just the ones in a given list - like it is now when sorting by file path, for example. So say you've set up a code block to show a tree of edges going down from the current note. The traversal will run as normal, getting the nested list of edges. Then we can sort them, doing something like the following (pseudo-codey for now):

const sort_by_field(graph: Graph, edges: Edge[], field: string) => edges.sort((a, b) => {
    const neighbours_a = graph.neighbours(a).filter(e => a.field === field)
    const neighbours_b = graph.neighbours(b).filter(e => b.field === field)

    if (!neighbours_a.length || !neighbours_b.length) {
        return 0
    } else {
        return neighbours_a[0].path - neighbours_b[0].path
    }
})

This achieves the same result as precomputing a next traversal. But the key here is that we don't need a chain, it can be done for each node in isolation.

If a node doesn't have outgoing neighbours for a given field, the traversal would have ended anyway. If a node has multiple outgoing neighbours for a given field, there would have been ambiguity in the traversal method as well, so we just take the first.

What do you think? I believe this should work

SkepticMystic commented 8 months ago

@soulsynapse @tsbertalan I believe I've got this working! In 4.0.22-beta, you can now sort by the neighbour of some field using the following syntax:

sort: neighbour:<field> desc

Let me know if it does what you're looking for, or if you run into any issues

soulsynapse commented 8 months ago

WOW!! Works great!!

The reason I was hoping for this is that there's an entire subset of zettelkasten called folgezettel. There isn't really a solution for this in Obsidian right now.

But this makes it possible. What a game-changer!

SkepticMystic commented 8 months ago

Glad to hear it :) I'll close this out for now then, thanks!

tsbertalan commented 3 months ago

@SkepticMystic Do you mean something like this?

type: tree
title: false
dir: down
depth: -1
sort: neighbour:next desc

This seems to still be sorting by path or basename for me.

tsbertalan commented 2 weeks ago

Now on 3.6.11, I have a block like

type: tree
fields: [down]
sort: neighbour-field:next
mermaid-direction: TB
depth: [0,0]

It almost seems to work, but there is one node C out of place in the order (and another E for which that order is not defined that seems to be arbitrarily slotted in where C should go):

In this example, A-D all have up relationships to the current page, and mutual next and prev to each other. E has up to the current page, but nothing else.

I expect A-D to be listed in order, with E shown anywhere in there, but I get instead C,A,B,E,D

Update: I revisited this in #575