From 43775c545958163d90682740356b20449b17a4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ra=C4=8D=C3=A1k?= Date: Thu, 12 Dec 2024 19:36:24 +0100 Subject: [PATCH 1/3] Implement feedback from QA --- db/model/Gdoc/GdocBase.ts | 12 +++++++- db/model/Gdoc/enrichedToRaw.ts | 6 ++++ db/model/Gdoc/exampleEnrichedBlocks.ts | 29 +++++++++++++++++++ db/model/Gdoc/rawToArchie.ts | 10 +++++++ db/model/Gdoc/rawToEnriched.ts | 8 +++++ .../types/src/gdocTypes/ArchieMlComponents.ts | 4 +++ packages/@ourworldindata/utils/src/index.ts | 7 ++++- packages/@ourworldindata/utils/src/string.ts | 5 ++++ site/gdocs/components/ArticleBlock.tsx | 13 ++++----- site/gdocs/components/Callout.scss | 3 ++ site/gdocs/components/Callout.tsx | 28 ++++++++++++++++++ site/gdocs/components/Donors.tsx | 6 ++-- site/gdocs/components/People.scss | 18 +++++++++++- site/gdocs/components/Person.scss | 22 ++++++++++++-- site/gdocs/components/Person.tsx | 21 +++++++++++--- site/gdocs/components/centered-article.scss | 12 +++++--- site/gdocs/pages/AboutPage.scss | 27 ++++++++++++----- site/gdocs/pages/AboutPage.tsx | 21 ++++++++++++++ site/owid.scss | 8 +++++ 19 files changed, 230 insertions(+), 30 deletions(-) create mode 100644 site/gdocs/components/Callout.scss create mode 100644 site/gdocs/components/Callout.tsx diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index e1e7218ef3f..69fe5c49611 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -312,6 +312,17 @@ export class GdocBase implements OwidGdocBaseInterface { block: OwidEnrichedGdocBlock ): DbInsertPostGdocLink[] | void { const links: DbInsertPostGdocLink[] = match(block) + .with({ type: "person" }, (block) => { + if (!block.url) return [] + return [ + createLinkFromUrl({ + url: block.url, + source: this, + componentType: block.type, + text: block.name, + }), + ] + }) .with({ type: "prominent-link" }, (block) => [ createLinkFromUrl({ url: block.url, @@ -559,7 +570,6 @@ export class GdocBase implements OwidGdocBaseInterface { "numbered-list", "people", "people-rows", - "person", "pull-quote", "sdg-grid", "sdg-toc", diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index db819e092e4..5227f4263b8 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -228,7 +228,13 @@ export function enrichedBlockToRawBlock( image: b.image, name: b.name, title: b.title, + url: b.url, text: b.text.map(enrichedBlockToRawBlock) as RawBlockText[], + socials: b.socials?.map((social) => ({ + type: social.type, + url: social.url, + text: social.text, + })), }, }) ) diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index f6189a6cc4b..ebef8ce7685 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -53,7 +53,36 @@ const enrichedBlockPerson: EnrichedBlockPerson = { type: "person", image: "example.png", name: "Max Roser", + title: "Founder and Executive Co-Director", + url: "https://docs.google.com/document/d/7Kj2Pq5MvNxRzF8tLwB3nY9pQx4vC8mXkHs6jWn3-Hy/edit", text: [enrichedBlockText], + socials: [ + { + type: SocialLinkType.X, + url: "https://x.com/MaxCRoser", + text: "@MaxCRoser", + parseErrors: [], + }, + { + type: SocialLinkType.Mastodon, + url: "https://mas.to/@maxroser", + text: "@maxroser", + parseErrors: [], + }, + { + type: SocialLinkType.Bluesky, + url: "https://bsky.app/profile/maxroser.bsky.social", + text: "@maxroser.bsky.social", + parseErrors: [], + }, + { + type: SocialLinkType.Threads, + url: "https://www.threads.net/@max.roser.ox", + text: "@max.roser.ox", + parseErrors: [], + }, + ], + parseErrors: [], } diff --git a/db/model/Gdoc/rawToArchie.ts b/db/model/Gdoc/rawToArchie.ts index d151cb0f40e..c2c9b50803d 100644 --- a/db/model/Gdoc/rawToArchie.ts +++ b/db/model/Gdoc/rawToArchie.ts @@ -269,11 +269,21 @@ function* rawBlockPersonToArchieMLString( yield* propertyToArchieMLString("image", block.value) yield* propertyToArchieMLString("name", block.value) yield* propertyToArchieMLString("title", block.value) + yield* propertyToArchieMLString("url", block.value) yield "[.+text]" for (const b of block.value.text) { yield* OwidRawGdocBlockToArchieMLStringGenerator(b) } yield "[]" + if (block.value.socials?.length) { + yield "[.socials]" + for (const b of block.value.socials) { + yield* propertyToArchieMLString("type", b) + yield* propertyToArchieMLString("url", b) + yield* propertyToArchieMLString("text", b) + } + yield "[]" + } yield "{}" } diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index 5cb8230ea2a..dd7dd7990fb 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -843,6 +843,7 @@ const parsePerson = (raw: RawBlockPerson): EnrichedBlockPerson => { image: raw.value.image, name: raw.value.name, title: raw.value.title, + url: extractUrl(raw.value.url), text: raw.value.text.map(parseText), socials: raw.value.socials?.map(parseSocialLink), parseErrors: [], @@ -1369,6 +1370,12 @@ function parseCallout(raw: RawBlockCallout): EnrichedBlockCallout { text: [], }) + if (raw.value.icon && raw.value.icon !== "info") { + return createError({ + message: "Only the 'info' icon is supported for callouts", + }) + } + if (!raw.value.text) { return createError({ message: "No text provided for callout block" }) } @@ -1394,6 +1401,7 @@ function parseCallout(raw: RawBlockCallout): EnrichedBlockCallout { return { type: "callout", + icon: raw.value.icon, parseErrors: [], text: excludeNullish(enrichedTextBlocks), title: raw.value.title, diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index 90ea4c7c8ef..d1a89ff651d 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -284,6 +284,7 @@ export type RawBlockPerson = { image?: string name: string title?: string + url?: string text: RawBlockText[] socials?: RawSocialLink[] } @@ -305,6 +306,7 @@ export type EnrichedBlockPerson = { image?: string name: string title?: string + url?: string text: EnrichedBlockText[] socials?: EnrichedSocialLink[] } & EnrichedBlockWithParseErrors @@ -511,6 +513,7 @@ export type EnrichedBlockProminentLink = { export type RawBlockCallout = { type: "callout" value: { + icon?: "info" title?: string text: (RawBlockText | RawBlockHeading | RawBlockList)[] } @@ -518,6 +521,7 @@ export type RawBlockCallout = { export type EnrichedBlockCallout = { type: "callout" + icon?: "info" title?: string text: (EnrichedBlockText | EnrichedBlockHeading | EnrichedBlockList)[] } & EnrichedBlockWithParseErrors diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index 520058b256e..1a98410e6ce 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -294,7 +294,12 @@ export { Url, setWindowUrl, getWindowUrl } from "./urls/Url.js" export { type UrlMigration, performUrlMigrations } from "./urls/UrlMigration.js" -export { camelCaseProperties, titleCase, toAsciiQuotes } from "./string.js" +export { + camelCaseProperties, + titleCase, + toAsciiQuotes, + removeDiacritics, +} from "./string.js" export { serializeJSONForHTML, deserializeJSONFromHTML } from "./serializers.js" diff --git a/packages/@ourworldindata/utils/src/string.ts b/packages/@ourworldindata/utils/src/string.ts index b6dae99eaee..056fe1e7c8e 100644 --- a/packages/@ourworldindata/utils/src/string.ts +++ b/packages/@ourworldindata/utils/src/string.ts @@ -35,3 +35,8 @@ export const titleCase = (str: string): string => { export function toAsciiQuotes(str: string): string { return str.replace(/[“”]/g, '"').replace(/[‘’]/g, "'") } + +// https://stackoverflow.com/a/37511463/9846837 +export function removeDiacritics(str: string): string { + return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "") +} diff --git a/site/gdocs/components/ArticleBlock.tsx b/site/gdocs/components/ArticleBlock.tsx index c644ec1c3cc..6f3b20da71a 100644 --- a/site/gdocs/components/ArticleBlock.tsx +++ b/site/gdocs/components/ArticleBlock.tsx @@ -1,6 +1,7 @@ import React from "react" import cx from "classnames" +import Callout from "./Callout.js" import ChartStory from "./ChartStory.js" import Scroller from "./Scroller.js" import Chart from "./Chart.js" @@ -261,14 +262,10 @@ export default function ArticleBlock({ /> )) .with({ type: "callout" }, (block) => ( -
- {block.title ? ( -

{block.title}

- ) : null} - {block.text.map((textBlock, i) => ( - - ))} -
+ )) .with({ type: "chart-story" }, (block) => ( + {icon && } + {block.title ? ( +

{block.title}

+ ) : null} + + + ) +} diff --git a/site/gdocs/components/Donors.tsx b/site/gdocs/components/Donors.tsx index 34970caffb3..f3d05db2475 100644 --- a/site/gdocs/components/Donors.tsx +++ b/site/gdocs/components/Donors.tsx @@ -1,12 +1,14 @@ import * as React from "react" -import { groupBy } from "@ourworldindata/utils" +import { groupBy, removeDiacritics } from "@ourworldindata/utils" import { useDonors } from "../utils.js" export default function Donors({ className }: { className?: string }) { const donors = useDonors() if (!donors) return null - const donorsByLetter = groupBy(donors, (donor) => donor[0]) + const donorsByLetter = groupBy(donors, (donor) => + removeDiacritics(donor[0].toUpperCase()) + ) return (
diff --git a/site/gdocs/components/People.scss b/site/gdocs/components/People.scss index 2537255eea7..93ed3c61731 100644 --- a/site/gdocs/components/People.scss +++ b/site/gdocs/components/People.scss @@ -1,5 +1,17 @@ .people { - row-gap: 16px; + display: grid; + row-gap: 48px; + margin-bottom: 24px; + + .article-block__text, + .article-block__list, + .article-block__numbered-list { + font-size: 16px; + } + + @include sm-only { + row-gap: 40px; + } } .people-cols-2 { @@ -15,6 +27,10 @@ } .people-cols-4 { + .person-heading { + font-size: 18px; + } + @include lg-up { .article-block__text { @include body-3-medium; diff --git a/site/gdocs/components/Person.scss b/site/gdocs/components/Person.scss index 6f49bc591d5..d6db0c63feb 100644 --- a/site/gdocs/components/Person.scss +++ b/site/gdocs/components/Person.scss @@ -1,12 +1,15 @@ .person { display: flex; gap: var(--grid-gap); - margin-bottom: 8px; @include sm-only { flex-direction: column; gap: 16px; } + + & :last-child { + margin-bottom: 0; + } } .person-image-container { @@ -21,6 +24,10 @@ } .person-image { + picture { + display: flex; // Fix extra padding at the bottom. + } + img { border-radius: 50%; } @@ -35,19 +42,30 @@ flex-direction: column; gap: 2px; margin-bottom: 8px; + + a { + color: inherit; + } } .person-heading { + @include h2-bold; margin: 0; + + @include sm-only { + @include h3-bold; + margin: 0; + } } .person-title { + line-height: 19px; color: $blue-60; } .person-socials { margin-top: 16px; - margin-bottom: 16px; + font-size: 14px; ul { display: flex; diff --git a/site/gdocs/components/Person.tsx b/site/gdocs/components/Person.tsx index e3c9f93dbc9..c64750b2260 100644 --- a/site/gdocs/components/Person.tsx +++ b/site/gdocs/components/Person.tsx @@ -1,18 +1,31 @@ import * as React from "react" +import { useMediaQuery } from "usehooks-ts" -import { EnrichedBlockPerson } from "@ourworldindata/types" +import { getCanonicalUrl } from "@ourworldindata/components" +import { EnrichedBlockPerson, OwidGdocType } from "@ourworldindata/types" +import { SMALL_BREAKPOINT_MEDIA_QUERY } from "../../SiteConstants.js" +import { useLinkedDocument } from "../utils.js" import { ArticleBlocks } from "./ArticleBlocks.js" import Image from "./Image.js" -import { useMediaQuery } from "usehooks-ts" -import { SMALL_BREAKPOINT_MEDIA_QUERY } from "../../SiteConstants.js" import { Socials } from "./Socials.js" export default function Person({ person }: { person: EnrichedBlockPerson }) { + const { linkedDocument } = useLinkedDocument(person.url ?? "") const isSmallScreen = useMediaQuery(SMALL_BREAKPOINT_MEDIA_QUERY) + const slug = linkedDocument?.slug + const url = slug + ? getCanonicalUrl("", { + slug, + content: { type: OwidGdocType.Author }, + }) + : undefined + + const heading =

{person.name}

+ const header = (
-

{person.name}

+ {url ? {heading} : heading} {person.title && ( {person.title} )} diff --git a/site/gdocs/components/centered-article.scss b/site/gdocs/components/centered-article.scss index d91a4cf958c..abcb1ce0f5c 100644 --- a/site/gdocs/components/centered-article.scss +++ b/site/gdocs/components/centered-article.scss @@ -464,14 +464,11 @@ h3.article-block__heading { } } -.image--has-outline + .article-block__image-caption { - margin-top: 8px; -} - .article-block__image-caption { @include body-3-medium-italic; color: $blue-60; text-align: center; + margin-top: 16px; } .article-block__chart + .article-block__heading { @@ -1053,6 +1050,7 @@ div.raw-html-table__container { // A small grey block .article-block__callout { h4 { + display: inline; color: $blue-90; margin-bottom: 8px; margin-top: 8px; @@ -1142,6 +1140,12 @@ div.raw-html-table__container { } } +@include sm-only { + .article-block__text + .article-block__prominent-link { + margin-top: 8px; + } +} + .article-block__key-insights { .slide[data-active="true"] { // Have to override the WP styles, which we can undo once they're removed diff --git a/site/gdocs/pages/AboutPage.scss b/site/gdocs/pages/AboutPage.scss index 7a32487fd0a..4133c0f0662 100644 --- a/site/gdocs/pages/AboutPage.scss +++ b/site/gdocs/pages/AboutPage.scss @@ -54,10 +54,6 @@ @include h1-semibold; color: $blue-60; margin-top: 0; - - &:has(+ .article-block__side-by-side) { - margin-bottom: 8px; - } } h3.article-block__heading { @@ -95,20 +91,31 @@ .article-block__callout { margin-top: 0; margin-bottom: 24px; - } - .article-block__chart { - margin-bottom: 24px; + .article-block__text { + font-size: 14px; + } } .article-block__code-snippet { margin-bottom: 16px; } + .article-block__chart, .article-block__image { margin: 0 0 24px; } + .article-block__image-download-button-container { + @include touch-device { + display: none; + } + } + + .article-block__people + .article-block__heading { + margin-top: 24px; + } + .article-block__prominent-link { margin-bottom: 24px; } @@ -122,6 +129,12 @@ } } + .article-block__side-by-side { + :first-child { + margin-top: 0; + } + } + .article-block__donors { margin-top: 32px; padding-top: 40px; diff --git a/site/gdocs/pages/AboutPage.tsx b/site/gdocs/pages/AboutPage.tsx index 9b7c30336ef..f5cc3d44821 100644 --- a/site/gdocs/pages/AboutPage.tsx +++ b/site/gdocs/pages/AboutPage.tsx @@ -1,6 +1,7 @@ import cx from "classnames" import { isEmpty } from "lodash" import * as React from "react" +import { useEffect, useRef } from "react" import { OwidGdocAboutInterface } from "@ourworldindata/types" import { ABOUT_LINKS } from "../../SiteAbout.js" @@ -28,6 +29,25 @@ export default function AboutPage({ content, slug }: OwidGdocAboutInterface) { } function AboutNav({ slug }: { slug: string }) { + const activeLinkRef = useRef(null) + + // Scroll the nav to the active link, since it might not be visible + // on mobile. + useEffect(() => { + const activeLink = activeLinkRef.current + if (activeLink) { + const nav = activeLink.closest("nav") + if (nav) { + const activeLinkOffset = activeLink.offsetLeft + const navWidth = nav.offsetWidth + const activeLinkWidth = activeLink.offsetWidth + // Center the active link. + nav.scrollLeft = + activeLinkOffset - (navWidth - activeLinkWidth) / 2 + } + } + }, []) + return (