Skip to content

Commit

Permalink
Frontmatter GUI (#2104)
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsp authored May 18, 2022
1 parent 16985ac commit dd4097f
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 8 deletions.
8 changes: 8 additions & 0 deletions frontend/components/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { IsolatedCell } from "./Cell.js"
import { RawHTMLContainer } from "./CellOutput.js"
import { RecordingPlaybackUI, RecordingUI } from "./RecordingUI.js"
import { HijackExternalLinksToOpenInNewTab } from "./HackySideStuff/HijackExternalLinksToOpenInNewTab.js"
import { FrontMatterInput } from "./FrontmatterInput.js"

// This is imported asynchronously - uncomment for development
// import environment from "../common/Environment.js"
Expand Down Expand Up @@ -1384,6 +1385,13 @@ patch: ${JSON.stringify(
/>`
: null
}
<${FrontMatterInput}
remote_frontmatter=${notebook.metadata?.frontmatter}
set_remote_frontmatter=${(newval) =>
this.actions.update_notebook((nb) => {
nb.metadata["frontmatter"] = newval
})}
/>
${launch_params.preamble_html ? html`<${RawHTMLContainer} body=${launch_params.preamble_html} className=${"preamble"} />` : null}
<${Main}>
<${Preamble}
Expand Down
19 changes: 15 additions & 4 deletions frontend/components/ExportBanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,21 @@ export const ExportBanner = ({ onClose, notebookfile_url, notebookexport_url, st
`
: null
}
<button title="Close" class="toggle_export" onClick=${() => onClose()}>
<span></span>
</button>
<div class="export_small_btns">
<button
title="Edit frontmatter"
class="toggle_frontmatter_edit"
onClick=${() => {
onClose()
window.dispatchEvent(new CustomEvent("open pluto frontmatter"))
}}
>
<span></span>
</button>
<button title="Close" class="toggle_export" onClick=${() => onClose()}>
<span></span>
</button>
</div>
</div>
</aside>
`
Expand Down
164 changes: 164 additions & 0 deletions frontend/components/FrontmatterInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { html, Component, useRef, useLayoutEffect, useState, useEffect } from "../imports/Preact.js"
import { has_ctrl_or_cmd_pressed } from "../common/KeyboardShortcuts.js"

import "https://cdn.jsdelivr.net/gh/fonsp/rebel-tag-input@1.0.4/lib/rebel-tag-input.mjs"

//@ts-ignore
import dialogPolyfill from "https://cdn.jsdelivr.net/npm/dialog-polyfill@0.5.6/dist/dialog-polyfill.esm.min.js"

/**
* @param {{
* remote_frontmatter: Record<String,any>?,
* set_remote_frontmatter: (newval: Record<String,any>) => Promise<void>,
* }} props
* */
export const FrontMatterInput = ({ remote_frontmatter, set_remote_frontmatter }) => {
const [frontmatter, set_frontmatter] = useState(remote_frontmatter ?? {})

useEffect(() => {
set_frontmatter(remote_frontmatter ?? {})
}, [remote_frontmatter])

// useEffect(() => {
// console.log("New frontmatter:", frontmatter)
// }, [frontmatter])

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

const dialog_ref = useRef(/** @type {HTMLDialogElement} */ (null))
useLayoutEffect(() => {
dialogPolyfill.registerDialog(dialog_ref.current)
})

//@ts-ignore
const open = () => dialog_ref.current.showModal()
//@ts-ignore
const close = () => dialog_ref.current.close()

const cancel = () => {
set_frontmatter(remote_frontmatter ?? {})
close()
}
const submit = () => {
set_remote_frontmatter(frontmatter).then(() => alert("Frontmatter synchronized ✔\n\nThese parameters will be used in future exports."))
close()
}

useLayoutEffect(() => {
window.addEventListener("open pluto frontmatter", open)
return () => {
window.removeEventListener("open pluto frontmatter", open)
}
}, [])

useLayoutEffect(() => {
const listener = (e) => {
if (dialog_ref.current.contains(e.target)) if (e.key === "Enter" && has_ctrl_or_cmd_pressed(e)) submit()
}
window.addEventListener("keydown", listener)
return () => {
window.removeEventListener("keydown", listener)
}
}, [])

const frontmatter_with_defaults = {
title: null,
description: null,
date: null,
tags: [],
...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"
onClick=${() => {
set_frontmatter((fm) => Object.fromEntries(Object.entries(fm).filter(([k]) => k !== key)))
}}
>
</button>
`
})}
<button
class="addentry"
onClick=${() => {
const fieldname = prompt("Field name:")
if (fieldname) {
set_frontmatter((fm) => ({ ...fm, [fieldname]: null }))
}
}}
>
Add entry +
</button>
</div>
<div class="final"><button onClick=${cancel}>Cancel</button><button onClick=${submit}>Save</button></div>
</dialog>`
}

const special_field_names = ["tags", "date", "license"]

const field_type = (name) => {
for (const t of special_field_names) {
if (name === t || name.endsWith(`_${t}`)) {
return t
}
}
return "text"
}

const Input = ({ value, on_value, type, id }) => {
const input_ref = useRef(/** @type {HTMLInputElement} */ (null))

useLayoutEffect(() => {
input_ref.current.value = value
}, [input_ref.current, value])

useLayoutEffect(() => {
const listener = (e) => {
on_value(input_ref.current.value)
}

input_ref.current.addEventListener("input", listener)
return () => {
input_ref.current.removeEventListener("input", listener)
}
}, [input_ref.current])

return type === "tags"
? html`<rbl-tag-input id=${id} ref=${input_ref} />`
: type === "license"
? LicenseInput({ ref: input_ref, id })
: html`<input type=${type} id=${id} ref=${input_ref} />`
}

// https://choosealicense.com/licenses/
// and check https://github.com/sindresorhus/spdx-license-list/blob/HEAD/spdx-simple.json
const code_licenses = ["AGPL-3.0", "GPL-3.0", "LGPL-3.0", "MPL-2.0", "Apache-2.0", "MIT", "BSL-1.0", "Unlicense"]

// https://creativecommons.org/about/cclicenses/
// and check https://github.com/sindresorhus/spdx-license-list/blob/HEAD/spdx-simple.json
const creative_licenses = ["CC-BY-4.0", "CC-BY-SA-4.0", "CC-BY-NC-4.0", "CC-BY-NC-SA-4.0", "CC-BY-ND-4.0", "CC-BY-NC-ND-4.0", "CC0-1.0"]

const licenses = [...code_licenses, ...creative_licenses]

const LicenseInput = ({ ref, id }) => {
return html`
<input ref=${ref} id=${id} type="text" list="oss-licenses" />
<datalist id="oss-licenses">${licenses.map((name) => html`<option>${name}</option>`)}</datalist>
`
}
2 changes: 1 addition & 1 deletion frontend/components/Notebook.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const render_cell_outputs_minimum = 20
* selected_cells: Array<string>,
* is_initializing: boolean,
* is_process_ready: boolean,
* disable_input: any,
* disable_input: boolean,
* }} props
* */
export const Notebook = ({ notebook, cell_inputs_local, last_created_cell, selected_cells, is_initializing, is_process_ready, disable_input }) => {
Expand Down
54 changes: 51 additions & 3 deletions frontend/editor.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import url("https://cdn.jsdelivr.net/npm/dialog-polyfill@0.5.6/dist/dialog-polyfill.css");

/* @import url("https://fonts.googleapis.com/css?family=Roboto+Mono:400,400i,500,700&display=swap&subset=cyrillic,cyrillic-ext,greek,greek-ext,latin-ext"); */
@import url("https://cdn.jsdelivr.net/npm/@fontsource/roboto-mono@4.4.5/400.css");
@import url("https://cdn.jsdelivr.net/npm/@fontsource/roboto-mono@4.4.5/400-italic.css");
Expand Down Expand Up @@ -32,6 +34,8 @@
--sans-serif-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
--lato-ui-font-stack: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Cantarell, Helvetica, Arial, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif;
--system-ui-font-stack: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Cantarell, Helvetica, Arial, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif;

color-scheme: light dark;
}
Expand Down Expand Up @@ -544,9 +548,11 @@ a.export_card section {
/* font-size: 14px; */
padding: 3px;
}
aside#export button.toggle_export {
aside#export .export_small_btns {
margin-left: auto;
height: 60px;
display: flex;
flex-direction: row;
}
body.static_preview button.toggle_export {
display: none;
Expand Down Expand Up @@ -657,14 +663,16 @@ pluto-filepicker button:disabled {
}

button.start_stop_recording,
button.toggle_export {
button.toggle_export,
.export_small_btns button {
border: none;
background: none;
cursor: pointer;
opacity: 0.5;
}
button.start_stop_recording span,
button.toggle_export span {
button.toggle_export span,
.export_small_btns button span {
display: block;
content: " " !important;
background-size: 25px 25px;
Expand All @@ -686,6 +694,10 @@ aside#export button.toggle_export span {
background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-outline.svg");
filter: var(--image-filters);
}
aside#export button.toggle_frontmatter_edit span {
background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/newspaper-outline.svg");
filter: var(--image-filters);
}
nav#at_the_top:after {
margin-left: auto;
align-self: center;
Expand Down Expand Up @@ -1427,6 +1439,7 @@ pluto-input > button > span {
/* to make it feel smooth: */
transition: opacity 0.25s ease-in-out;
}
.export_small_btns button,
button.toggle_export,
button.start_stop_recording,
pluto-cell:hover > button,
Expand All @@ -1436,6 +1449,7 @@ pluto-input > button > span {
opacity: 0.4;
transition: opacity 0.25s ease-in-out;
}
.export_small_btns button:hover,
button.toggle_export:hover,
button.start_stop_recording:hover,
.overlay-button button:hover,
Expand Down Expand Up @@ -2867,3 +2881,37 @@ pluto-cell.hooked_up pluto-output {
border-bottom: solid 2px;
border-color: var(--pluto-cell-force-color);
}

.fm-table {
display: grid;
grid-template-columns: auto 1fr min-content;
gap: 0.3em 1em;
}

.pluto-frontmatter {
font-family: var(--system-ui-font-stack);
width: min(31rem, 90vw);
color: var(--black);
background: var(--export-bg-color);
}

.pluto-frontmatter button {
cursor: pointer;
}

.pluto-frontmatter .deletefield {
align-self: baseline;
}

.pluto-frontmatter .addentry {
grid-column: 1/3;
margin-top: 0.5em;
background-color: transparent;
}

.pluto-frontmatter .final {
display: flex;
margin-top: 2rem;
justify-content: flex-end;
gap: 0.5em;
}

0 comments on commit dd4097f

Please sign in to comment.