Skip to content

Feat/Ordered Lists#257

Open
GrahameGW wants to merge 15 commits intohyperlink-academy:mainfrom
GrahameGW:feat/ordered-list
Open

Feat/Ordered Lists#257
GrahameGW wants to merge 15 commits intohyperlink-academy:mainfrom
GrahameGW:feat/ordered-list

Conversation

@GrahameGW
Copy link

Implements an ordered list block. Includes the ability to edit the list numbers (single click on them), has all the auto-cascade/renumbering logic, and correctly rolls numbers forward or backwards with indents. I also moved a couple of files around because intellisense was complaining about using react hooks in weird places and it seemed straightforward enough to split getBlocks into its own file. I added a "list style" type as well that could potentially be merged w/ unordered lists and/or allow for the creation of other numbering types (letters, roman numerals)

Typescript is not my strong suit, so I'm sure there's some funky stuff going on here I did wrong. I'd love any feedback you guys might have.

@vercel
Copy link

vercel bot commented Jan 18, 2026

@GrahameGW is attempting to deploy a commit to the Hyperlink Team on Vercel.

A member of the Team first needs to authorize it.

@vercel
Copy link

vercel bot commented Jan 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
minilink Error Error Jan 28, 2026 6:28pm

Request Review

@jaredpereira
Copy link
Contributor

This looks awesome! Unsure why it isn't building on vercel, I'll take a look at that, but in the mean time I built it locally and tested.

The only logic issue I found so far is that indent should have the same logic as outdent for calculating the new items index.

In terms of styling the two issues are:

  1. the indentation doesn't match ordered lists properly
  2. The list number editor should be styled to match other form inputs we have. It's a bit of a wierd input, but I think mainly just padding and rounding the borders and using the border color should work fine.

After that the only remaining piece is adding ordered lists to the block lexicons for when posts are published. This is slightly complicated by the current implementation of unordered list which assumes all children are also an unordered list. I think the most backwards compatible approach here is to add another property, for orderedChildren, or something like that, which just takes from an orderedList lexicon. Let me try and spec something out here, but happy to hear if you have any ideas!

@bschlagel
Copy link
Contributor

Thanks @GrahameGW this is awesome! Just did some testing too, a few more comments:

  • I think I'd expect 1) to work as well as 1. (looks like commonmark spec has both) - and probably nice if typing the numbered list start strings works from unordered list item as well
  • for shortcut, we could have cmd-alt-L cycle through instead of toggle unordered list? or maybe fine with no shortcut
  • didn't realize the numbers were editable til seeing Jared's comment…not a thing I exected tbh but cool!
  • small issue - if I have list items (1, 2, 3), and mult-select (2, 3) and hit tab, they both indent as (1) instead of (1, 2)
  • noting mostly for Jared as it's an existing bug: multiselected list items don't outdenting properly, should fix + make sure they re-number correctly too
  • we should handle copy-pasted lists too! both from within leaflet, and external markdown copied in (right now looks like ordered lists paste as unordered list)

@GrahameGW
Copy link
Author

GrahameGW commented Feb 3, 2026

After that the only remaining piece is adding ordered lists to the block lexicons for when posts are published. This is slightly complicated by the current implementation of unordered list which assumes all children are also an unordered list. I think the most backwards compatible approach here is to add another property, for orderedChildren, or something like that, which just takes from an orderedList lexicon. Let me try and spec something out here, but happy to hear if you have any ideas!

So originally what I was trying to do was actually flatten the list entirely, and make indent depth a simple property rather than have nested lists in the way unordered list was implemented. The logic for this is that I get really frustrated at most list implementations where they force you to restart the list if you have a break for a picture or some other element that doesn't use list styling. If I understand the lexicons correctly, this would be easy--you just add properties. Of course, with legacy lexicons out there you end up with two implementations. This was one of the reasons I ended up not going that route; I didn't want to handle migration scripts for legacy lists and that was even before considering existing ones.

I think you can do it in the same manner as unordered children do it; there's some elegance to that as they will be structurally identical, the ordered lists just have a few extra properties. Provided there's no hard-coupled logic to numbering (e.g., you don't have to go sequentially), then it's fine and solves the problem of the "I want a picture in my list." thing.

The only logic issue I found so far is that indent should have the same logic as outdent for calculating the new items index.

I'm not entirely clear what the logical issue is here, but I did find a bug (I think) so I've implemented a fix and hopefully that will resolve it, and maybe that's the issue you're referring to. Basically, when I have a list:

  1. foo
    1. baz
    2. buzz
  2. blah

If I indent "blah" I would expect it to be 3., but it is going to 1. So I've fixed that


Otherwise, I'm working on the multiselect and copy paste. Might finish up tonight, might be later in the week on that.

jaredpereira and others added 10 commits February 8, 2026 02:45
Add the missing "of" between "list" and "Bluesky" in the "Show Mentions"
toggle description across both PostSettings and PostOptions components.

Changes:
- components/PostSettings.tsx: "Display a list Bluesky mentions..." → "Display a list of Bluesky mentions..."
- app/lish/[did]/[publication]/dashboard/settings/PostOptions.tsx: Same fix

https://claude.ai/code/session_01UmMsAY9kbwJy4p1nD6sCh8

Co-authored-by: Claude <noreply@anthropic.com>
@GrahameGW
Copy link
Author

@jaredpereira and @bschlagel -- I had to do some rewrites but as far as I can tell this is pretty solid now. The only thing missing is the lexicon stuff. I would assume the way forward is to add optional arrays as a property for ordered list item children to unordered lists, and then have a new lexicon set for ordered lists (that includes the optional array for unordered list children). I can give that a try if you'd like, let me know what you think, and if you find anything else broken.

The PR has a few of my working commits; do you want me to rebase or otherwise cleanup, or is it fine how it is?

@jaredpereira
Copy link
Contributor

Yep that's the lexicon strategy I think makes the most sense! The places to touch for that are the lexicons themselves and then PostContent.tsx file for rendering the published page based on the contents of the lexicon. Also happy to implement as well if you'd prefer, you've done the hard part here!

This is what I ended up with when sketching out the lexicons:

{
  "lexicon": 1,
  "id": "pub.leaflet.blocks.orderedList",
  "defs": {
    "main": {
      "type": "object",
      "required": [
        "children"
      ],
      "properties": {
        "children": {
          "type": "array",
          "items": {
            "type": "ref",
            "ref": "#listItem"
          }
        }
      }
    },
    "listItem": {
      "type": "object",
      "required": [
        "content",
        "index"
      ],
      "properties": {
        "content": {
          "type": "union",
          "refs": [
            "pub.leaflet.blocks.text",
            "pub.leaflet.blocks.header",
            "pub.leaflet.blocks.image"
          ]
        },
        "index": {
          "type": "integer",
          "description": "The display number for this list item."
        },
        "children": {
          "type": "array",
          "description": "Nested ordered list items. Mutually exclusive with unorderedListChildren; if both are present, children takes precedence.",
          "items": {
            "type": "ref",
            "ref": "#listItem"
          }
        },
        "unorderedListChildren": {
          "type": "ref",
          "description": "A nested unordered list. Mutually exclusive with children; if both are present, children takes precedence.",
          "ref": "pub.leaflet.blocks.unorderedList"
        }
      }
    }
  }
}

and for a new property on unorderedList:

         "children": {
           "type": "array",
+          "description": "Nested unordered list items. Mutually exclusive with orderedListChildren; if both are present, children takes precedence.",
           "items": {
             "type": "ref",
             "ref": "#listItem"
           }
+        },
+        "orderedListChildren": {
+          "type": "ref",
+          "description": "A nested ordered list. Mutually exclusive with children; if both are present, children takes precedence.",
+          "ref": "pub.leaflet.blocks.orderedList"
         }

@GrahameGW
Copy link
Author

Yeah cool. I won't be able to get to it before the weekend at the earliest so feel free to ninja me on this, but otherwise I'll try and sort it out by Sunday.

@GrahameGW
Copy link
Author

@jaredpereira I think it's working with the new lexicon? Not totally convinced on the workflow but it seems to be showing up properly in my dev PDS so hopefully it's correct. I did slightly tweak your orderedList implementation to put the startIndex on the parent instead of having each one on a list item; while I think it's better from a data standpoint to do it per-item, the actual implementation sets it at the root only; messing with numbering just starts a new list block if it doesn't match the list above.

Lemme know what else is broke; managing the lexicons is definitely new for me but I think I'm close anyway.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants