Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

head, header and footer can be specified as a function #1255

Merged
merged 9 commits into from
May 15, 2024
Merged

Conversation

Fil
Copy link
Contributor

@Fil Fil commented Apr 17, 2024

head, header and footer can be specified as a function that receives the page’s meta data (path, title and normalized front matter), and returns an HTML fragment (string).

closes #56

Partially addresses #1036, but I'm not sure how we can (or if we should) expose the meta data more generally:

  • to the page renderer, so that we could pass the values to be rendered SSR (#931); probably in post-processing?
  • to the page runtime (via a define("meta", …))?

Partially addresses #1161.

@Fil Fil requested a review from mbostock April 17, 2024 08:13
@Fil Fil mentioned this pull request Apr 17, 2024
4 tasks
@mbostock
Copy link
Member

Why is it necessary to remove the strict normalization of the front matter? I don’t think we want to encourage users to put anything they want in the front matter; that will make it harder for us to introduce new options in the future without breaking projects. What is the use case for adding arbitrary things into the front matter?

@Fil
Copy link
Contributor Author

Fil commented Apr 17, 2024

The idea is coming from metadata that I would like to both display in the page, and consume outside.

For example, author (to create a byline), source (which I use in pangea to refer to the original notebook, when applicable), tags, date_published, og_image, thumbnail, etc.

Some of these might go in the head (to inform opengraph tags), some in the indexation / listings (typically, the tags), yet others in the footer (e.g. tags with links to the tags pages.

Maybe it's the wrong approach and we should reserve front matter for “official business”, but then we'd need another mechanism that would be more extensible in user land. Maybe it's just a "meta" key in front matter that is not normalized, and made available both for SSR and for client-side JS?

Anyway I can dumb down this PR so that it only does the "head as a function" part and finally closes #56, and we continue the discussion on the extensibility separately.

@mbostock
Copy link
Member

mbostock commented Apr 17, 2024

Yes, let’s separate these things. I’d definitely want the “user space” for the front matter to be isolated from Framework’s options, say by using a meta or data option.

This also overlaps with the page loader concept #1216 which allows the entire page (not just the head, header, footer) to be dynamically generated. But I think it’s still likely reasonable to allow these options to be specified as functions in the config.

@Fil Fil mentioned this pull request Apr 27, 2024
@Fil Fil force-pushed the fil/head-function branch from c68338d to 07d3669 Compare May 15, 2024 14:06
@Fil
Copy link
Contributor Author

Fil commented May 15, 2024

rebased

src/config.ts Outdated
/**
* A function that generates a page fragment such as head, header or footer.
*/
type PageFragmentFunction = (title: string | null, data: FrontMatter, path: string) => string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would rather use named arguments here (({title, data, path})), not positional ((title, data, path)), so that it’s easier for us to add more arguments in the future without it becoming unwieldy.

Also this type is referenced by Config and hence should be exported.

src/config.ts Outdated Show resolved Hide resolved
src/markdown.ts Outdated Show resolved Hide resolved
test/input/build/fragments/index.md Outdated Show resolved Hide resolved
docs/config.md Outdated Show resolved Hide resolved
src/config.ts Outdated Show resolved Hide resolved
@mbostock
Copy link
Member

Looks like you’ll need to handle the case where the function returns null. (I think it should do that for consistency with the statically-supplied value.)

@Fil
Copy link
Contributor Author

Fil commented May 15, 2024

showing "undefined" or "null" was not very pretty (but … informative!) :)

@Fil Fil enabled auto-merge (squash) May 15, 2024 16:39
@Fil Fil merged commit 83397e1 into main May 15, 2024
4 checks passed
@Fil Fil deleted the fil/head-function branch May 15, 2024 16:40
@dleeftink
Copy link

I've tried defining a dynamic head function by following the footer example, to no avail. When testing it for header and footer, rather than evaluating the function it returns the function as string in the rendered HTML (when in dev preview, at least). Maybe I am doing something wrong?

@Fil
Copy link
Contributor Author

Fil commented Jul 25, 2024

@dleeftink What version of framework are you using?

@dleeftink
Copy link

dleeftink commented Jul 25, 2024

@Fil an older one it seems (1.5.1), updated now tot 1.9.0 and no issues with the main observablehq.config.js. Just wondering how to override on a per file basis? Adding an arrow function to the YAML frontmatter of a page like so doesn't seem right:

footer: ()=>`test`

@dleeftink
Copy link

dleeftink commented Jul 27, 2024

Also, how about making the per-page overrides configurable to replace (append=false) or append (append=true) to the globally defined functions in config.js? This way pages don't interfere with the global settings if only minor additions are required. Something like:

header: ({append = true}) => `<i>some subtitle</i>`

Maybe overrides can even be configured to replace/append to specific metadata fields, for instance to append a date to a globally defined title or a metadata page description. Not sure what that interface would like tho as it involves creating per-field flags for potentially nested frontmatter items.

EDIT:
After some thinking, maybe just pass the return string/DocFragment defined by the global config.js to the per-page override?

@Fil
Copy link
Contributor Author

Fil commented Aug 1, 2024

The current logic works the other way around: the function you define in observablehq.config.ts is downstream from the page, not upstream — it receives as input the page's meta data (slug, title and front-matter). One aspect we could make better is to allow "non normalized" front matter fields, so you could e.g. define categories: x, y, z in the front matter and use those in the footer. If you want to open a new issue, please try to provide details of the use case you have in mind.

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.

Footer “edit me” link
3 participants