Single File Notebook export format implemented in userspace

A userspace notebook exporter that serializes notebooks into a single HTML file. Works from file://, syncs to S3, stays unminified for easy editing, works offline, and is editable with a text editor and is recursively self-sustaining. LFG!!!

5 Likes

This worked well with a complex, media-heavy notebook of mine. It’s nice to have an archive of that project. I might not have made one without this existing. Thank you!

1 Like

Hi Tom. Thank you for this tool! I noticed that some HTML fragments that I had in a table acquire an observablehq root (and resulting ‘file not found’ when I click them). Is this a parameter you can set in this manner:

viewof parameters = Inputs.bind(
  exporter(),
  localStorageView(`exporter-https://observablehq.com/d/f0fa398a5407d813`, {
    json: true,
    defaultValue: exporter().value
  })
)

… Also, if I don’t wish to import the exporter in a notebook (as that would be rendered along with the notebook), where in your code would I configure this? I see the exporter function with a placeholder to the notebook URL and a blank value – is that it?

createShowable(
              Inputs.text({
                value: "",
                placeholder: "https://observablehq.com/@tomlarkworthy/exporter"
              })

I don’t understand the first question. I would need a link. Feel free to DM. I was jsut messing around with that notebook so possible I broke it.

If you don’t want the exporter to be included in the notebook, then use the export from notebook URL so your exporter is in a different notebook than the one being exported. I guess the notebook would need to be in an unindexed but public URL though.

To avoid you having to keep rekeying in the export settings, like the notebookURL, you could instantiate a notebook exporter, then set it parameters programatically. I think you found the component that you need to set, but you don’t have to do it in source, as its built out of a view which supports back-writing.

EDIT:

thinking about it I jsut added the notebook_url as a top level parameter to make it simpler to adjust

I appreciate your time and help, Tom.

For the first question, in the compiled output, there’s a top-level setting that is
<base href="https://observablehq.com"></base>

The result of having this code included is that the HTML links I had in my notebook would resolve to
https://observablehq.com + (so, for example, https://observablehq.com/003t0000111sut.html). Therefore, if a user clicked the link (which is hard-coded in the data I was using), they’d land at a non-existing Observable notebook.

If I change that setting to, e.g. https://myhostdomain.com/ , then my links start working. So I was trying to figure out where in the exporter I could change this value.

yeah so there are lots of no-brainer degrees of freedom that need adding as configurables. The <base> is something that should be configurable, and I would push that up into the options like I did for “notebook_url”. I don’t think stuff like that needs a UI.

the notebook_url is actually also plumbed into the <title> so you can follow that to see how to get options into the export process. Note in the export template the title is customized with ${title}. We should switch the hardcoded base href with ${base}, but default to https://observablehq.com

With forking forking properly its quite easy to experiment try things out, I would be happy to add suggestions but also I will add that option too if its not easy.

1 Like

The export now remembers the hash URL, which can simplify passing a small amount of view state between exports. I like the hash URL because it can be manipulated by the user, its brutalist.

Under the hood the hash URL is stored as a local fileAttachment Local FileAttachments / Tom Larkworthy | Observable. As fileAttachments survive export, so does this state, so it can be restored on file:// load.

So this is all implemented in userspace without any special consideration in the serialised file representation.

Being able to programatically persist state in file attachments is a game changer for long lived work. For large amounts of data you want to pass between exports, using a mutable fileAttachment is better, as hash URL is limited to 2kb so its a scarce resource, but with fileAttachments you can store unbounded amounts of binary data between exports

1 Like