Skip to content

Commit

Permalink
Add tooltips when pointing at an ingredient in the instructions.
Browse files Browse the repository at this point in the history
The tooltips repeat the quantity and preparation from the main
ingredient list.
  • Loading branch information
jyasskin authored Feb 11, 2024
1 parent 5264967 commit 1866edb
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 11 deletions.
23 changes: 23 additions & 0 deletions webserver/e2e/recipe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,26 @@ test("Shows author's name", async ({ page, testUser, testRecipe, testLogin }) =>
await expect(page).toHaveURL(/.*\/r\/recipeauthor$/);
await expect(page).toHaveTitle("User Name's Recipes");
});

test('Shows ingredient tooltips', async ({ page, testUser, testRecipe }) => {
const user = await testUser.create({ username: 'testuser', name: "User Name" });
await testRecipe.create({
author: { connect: { id: user.id } },
name: "Test Recipe",
slug: "test-recipe",
ingredients: {
create: [
{ order: 0, amount: "1", unit: "cup", name: "flour", preparation: "sifted" },
{ order: 2, amount: "3", name: "Funny ingredient'name" },
]
},
steps: ["1. Add the flour.\n2. Add some Funny ingredient'name."],
})
await page.goto('/r/testuser/test-recipe');

await page.getByText("Add the flour").locator('span[tabindex]').hover();
await expect.soft(page.getByRole('tooltip')).toHaveText("1 cup flour, sifted");

await page.getByText("Add some Funny").locator('span[tabindex]').hover();
await expect.soft(page.getByRole('tooltip')).toHaveText("3 Funny ingredient'name");
});
2 changes: 2 additions & 0 deletions webserver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"dset": "^3.1.3",
"express": "^4.18.2",
"google-auth-library": "^9.6.2",
"lit": "^3.1.1",
"mdast-util-find-and-replace": "^3.0.1",
"n3": "^1.17.2",
"prisma": "^5.9.1",
"rdf-dereference": "^2.2.0",
Expand Down
6 changes: 6 additions & 0 deletions webserver/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions webserver/src/components/Markdown.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import { md } from "@lib/markdown";
type Props = {
source: string;
ingredientNames?: string[];
};
const { source } = Astro.props;
const processed = await md.process(source);
const { source, ingredientNames } = Astro.props;
const processed = md().data({ ingredientNames }).processSync(source);
---

<script>
import "@components/recipe-ingredient";
</script>

<Fragment set:html={String(processed)} />
40 changes: 40 additions & 0 deletions webserver/src/components/recipe-ingredient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import slugify from '@lib/slugify';
import '@shoelace-style/shoelace/dist/components/tooltip/tooltip.js';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';

@customElement('recipe-ingredient')
export class RecipeIngredient extends LitElement {
static override styles = css`
span {
background-color: var(--yellow-highlight);
outline: solid thin var(--yellow-outline);
border-radius: .5ex;
}
span:focus-visible {
outline: solid var(--yellow-outline-focused);
}
`;

@property({ attribute: 'ingredient-id' })
ingredientId: string | undefined = undefined;

@state()
private fullIngredient: string = "";

override connectedCallback() {
super.connectedCallback()
if (this.ingredientId === undefined && this.textContent) {
this.ingredientId = slugify(this.textContent);
}
this.fullIngredient = document.getElementById(this.ingredientId ?? "")?.innerText ?? "";
}

override render() {
if (this.fullIngredient === "") {
return html`<slot></slot>`;
} else {
return html`<sl-tooltip content=${this.fullIngredient}><span tabindex=0><slot></slot></span></sl-tooltip>`;
}
}
}
37 changes: 34 additions & 3 deletions webserver/src/lib/markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@

import rehypeSanitize from 'rehype-sanitize';
import type { PhrasingContent, Root } from 'mdast';
import { findAndReplace } from 'mdast-util-find-and-replace';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import rehypeStringify from 'rehype-stringify';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import { unified } from 'unified';
import { unified, type Plugin } from 'unified';

const wrapIngredients: Plugin<[], Root> = function () {
function replace(value: string): PhrasingContent {
return {
type: 'text',
value,
data: {
hName: 'recipe-ingredient',
hChildren: [{ type: 'text', value }],
}
};
}
return (tree, _file) => {
const ingredientNames = this.data('ingredientNames');
if (ingredientNames) {
findAndReplace(tree, ingredientNames.map(ingredient => [ingredient, replace]));
}
};
};

declare module 'unified' {
interface Data {
ingredientNames?: Array<string> | undefined
}
}

export const md = unified()
.use(remarkParse)
.use(wrapIngredients)
.use(remarkRehype)
.use(rehypeSanitize)
.use(rehypeSanitize, {
...defaultSchema,
tagNames: [...defaultSchema.tagNames ?? [], 'recipe-ingredient'],
})
.use(rehypeStringify)
.freeze();
20 changes: 14 additions & 6 deletions webserver/src/pages/r/[username]/[slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import RenderSource from "@components/RenderSource.astro";
import Layout from "@layouts/Layout.astro";
import { getLogin } from "@lib/login-cookie";
import { prisma } from "@lib/prisma";
import slugify from "@lib/slugify";
import type {
Category,
Recipe,
Expand Down Expand Up @@ -51,14 +52,20 @@ if (slug && username) {
});
}
RenderSource == RenderSource;
const ingredientNames = recipe?.ingredients.map(
(ingredient) => ingredient.name
);
---

<script>
import "@github/relative-time-element";
</script>

<Layout title={recipe?.name ?? "No such recipe"} user={activeUser} showScreenLock>
<Layout
title={recipe?.name ?? "No such recipe"}
user={activeUser}
showScreenLock
>
{
recipe ? (
<div class="recipe" itemscope itemtype="https://schema.org/Recipe">
Expand Down Expand Up @@ -88,6 +95,7 @@ RenderSource == RenderSource;
itemprop="recipeIngredient"
itemscope
itemtype="https://schema.org/HowToSupply"
id={slugify(ingredient.name)}
>
{ingredient.amount ? (
<span
Expand Down Expand Up @@ -118,13 +126,13 @@ RenderSource == RenderSource;
<h3>Instructions</h3>
{recipe.steps.length === 1 ? (
<div itemprop="recipeInstructions">
<Markdown source={recipe.steps[0]!} />
<Markdown source={recipe.steps[0]!} {ingredientNames} />
</div>
) : (
<ol>
{recipe.steps.map((step) => (
<li itemprop="recipeInstructions">
<Markdown source={step} />
<Markdown source={step} {ingredientNames} />
</li>
))}
</ol>
Expand All @@ -139,7 +147,7 @@ RenderSource == RenderSource;
<li itemprop="recipeCategory">
<a
href={`/search?category=${encodeURIComponent(
category.name.replaceAll(" ", "_"),
category.name.replaceAll(" ", "_")
)}`}
>
{category.name}
Expand All @@ -164,7 +172,7 @@ RenderSource == RenderSource;
/>
) : (
<RecipeNote note={note} />
),
)
)}
</ol>
{activeUser ? (
Expand Down
5 changes: 5 additions & 0 deletions webserver/src/style/main.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import "@shoelace-style/shoelace/dist/themes/light.css";

/*
header
Expand All @@ -11,6 +13,9 @@ container (flexbox)
:root {
--yellow: oklch(76% 0.136 79);
--yellow-bg: oklch(95% 0.02 79);
--yellow-highlight: oklch(97.5% 0.01 79);
--yellow-outline: oklch(76% 0.136 79 / 0.25);
--yellow-outline-focused: oklch(76% 0.136 79 / 0.5);
--navy: oklch(29% 0.033 259);
--light-navy: oklch(36% 0.054 259);
}
Expand Down
2 changes: 2 additions & 0 deletions webserver/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"@style/*": ["src/style/*"]
},
"exactOptionalPropertyTypes": false,
"experimentalDecorators": true,
"useDefineForClassFields": false,
"jsx": "preserve",
"jsxImportSource": "solid-js"
}
Expand Down

0 comments on commit 1866edb

Please sign in to comment.