From 52e471d37b5281ac3fbac1e59d8537b75f190de6 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 30 Oct 2023 13:55:27 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=B0=20Preview=20in=20frontmatter=20edi?= =?UTF-8?q?tor=20(#2688)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/common/useDialog.js | 9 +- frontend/components/Editor.js | 3 +- frontend/components/FrontmatterInput.js | 21 ++- frontend/components/welcome/FeaturedCard.js | 19 ++- frontend/editor.css | 15 +++ frontend/featured-card.css | 142 ++++++++++++++++++++ frontend/index.css | 123 +---------------- frontend/welcome.css | 22 --- 8 files changed, 195 insertions(+), 159 deletions(-) create mode 100644 frontend/featured-card.css diff --git a/frontend/common/useDialog.js b/frontend/common/useDialog.js index 7f52138f7a..176c5a99a0 100644 --- a/frontend/common/useDialog.js +++ b/frontend/common/useDialog.js @@ -13,12 +13,9 @@ export const useDialog = ({ light_dismiss = false } = {}) => { if (dialog_ref.current != null) dialogPolyfill.registerDialog(dialog_ref.current) }, [dialog_ref.current]) - //@ts-ignore - const open = () => dialog_ref.current.showModal() - //@ts-ignore - const close = () => dialog_ref.current.close() - //@ts-ignore - const toggle = () => (dialog_ref.current.open ? dialog_ref.current?.close() : dialog_ref.current?.showModal()) + const open = () => dialog_ref.current?.showModal() + const close = () => dialog_ref.current?.close() + const toggle = () => (dialog_ref.current?.open === true ? dialog_ref.current?.close() : dialog_ref.current?.showModal()) useEffect(() => { if (light_dismiss) { diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index f612b23eab..0260e3acc6 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -1562,7 +1562,8 @@ patch: ${JSON.stringify( )} /> <${EditorLaunchBackendButton} editor=${this} launch_params=${launch_params} status=${status} /> - <${FrontMatterInput} + <${FrontMatterInput} + filename=${notebook.shortpath} remote_frontmatter=${notebook.metadata?.frontmatter} set_remote_frontmatter=${(newval) => this.actions.update_notebook((nb) => { diff --git a/frontend/components/FrontmatterInput.js b/frontend/components/FrontmatterInput.js index 69fe2cc3b1..194e9dae83 100644 --- a/frontend/components/FrontmatterInput.js +++ b/frontend/components/FrontmatterInput.js @@ -8,14 +8,16 @@ import "https://cdn.jsdelivr.net/gh/fonsp/rebel-tag-input@1.0.6/lib/rebel-tag-in import dialogPolyfill from "https://cdn.jsdelivr.net/npm/dialog-polyfill@0.5.6/dist/dialog-polyfill.esm.min.js" import immer from "../imports/immer.js" import { useDialog } from "../common/useDialog.js" +import { FeaturedCard } from "./welcome/FeaturedCard.js" /** * @param {{ + * filename: String, * remote_frontmatter: Record?, * set_remote_frontmatter: (newval: Record) => Promise, * }} props * */ -export const FrontMatterInput = ({ remote_frontmatter, set_remote_frontmatter }) => { +export const FrontMatterInput = ({ filename, remote_frontmatter, set_remote_frontmatter }) => { const [frontmatter, set_frontmatter] = useState(remote_frontmatter ?? {}) useEffect(() => { @@ -138,6 +140,19 @@ export const FrontMatterInput = ({ remote_frontmatter, set_remote_frontmatter }) 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.

+
+

Preview

+ <${FeaturedCard} + entry=${ + /** @type {import("./welcome/Featured.js").SourceManifestNotebookEntry} */ ({ + id: filename.replace(/\.jl$/, ""), + hash: "xx", + frontmatter, + }) + } + disable_links=${true} + /> +
${entries_input(frontmatter_with_defaults, ``)} ${!_.isArray(frontmatter_with_defaults.author) @@ -205,11 +220,13 @@ const Input = ({ value, on_value, type, id }) => { } }, [input_ref.current]) + const placeholder = type === "url" ? "https://..." : undefined + return type === "tags" ? html`` : type === "license" ? LicenseInput({ ref: input_ref, id }) - : html`` + : html`` } // https://choosealicense.com/licenses/ diff --git a/frontend/components/welcome/FeaturedCard.js b/frontend/components/welcome/FeaturedCard.js index 00a33d9efd..3c957c4f59 100644 --- a/frontend/components/welcome/FeaturedCard.js +++ b/frontend/components/welcome/FeaturedCard.js @@ -9,19 +9,22 @@ const str_to_degree = (s) => ([...s].reduce((a, b) => a + b.charCodeAt(0), 0) * /** * @param {{ - * source_manifest: import("./Featured.js").SourceManifest, + * source_manifest?: import("./Featured.js").SourceManifest, * entry: import("./Featured.js").SourceManifestNotebookEntry, * direct_html_links: boolean, + * disable_links: boolean, * }} props */ -export const FeaturedCard = ({ entry, source_manifest, direct_html_links }) => { +export const FeaturedCard = ({ entry, source_manifest, direct_html_links, disable_links }) => { const title = entry.frontmatter?.title - const { source_url } = source_manifest + const { source_url } = source_manifest ?? {} const u = (/** @type {string | null | undefined} */ x) => source_url == null - ? x + ? _.isEmpty(x) + ? null + : x : x == null ? null : // URLs are relative to the source URL... @@ -32,7 +35,9 @@ export const FeaturedCard = ({ entry, source_manifest, direct_html_links }) => { ).href // `direct_html_links` means that we will navigate you directly to the exported HTML file. Otherwise, we use our local editor, with the exported state as parameters. This lets users run the featured notebooks locally. - const href = direct_html_links + const href = disable_links + ? "#" + : direct_html_links ? u(entry.html_path) : with_query_params(`edit`, { statefile: u(entry.statefile_path), @@ -41,7 +46,7 @@ export const FeaturedCard = ({ entry, source_manifest, direct_html_links }) => { disable_ui: `true`, name: title == null ? null : `sample ${title}`, pluto_server_url: `.`, - slider_server_url: u(source_manifest.slider_server_url), + slider_server_url: u(source_manifest?.slider_server_url), }) const author = author_info(entry.frontmatter) @@ -96,7 +101,7 @@ const author_info_item = (x) => { } else if (x instanceof Object) { let { name, image, url } = x - if (image == null && url != null) { + if (image == null && !_.isEmpty(url)) { image = url + ".png?size=48" } diff --git a/frontend/editor.css b/frontend/editor.css index e349ee748e..5ac2ebe9b8 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -23,6 +23,8 @@ @import url("./light_color.css"); @import url("./dark_color.css"); +@import url("./featured-card.css"); + /* VARIABLES */ :root { @@ -3501,6 +3503,19 @@ pluto-cell.hooked_up pluto-output { padding: 1em 1.5em; } +.pluto-frontmatter .card-preview { + background: var(--white); + padding: 1.2rem 1.1rem; + margin: 1rem 0; + box-shadow: inset 0px 0px 15px -4px #00000054; + border-radius: 1rem; +} + +.pluto-frontmatter .card-preview > h2 { + margin-block-start: 0; + color: var(--black); +} + .pluto-frontmatter button { cursor: pointer; background-color: var(--frontmatter-button-bg-color); diff --git a/frontend/featured-card.css b/frontend/featured-card.css new file mode 100644 index 0000000000..e00e2319e7 --- /dev/null +++ b/frontend/featured-card.css @@ -0,0 +1,142 @@ +:root { + --card-width: 15rem; +} + +featured-card { + --card-color: hsl(var(--card-color-hue), 77%, 82%); + --card-border-radius: 10px; + --card-border-width: 3px; + + display: block; + /* width: var(--card-width); */ + border: var(--card-border-width) solid var(--card-color); + border-radius: var(--card-border-radius); + margin: 10px; + padding-bottom: 0.3rem; + box-shadow: 0px 2px 6px 0px #00000014; + font-family: var(--inter-ui-font-stack); + position: relative; + word-break: break-word; + hyphens: auto; + background: var(--index-card-bg); + max-width: var(--card-width); +} + +featured-card .banner img { + --zz: calc(var(--card-border-radius) - var(--card-border-width)); + width: 100%; + /* height: 8rem; */ + aspect-ratio: 3/2; + object-fit: cover; + /* background-color: hsl(16deg 100% 66%); */ + background: var(--card-color); + border-radius: var(--zz) var(--zz) 0 0; + flex: 1 1 200px; + min-width: 0; +} + +featured-card a { + text-decoration: none; + /* font-weight: 800; */ +} + +featured-card a.banner { + display: flex; +} + +featured-card .author { + font-weight: 600; +} + +featured-card .author { + position: absolute; + top: 0.3em; + right: 0.3em; + background: var(--welcome-card-author-backdrop); + /* background: hsl(var(--card-color-hue) 34% 46% / 59%); */ + backdrop-filter: blur(15px); + color: black; + border-radius: 117px; + /* height: 2.5em; */ + padding: 0.3em; + padding-right: 0.8em; + display: flex; +} + +featured-card .author img { + --size: 1.6em; + /* margin: 0.4em 0.4em; */ + /* margin-bottom: -0.4em; */ + width: var(--size); + height: var(--size); + object-fit: cover; + border-radius: 100%; + background: #b6b6b6; + display: inline-block; + overflow: hidden; +} + +featured-card .author a { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.4ch; +} + +featured-card h3 a { + padding: 0.6em; + padding-bottom: 0; + -webkit-line-clamp: 2; + display: inline-block; + display: -webkit-inline-box; + -webkit-box-orient: vertical; + overflow: hidden; + background: var(--index-card-bg); + border-radius: 0.6em; + /* border-top-left-radius: 0; */ +} + +featured-card p { + margin: 0.3rem 0.8rem; + /* padding-top: 0; */ + /* margin-block: 0; */ + color: #838383; + -webkit-line-clamp: 4; + display: inline-block; + display: -webkit-inline-box; + -webkit-box-orient: vertical; + overflow: hidden; +} + +featured-card h3 { + margin: -1.1rem 0rem 0rem 0rem; +} + +featured-card.big { + grid-column-end: span 2; + grid-row-end: span 2; + /* width: 2000px; */ +} + +featured-card.big .banner img { + height: 16rem; +} + +featured-card.special::before { + content: "New!"; + font-size: 1.4rem; + font-weight: 700; + text-transform: uppercase; + font-style: italic; + display: block; + background: #fcf492; + color: #833bc6; + text-shadow: 0 0 1px #ff6767; + position: absolute; + transform: translateY(calc(-100% - -15px)) rotate(-5deg); + padding: 2px 19px; + left: -9px; + /* right: 51px; */ + /* border: 2px solid #ffca62; */ + pointer-events: none; +} diff --git a/frontend/index.css b/frontend/index.css index a4ae4b25e2..562862c38a 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -7,6 +7,8 @@ @import url("light_color.css"); @import url("dark_color.css"); +@import url("featured-card.css"); + * { box-sizing: border-box; } @@ -316,30 +318,6 @@ body.loading loading-bar { } } -:root { - --card-width: 15rem; -} - -featured-card { - --card-color: hsl(var(--card-color-hue), 77%, 82%); - --card-border-radius: 10px; - --card-border-width: 3px; - - display: block; - /* width: var(--card-width); */ - border: var(--card-border-width) solid var(--card-color); - border-radius: var(--card-border-radius); - margin: 10px; - padding-bottom: 0.3rem; - box-shadow: 0px 2px 6px 0px #00000014; - font-family: var(--inter-ui-font-stack); - position: relative; - word-break: break-word; - hyphens: auto; - background: var(--index-card-bg); - max-width: var(--card-width); -} - .card-list { display: grid; /* grid-auto-columns: 50px; */ @@ -350,103 +328,6 @@ featured-card { justify-items: stretch; } -featured-card .banner img { - --zz: calc(var(--card-border-radius) - var(--card-border-width)); - width: 100%; - /* height: 8rem; */ - aspect-ratio: 3/2; - object-fit: cover; - /* background-color: hsl(16deg 100% 66%); */ - background: var(--card-color); - border-radius: var(--zz) var(--zz) 0 0; - flex: 1 1 200px; - min-width: 0; -} - -featured-card .author img { - --size: 1.6em; - /* margin: 0.4em 0.4em; */ - /* margin-bottom: -0.4em; */ - width: var(--size); - height: var(--size); - object-fit: cover; - border-radius: 100%; - background: #b6b6b6; - display: inline-block; - overflow: hidden; -} - -featured-card a { - text-decoration: none; - /* font-weight: 800; */ -} - -featured-card a.banner { - display: flex; -} - -featured-card .author { - font-weight: 600; -} - -featured-card h3 a { - padding: 0.6em; - padding-bottom: 0; - -webkit-line-clamp: 2; - display: inline-block; - display: -webkit-inline-box; - -webkit-box-orient: vertical; - overflow: hidden; - background: var(--index-card-bg); - border-radius: 0.6em; - /* border-top-left-radius: 0; */ -} - -featured-card p { - margin: 0.3rem 0.8rem; - /* padding-top: 0; */ - /* margin-block: 0; */ - color: #838383; - -webkit-line-clamp: 4; - display: inline-block; - display: -webkit-inline-box; - -webkit-box-orient: vertical; - overflow: hidden; -} - -featured-card h3 { - margin: -1.1rem 0rem 0rem 0rem; -} - -featured-card.big { - grid-column-end: span 2; - grid-row-end: span 2; - /* width: 2000px; */ -} - -featured-card.big .banner img { - height: 16rem; -} - -featured-card.special::before { - content: "New!"; - font-size: 1.4rem; - font-weight: 700; - text-transform: uppercase; - font-style: italic; - display: block; - background: #fcf492; - color: #833bc6; - text-shadow: 0 0 1px #ff6767; - position: absolute; - transform: translateY(calc(-100% - -15px)) rotate(-5deg); - padding: 2px 19px; - left: -9px; - /* right: 51px; */ - /* border: 2px solid #ffca62; */ - pointer-events: none; -} - .navigating-away-banner { width: 100vw; min-height: 70vh; diff --git a/frontend/welcome.css b/frontend/welcome.css index 87436e1cea..0aa2f54aef 100644 --- a/frontend/welcome.css +++ b/frontend/welcome.css @@ -222,28 +222,6 @@ h1 { color: #757575; } -.author { - position: absolute; - top: 0.3em; - right: 0.3em; - background: var(--welcome-card-author-backdrop); - /* background: hsl(var(--card-color-hue) 34% 46% / 59%); */ - backdrop-filter: blur(15px); - color: black; - border-radius: 117px; - /* height: 2.5em; */ - padding: 0.3em; - padding-right: 0.8em; - display: flex; -} - -.author a { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.4ch; -} - #github img { aspect-ratio: 1; filter: var(--image-filters);