Skip to content

Commit

Permalink
Frontmatter GUI: multiple authors (#2650)
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsp authored Sep 18, 2023
1 parent 06589c7 commit 690b401
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 28 deletions.
125 changes: 97 additions & 28 deletions frontend/components/FrontmatterInput.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { html, Component, useRef, useLayoutEffect, useState, useEffect } from "../imports/Preact.js"
import { has_ctrl_or_cmd_pressed } from "../common/KeyboardShortcuts.js"
import _ from "../imports/lodash.js"

import "https://cdn.jsdelivr.net/gh/fonsp/[email protected]/lib/rebel-tag-input.mjs"

//@ts-ignore
import dialogPolyfill from "https://cdn.jsdelivr.net/npm/[email protected]/dist/dialog-polyfill.esm.min.js"
import immer from "../imports/immer.js"

/**
* @param {{
Expand All @@ -23,9 +25,12 @@ export const FrontMatterInput = ({ remote_frontmatter, set_remote_frontmatter })
// console.log("New frontmatter:", frontmatter)
// }, [frontmatter])

const fm_setter = (key) => (value) => {
set_frontmatter((fm) => ({ ...fm, [key]: value }))
}
const fm_setter = (key) => (value) =>
set_frontmatter(
immer((fm) => {
_.set(fm, key, value)
})
)

const dialog_ref = useRef(/** @type {HTMLDialogElement?} */ (null))
useLayoutEffect(() => {
Expand All @@ -42,10 +47,26 @@ export const FrontMatterInput = ({ remote_frontmatter, set_remote_frontmatter })
close()
}
const submit = () => {
set_remote_frontmatter(frontmatter).then(() => alert("Frontmatter synchronized ✔\n\nThese parameters will be used in future exports."))
set_remote_frontmatter(clean_data(frontmatter) ?? {}).then(() =>
alert("Frontmatter synchronized ✔\n\nThese parameters will be used in future exports.")
)
close()
}

const clean_data = (obj) => {
let a = _.isPlainObject(obj)
? Object.fromEntries(
Object.entries(obj)
.map(([key, val]) => [key, clean_data(val)])
.filter(([key, val]) => val != null)
)
: _.isArray(obj)
? obj.map(clean_data).filter((x) => x != null)
: obj

return _.isEmpty(a) ? null : a
}

useLayoutEffect(() => {
window.addEventListener("open pluto frontmatter", open)
return () => {
Expand All @@ -68,43 +89,91 @@ export const FrontMatterInput = ({ remote_frontmatter, set_remote_frontmatter })
description: null,
date: null,
tags: [],
author: [{}],
...frontmatter,
}

return html`<dialog ref=${dialog_ref} class="pluto-frontmatter">
<h1>Frontmatter</h1>
<p>
If you are publishing this notebook on the web, you can set the parameters below to provide HTML metadata. This is useful for search engines and
social media.
</p>
<div class="fm-table">
${Object.entries(frontmatter_with_defaults).map(([key, value]) => {
let id = `fm-${key}`
return html`
<label for=${id}>${key}</label>
<${Input} type=${field_type(key)} id=${id} value=${value} on_value=${fm_setter(key)} />
<button
class="deletefield"
title="Delete field"
onClick=${() => {
set_frontmatter((fm) => Object.fromEntries(Object.entries(fm).filter(([k]) => k !== key)))
}}
>
</button>
`
})}
const show_entry = ([key, value]) => !((_.isArray(value) && field_type(key) !== "tags") || _.isPlainObject(value))

const entries_input = (data, base_path) => {
return html`
${Object.entries(data)
.filter(show_entry)
.map(([key, value]) => {
let path = `${base_path}${key}`
let id = `fm-${path}`
return html`
<label for=${id}>${key}</label>
<${Input} type=${field_type(key)} id=${id} value=${value} on_value=${fm_setter(path)} />
<button
class="deletefield"
title="Delete field"
onClick=${() => {
// TODO
set_frontmatter(
immer((fm) => {
_.unset(fm, path)
})
)
}}
>
</button>
`
})}
<button
class="addentry"
onClick=${() => {
const fieldname = prompt("Field name:")
if (fieldname) {
set_frontmatter((fm) => ({ ...fm, [fieldname]: null }))
set_frontmatter(
immer((fm) => {
_.set(fm, `${base_path}${fieldname}`, null)
})
)
}
}}
>
Add entry +
</button>
`
}

return html`<dialog ref=${dialog_ref} class="pluto-frontmatter">
<h1>Frontmatter</h1>
<p>
If you are publishing this notebook on the web, you can set the parameters below to provide HTML metadata. This is useful for search engines and
social media.
</p>
<div class="fm-table">
${entries_input(frontmatter_with_defaults, ``)}
${!_.isArray(frontmatter_with_defaults.author)
? null
: frontmatter_with_defaults.author.map((author, i) => {
let author_with_defaults = {
name: null,
url: null,
...author,
}
return html`
<fieldset class="fm-table">
<legend>Author ${i + 1}</legend>
${entries_input(author_with_defaults, `author[${i}].`)}
</fieldset>
`
})}
${!_.isArray(frontmatter_with_defaults.author)
? null
: html`<button
class="addentry"
onClick=${() => {
set_frontmatter((fm) => ({ ...fm, author: [...(fm?.author ?? []), {}] }))
}}
>
Add author +
</button>`}
</div>
<div class="final"><button onClick=${cancel}>Cancel</button><button onClick=${submit}>Save</button></div>
Expand Down
4 changes: 4 additions & 0 deletions frontend/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -3445,6 +3445,10 @@ pluto-cell.hooked_up pluto-output {
margin-top: 0.5em;
}

.pluto-frontmatter fieldset {
grid-column: 1/4;
}

.pluto-frontmatter .final {
display: flex;
margin-top: 2rem;
Expand Down

0 comments on commit 690b401

Please sign in to comment.