From 0004d02b115ea91f56f0719afc8bae026505ca4f Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Wed, 17 Jan 2024 16:24:52 +0000 Subject: [PATCH 01/51] :construction: (gdocs) add key indicator component prototype --- adminSiteClient/gdocsDeploy.ts | 6 +- baker/GrapherBaker.tsx | 84 ++++++------------- baker/SiteBaker.tsx | 76 ++++++++++++----- db/model/Gdoc/GdocBase.ts | 51 ++++++++++- db/model/Gdoc/GdocPost.ts | 22 ++++- db/model/Gdoc/enrichedToMarkdown.ts | 11 +++ db/model/Gdoc/enrichedToRaw.ts | 16 +++- db/model/Gdoc/exampleEnrichedBlocks.ts | 6 ++ db/model/Gdoc/rawToArchie.ts | 24 +++++- db/model/Gdoc/rawToEnriched.ts | 77 ++++++++++++++--- db/model/Variable.ts | 67 +++++++++++++-- .../types/src/gdocTypes/ArchieMlComponents.ts | 20 +++++ .../types/src/gdocTypes/Datapage.ts | 6 +- .../types/src/gdocTypes/Gdoc.ts | 7 ++ packages/@ourworldindata/types/src/index.ts | 3 + packages/@ourworldindata/utils/src/Util.ts | 3 +- site/DataInsightsIndexPageContent.tsx | 1 + site/DataPageV2Content.tsx | 2 + site/gdocs/OwidGdoc.tsx | 9 +- site/gdocs/components/ArticleBlock.tsx | 5 ++ site/gdocs/components/Chart.scss | 4 + site/gdocs/components/KeyIndicator.tsx | 46 ++++++++++ site/gdocs/utils.tsx | 22 ++++- 23 files changed, 456 insertions(+), 112 deletions(-) create mode 100644 site/gdocs/components/KeyIndicator.tsx diff --git a/adminSiteClient/gdocsDeploy.ts b/adminSiteClient/gdocsDeploy.ts index e441d69af37..98dc19d2b2a 100644 --- a/adminSiteClient/gdocsDeploy.ts +++ b/adminSiteClient/gdocsDeploy.ts @@ -1,12 +1,11 @@ +import { isEqual, omit } from "@ourworldindata/utils" import { OwidGdoc, OwidGdocBaseInterface, OwidGdocPostContent, - isEqual, - omit, OwidGdocDataInsightContent, OwidGdocType, -} from "@ourworldindata/utils" +} from "@ourworldindata/types" import { GDOC_DIFF_OMITTABLE_PROPERTIES } from "./GdocsDiff.js" import { GDOCS_DETAILS_ON_DEMAND_ID } from "../settings/clientSettings.js" @@ -52,6 +51,7 @@ export const checkIsLightningUpdate = ( breadcrumbs: true, errors: true, linkedCharts: true, + linkedIndicators: true, linkedDocuments: true, relatedCharts: true, revisionId: true, diff --git a/baker/GrapherBaker.tsx b/baker/GrapherBaker.tsx index 37d52ff0ecf..30f85be28de 100644 --- a/baker/GrapherBaker.tsx +++ b/baker/GrapherBaker.tsx @@ -7,22 +7,12 @@ import { urlToSlug, without, deserializeJSONFromHTML, - OwidVariableDataMetadataDimensions, uniq, - JsonError, keyBy, - DimensionProperty, - OwidVariableWithSource, mergePartialGrapherConfigs, - OwidChartDimensionInterface, compact, - OwidGdocPostInterface, merge, - EnrichedFaq, - FaqEntryData, - FaqDictionary, partition, - ImageMetadata, } from "@ourworldindata/utils" import { getRelatedArticles, @@ -45,13 +35,25 @@ import * as db from "../db/db.js" import { glob } from "glob" import { isPathRedirectedToExplorer } from "../explorerAdminServer/ExplorerRedirects.js" import { getPostEnrichedBySlug } from "../db/model/Post.js" -import { ChartTypeName, GrapherInterface } from "@ourworldindata/types" +import { + JsonError, + GrapherInterface, + OwidVariableDataMetadataDimensions, + DimensionProperty, + OwidVariableWithSource, + OwidChartDimensionInterface, + OwidGdocPostInterface, + EnrichedFaq, + FaqEntryData, + FaqDictionary, + ImageMetadata, +} from "@ourworldindata/types" import workerpool from "workerpool" import ProgressBar from "progress" import { getVariableData, - getVariableMetadata, getMergedGrapherConfigForVariable, + getVariableOfDatapageIfApplicable, } from "../db/model/Variable.js" import { getDatapageDataV2, getDatapageGdoc } from "../datapage/Datapage.js" import { ExplorerProgram } from "../explorer/ExplorerProgram.js" @@ -61,7 +63,6 @@ import { logErrorAndMaybeSendToBugsnag } from "../serverUtils/errorLog.js" import { parseFaqs } from "../db/model/Gdoc/rawToEnriched.js" import { GdocPost } from "../db/model/Gdoc/GdocPost.js" import { getShortPageCitation } from "../site/gdocs/utils.js" -import { isEmpty } from "lodash" import { getSlugForTopicTag, getTagToSlugMap } from "./GrapherBakingUtils.js" const renderDatapageIfApplicable = async ( @@ -70,50 +71,19 @@ const renderDatapageIfApplicable = async ( publishedExplorersBySlug?: Record, imageMetadataDictionary?: Record ) => { - // If we have a single Y variable and that one has a schema version >= 2, - // meaning it has the metadata to render a datapage, AND if the metadata includes - // text for at least one of the description* fields or titlePublic, then we show the datapage - // based on this information. - const yVariableIds = grapher - .dimensions!.filter((d) => d.property === DimensionProperty.y) - .map((d) => d.variableId) - const xVariableIds = grapher - .dimensions!.filter((d) => d.property === DimensionProperty.x) - .map((d) => d.variableId) - // Make a data page for single indicator indicator charts. - // For scatter plots we want to only show a data page if it has no X variable mapped, which - // is a special case where time is the X axis. Marimekko charts are the other chart that uses - // the X dimension but there we usually map population on X which should not prevent us from - // showing a data page. - if ( - yVariableIds.length === 1 && - (grapher.type !== ChartTypeName.ScatterPlot || - xVariableIds.length === 0) - ) { - const variableId = yVariableIds[0] - const variableMetadata = await getVariableMetadata(variableId) - - if ( - variableMetadata.schemaVersion !== undefined && - variableMetadata.schemaVersion >= 2 && - (!isEmpty(variableMetadata.descriptionShort) || - !isEmpty(variableMetadata.descriptionProcessing) || - !isEmpty(variableMetadata.descriptionKey) || - !isEmpty(variableMetadata.descriptionFromProducer) || - !isEmpty(variableMetadata.presentation?.titlePublic)) - ) { - return await renderDataPageV2({ - variableId, - variableMetadata, - isPreviewing: isPreviewing, - useIndicatorGrapherConfigs: false, - pageGrapher: grapher, - publishedExplorersBySlug, - imageMetadataDictionary, - }) - } - } - return undefined + const variable = await getVariableOfDatapageIfApplicable(grapher) + + if (!variable) return undefined + + return await renderDataPageV2({ + variableId: variable.id, + variableMetadata: variable.metadata, + isPreviewing: isPreviewing, + useIndicatorGrapherConfigs: false, + pageGrapher: grapher, + publishedExplorersBySlug, + imageMetadataDictionary, + }) } /** diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index 60c6a3f5086..24d0a69fc9c 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -47,6 +47,7 @@ import { countries, FullPost, LinkedChart, + LinkedIndicator, extractDetailsFromSyntax, OwidGdocErrorMessageType, ImageMetadata, @@ -54,6 +55,7 @@ import { OwidGdocType, DATA_INSIGHTS_INDEX_PAGE_SIZE, OwidGdocMinimalPostInterface, + excludeUndefined, } from "@ourworldindata/utils" import { execWrapper } from "../db/execWrapper.js" @@ -84,6 +86,10 @@ import { import pMap from "p-map" import { GdocDataInsight } from "../db/model/Gdoc/GdocDataInsight.js" import { fullGdocToMinimalGdoc } from "../db/model/Gdoc/gdocUtils.js" +import { + getVariableMetadata, + getVariableOfDatapageIfApplicable, +} from "../db/model/Variable.js" type PrefetchedAttachments = { linkedDocuments: Record @@ -92,6 +98,7 @@ type PrefetchedAttachments = { graphers: Record explorers: Record } + linkedIndicators: Record } // These aren't all "wordpress" steps @@ -283,7 +290,7 @@ export class SiteBaker { return without(existingSlugs, ...postSlugsFromDb) } - // Prefetches all linkedDocuments, imageMetadata, and linkedCharts instead of having to fetch them + // Prefetches all linkedDocuments, imageMetadata, linkedCharts, and linkedIndicators instead of having to fetch them // for each individual gdoc. Optionally takes a tuple of string arrays to pick from the prefetched // dictionaries. _prefetchedAttachmentsCache: PrefetchedAttachments | undefined = undefined @@ -311,23 +318,38 @@ export class SiteBaker { `${BAKED_BASE_URL}/default-thumbnail.jpg`, })) ) + // Includes redirects - const publishedChartsBySlug = await Chart.mapSlugsToConfigs().then( - (results) => - results.reduce( - (acc, cur) => ({ - ...acc, - [cur.slug]: { - originalSlug: cur.slug, - resolvedUrl: `${BAKED_GRAPHER_URL}/${cur.config.slug}`, - queryString: "", - title: cur.config.title || "", - thumbnail: `${BAKED_GRAPHER_EXPORTS_BASE_URL}/${cur.config.slug}.svg`, - }, - }), - {} as Record - ) + const publishedChartsRaw = await Chart.mapSlugsToConfigs() + const publishedCharts: LinkedChart[] = await Promise.all( + publishedChartsRaw.map(async (chart) => { + const datapageIndicator = + await getVariableOfDatapageIfApplicable(chart.config) + return { + originalSlug: chart.slug, + resolvedUrl: `${BAKED_GRAPHER_URL}/${chart.config.slug}`, + queryString: "", + title: chart.config.title || "", + thumbnail: `${BAKED_GRAPHER_EXPORTS_BASE_URL}/${chart.config.slug}.svg`, + indicatorId: datapageIndicator?.id, + } + }) + ) + const publishedChartsBySlug = keyBy(publishedCharts, "originalSlug") + + const datapageIndicatorIds = excludeUndefined( + publishedCharts.map((chart) => chart.indicatorId) + ) + const datapageIndicators: LinkedIndicator[] = await Promise.all( + datapageIndicatorIds.map(async (indicatorId: number) => { + const metadata = await getVariableMetadata(indicatorId) + return { + id: indicatorId, + titlePublic: metadata.presentation?.titlePublic, + } + }) ) + const datapageIndicatorsById = keyBy(datapageIndicators, "id") const prefetchedAttachments = { linkedDocuments: publishedGdocsDictionary, @@ -336,6 +358,7 @@ export class SiteBaker { explorers: publishedExplorersBySlug, graphers: publishedChartsBySlug, }, + linkedIndicators: datapageIndicatorsById, } this._prefetchedAttachmentsCache = prefetchedAttachments } @@ -356,6 +379,16 @@ export class SiteBaker { .map((gdoc) => gdoc["featured-image"]) .filter((filename): filename is string => !!filename) + const linkedGrapherCharts = pick( + this._prefetchedAttachmentsCache.linkedCharts.graphers, + linkedGrapherSlugs + ) + const linkedIndicatorIds = excludeUndefined( + Object.values(linkedGrapherCharts).map( + (chart) => chart.indicatorId + ) + ) + return { linkedDocuments, imageMetadata: pick( @@ -364,11 +397,7 @@ export class SiteBaker { ), linkedCharts: { graphers: { - ...pick( - this._prefetchedAttachmentsCache.linkedCharts - .graphers, - linkedGrapherSlugs - ), + ...linkedGrapherCharts, }, explorers: { ...pick( @@ -378,6 +407,10 @@ export class SiteBaker { ), }, }, + linkedIndicators: pick( + this._prefetchedAttachmentsCache.linkedIndicators, + linkedIndicatorIds + ), } } return this._prefetchedAttachmentsCache @@ -464,6 +497,7 @@ export class SiteBaker { ...attachments.linkedCharts.graphers, ...attachments.linkedCharts.explorers, } + publishedGdoc.linkedIndicators = attachments.linkedIndicators // this is a no-op if the gdoc doesn't have an all-chart block await publishedGdoc.loadRelatedCharts() diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 8b7d9708cb8..4aec02acb6b 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -12,6 +12,7 @@ import { import { getUrlTarget } from "@ourworldindata/components" import { LinkedChart, + LinkedIndicator, keyBy, excludeNull, ImageMetadata, @@ -33,6 +34,9 @@ import { BreadcrumbItem, MinimalDataInsightInterface, OwidGdocMinimalPostInterface, + getFeaturedImageFilename, + OwidGdoc, + urlToSlug, } from "@ourworldindata/utils" import { BAKED_GRAPHER_URL } from "../../../settings/serverSettings.js" import { google } from "googleapis" @@ -55,6 +59,10 @@ import { } from "./gdocUtils.js" import { OwidGoogleAuth } from "../../OwidGoogleAuth.js" import { enrichedBlocksToMarkdown } from "./enrichedToMarkdown.js" +import { + getVariableMetadata, + getVariableOfDatapageIfApplicable, +} from "../Variable.js" @Entity("tags") export class Tag extends BaseEntity implements TagInterface { @@ -98,6 +106,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { errors: OwidGdocErrorMessage[] = [] imageMetadata: Record = {} linkedCharts: Record = {} + linkedIndicators: Record = {} linkedDocuments: Record = {} latestDataInsights: MinimalDataInsightInterface[] = [] @@ -268,6 +277,20 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { return [...this.filenames, ...featuredImages] } + get linkedKeyIndicatorSlugs(): string[] { + const slugs = new Set() + for (const enrichedBlockSource of this.enrichedBlockSources) { + for (const block of enrichedBlockSource) { + traverseEnrichedBlocks(block, (block) => { + if (block.type === "key-indicator") { + slugs.add(urlToSlug(block.datapageUrl)) + } + }) + } + } + return [...slugs] + } + get linkedChartSlugs(): { grapher: string[]; explorer: string[] } { const { grapher, explorer } = this.links.reduce( (slugsByLinkType, { linkType, target }) => { @@ -282,6 +305,8 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { } ) + this.linkedKeyIndicatorSlugs.forEach((slug) => grapher.add(slug)) + return { grapher: [...grapher], explorer: [...explorer] } } @@ -508,7 +533,8 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { "sticky-left", "sticky-right", "table", - "text" + "text", + "key-indicator" ), }, () => [] @@ -549,11 +575,14 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { if (!chart) return const resolvedSlug = chart.config.slug ?? "" const resolvedTitle = chart.config.title ?? "" + const datapageIndicator = + await getVariableOfDatapageIfApplicable(chart.config) const linkedChart: LinkedChart = { originalSlug, title: resolvedTitle, resolvedUrl: `${BAKED_GRAPHER_URL}/${resolvedSlug}`, thumbnail: `${BAKED_GRAPHER_EXPORTS_BASE_URL}/${resolvedSlug}.svg`, + indicatorId: datapageIndicator?.id, } return linkedChart } @@ -581,6 +610,25 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { ) } + async loadLinkedIndicators(): Promise { + const linkedIndicators = await Promise.all( + this.linkedKeyIndicatorSlugs.map(async (originalSlug) => { + const linkedChart = this.linkedCharts[originalSlug] + if (!linkedChart || !linkedChart.indicatorId) return + const metadata = await getVariableMetadata( + linkedChart.indicatorId + ) + const linkedIndicator: LinkedIndicator = { + id: linkedChart.indicatorId, + titlePublic: metadata.presentation?.titlePublic, + } + return linkedIndicator + }) + ).then(excludeNullish) + + this.linkedIndicators = keyBy(linkedIndicators, "id") + } + async loadLinkedDocuments(): Promise { const linkedDocuments: OwidGdocMinimalPostInterface[] = await Promise.all( @@ -707,6 +755,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { await this.loadLinkedDocuments() await this.loadImageMetadata() await this.loadLinkedCharts(publishedExplorersBySlug) + await this.loadLinkedIndicators() // depends on linked charts await this._loadSubclassAttachments() await this.validate(publishedExplorersBySlug) } diff --git a/db/model/Gdoc/GdocPost.ts b/db/model/Gdoc/GdocPost.ts index 2a74cd05424..142d66e0ee9 100644 --- a/db/model/Gdoc/GdocPost.ts +++ b/db/model/Gdoc/GdocPost.ts @@ -12,7 +12,8 @@ import { RawBlockText, RelatedChart, OwidGdocMinimalPostInterface, -} from "@ourworldindata/utils" +} from "@ourworldindata/types" +import { traverseEnrichedBlocks, urlToSlug } from "@ourworldindata/utils" import { GDOCS_DETAILS_ON_DEMAND_ID } from "../../../settings/serverSettings.js" import { formatCitation, @@ -144,6 +145,25 @@ export class GdocPost extends GdocBase implements OwidGdocPostInterface { } } + // Validate that charts referenced in key-indicator blocks render a datapage + for (const enrichedBlockSource of this.enrichedBlockSources) { + enrichedBlockSource.forEach((block) => + traverseEnrichedBlocks(block, (block) => { + if (block.type === "key-indicator" && block.datapageUrl) { + const slug = urlToSlug(block.datapageUrl) + const linkedChart = this.linkedCharts?.[slug] + if (!linkedChart?.indicatorId) { + errors.push({ + property: "body", + type: OwidGdocErrorMessageType.Error, + message: `Grapher chart with slug ${slug} is not a datapage`, + }) + } + } + }) + ) + } + return errors } diff --git a/db/model/Gdoc/enrichedToMarkdown.ts b/db/model/Gdoc/enrichedToMarkdown.ts index 1106b9d75af..e9ef6cb4f57 100644 --- a/db/model/Gdoc/enrichedToMarkdown.ts +++ b/db/model/Gdoc/enrichedToMarkdown.ts @@ -271,5 +271,16 @@ ${links}` ).join("\n\n> ") return `> ${text}` + b.citation ? `\n-- ${b.citation}` : "" }) + .with({ type: "key-indicator" }, (b): string | undefined => + markdownComponent( + "KeyIndicator", + { + datapageUrl: b.datapageUrl, + title: b.title, + // blurb ignored + }, + exportComponents + ) + ) .exhaustive() } diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index 5eef3e6bb02..2c619fbed27 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -36,7 +36,8 @@ import { RawBlockVideo, RawBlockTable, RawBlockBlockquote, -} from "@ourworldindata/utils" + RawBlockKeyIndicator, +} from "@ourworldindata/types" import { spanToHtmlString } from "./gdocUtils.js" import { match, P } from "ts-pattern" @@ -433,5 +434,18 @@ export function enrichedBlockToRawBlock( }, } }) + .with({ type: "key-indicator" }, (b): RawBlockKeyIndicator => { + return { + type: "key-indicator", + value: { + datapageUrl: b.datapageUrl, + blurb: b.blurb.map((enriched) => ({ + type: "text", + value: spansToHtmlText(enriched.value), + })), + title: b.title, + }, + } + }) .exhaustive() } diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index a164992f511..6316bba6dab 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -535,4 +535,10 @@ export const enrichedBlockExamples: Record< citation: "Max Roser", parseErrors: [], }, + "key-indicator": { + type: "key-indicator", + datapageUrl: "https://ourworldindata.org/grapher/life-expectancy", + blurb: [enrichedBlockText], + parseErrors: [], + }, } diff --git a/db/model/Gdoc/rawToArchie.ts b/db/model/Gdoc/rawToArchie.ts index 28210b86be4..cbec65b58cf 100644 --- a/db/model/Gdoc/rawToArchie.ts +++ b/db/model/Gdoc/rawToArchie.ts @@ -32,11 +32,12 @@ import { RawBlockExpandableParagraph, RawBlockAlign, RawBlockEntrySummary, - isArray, RawBlockTable, RawBlockTableRow, RawBlockBlockquote, -} from "@ourworldindata/utils" + RawBlockKeyIndicator, +} from "@ourworldindata/types" +import { isArray } from "@ourworldindata/utils" import { match } from "ts-pattern" export function appendDotEndIfMultiline( @@ -620,6 +621,24 @@ function* rawBlockTableToArchieMLString( yield "{}" } +function* rawBlockKeyIndicatorToArchieMLString( + block: RawBlockKeyIndicator +): Generator { + yield "{.key-indicator}" + if (typeof block.value !== "string") { + yield* propertyToArchieMLString("datapageUrl", block.value) + yield* propertyToArchieMLString("title", block.value) + if (block.value.blurb) { + yield "[.+blurb]" + for (const textBlock of block.value.blurb) { + yield* OwidRawGdocBlockToArchieMLStringGenerator(textBlock) + } + yield "[]" + } + } + yield "{}" +} + export function* OwidRawGdocBlockToArchieMLStringGenerator( block: OwidRawGdocBlock | RawBlockTableRow ): Generator { @@ -684,6 +703,7 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator( .with({ type: "table" }, rawBlockTableToArchieMLString) .with({ type: "table-row" }, rawBlockRowToArchieMLString) .with({ type: "blockquote" }, rawBlockBlockquoteToArchieMLString) + .with({ type: "key-indicator" }, rawBlockKeyIndicatorToArchieMLString) .exhaustive() yield* content } diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index bd497e098bd..3b5633e2748 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -3,7 +3,6 @@ import { ChartPositionChoice, ChartControlKeyword, ChartTabKeyword, - compact, EnrichedBlockAside, EnrichedBlockCallout, EnrichedBlockChart, @@ -33,12 +32,11 @@ import { EnrichedRecircLink, EnrichedScrollerItem, EnrichedSDGGridItem, - isArray, + EnrichedBlockKeyIndicator, OwidEnrichedGdocBlock, OwidRawGdocBlock, OwidGdocErrorMessage, ParseError, - partition, RawBlockAdditionalCharts, RawBlockAside, RawBlockCallout, @@ -61,23 +59,18 @@ import { RawBlockStickyLeftContainer, RawBlockStickyRightContainer, RawBlockText, + RawBlockKeyIndicator, Span, SpanSimpleText, - omitUndefinedValues, EnrichedBlockSimpleText, BlockImageSize, checkIsBlockImageSize, RawBlockTopicPageIntro, EnrichedBlockTopicPageIntro, - Url, EnrichedTopicPageIntroRelatedTopic, DetailDictionary, EnrichedDetail, - checkIsPlainObjectWithGuard, EnrichedBlockKeyInsightsSlide, - keyBy, - filterValidStringValues, - uniq, RawBlockResearchAndWriting, RawBlockResearchAndWritingLink, EnrichedBlockResearchAndWriting, @@ -89,7 +82,6 @@ import { EnrichedBlockAllCharts, RefDictionary, OwidGdocErrorMessageType, - excludeNullish, RawBlockResearchAndWritingRow, EnrichedBlockAlign, HorizontalAlign, @@ -109,7 +101,19 @@ import { tableTemplates, RawBlockBlockquote, EnrichedBlockBlockquote, +} from "@ourworldindata/types" +import { traverseEnrichedSpan, + keyBy, + filterValidStringValues, + uniq, + excludeNullish, + checkIsPlainObjectWithGuard, + omitUndefinedValues, + Url, + isArray, + partition, + compact, } from "@ourworldindata/utils" import { checkIsInternalLink } from "@ourworldindata/components" import { @@ -194,6 +198,7 @@ export function parseRawBlocksToEnrichedBlocks( .with({ type: "align" }, parseAlign) .with({ type: "entry-summary" }, parseEntrySummary) .with({ type: "table" }, parseTable) + .with({ type: "key-indicator" }, parseKeyIndicator) .exhaustive() } @@ -1879,3 +1884,55 @@ export function parseRefs({ return { definitions: parsedRefs, errors: refErrors } } + +const parseKeyIndicator = ( + raw: RawBlockKeyIndicator +): EnrichedBlockKeyIndicator => { + const createError = ( + error: ParseError, + datapageUrl: string + ): EnrichedBlockKeyIndicator => ({ + type: "key-indicator", + datapageUrl, + blurb: [], + parseErrors: [error], + }) + + const val = raw.value + + if (typeof val === "string") + return createError( + { + message: "Value is a string, not an object with properties", + }, + "" + ) + + if (!val.datapageUrl) + return createError( + { message: "datapageUrl property is missing or empty" }, + "" + ) + + const url = extractUrl(val.datapageUrl) + + if (!isArray(val.blurb)) + return createError( + { + message: + "Blurb is not a freeform array. Make sure you've written [.+blurb]", + }, + url + ) + + const parsedBlurb = val.blurb.map(parseText) + const parsedBlurbErrors = parsedBlurb.flatMap((block) => block.parseErrors) + + return omitUndefinedValues({ + type: "key-indicator", + datapageUrl: url, + blurb: parsedBlurb, + title: val.title, + parseErrors: parsedBlurbErrors, + }) as EnrichedBlockKeyIndicator +} diff --git a/db/model/Variable.ts b/db/model/Variable.ts index f0235ed0ccf..f6249a3268f 100644 --- a/db/model/Variable.ts +++ b/db/model/Variable.ts @@ -1,6 +1,14 @@ import _ from "lodash" import { Writable } from "stream" import * as db from "../db.js" +import { retryPromise, isEmpty } from "@ourworldindata/utils" +import { + getVariableDataRoute, + getVariableMetadataRoute, +} from "@ourworldindata/grapher" +import pl from "nodejs-polars" +import { DATA_API_URL } from "../../settings/serverSettings.js" +import { escape } from "mysql" import { OwidChartDimensionInterface, OwidVariableDisplayConfigInterface, @@ -11,18 +19,12 @@ import { DataValueResult, OwidVariableWithSourceAndDimension, OwidVariableId, - retryPromise, + ChartTypeName, + DimensionProperty, OwidLicense, GrapherInterface, OwidProcessingLevel, -} from "@ourworldindata/utils" -import { - getVariableDataRoute, - getVariableMetadataRoute, -} from "@ourworldindata/grapher" -import pl from "nodejs-polars" -import { DATA_API_URL } from "../../settings/serverSettings.js" -import { escape } from "mysql" +} from "@ourworldindata/types" export interface VariableRow { id: number @@ -490,6 +492,53 @@ export const readSQLasDF = async ( return createDataFrame(await db.queryMysql(sql, params)) } +export async function getVariableOfDatapageIfApplicable( + grapher: GrapherInterface +): Promise< + | { + id: number + metadata: OwidVariableWithSourceAndDimension + } + | undefined +> { + // If we have a single Y variable and that one has a schema version >= 2, + // meaning it has the metadata to render a datapage, AND if the metadata includes + // text for at least one of the description* fields or titlePublic, then we show the datapage + // based on this information. + const yVariableIds = grapher + .dimensions!.filter((d) => d.property === DimensionProperty.y) + .map((d) => d.variableId) + const xVariableIds = grapher + .dimensions!.filter((d) => d.property === DimensionProperty.x) + .map((d) => d.variableId) + // Make a data page for single indicator indicator charts. + // For scatter plots we want to only show a data page if it has no X variable mapped, which + // is a special case where time is the X axis. Marimekko charts are the other chart that uses + // the X dimension but there we usually map population on X which should not prevent us from + // showing a data page. + if ( + yVariableIds.length === 1 && + (grapher.type !== ChartTypeName.ScatterPlot || + xVariableIds.length === 0) + ) { + const variableId = yVariableIds[0] + const variableMetadata = await getVariableMetadata(variableId) + + if ( + variableMetadata.schemaVersion !== undefined && + variableMetadata.schemaVersion >= 2 && + (!isEmpty(variableMetadata.descriptionShort) || + !isEmpty(variableMetadata.descriptionProcessing) || + !isEmpty(variableMetadata.descriptionKey) || + !isEmpty(variableMetadata.descriptionFromProducer) || + !isEmpty(variableMetadata.presentation?.titlePublic)) + ) { + return { id: variableId, metadata: variableMetadata } + } + } + return undefined +} + /** * Perform regex search over the variables table. */ diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index ef32a56a413..9d5bfb12147 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -86,6 +86,24 @@ export type EnrichedBlockChart = { tabs?: ChartTabKeyword[] } & EnrichedBlockWithParseErrors +export type RawBlockKeyIndicatorValue = { + datapageUrl?: string + title?: string + blurb?: RawBlockText[] +} + +export type RawBlockKeyIndicator = { + type: "key-indicator" + value: RawBlockKeyIndicatorValue | ArchieMLUnexpectedNonObjectValue +} + +export type EnrichedBlockKeyIndicator = { + type: "key-indicator" + datapageUrl: string + blurb: EnrichedBlockText[] + title?: string +} & EnrichedBlockWithParseErrors + export type RawBlockScroller = { type: "scroller" value: OwidRawGdocBlock[] | ArchieMLUnexpectedNonObjectValue @@ -723,6 +741,7 @@ export type OwidRawGdocBlock = | RawBlockEntrySummary | RawBlockTable | RawBlockBlockquote + | RawBlockKeyIndicator export type OwidEnrichedGdocBlock = | EnrichedBlockAllCharts @@ -760,3 +779,4 @@ export type OwidEnrichedGdocBlock = | EnrichedBlockEntrySummary | EnrichedBlockTable | EnrichedBlockBlockquote + | EnrichedBlockKeyIndicator diff --git a/packages/@ourworldindata/types/src/gdocTypes/Datapage.ts b/packages/@ourworldindata/types/src/gdocTypes/Datapage.ts index 59aca6cfa0e..3ffa2a226d9 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Datapage.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Datapage.ts @@ -146,7 +146,11 @@ export type DataPageParseError = { message: string; path?: string } export type FaqEntryData = Pick< OwidGdocPostInterface, - "linkedCharts" | "linkedDocuments" | "relatedCharts" | "imageMetadata" + | "linkedCharts" + | "linkedIndicators" + | "linkedDocuments" + | "relatedCharts" + | "imageMetadata" > & { faqs: OwidEnrichedGdocBlock[] } diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index c3531711b57..32af2e41d4a 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -23,6 +23,12 @@ export interface LinkedChart { resolvedUrl: string title: string thumbnail?: string + indicatorId?: number // in case of a datapage +} + +export interface LinkedIndicator { + id: number + titlePublic?: string } export enum OwidGdocType { @@ -46,6 +52,7 @@ export interface OwidGdocBaseInterface { breadcrumbs?: BreadcrumbItem[] | null linkedDocuments?: Record linkedCharts?: Record + linkedIndicators?: Record imageMetadata?: Record relatedCharts?: RelatedChart[] tags?: Tag[] diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index bc64d838285..74be08055e9 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -191,6 +191,7 @@ export { type RawBlockText, type RawBlockTopicPageIntro, type RawBlockUrl, + type RawBlockKeyIndicator, tableTemplates, type TableTemplate, tableSizes, @@ -248,6 +249,7 @@ export { type EnrichedBlockTable, type EnrichedBlockTableRow, type EnrichedBlockTableCell, + type EnrichedBlockKeyIndicator, type RawBlockResearchAndWritingRow, } from "./gdocTypes/ArchieMlComponents.js" export { @@ -277,6 +279,7 @@ export { type OwidArticleBackportingStatistics, type LinkedChart, OwidGdocLinkType, + type LinkedIndicator, DYNAMIC_COLLECTION_PAGE_CONTAINER_ID, } from "./gdocTypes/Gdoc.js" diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index e06849c8a84..9a856c2373a 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1643,7 +1643,8 @@ export function traverseEnrichedBlocks( "sdg-toc", "topic-page-intro", "all-charts", - "entry-summary" + "entry-summary", + "key-indicator" ), }, callback diff --git a/site/DataInsightsIndexPageContent.tsx b/site/DataInsightsIndexPageContent.tsx index 481e9a222b8..49b0bc00654 100644 --- a/site/DataInsightsIndexPageContent.tsx +++ b/site/DataInsightsIndexPageContent.tsx @@ -106,6 +106,7 @@ export const DataInsightsIndexPageContent = ( imageMetadata, linkedCharts, linkedDocuments, + linkedIndicators: {}, // not needed for data insights relatedCharts: [], // not needed for the index page latestDataInsights: [], // not needed for the index page }} diff --git a/site/DataPageV2Content.tsx b/site/DataPageV2Content.tsx index ee8ae4bbf17..f87e6663368 100644 --- a/site/DataPageV2Content.tsx +++ b/site/DataPageV2Content.tsx @@ -165,6 +165,7 @@ export const DataPageV2Content = ({ linkedDocuments = {}, imageMetadata = {}, linkedCharts = {}, + linkedIndicators = {}, relatedCharts = [], } = faqEntries ?? {} @@ -237,6 +238,7 @@ export const DataPageV2Content = ({ linkedDocuments, imageMetadata, linkedCharts, + linkedIndicators, relatedCharts, }} > diff --git a/site/gdocs/OwidGdoc.tsx b/site/gdocs/OwidGdoc.tsx index 7adacef2069..e1357c994d5 100644 --- a/site/gdocs/OwidGdoc.tsx +++ b/site/gdocs/OwidGdoc.tsx @@ -3,14 +3,16 @@ import ReactDOM from "react-dom" import { LinkedChart, getOwidGdocFromJSON, + LinkedIndicator, + OwidGdocPostInterface, ImageMetadata, RelatedChart, - get, OwidGdocType, OwidGdoc as OwidGdocInterface, MinimalDataInsightInterface, OwidGdocMinimalPostInterface, -} from "@ourworldindata/utils" +} from "@ourworldindata/types" +import { get, getOwidGdocFromJSON } from "@ourworldindata/utils" import { DebugProvider } from "./DebugContext.js" import { match, P } from "ts-pattern" import { GdocPost } from "./pages/GdocPost.js" @@ -19,6 +21,7 @@ import { Fragment } from "./pages/Fragment.js" export const AttachmentsContext = createContext<{ linkedCharts: Record + linkedIndicators: Record linkedDocuments: Record imageMetadata: Record relatedCharts: RelatedChart[] @@ -27,6 +30,7 @@ export const AttachmentsContext = createContext<{ linkedDocuments: {}, imageMetadata: {}, linkedCharts: {}, + linkedIndicators: {}, relatedCharts: [], latestDataInsights: [], }) @@ -97,6 +101,7 @@ export function OwidGdoc({ linkedDocuments: get(props, "linkedDocuments", {}), imageMetadata: get(props, "imageMetadata", {}), linkedCharts: get(props, "linkedCharts", {}), + linkedIndicators: get(props, "linkedIndicators", {}), relatedCharts: get(props, "relatedCharts", []), latestDataInsights: get(props, "latestDataInsights", []), }} diff --git a/site/gdocs/components/ArticleBlock.tsx b/site/gdocs/components/ArticleBlock.tsx index 7de53f5cf1a..dbd8506547f 100644 --- a/site/gdocs/components/ArticleBlock.tsx +++ b/site/gdocs/components/ArticleBlock.tsx @@ -34,6 +34,7 @@ import { ResearchAndWriting } from "./ResearchAndWriting.js" import { AllCharts } from "./AllCharts.js" import Video from "./Video.js" import { Table } from "./Table.js" +import KeyIndicator from "./KeyIndicator.js" export type Container = | "default" @@ -69,6 +70,7 @@ const layouts: { [key in Container]: Layouts} = { ["image--narrow"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 col-sm-start-2 span-sm-cols-12", ["image--wide"]: "col-start-4 span-cols-8 col-md-start-2 span-md-cols-12", ["image-caption"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", + ["key-indicator"]: "col-start-5 span-cols-6", ["key-insights"]: "col-start-2 span-cols-12", ["list"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["numbered-list"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", @@ -623,6 +625,9 @@ export default function ArticleBlock({ ) }) + .with({ type: "key-indicator" }, (block) => ( + + )) .exhaustive() return ( diff --git a/site/gdocs/components/Chart.scss b/site/gdocs/components/Chart.scss index bb53cdc377b..17645ed9212 100644 --- a/site/gdocs/components/Chart.scss +++ b/site/gdocs/components/Chart.scss @@ -5,3 +5,7 @@ figure.chart { figure.explorer { height: 700px; } + +div.margin-0 figure { + margin: 0; +} diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx new file mode 100644 index 00000000000..0c574a2c1ed --- /dev/null +++ b/site/gdocs/components/KeyIndicator.tsx @@ -0,0 +1,46 @@ +import React from "react" +import { EnrichedBlockKeyIndicator } from "@ourworldindata/types" +import Chart from "./Chart.js" +import Paragraph from "./Paragraph.js" +import { useLinkedChart, useLinkedIndicator } from "../utils.js" + +export default function KeyIndicator({ + d, + className, +}: { + d: EnrichedBlockKeyIndicator + className?: string +}) { + const { linkedChart } = useLinkedChart(d.datapageUrl) + const { linkedIndicator } = useLinkedIndicator( + linkedChart?.indicatorId ?? 0 + ) + + if (!linkedChart) return null + if (!linkedIndicator) return null + + return ( +
+
+ Custom title: {d.title} +
+
+ Default title: {linkedChart?.title} +
+
+ Blurb: + {d.blurb.map((textBlock, i) => ( + + ))} +
+ +
+ Linked indicator with metadata: + {JSON.stringify(linkedIndicator, null, 2)} +
+
+ ) +} diff --git a/site/gdocs/utils.tsx b/site/gdocs/utils.tsx index 57e6984a966..21ee9d8664c 100644 --- a/site/gdocs/utils.tsx +++ b/site/gdocs/utils.tsx @@ -6,12 +6,12 @@ import { SpanLink, ImageMetadata, LinkedChart, - Url, OwidGdocPostContent, - formatAuthors, OwidGdocMinimalPostInterface, OwidGdocType, -} from "@ourworldindata/utils" + LinkedIndicator, +} from "@ourworldindata/types" +import { formatAuthors, Url } from "@ourworldindata/utils" import { match } from "ts-pattern" import { AttachmentsContext } from "./OwidGdoc.js" @@ -106,6 +106,22 @@ export const useLinkedChart = ( } } +export const useLinkedIndicator = ( + id: number +): { linkedIndicator?: LinkedIndicator; errorMessage?: string } => { + const { linkedIndicators } = useContext(AttachmentsContext) + + const linkedIndicator = linkedIndicators?.[id] + + if (!linkedIndicator) { + return { + errorMessage: `Indicator with id ${id} not found`, + } + } + + return { linkedIndicator } +} + export const useImage = ( filename: string | undefined ): ImageMetadata | undefined => { From 4db7b158599f346f00d5fb281dc6324cad524a2e Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 22 Jan 2024 10:30:19 +0000 Subject: [PATCH 02/51] :honeybee: (gdocs) resolve import issues after rebasing --- db/model/Gdoc/GdocBase.ts | 2 -- site/gdocs/OwidGdoc.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 4aec02acb6b..6b8910d5a12 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -34,8 +34,6 @@ import { BreadcrumbItem, MinimalDataInsightInterface, OwidGdocMinimalPostInterface, - getFeaturedImageFilename, - OwidGdoc, urlToSlug, } from "@ourworldindata/utils" import { BAKED_GRAPHER_URL } from "../../../settings/serverSettings.js" diff --git a/site/gdocs/OwidGdoc.tsx b/site/gdocs/OwidGdoc.tsx index e1357c994d5..6e70c9e1a61 100644 --- a/site/gdocs/OwidGdoc.tsx +++ b/site/gdocs/OwidGdoc.tsx @@ -2,9 +2,7 @@ import React, { createContext } from "react" import ReactDOM from "react-dom" import { LinkedChart, - getOwidGdocFromJSON, LinkedIndicator, - OwidGdocPostInterface, ImageMetadata, RelatedChart, OwidGdocType, From cb9699027f512b8b0512db8209f640ca3e60df33 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Thu, 25 Jan 2024 09:26:32 +0000 Subject: [PATCH 03/51] :sparkles: (gdocs) make key indicator blurb optional --- db/model/Gdoc/enrichedToRaw.ts | 10 ++++++---- db/model/Gdoc/exampleEnrichedBlocks.ts | 1 - db/model/Gdoc/rawToEnriched.ts | 8 ++++---- .../types/src/gdocTypes/ArchieMlComponents.ts | 2 +- site/gdocs/components/KeyIndicator.tsx | 14 ++++++++------ 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index 2c619fbed27..871ec8a136f 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -439,11 +439,13 @@ export function enrichedBlockToRawBlock( type: "key-indicator", value: { datapageUrl: b.datapageUrl, - blurb: b.blurb.map((enriched) => ({ - type: "text", - value: spansToHtmlText(enriched.value), - })), title: b.title, + blurb: b.blurb + ? b.blurb.map((enriched) => ({ + type: "text", + value: spansToHtmlText(enriched.value), + })) + : undefined, }, } }) diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index 6316bba6dab..446e3981c65 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -538,7 +538,6 @@ export const enrichedBlockExamples: Record< "key-indicator": { type: "key-indicator", datapageUrl: "https://ourworldindata.org/grapher/life-expectancy", - blurb: [enrichedBlockText], parseErrors: [], }, } diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index 3b5633e2748..ca948c5bfe5 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -1894,7 +1894,6 @@ const parseKeyIndicator = ( ): EnrichedBlockKeyIndicator => ({ type: "key-indicator", datapageUrl, - blurb: [], parseErrors: [error], }) @@ -1916,7 +1915,7 @@ const parseKeyIndicator = ( const url = extractUrl(val.datapageUrl) - if (!isArray(val.blurb)) + if (val.blurb && !isArray(val.blurb)) return createError( { message: @@ -1925,13 +1924,14 @@ const parseKeyIndicator = ( url ) - const parsedBlurb = val.blurb.map(parseText) + const blurb = val.blurb ?? [] + const parsedBlurb = blurb.map(parseText) const parsedBlurbErrors = parsedBlurb.flatMap((block) => block.parseErrors) return omitUndefinedValues({ type: "key-indicator", datapageUrl: url, - blurb: parsedBlurb, + blurb: parsedBlurb.length > 0 ? parsedBlurb : undefined, title: val.title, parseErrors: parsedBlurbErrors, }) as EnrichedBlockKeyIndicator diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index 9d5bfb12147..3402ec0db86 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -100,8 +100,8 @@ export type RawBlockKeyIndicator = { export type EnrichedBlockKeyIndicator = { type: "key-indicator" datapageUrl: string - blurb: EnrichedBlockText[] title?: string + blurb?: EnrichedBlockText[] } & EnrichedBlockWithParseErrors export type RawBlockScroller = { diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 0c574a2c1ed..f64d33986d0 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -27,12 +27,14 @@ export default function KeyIndicator({
Default title: {linkedChart?.title}
-
- Blurb: - {d.blurb.map((textBlock, i) => ( - - ))} -
+ {d.blurb && ( +
+ Blurb: + {d.blurb.map((textBlock, i) => ( + + ))} +
+ )} Date: Thu, 25 Jan 2024 10:45:15 +0000 Subject: [PATCH 04/51] :sparkles: (gdocs) initial design implemention --- baker/SiteBaker.tsx | 3 +- db/model/Gdoc/GdocBase.ts | 3 +- .../src/IndicatorKeyData/IndicatorKeyData.tsx | 6 +- .../@ourworldindata/types/src/OwidVariable.ts | 3 +- .../types/src/gdocTypes/Gdoc.ts | 5 +- packages/@ourworldindata/utils/src/index.ts | 2 + .../utils/src/metadataHelpers.ts | 37 +++++++ site/DataPageV2Content.tsx | 24 ++--- site/gdocs/components/ArticleBlock.tsx | 2 +- site/gdocs/components/KeyIndicator.scss | 68 +++++++++++++ site/gdocs/components/KeyIndicator.tsx | 99 +++++++++++++++---- site/owid.scss | 1 + 12 files changed, 208 insertions(+), 45 deletions(-) create mode 100644 site/gdocs/components/KeyIndicator.scss diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index 24d0a69fc9c..88ba28e54ec 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -56,6 +56,7 @@ import { DATA_INSIGHTS_INDEX_PAGE_SIZE, OwidGdocMinimalPostInterface, excludeUndefined, + grabMetadataForGdocLinkedIndicator, } from "@ourworldindata/utils" import { execWrapper } from "../db/execWrapper.js" @@ -345,7 +346,7 @@ export class SiteBaker { const metadata = await getVariableMetadata(indicatorId) return { id: indicatorId, - titlePublic: metadata.presentation?.titlePublic, + ...grabMetadataForGdocLinkedIndicator(metadata), } }) ) diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 6b8910d5a12..bbf38e42684 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -35,6 +35,7 @@ import { MinimalDataInsightInterface, OwidGdocMinimalPostInterface, urlToSlug, + grabMetadataForGdocLinkedIndicator, } from "@ourworldindata/utils" import { BAKED_GRAPHER_URL } from "../../../settings/serverSettings.js" import { google } from "googleapis" @@ -618,7 +619,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { ) const linkedIndicator: LinkedIndicator = { id: linkedChart.indicatorId, - titlePublic: metadata.presentation?.titlePublic, + ...grabMetadataForGdocLinkedIndicator(metadata), } return linkedIndicator }) diff --git a/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx b/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx index eae6ba63347..b8457d9d404 100644 --- a/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx +++ b/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx @@ -12,21 +12,23 @@ export const makeSource = ({ attribution, owidProcessingLevel, isEmbeddedInADataPage, + hideProcessingLevel = false, }: { attribution?: string owidProcessingLevel?: OwidProcessingLevel isEmbeddedInADataPage?: boolean + hideProcessingLevel?: boolean }): React.ReactNode => { if (!attribution) return null const isEmbedded = isEmbeddedInADataPage ?? true const processingLevelPhrase = getPhraseForProcessingLevel(owidProcessingLevel) - const hideProcessingPhase = + const hideProcessingPhrase = hideProcessingLevel || attribution.toLowerCase() === "our world in data" return ( <> - {!hideProcessingPhase && ( + {!hideProcessingPhrase && ( <> {" – "} {isEmbedded ? ( diff --git a/packages/@ourworldindata/types/src/OwidVariable.ts b/packages/@ourworldindata/types/src/OwidVariable.ts index 6aeb2d91dfd..a49e227733a 100644 --- a/packages/@ourworldindata/types/src/OwidVariable.ts +++ b/packages/@ourworldindata/types/src/OwidVariable.ts @@ -1,6 +1,7 @@ import { OwidOrigin } from "./OwidOrigin.js" import { OwidSource } from "./OwidSource.js" import { OwidVariableDisplayConfigInterface } from "./OwidVariableDisplayConfigInterface.js" +import { GrapherInterface } from "./grapherTypes/GrapherTypes.js" export interface OwidVariableWithSource { id: number @@ -71,7 +72,7 @@ export interface OwidVariablePresentation { attribution?: string topicTagsLinks?: string[] faqs?: FaqLink[] - grapherConfigETL?: string + grapherConfigETL?: GrapherInterface } export type OwidProcessingLevel = "minor" | "major" diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index 32af2e41d4a..ba7e1bfd8e8 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -28,7 +28,10 @@ export interface LinkedChart { export interface LinkedIndicator { id: number - titlePublic?: string + title: string + dateRange?: string + lastUpdated?: string + attributionUnshortened?: string } export enum OwidGdocType { diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index 7587daa0ee4..e259bd14943 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -132,6 +132,8 @@ export { formatSourceDate, getCitationLong, getCitationShort, + grabMetadataForGdocLinkedIndicator, + getAttributionUnshortened, } from "./metadataHelpers.js" export { diff --git a/packages/@ourworldindata/utils/src/metadataHelpers.ts b/packages/@ourworldindata/utils/src/metadataHelpers.ts index bb1a344321c..12c98a5deea 100644 --- a/packages/@ourworldindata/utils/src/metadataHelpers.ts +++ b/packages/@ourworldindata/utils/src/metadataHelpers.ts @@ -5,6 +5,8 @@ import { DisplaySource, IndicatorTitleWithFragments, OwidSource, + OwidVariableWithSourceAndDimension, + LinkedIndicator, } from "@ourworldindata/types" import { compact, uniq, last, excludeUndefined } from "./Util" import dayjs from "./dayjs.js" @@ -194,6 +196,7 @@ const getYearSuffixFromOrigin = (o: OwidOrigin): string => { if (year) return ` (${year})` else return "" } + export const getCitationShort = ( origins: OwidOrigin[], attributions: string[], @@ -261,6 +264,7 @@ export const getCitationLong = ( canonicalUrl ? `Retrieved ${today} from ${canonicalUrl}` : undefined, ]).join(" ") } + export const formatSourceDate = ( date: string | undefined, format: string @@ -269,3 +273,36 @@ export const formatSourceDate = ( if (!parsedDate.isValid()) return date || null return parsedDate.format(format) } + +export function getAttributionUnshortened({ + origins, + attributions, +}: { + origins: OwidOrigin[] + attributions: string[] +}): string { + const producersWithYear = uniq( + origins.map((o) => `${o.producer}${getYearSuffixFromOrigin(o)}`) + ) + const attributionFragments = attributions ?? producersWithYear + return attributionFragments.join("; ") +} + +export function grabMetadataForGdocLinkedIndicator( + metadata: OwidVariableWithSourceAndDimension +): Omit { + return { + title: + metadata.presentation?.titlePublic ?? + metadata.presentation?.grapherConfigETL?.title ?? + metadata.display?.name ?? + metadata.name ?? + "", + dateRange: metadata.timespan, + lastUpdated: getLastUpdatedFromVariable(metadata), + attributionUnshortened: getAttributionUnshortened({ + attributions: getAttributionFragmentsFromVariable(metadata), + origins: metadata.origins ?? [], + }), + } +} diff --git a/site/DataPageV2Content.tsx b/site/DataPageV2Content.tsx index f87e6663368..a823dd4b1d8 100644 --- a/site/DataPageV2Content.tsx +++ b/site/DataPageV2Content.tsx @@ -34,12 +34,12 @@ import { DataPageRelatedResearch, isEmpty, excludeUndefined, - OwidOrigin, DataPageDataV2, getCitationShort, GrapherInterface, getCitationLong, joinTitleFragments, + getAttributionUnshortened, } from "@ourworldindata/utils" import { AttachmentsContext, DocumentContext } from "./gdocs/OwidGdoc.js" import StickyNav from "./blocks/StickyNav.js" @@ -126,24 +126,12 @@ export const DataPageV2Content = ({ datapageData.descriptionKey && datapageData.descriptionKey.length > 0 const sourcesForDisplay = prepareSourcesForDisplay(datapageData) - const getYearSuffixFromOrigin = (o: OwidOrigin) => { - const year = o.dateAccessed - ? dayjs(o.dateAccessed, ["YYYY-MM-DD", "YYYY"]).year() - : o.datePublished - ? dayjs(o.datePublished, ["YYYY-MM-DD", "YYYY"]).year() - : undefined - if (year) return ` (${year})` - else return "" - } const producers = uniq(datapageData.origins.map((o) => `${o.producer}`)) - const producersWithYear = uniq( - datapageData.origins.map( - (o) => `${o.producer}${getYearSuffixFromOrigin(o)}` - ) - ) - const attributionFragments = datapageData.attributions ?? producersWithYear - const attributionUnshortened = attributionFragments.join("; ") + const attributionUnshortened = getAttributionUnshortened({ + attributions: datapageData.attributions, + origins: datapageData.origins, + }) const citationShort = getCitationShort( datapageData.origins, datapageData.attributions, @@ -751,7 +739,7 @@ const KeyDataTable = (props: { const links = makeLinks({ link: datapageData.source?.link }) return ( -
+
{datapageData.descriptionShort && (
diff --git a/site/gdocs/components/ArticleBlock.tsx b/site/gdocs/components/ArticleBlock.tsx index dbd8506547f..925733200d3 100644 --- a/site/gdocs/components/ArticleBlock.tsx +++ b/site/gdocs/components/ArticleBlock.tsx @@ -70,7 +70,7 @@ const layouts: { [key in Container]: Layouts} = { ["image--narrow"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 col-sm-start-2 span-sm-cols-12", ["image--wide"]: "col-start-4 span-cols-8 col-md-start-2 span-md-cols-12", ["image-caption"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", - ["key-indicator"]: "col-start-5 span-cols-6", + ["key-indicator"]: "col-start-2 span-cols-12", ["key-insights"]: "col-start-2 span-cols-12", ["list"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["numbered-list"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss new file mode 100644 index 00000000000..71ef90c90c6 --- /dev/null +++ b/site/gdocs/components/KeyIndicator.scss @@ -0,0 +1,68 @@ +.key-indicator { + $frame-padding: 24px; + $padding: 16px; + + background-color: $blue-10; + padding: $frame-padding; + margin-bottom: $frame-padding; + + .indicator-title { + @include body-2-semibold; + color: $blue-60; + margin-bottom: 14px; + } + + .narrative-title { + @include h3-bold; + color: $blue-90; + margin-top: 0; + margin-bottom: 8px; + } + + .blurb { + @include body-2-regular; + color: $blue-90; + margin-bottom: $padding; + } + + .blurb a { + @include owid-link-90; + } + + .metadata--border-top { + border-top: solid rgba(164, 182, 202, 0.5); + padding-top: $padding; + } + + .metadata-entry { + @include body-3-medium; + margin-bottom: $padding; + } + + .metadata-entry__title { + color: $blue-50; + } + + .metadata-entry__value { + color: $blue-90; + } + + .datapage-link { + @include body-3-medium; + + display: block; + @include sm-up { + display: inline-block; + } + + height: 40px; + padding: 8px 24px; + text-align: center; + color: $white; + background-color: $blue-60; + cursor: pointer; + border: none; + + margin-top: $padding; + } +} diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index f64d33986d0..1bae644ce86 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -1,5 +1,16 @@ import React from "react" -import { EnrichedBlockKeyIndicator } from "@ourworldindata/types" +import cx from "classnames" + +import { + EnrichedBlockKeyIndicator, + EnrichedBlockText, +} from "@ourworldindata/types" +import { + makeSource, + makeDateRange, + makeLastUpdated, +} from "@ourworldindata/components" + import Chart from "./Chart.js" import Paragraph from "./Paragraph.js" import { useLinkedChart, useLinkedIndicator } from "../utils.js" @@ -19,30 +30,78 @@ export default function KeyIndicator({ if (!linkedChart) return null if (!linkedIndicator) return null + const source = makeSource({ + attribution: linkedIndicator.attributionUnshortened, + hideProcessingLevel: true, + }) + const dateRange = makeDateRange({ + dateRange: linkedIndicator.dateRange, + }) + const lastUpdated = makeLastUpdated({ + lastUpdated: linkedIndicator.lastUpdated, + }) + return ( -
-
- Custom title: {d.title} -
-
- Default title: {linkedChart?.title} -
- {d.blurb && ( -
- Blurb: - {d.blurb.map((textBlock, i) => ( - - ))} +
+
+
{linkedIndicator.title}
+ {d.title &&

{d.title}

} + {d.blurb && ( +
+ {d.blurb.map( + (textBlock: EnrichedBlockText, i: number) => ( + + ) + )} +
+ )} +
+ {source && ( +
+
Source
+
+ {source} +
+
+ )} + {dateRange && ( +
+
+ Date range +
+
+ {dateRange} +
+
+ )} + {lastUpdated && ( +
+
+ Last updated +
+
+ {lastUpdated} +
+
+ )}
- )} + + Explore and learn more about this data + +
-
- Linked indicator with metadata: - {JSON.stringify(linkedIndicator, null, 2)} -
) } diff --git a/site/owid.scss b/site/owid.scss index 1838caee6a9..6be7a3a8209 100644 --- a/site/owid.scss +++ b/site/owid.scss @@ -94,6 +94,7 @@ @import "./gdocs/components/AdditionalCharts.scss"; @import "./gdocs/components/ResearchAndWriting.scss"; @import "./gdocs/components/Chart.scss"; +@import "./gdocs/components/KeyIndicator.scss"; @import "./DataPage.scss"; @import "./DataPageContent.scss"; @import "./detailsOnDemand.scss"; From bbbec74362e1cb87f065e24b8aa92668fca0dd05 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Thu, 25 Jan 2024 10:51:29 +0000 Subject: [PATCH 05/51] =?UTF-8?q?=F0=9F=A4=96=20style:=20prettify=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/src/IndicatorKeyData/IndicatorKeyData.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx b/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx index b8457d9d404..ec4bee4603a 100644 --- a/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx +++ b/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx @@ -23,8 +23,8 @@ export const makeSource = ({ const isEmbedded = isEmbeddedInADataPage ?? true const processingLevelPhrase = getPhraseForProcessingLevel(owidProcessingLevel) - const hideProcessingPhrase = hideProcessingLevel || - attribution.toLowerCase() === "our world in data" + const hideProcessingPhrase = + hideProcessingLevel || attribution.toLowerCase() === "our world in data" return ( <> From 98608da4c1eb3e2af4a59e83143d2f4a66711901 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Thu, 25 Jan 2024 11:36:11 +0000 Subject: [PATCH 06/51] :lipstick: (gdcos) minor css changes --- site/gdocs/components/KeyIndicator.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index 71ef90c90c6..c5a521cb102 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -4,6 +4,7 @@ background-color: $blue-10; padding: $frame-padding; + margin: 0 (-$frame-padding); margin-bottom: $frame-padding; .indicator-title { @@ -55,7 +56,7 @@ display: inline-block; } - height: 40px; + min-height: 40px; padding: 8px 24px; text-align: center; color: $white; From c0f173b8414112e1b628c7dceee3ab3411745cec Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 29 Jan 2024 08:40:00 +0000 Subject: [PATCH 07/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20implement=20key=20i?= =?UTF-8?q?ndicator=20designs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/Chart.tsx | 9 +++-- site/gdocs/components/KeyIndicator.scss | 48 +++++++++++++++++++------ site/gdocs/components/KeyIndicator.tsx | 16 +++++++-- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/site/gdocs/components/Chart.tsx b/site/gdocs/components/Chart.tsx index 73e0120b727..86bd964e51e 100644 --- a/site/gdocs/components/Chart.tsx +++ b/site/gdocs/components/Chart.tsx @@ -20,9 +20,11 @@ import { ExplorerProps } from "../../../explorer/Explorer.js" export default function Chart({ d, className, + shouldOptimizeForHorizontalSpace = true, }: { d: EnrichedBlockChart className?: string + shouldOptimizeForHorizontalSpace?: boolean }) { const refChartContainer = useRef(null) useEmbedChart(0, refChartContainer) @@ -42,11 +44,8 @@ export default function Chart({ // applies to both charts and explorers const common = { - // On mobile, we optimize for horizontal space by having Grapher bleed onto the edges horizontally. - // We want to do this for all stand-alone charts and charts in a Key Insights block, but not for charts - // listed in an All Charts block. The component is not used to render charts in an All Charts block, - // so we can just set this to true here. - shouldOptimizeForHorizontalSpace: true, + // On mobile, we optimize for horizontal space by having Grapher bleed onto the edges horizontally + shouldOptimizeForHorizontalSpace, } // props passed to explorers diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index c5a521cb102..7c509e019fc 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -1,11 +1,13 @@ .key-indicator { - $frame-padding: 24px; - $padding: 16px; - background-color: $blue-10; - padding: $frame-padding; - margin: 0 (-$frame-padding); - margin-bottom: $frame-padding; + + margin: 0 -24px 24px; + padding: 16px 24px; + + @include sm-only { + margin: 0 -16px 16px; + padding: 16px; + } .indicator-title { @include body-2-semibold; @@ -23,21 +25,35 @@ .blurb { @include body-2-regular; color: $blue-90; - margin-bottom: $padding; + margin-bottom: 16px; + + p:first-of-type { + margin-top: 0; + } + + p:last-of-type { + margin-bottom: 0; + } } .blurb a { @include owid-link-90; } + .metadata { + @include sm-only { + display: none; + } + } + .metadata--border-top { border-top: solid rgba(164, 182, 202, 0.5); - padding-top: $padding; + padding-top: 16px; } .metadata-entry { @include body-3-medium; - margin-bottom: $padding; + margin-bottom: 16px; } .metadata-entry__title { @@ -63,7 +79,19 @@ background-color: $blue-60; cursor: pointer; border: none; + } + + .datapage-link-desktop { + margin-top: 8px; + @include sm-only { + display: none; + } + } - margin-top: $padding; + .datapage-link-mobile { + margin-top: 16px; + @include sm-up { + display: none; + } } } diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 1bae644ce86..97c12f64028 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -43,7 +43,7 @@ export default function KeyIndicator({ return (
-
+
{linkedIndicator.title}
{d.title &&

{d.title}

} {d.blurb && ( @@ -94,14 +94,24 @@ export default function KeyIndicator({
)}
- + Explore and learn more about this data
+ + Explore and learn more about this data +
) } From 5b071a1b8cb57ee5f2dfef6c4c8d82485381964d Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 29 Jan 2024 08:45:40 +0000 Subject: [PATCH 08/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20add=20icon=20on=20m?= =?UTF-8?q?obile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicator.scss | 5 +++++ site/gdocs/components/KeyIndicator.tsx | 3 +++ 2 files changed, 8 insertions(+) diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index 7c509e019fc..151afe23b6f 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -93,5 +93,10 @@ @include sm-up { display: none; } + + svg { + font-size: 0.75rem; + margin-left: 8px; + } } } diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 97c12f64028..90d8ed1d312 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -1,5 +1,7 @@ import React from "react" import cx from "classnames" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js" +import { faArrowRight } from "@fortawesome/free-solid-svg-icons" import { EnrichedBlockKeyIndicator, @@ -111,6 +113,7 @@ export default function KeyIndicator({ href={d.datapageUrl} > Explore and learn more about this data +
) From cfed5a8158b9becfa0a12b0b943ffbe8cca62dba Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 29 Jan 2024 10:24:49 +0000 Subject: [PATCH 09/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20add=20description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts | 1 + packages/@ourworldindata/utils/src/metadataHelpers.ts | 1 + site/gdocs/components/KeyIndicator.scss | 7 +++++++ site/gdocs/components/KeyIndicator.tsx | 5 +++++ 4 files changed, 14 insertions(+) diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index ba7e1bfd8e8..04b428e6fbc 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -32,6 +32,7 @@ export interface LinkedIndicator { dateRange?: string lastUpdated?: string attributionUnshortened?: string + descriptionShort?: string } export enum OwidGdocType { diff --git a/packages/@ourworldindata/utils/src/metadataHelpers.ts b/packages/@ourworldindata/utils/src/metadataHelpers.ts index 12c98a5deea..03fdfe2b0a0 100644 --- a/packages/@ourworldindata/utils/src/metadataHelpers.ts +++ b/packages/@ourworldindata/utils/src/metadataHelpers.ts @@ -304,5 +304,6 @@ export function grabMetadataForGdocLinkedIndicator( attributions: getAttributionFragmentsFromVariable(metadata), origins: metadata.origins ?? [], }), + descriptionShort: metadata.descriptionShort, } } diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index 151afe23b6f..c0998f0e54f 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -64,6 +64,13 @@ color: $blue-90; } + .description { + @include body-3-medium; + color: $blue-60; + margin-top: 0; + margin-bottom: 16px; + } + .datapage-link { @include body-3-medium; diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 90d8ed1d312..6de78d439b4 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -96,6 +96,11 @@ export default function KeyIndicator({
)}
+ {linkedIndicator.descriptionShort && ( +

+ {linkedIndicator.descriptionShort} +

+ )} Date: Mon, 29 Jan 2024 13:45:51 +0000 Subject: [PATCH 10/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20show=20short=20attr?= =?UTF-8?q?ibution=20as=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@ourworldindata/types/src/gdocTypes/Gdoc.ts | 3 ++- .../utils/src/metadataHelpers.ts | 6 ++---- site/gdocs/components/KeyIndicator.tsx | 17 ++++++++--------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index 04b428e6fbc..5bee514bec6 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -29,9 +29,10 @@ export interface LinkedChart { export interface LinkedIndicator { id: number title: string + titleVariant?: string + attributionShort?: string dateRange?: string lastUpdated?: string - attributionUnshortened?: string descriptionShort?: string } diff --git a/packages/@ourworldindata/utils/src/metadataHelpers.ts b/packages/@ourworldindata/utils/src/metadataHelpers.ts index 03fdfe2b0a0..aa3d99ec627 100644 --- a/packages/@ourworldindata/utils/src/metadataHelpers.ts +++ b/packages/@ourworldindata/utils/src/metadataHelpers.ts @@ -298,12 +298,10 @@ export function grabMetadataForGdocLinkedIndicator( metadata.display?.name ?? metadata.name ?? "", + titleVariant: metadata.presentation?.titleVariant, + attributionShort: metadata.presentation?.attributionShort, dateRange: metadata.timespan, lastUpdated: getLastUpdatedFromVariable(metadata), - attributionUnshortened: getAttributionUnshortened({ - attributions: getAttributionFragmentsFromVariable(metadata), - origins: metadata.origins ?? [], - }), descriptionShort: metadata.descriptionShort, } } diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 6de78d439b4..66e42d81506 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -7,11 +7,8 @@ import { EnrichedBlockKeyIndicator, EnrichedBlockText, } from "@ourworldindata/types" -import { - makeSource, - makeDateRange, - makeLastUpdated, -} from "@ourworldindata/components" +import { makeDateRange, makeLastUpdated } from "@ourworldindata/components" +import { capitalize, joinTitleFragments } from "@ourworldindata/utils" import Chart from "./Chart.js" import Paragraph from "./Paragraph.js" @@ -32,10 +29,12 @@ export default function KeyIndicator({ if (!linkedChart) return null if (!linkedIndicator) return null - const source = makeSource({ - attribution: linkedIndicator.attributionUnshortened, - hideProcessingLevel: true, - }) + const source = capitalize( + joinTitleFragments( + linkedIndicator.attributionShort, + linkedIndicator.titleVariant + ) + ) const dateRange = makeDateRange({ dateRange: linkedIndicator.dateRange, }) From 1a25cc6caace9b5abe4c00b061aaeea10ce9e4b5 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 29 Jan 2024 13:58:27 +0000 Subject: [PATCH 11/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20render=20short?= =?UTF-8?q?=20description=20as=20markdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicator.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 66e42d81506..bfe7e50f667 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -7,7 +7,11 @@ import { EnrichedBlockKeyIndicator, EnrichedBlockText, } from "@ourworldindata/types" -import { makeDateRange, makeLastUpdated } from "@ourworldindata/components" +import { + makeDateRange, + makeLastUpdated, + SimpleMarkdownText, +} from "@ourworldindata/components" import { capitalize, joinTitleFragments } from "@ourworldindata/utils" import Chart from "./Chart.js" @@ -96,9 +100,11 @@ export default function KeyIndicator({ )}
{linkedIndicator.descriptionShort && ( -

- {linkedIndicator.descriptionShort} -

+
+ +
)}
Date: Tue, 30 Jan 2024 09:22:40 +0000 Subject: [PATCH 12/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20update=20heading?= =?UTF-8?q?=20level?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index bfe7e50f667..678d95098ae 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -50,7 +50,7 @@ export default function KeyIndicator({
{linkedIndicator.title}
- {d.title &&

{d.title}

} + {d.title &&

{d.title}

} {d.blurb && (
{d.blurb.map( From 44a4d841c93434be3ab270ba85fe532f57572e92 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 30 Jan 2024 09:39:45 +0000 Subject: [PATCH 13/51] =?UTF-8?q?=F0=9F=8E=89=20(gdocs)=20add=20optional?= =?UTF-8?q?=20Source=20overwrite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/enrichedToMarkdown.ts | 1 + db/model/Gdoc/enrichedToRaw.ts | 1 + db/model/Gdoc/rawToArchie.ts | 1 + db/model/Gdoc/rawToEnriched.ts | 1 + .../types/src/gdocTypes/ArchieMlComponents.ts | 2 ++ site/gdocs/components/KeyIndicator.tsx | 12 +++++++----- 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/db/model/Gdoc/enrichedToMarkdown.ts b/db/model/Gdoc/enrichedToMarkdown.ts index e9ef6cb4f57..eb63609954a 100644 --- a/db/model/Gdoc/enrichedToMarkdown.ts +++ b/db/model/Gdoc/enrichedToMarkdown.ts @@ -277,6 +277,7 @@ ${links}` { datapageUrl: b.datapageUrl, title: b.title, + source: b.source, // blurb ignored }, exportComponents diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index 871ec8a136f..a8dcf6376a1 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -446,6 +446,7 @@ export function enrichedBlockToRawBlock( value: spansToHtmlText(enriched.value), })) : undefined, + source: b.source, }, } }) diff --git a/db/model/Gdoc/rawToArchie.ts b/db/model/Gdoc/rawToArchie.ts index cbec65b58cf..6f802b98071 100644 --- a/db/model/Gdoc/rawToArchie.ts +++ b/db/model/Gdoc/rawToArchie.ts @@ -628,6 +628,7 @@ function* rawBlockKeyIndicatorToArchieMLString( if (typeof block.value !== "string") { yield* propertyToArchieMLString("datapageUrl", block.value) yield* propertyToArchieMLString("title", block.value) + yield* propertyToArchieMLString("source", block.value) if (block.value.blurb) { yield "[.+blurb]" for (const textBlock of block.value.blurb) { diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index ca948c5bfe5..f352ae6b701 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -1933,6 +1933,7 @@ const parseKeyIndicator = ( datapageUrl: url, blurb: parsedBlurb.length > 0 ? parsedBlurb : undefined, title: val.title, + source: val.source, parseErrors: parsedBlurbErrors, }) as EnrichedBlockKeyIndicator } diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index 3402ec0db86..c70216271cf 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -90,6 +90,7 @@ export type RawBlockKeyIndicatorValue = { datapageUrl?: string title?: string blurb?: RawBlockText[] + source?: string } export type RawBlockKeyIndicator = { @@ -102,6 +103,7 @@ export type EnrichedBlockKeyIndicator = { datapageUrl: string title?: string blurb?: EnrichedBlockText[] + source?: string } & EnrichedBlockWithParseErrors export type RawBlockScroller = { diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 678d95098ae..a552bd3bb70 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -33,12 +33,14 @@ export default function KeyIndicator({ if (!linkedChart) return null if (!linkedIndicator) return null - const source = capitalize( - joinTitleFragments( - linkedIndicator.attributionShort, - linkedIndicator.titleVariant + const source = + d.source || + capitalize( + joinTitleFragments( + linkedIndicator.attributionShort, + linkedIndicator.titleVariant + ) ) - ) const dateRange = makeDateRange({ dateRange: linkedIndicator.dateRange, }) From 2380d0e58f9f03905ff571701413ef1bc920c251 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 30 Jan 2024 14:44:36 +0000 Subject: [PATCH 14/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20hide=20metadata=20d?= =?UTF-8?q?escription=20on=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicator.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index c0998f0e54f..f9442f9efa8 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -40,7 +40,8 @@ @include owid-link-90; } - .metadata { + .metadata, + .description { @include sm-only { display: none; } From 51ee83fe620b52bddc83d202e110f07c41c664dc Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Thu, 1 Feb 2024 09:06:25 +0000 Subject: [PATCH 15/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20update=20key=20indi?= =?UTF-8?q?cator=20design?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/src/styles/typography.scss | 9 ++ site/gdocs/components/KeyIndicator.scss | 56 +++---- site/gdocs/components/KeyIndicator.tsx | 142 ++++++++++-------- 3 files changed, 118 insertions(+), 89 deletions(-) diff --git a/packages/@ourworldindata/components/src/styles/typography.scss b/packages/@ourworldindata/components/src/styles/typography.scss index 6bde1b3849b..cf930ae4fcd 100644 --- a/packages/@ourworldindata/components/src/styles/typography.scss +++ b/packages/@ourworldindata/components/src/styles/typography.scss @@ -260,6 +260,15 @@ body { @include subtitle-2; } +@mixin subtitle-2-bold { + @include subtitle-2; + font-weight: 700; +} + +.subtitle-2-bold { + @include subtitle-2-bold; +} + @mixin overline-black-caps { font-family: $sans-serif-font-stack; font-size: 0.75rem; diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index f9442f9efa8..07074fcda58 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -1,31 +1,43 @@ .key-indicator { - background-color: $blue-10; + background-color: $blue-5; margin: 0 -24px 24px; - padding: 16px 24px; + padding: 24px; @include sm-only { margin: 0 -16px 16px; padding: 16px; } + .left { + @include sm-only { + margin-bottom: 16px; + } + } + .indicator-title { @include body-2-semibold; - color: $blue-60; - margin-bottom: 14px; + color: $blue-90; + margin-bottom: 16px; + } + + .indicator-source { + @include body-3-medium; + display: inline-block; + margin-left: 8px; + color: $blue-50; } .narrative-title { - @include h3-bold; + @include subtitle-2-bold; color: $blue-90; margin-top: 0; - margin-bottom: 8px; + margin-bottom: 16px; } .blurb { @include body-2-regular; color: $blue-90; - margin-bottom: 16px; p:first-of-type { margin-top: 0; @@ -40,21 +52,8 @@ @include owid-link-90; } - .metadata, - .description { - @include sm-only { - display: none; - } - } - - .metadata--border-top { - border-top: solid rgba(164, 182, 202, 0.5); - padding-top: 16px; - } - .metadata-entry { @include body-3-medium; - margin-bottom: 16px; } .metadata-entry__title { @@ -87,10 +86,20 @@ background-color: $blue-60; cursor: pointer; border: none; + + &:hover { + background-color: $blue-90; + } + + svg { + font-size: 0.75rem; + margin-left: 8px; + } } + // TODO: should this be at the bottom of the section? .datapage-link-desktop { - margin-top: 8px; + margin-top: 24px; @include sm-only { display: none; } @@ -101,10 +110,5 @@ @include sm-up { display: none; } - - svg { - font-size: 0.75rem; - margin-left: 8px; - } } } diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index a552bd3bb70..3393bfbd83e 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -6,6 +6,7 @@ import { faArrowRight } from "@fortawesome/free-solid-svg-icons" import { EnrichedBlockKeyIndicator, EnrichedBlockText, + LinkedIndicator, } from "@ourworldindata/types" import { makeDateRange, @@ -41,78 +42,25 @@ export default function KeyIndicator({ linkedIndicator.titleVariant ) ) - const dateRange = makeDateRange({ - dateRange: linkedIndicator.dateRange, - }) - const lastUpdated = makeLastUpdated({ - lastUpdated: linkedIndicator.lastUpdated, - }) return (
-
-
{linkedIndicator.title}
- {d.title &&

{d.title}

} - {d.blurb && ( -
- {d.blurb.map( - (textBlock: EnrichedBlockText, i: number) => ( - - ) - )} -
- )} -
) } + +function IndicatorNarrative({ + block, +}: { + block: EnrichedBlockKeyIndicator +}): JSX.Element { + return ( + <> + {block.title &&

{block.title}

} + {block.blurb && ( +
+ {block.blurb.map( + (textBlock: EnrichedBlockText, i: number) => ( + + ) + )} +
+ )} + + ) +} + +function IndicatorMetadata({ + linkedIndicator, +}: { + linkedIndicator: LinkedIndicator +}): JSX.Element { + const dateRange = makeDateRange({ + dateRange: linkedIndicator.dateRange, + }) + const lastUpdated = makeLastUpdated({ + lastUpdated: linkedIndicator.lastUpdated, + }) + + return ( + <> + {linkedIndicator.descriptionShort && ( +
+ +
+ )} +
+ {dateRange && ( +
+
Date range
+
{dateRange}
+
+ )} + {lastUpdated && ( +
+
+ Last updated +
+
+ {lastUpdated} +
+
+ )} +
+ + ) +} From d0c7bf8300480486db516cf744d5f9cb30bbdd56 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Thu, 1 Feb 2024 09:11:38 +0000 Subject: [PATCH 16/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20update=20narrati?= =?UTF-8?q?ve=20title=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/src/styles/typography.scss | 9 --------- site/gdocs/components/KeyIndicator.scss | 4 +++- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/@ourworldindata/components/src/styles/typography.scss b/packages/@ourworldindata/components/src/styles/typography.scss index cf930ae4fcd..6bde1b3849b 100644 --- a/packages/@ourworldindata/components/src/styles/typography.scss +++ b/packages/@ourworldindata/components/src/styles/typography.scss @@ -260,15 +260,6 @@ body { @include subtitle-2; } -@mixin subtitle-2-bold { - @include subtitle-2; - font-weight: 700; -} - -.subtitle-2-bold { - @include subtitle-2-bold; -} - @mixin overline-black-caps { font-family: $sans-serif-font-stack; font-size: 0.75rem; diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index 07074fcda58..35b98440a6e 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -29,7 +29,9 @@ } .narrative-title { - @include subtitle-2-bold; + font-size: 1.5rem; + font-weight: 700; + line-height: calc(28 / 22); color: $blue-90; margin-top: 0; margin-bottom: 16px; From 725cf32014104bcebeda7a08dfbbd1635bb92e01 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Fri, 2 Feb 2024 16:32:01 +0000 Subject: [PATCH 17/51] =?UTF-8?q?=F0=9F=90=9B=20(gdocs)=20link=20to=20the?= =?UTF-8?q?=20resolved=20url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicator.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 3393bfbd83e..5e813740b92 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -57,7 +57,7 @@ export default function KeyIndicator({ )} Explore and learn more about this data @@ -65,12 +65,16 @@ export default function KeyIndicator({
Explore and learn more about this data From ac801d3ab590c543db22f25fb03de7972c562faa Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Fri, 2 Feb 2024 16:53:54 +0000 Subject: [PATCH 18/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20require=20title?= =?UTF-8?q?=20&=20blurb=20properties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/enrichedToRaw.ts | 10 +- db/model/Gdoc/exampleEnrichedBlocks.ts | 2 + db/model/Gdoc/rawToEnriched.ts | 15 ++- .../types/src/gdocTypes/ArchieMlComponents.ts | 4 +- .../types/src/gdocTypes/Gdoc.ts | 6 +- .../utils/src/metadataHelpers.ts | 18 ++-- site/gdocs/components/KeyIndicator.tsx | 97 ++----------------- 7 files changed, 37 insertions(+), 115 deletions(-) diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index a8dcf6376a1..0cf570f1f8a 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -440,12 +440,10 @@ export function enrichedBlockToRawBlock( value: { datapageUrl: b.datapageUrl, title: b.title, - blurb: b.blurb - ? b.blurb.map((enriched) => ({ - type: "text", - value: spansToHtmlText(enriched.value), - })) - : undefined, + blurb: b.blurb.map((enriched) => ({ + type: "text", + value: spansToHtmlText(enriched.value), + })), source: b.source, }, } diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index 446e3981c65..c93721b7c35 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -538,6 +538,8 @@ export const enrichedBlockExamples: Record< "key-indicator": { type: "key-indicator", datapageUrl: "https://ourworldindata.org/grapher/life-expectancy", + title: "How did people's life expectancy change over time?", + blurb: [enrichedBlockText], parseErrors: [], }, } diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index f352ae6b701..d6ca2bdec7c 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -1894,6 +1894,8 @@ const parseKeyIndicator = ( ): EnrichedBlockKeyIndicator => ({ type: "key-indicator", datapageUrl, + title: "", + blurb: [], parseErrors: [error], }) @@ -1915,7 +1917,13 @@ const parseKeyIndicator = ( const url = extractUrl(val.datapageUrl) - if (val.blurb && !isArray(val.blurb)) + if (!val.title) + return createError( + { message: "title property is missing or empty" }, + url + ) + + if (!isArray(val.blurb)) return createError( { message: @@ -1924,14 +1932,13 @@ const parseKeyIndicator = ( url ) - const blurb = val.blurb ?? [] - const parsedBlurb = blurb.map(parseText) + const parsedBlurb = val.blurb.map(parseText) const parsedBlurbErrors = parsedBlurb.flatMap((block) => block.parseErrors) return omitUndefinedValues({ type: "key-indicator", datapageUrl: url, - blurb: parsedBlurb.length > 0 ? parsedBlurb : undefined, + blurb: parsedBlurb, title: val.title, source: val.source, parseErrors: parsedBlurbErrors, diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index c70216271cf..0d843b4efec 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -101,8 +101,8 @@ export type RawBlockKeyIndicator = { export type EnrichedBlockKeyIndicator = { type: "key-indicator" datapageUrl: string - title?: string - blurb?: EnrichedBlockText[] + title: string + blurb: EnrichedBlockText[] source?: string } & EnrichedBlockWithParseErrors diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index 5bee514bec6..235b78eb4ad 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -29,11 +29,7 @@ export interface LinkedChart { export interface LinkedIndicator { id: number title: string - titleVariant?: string - attributionShort?: string - dateRange?: string - lastUpdated?: string - descriptionShort?: string + source?: string } export enum OwidGdocType { diff --git a/packages/@ourworldindata/utils/src/metadataHelpers.ts b/packages/@ourworldindata/utils/src/metadataHelpers.ts index aa3d99ec627..369706e70b4 100644 --- a/packages/@ourworldindata/utils/src/metadataHelpers.ts +++ b/packages/@ourworldindata/utils/src/metadataHelpers.ts @@ -7,6 +7,7 @@ import { OwidSource, OwidVariableWithSourceAndDimension, LinkedIndicator, + joinTitleFragments, } from "@ourworldindata/types" import { compact, uniq, last, excludeUndefined } from "./Util" import dayjs from "./dayjs.js" @@ -293,15 +294,14 @@ export function grabMetadataForGdocLinkedIndicator( ): Omit { return { title: - metadata.presentation?.titlePublic ?? - metadata.presentation?.grapherConfigETL?.title ?? - metadata.display?.name ?? - metadata.name ?? + metadata.presentation?.titlePublic || + metadata.presentation?.grapherConfigETL?.title || + metadata.display?.name || + metadata.name || "", - titleVariant: metadata.presentation?.titleVariant, - attributionShort: metadata.presentation?.attributionShort, - dateRange: metadata.timespan, - lastUpdated: getLastUpdatedFromVariable(metadata), - descriptionShort: metadata.descriptionShort, + source: joinTitleFragments( + metadata.presentation?.attributionShort, + metadata.presentation?.titleVariant + ), } } diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 5e813740b92..1c9e491377e 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -6,14 +6,8 @@ import { faArrowRight } from "@fortawesome/free-solid-svg-icons" import { EnrichedBlockKeyIndicator, EnrichedBlockText, - LinkedIndicator, } from "@ourworldindata/types" -import { - makeDateRange, - makeLastUpdated, - SimpleMarkdownText, -} from "@ourworldindata/components" -import { capitalize, joinTitleFragments } from "@ourworldindata/utils" +import { capitalize } from "@ourworldindata/utils" import Chart from "./Chart.js" import Paragraph from "./Paragraph.js" @@ -34,14 +28,7 @@ export default function KeyIndicator({ if (!linkedChart) return null if (!linkedIndicator) return null - const source = - d.source || - capitalize( - joinTitleFragments( - linkedIndicator.attributionShort, - linkedIndicator.titleVariant - ) - ) + const source = capitalize(d.source || linkedIndicator.source) return (
@@ -50,11 +37,12 @@ export default function KeyIndicator({ {linkedIndicator.title}{" "} {source}
- {d.title || d.blurb ? ( - - ) : ( - - )} +

{d.title}

+
+ {d.blurb.map((textBlock: EnrichedBlockText, i: number) => ( + + ))} +
) } - -function IndicatorNarrative({ - block, -}: { - block: EnrichedBlockKeyIndicator -}): JSX.Element { - return ( - <> - {block.title &&

{block.title}

} - {block.blurb && ( -
- {block.blurb.map( - (textBlock: EnrichedBlockText, i: number) => ( - - ) - )} -
- )} - - ) -} - -function IndicatorMetadata({ - linkedIndicator, -}: { - linkedIndicator: LinkedIndicator -}): JSX.Element { - const dateRange = makeDateRange({ - dateRange: linkedIndicator.dateRange, - }) - const lastUpdated = makeLastUpdated({ - lastUpdated: linkedIndicator.lastUpdated, - }) - - return ( - <> - {linkedIndicator.descriptionShort && ( -
- -
- )} -
- {dateRange && ( -
-
Date range
-
{dateRange}
-
- )} - {lastUpdated && ( -
-
- Last updated -
-
- {lastUpdated} -
-
- )} -
- - ) -} From 8a440a0ae6ab16feca0b91c86ad6a82ea867087f Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 5 Feb 2024 10:50:50 +0000 Subject: [PATCH 19/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20minor=20css=20up?= =?UTF-8?q?dates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicator.scss | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index 35b98440a6e..b129be999ce 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -1,7 +1,7 @@ .key-indicator { background-color: $blue-5; - margin: 0 -24px 24px; + margin: 24px -24px; padding: 24px; @include sm-only { @@ -78,16 +78,21 @@ display: block; @include sm-up { - display: inline-block; + display: inline-flex; + align-items: center; } min-height: 40px; padding: 8px 24px; + margin-top: 16px; text-align: center; color: $white; background-color: $blue-60; cursor: pointer; border: none; + display: flex; + align-items: center; + justify-content: center; &:hover { background-color: $blue-90; @@ -99,16 +104,13 @@ } } - // TODO: should this be at the bottom of the section? .datapage-link-desktop { - margin-top: 24px; @include sm-only { display: none; } } .datapage-link-mobile { - margin-top: 16px; @include sm-up { display: none; } From 44e0e3c1c9e39dd1c4fabbbcb2e9fe054bd1f0fb Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 5 Feb 2024 14:11:39 +0000 Subject: [PATCH 20/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/GdocBase.ts | 31 +++++++++++++++++-- db/model/Gdoc/GdocPost.ts | 22 +------------ db/model/Gdoc/rawToEnriched.ts | 20 ++++++------ .../src/IndicatorKeyData/IndicatorKeyData.tsx | 8 ++--- .../types/src/gdocTypes/Gdoc.ts | 2 +- packages/@ourworldindata/utils/src/index.ts | 1 - .../utils/src/metadataHelpers.ts | 16 +--------- site/DataPageV2Content.tsx | 22 ++++++++++--- site/gdocs/components/KeyIndicator.tsx | 2 +- 9 files changed, 61 insertions(+), 63 deletions(-) diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index bbf38e42684..1d34439c5fb 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -521,6 +521,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { "horizontal-rule", "html", "image", + "key-indicator", "list", "missing-data", "numbered-list", @@ -532,8 +533,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { "sticky-left", "sticky-right", "table", - "text", - "key-indicator" + "text" ), }, () => [] @@ -744,8 +744,33 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { [] ) + // Validate that charts referenced in key-indicator blocks render a datapage + const contentErrors: OwidGdocErrorMessage[] = [] + for (const enrichedBlockSource of this.enrichedBlockSources) { + enrichedBlockSource.forEach((block) => + traverseEnrichedBlocks(block, (block) => { + if (block.type === "key-indicator" && block.datapageUrl) { + const slug = urlToSlug(block.datapageUrl) + const linkedChart = this.linkedCharts?.[slug] + if (linkedChart && !linkedChart.indicatorId) { + contentErrors.push({ + property: "body", + type: OwidGdocErrorMessageType.Error, + message: `Grapher chart with slug ${slug} is not a datapage`, + }) + } + } + }) + ) + } + const subclassErrors = await this._validateSubclass(this) - this.errors = [...filenameErrors, ...linkErrors, ...subclassErrors] + this.errors = [ + ...filenameErrors, + ...linkErrors, + ...contentErrors, + ...subclassErrors, + ] } async loadState( diff --git a/db/model/Gdoc/GdocPost.ts b/db/model/Gdoc/GdocPost.ts index 142d66e0ee9..2a74cd05424 100644 --- a/db/model/Gdoc/GdocPost.ts +++ b/db/model/Gdoc/GdocPost.ts @@ -12,8 +12,7 @@ import { RawBlockText, RelatedChart, OwidGdocMinimalPostInterface, -} from "@ourworldindata/types" -import { traverseEnrichedBlocks, urlToSlug } from "@ourworldindata/utils" +} from "@ourworldindata/utils" import { GDOCS_DETAILS_ON_DEMAND_ID } from "../../../settings/serverSettings.js" import { formatCitation, @@ -145,25 +144,6 @@ export class GdocPost extends GdocBase implements OwidGdocPostInterface { } } - // Validate that charts referenced in key-indicator blocks render a datapage - for (const enrichedBlockSource of this.enrichedBlockSources) { - enrichedBlockSource.forEach((block) => - traverseEnrichedBlocks(block, (block) => { - if (block.type === "key-indicator" && block.datapageUrl) { - const slug = urlToSlug(block.datapageUrl) - const linkedChart = this.linkedCharts?.[slug] - if (!linkedChart?.indicatorId) { - errors.push({ - property: "body", - type: OwidGdocErrorMessageType.Error, - message: `Grapher chart with slug ${slug} is not a datapage`, - }) - } - } - }) - ) - } - return errors } diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index d6ca2bdec7c..ba810c1fd3a 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -1890,7 +1890,7 @@ const parseKeyIndicator = ( ): EnrichedBlockKeyIndicator => { const createError = ( error: ParseError, - datapageUrl: string + datapageUrl = "" ): EnrichedBlockKeyIndicator => ({ type: "key-indicator", datapageUrl, @@ -1902,18 +1902,14 @@ const parseKeyIndicator = ( const val = raw.value if (typeof val === "string") - return createError( - { - message: "Value is a string, not an object with properties", - }, - "" - ) + return createError({ + message: "Value is a string, not an object with properties", + }) if (!val.datapageUrl) - return createError( - { message: "datapageUrl property is missing or empty" }, - "" - ) + return createError({ + message: "datapageUrl property is missing or empty", + }) const url = extractUrl(val.datapageUrl) @@ -1923,6 +1919,8 @@ const parseKeyIndicator = ( url ) + if (!val.blurb) return createError({ message: "blurb is missing" }, url) + if (!isArray(val.blurb)) return createError( { diff --git a/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx b/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx index ec4bee4603a..eae6ba63347 100644 --- a/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx +++ b/packages/@ourworldindata/components/src/IndicatorKeyData/IndicatorKeyData.tsx @@ -12,23 +12,21 @@ export const makeSource = ({ attribution, owidProcessingLevel, isEmbeddedInADataPage, - hideProcessingLevel = false, }: { attribution?: string owidProcessingLevel?: OwidProcessingLevel isEmbeddedInADataPage?: boolean - hideProcessingLevel?: boolean }): React.ReactNode => { if (!attribution) return null const isEmbedded = isEmbeddedInADataPage ?? true const processingLevelPhrase = getPhraseForProcessingLevel(owidProcessingLevel) - const hideProcessingPhrase = - hideProcessingLevel || attribution.toLowerCase() === "our world in data" + const hideProcessingPhase = + attribution.toLowerCase() === "our world in data" return ( <> - {!hideProcessingPhrase && ( + {!hideProcessingPhase && ( <> {" – "} {isEmbedded ? ( diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index 235b78eb4ad..694145cd9fe 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -29,7 +29,7 @@ export interface LinkedChart { export interface LinkedIndicator { id: number title: string - source?: string + attributionShort?: string } export enum OwidGdocType { diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index e259bd14943..fef71053693 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -133,7 +133,6 @@ export { getCitationLong, getCitationShort, grabMetadataForGdocLinkedIndicator, - getAttributionUnshortened, } from "./metadataHelpers.js" export { diff --git a/packages/@ourworldindata/utils/src/metadataHelpers.ts b/packages/@ourworldindata/utils/src/metadataHelpers.ts index 369706e70b4..064716558f9 100644 --- a/packages/@ourworldindata/utils/src/metadataHelpers.ts +++ b/packages/@ourworldindata/utils/src/metadataHelpers.ts @@ -275,20 +275,6 @@ export const formatSourceDate = ( return parsedDate.format(format) } -export function getAttributionUnshortened({ - origins, - attributions, -}: { - origins: OwidOrigin[] - attributions: string[] -}): string { - const producersWithYear = uniq( - origins.map((o) => `${o.producer}${getYearSuffixFromOrigin(o)}`) - ) - const attributionFragments = attributions ?? producersWithYear - return attributionFragments.join("; ") -} - export function grabMetadataForGdocLinkedIndicator( metadata: OwidVariableWithSourceAndDimension ): Omit { @@ -299,7 +285,7 @@ export function grabMetadataForGdocLinkedIndicator( metadata.display?.name || metadata.name || "", - source: joinTitleFragments( + attributionShort: joinTitleFragments( metadata.presentation?.attributionShort, metadata.presentation?.titleVariant ), diff --git a/site/DataPageV2Content.tsx b/site/DataPageV2Content.tsx index a823dd4b1d8..5c59a82651c 100644 --- a/site/DataPageV2Content.tsx +++ b/site/DataPageV2Content.tsx @@ -34,12 +34,12 @@ import { DataPageRelatedResearch, isEmpty, excludeUndefined, + OwidOrigin, DataPageDataV2, getCitationShort, GrapherInterface, getCitationLong, joinTitleFragments, - getAttributionUnshortened, } from "@ourworldindata/utils" import { AttachmentsContext, DocumentContext } from "./gdocs/OwidGdoc.js" import StickyNav from "./blocks/StickyNav.js" @@ -126,12 +126,24 @@ export const DataPageV2Content = ({ datapageData.descriptionKey && datapageData.descriptionKey.length > 0 const sourcesForDisplay = prepareSourcesForDisplay(datapageData) + const getYearSuffixFromOrigin = (o: OwidOrigin) => { + const year = o.dateAccessed + ? dayjs(o.dateAccessed, ["YYYY-MM-DD", "YYYY"]).year() + : o.datePublished + ? dayjs(o.datePublished, ["YYYY-MM-DD", "YYYY"]).year() + : undefined + if (year) return ` (${year})` + else return "" + } const producers = uniq(datapageData.origins.map((o) => `${o.producer}`)) + const producersWithYear = uniq( + datapageData.origins.map( + (o) => `${o.producer}${getYearSuffixFromOrigin(o)}` + ) + ) - const attributionUnshortened = getAttributionUnshortened({ - attributions: datapageData.attributions, - origins: datapageData.origins, - }) + const attributionFragments = datapageData.attributions ?? producersWithYear + const attributionUnshortened = attributionFragments.join("; ") const citationShort = getCitationShort( datapageData.origins, datapageData.attributions, diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 1c9e491377e..807ea4f4d63 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -28,7 +28,7 @@ export default function KeyIndicator({ if (!linkedChart) return null if (!linkedIndicator) return null - const source = capitalize(d.source || linkedIndicator.source) + const source = capitalize(d.source || linkedIndicator.attributionShort) return (
From 537847570635215df61329bf7af9f623fce2bbfb Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Wed, 7 Feb 2024 13:14:45 +0000 Subject: [PATCH 21/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20make=20title=20f?= =?UTF-8?q?ont-size=20smaller=20for=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicator.scss | 11 +++++++++-- site/gdocs/components/KeyIndicator.tsx | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index b129be999ce..66b6f8d875a 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -15,16 +15,19 @@ } } + .indicator-metadata { + margin-bottom: 16px; + } + .indicator-title { @include body-2-semibold; color: $blue-90; - margin-bottom: 16px; + margin-right: 8px; } .indicator-source { @include body-3-medium; display: inline-block; - margin-left: 8px; color: $blue-50; } @@ -35,6 +38,10 @@ color: $blue-90; margin-top: 0; margin-bottom: 16px; + + @include sm-only { + font-size: 1.25rem; + } } .blurb { diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 807ea4f4d63..881c750a26e 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -33,8 +33,10 @@ export default function KeyIndicator({ return (
-
- {linkedIndicator.title}{" "} +
+ + {linkedIndicator.title} + {" "} {source}

{d.title}

From beb4efd9846ecd8a44acb03e4611a3525fbbd2d5 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Wed, 7 Feb 2024 19:20:02 +0000 Subject: [PATCH 22/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20use=20--grid-gap?= =?UTF-8?q?=20for=20horizontal=20padding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicator.scss | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index 66b6f8d875a..0f0c40201a7 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -1,13 +1,10 @@ .key-indicator { - background-color: $blue-5; + --padding: var(--grid-gap, 24px); - margin: 24px -24px; - padding: 24px; + background-color: $blue-5; - @include sm-only { - margin: 0 -16px 16px; - padding: 16px; - } + margin: 24px calc(-1 * var(--padding)); + padding: var(--padding); .left { @include sm-only { From f75ea157d33991ffd350f70a3d94f2beda221313 Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Thu, 15 Feb 2024 19:00:04 +0000 Subject: [PATCH 23/51] =?UTF-8?q?=E2=9C=A8=20track=20links=20in=20key-indi?= =?UTF-8?q?cator=20block?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/GdocBase.ts | 10 +++++++++- db/model/Gdoc/rawToEnriched.ts | 2 +- packages/@ourworldindata/utils/src/Util.ts | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 1d34439c5fb..15a2bcf52ae 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -504,6 +504,15 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { }), ] }) + .with({ type: "key-indicator" }, (block) => { + return [ + Link.createFromUrl({ + url: block.datapageUrl, + source: this, + componentType: block.type, + }), + ] + }) .with( { // no urls directly on any of these blocks @@ -521,7 +530,6 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { "horizontal-rule", "html", "image", - "key-indicator", "list", "missing-data", "numbered-list", diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index ba810c1fd3a..9219abcbf0c 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -1903,7 +1903,7 @@ const parseKeyIndicator = ( if (typeof val === "string") return createError({ - message: "Value is a string, not an object with properties", + message: `key-indicator block must be written as "{.key-indicator}"`, }) if (!val.datapageUrl) diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index 9a856c2373a..96c93b53ddc 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1624,6 +1624,17 @@ export function traverseEnrichedBlocks( traverseEnrichedBlocks(node, callback, spanCallback) }) }) + .with( + { + type: "key-indicator", + }, + (keyIndicator) => { + callback(keyIndicator) + keyIndicator.blurb.forEach((node) => { + traverseEnrichedBlocks(node, callback, spanCallback) + }) + } + ) .with( { type: P.union( @@ -1643,8 +1654,7 @@ export function traverseEnrichedBlocks( "sdg-toc", "topic-page-intro", "all-charts", - "entry-summary", - "key-indicator" + "entry-summary" ), }, callback From 7325be4b251d1c34591584d5025207f6bb6cfc30 Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Thu, 15 Feb 2024 19:11:59 +0000 Subject: [PATCH 24/51] =?UTF-8?q?=F0=9F=94=A8=20add=20comment=20explaining?= =?UTF-8?q?=20LinkedIndicators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index 694145cd9fe..f406dfb2afd 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -26,6 +26,12 @@ export interface LinkedChart { indicatorId?: number // in case of a datapage } +/** + * A linked indicator is derived from a linked grapher's config (see: getVariableOfDatapageIfApplicable) + * e.g. https://ourworldindata.org/grapher/tomato-production -> config for grapher with { slug: "tomato-production" } -> indicator metadata + * currently we only attach a small amount of metadata that we need for key-indicator blocks. + * In the future we might want to attach more metadata, e.g. the indicator's description, source, etc + */ export interface LinkedIndicator { id: number title: string From aa13e0ac15e578a35d123c9cac95569121d633aa Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Wed, 24 Jan 2024 10:33:19 +0100 Subject: [PATCH 25/51] :construction: (gdocs) add chart-book component prototype --- db/model/Gdoc/GdocBase.ts | 3 +- db/model/Gdoc/enrichedToMarkdown.ts | 6 + db/model/Gdoc/enrichedToRaw.ts | 7 + db/model/Gdoc/exampleEnrichedBlocks.ts | 20 ++ db/model/Gdoc/rawToArchie.ts | 13 ++ db/model/Gdoc/rawToEnriched.ts | 54 +++++ .../types/src/gdocTypes/ArchieMlComponents.ts | 12 + .../types/src/gdocTypes/Gdoc.ts | 1 + packages/@ourworldindata/types/src/index.ts | 2 + packages/@ourworldindata/utils/src/Util.ts | 6 + site/gdocs/components/ArticleBlock.tsx | 5 + site/gdocs/components/ChartBook.scss | 94 ++++++++ site/gdocs/components/ChartBook.tsx | 221 ++++++++++++++++++ site/owid.scss | 1 + 14 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 site/gdocs/components/ChartBook.scss create mode 100644 site/gdocs/components/ChartBook.tsx diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 15a2bcf52ae..c48156e4c16 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -541,7 +541,8 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { "sticky-left", "sticky-right", "table", - "text" + "text", + "chart-book" ), }, () => [] diff --git a/db/model/Gdoc/enrichedToMarkdown.ts b/db/model/Gdoc/enrichedToMarkdown.ts index eb63609954a..c6322e81c4c 100644 --- a/db/model/Gdoc/enrichedToMarkdown.ts +++ b/db/model/Gdoc/enrichedToMarkdown.ts @@ -283,5 +283,11 @@ ${links}` exportComponents ) ) + .with({ type: "chart-book" }, (b): string | undefined => { + const keyIndicators = b.blocks.map((keyIndicatorBlock) => + enrichedBlockToMarkdown(keyIndicatorBlock, exportComponents) + ) + return `\n${keyIndicators.join("\n")}\n` + }) .exhaustive() } diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index 0cf570f1f8a..507cdc3dd6b 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -37,6 +37,7 @@ import { RawBlockTable, RawBlockBlockquote, RawBlockKeyIndicator, + RawBlockChartBook, } from "@ourworldindata/types" import { spanToHtmlString } from "./gdocUtils.js" import { match, P } from "ts-pattern" @@ -448,5 +449,11 @@ export function enrichedBlockToRawBlock( }, } }) + .with({ type: "chart-book" }, (b): RawBlockChartBook => { + return { + type: "chart-book", + value: b.blocks.map(enrichedBlockToRawBlock), + } + }) .exhaustive() } diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index c93721b7c35..dff799e810f 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -542,4 +542,24 @@ export const enrichedBlockExamples: Record< blurb: [enrichedBlockText], parseErrors: [], }, + "chart-book": { + type: "chart-book", + blocks: [ + { + type: "key-indicator", + datapageUrl: + "https://ourworldindata.org/grapher/life-expectancy", + blurb: [enrichedBlockText], + parseErrors: [], + }, + { + type: "key-indicator", + datapageUrl: + "https://ourworldindata.org/grapher/share-of-population-in-extreme-poverty", + blurb: [enrichedBlockText], + parseErrors: [], + }, + ], + parseErrors: [], + }, } diff --git a/db/model/Gdoc/rawToArchie.ts b/db/model/Gdoc/rawToArchie.ts index 6f802b98071..b86068c1de7 100644 --- a/db/model/Gdoc/rawToArchie.ts +++ b/db/model/Gdoc/rawToArchie.ts @@ -36,6 +36,7 @@ import { RawBlockTableRow, RawBlockBlockquote, RawBlockKeyIndicator, + RawBlockChartBook, } from "@ourworldindata/types" import { isArray } from "@ourworldindata/utils" import { match } from "ts-pattern" @@ -640,6 +641,17 @@ function* rawBlockKeyIndicatorToArchieMLString( yield "{}" } +function* rawBlockChartBookToArchieMLString( + block: RawBlockChartBook +): Generator { + yield "[.+chart-book]" + if (typeof block.value !== "string") { + for (const b of block.value) + yield* OwidRawGdocBlockToArchieMLStringGenerator(b) + } + yield "[]" +} + export function* OwidRawGdocBlockToArchieMLStringGenerator( block: OwidRawGdocBlock | RawBlockTableRow ): Generator { @@ -705,6 +717,7 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator( .with({ type: "table-row" }, rawBlockRowToArchieMLString) .with({ type: "blockquote" }, rawBlockBlockquoteToArchieMLString) .with({ type: "key-indicator" }, rawBlockKeyIndicatorToArchieMLString) + .with({ type: "chart-book" }, rawBlockChartBookToArchieMLString) .exhaustive() yield* content } diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index 9219abcbf0c..6c905764cce 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -101,6 +101,8 @@ import { tableTemplates, RawBlockBlockquote, EnrichedBlockBlockquote, + RawBlockChartBook, + EnrichedBlockChartBook, } from "@ourworldindata/types" import { traverseEnrichedSpan, @@ -199,6 +201,7 @@ export function parseRawBlocksToEnrichedBlocks( .with({ type: "entry-summary" }, parseEntrySummary) .with({ type: "table" }, parseTable) .with({ type: "key-indicator" }, parseKeyIndicator) + .with({ type: "chart-book" }, parseChartBook) .exhaustive() } @@ -1942,3 +1945,54 @@ const parseKeyIndicator = ( parseErrors: parsedBlurbErrors, }) as EnrichedBlockKeyIndicator } + +function parseChartBook(raw: RawBlockChartBook): EnrichedBlockChartBook { + const createError = (error: ParseError): EnrichedBlockChartBook => ({ + type: "chart-book", + blocks: [], + parseErrors: [error], + }) + + const warnings = [] + + if (!Array.isArray(raw.value)) { + return createError({ + message: + "chart-book is not a freeform array. Make sure you've written [.+chart-book]", + }) + } + + const blocks = raw.value + const keyIndicatorBlocks = blocks.filter( + (block) => block.type === "key-indicator" + ) + + if (keyIndicatorBlocks.length < blocks.length) { + warnings.push({ + message: + "chart-book contains blocks that are not key-indicators blocks", + isWarning: true, + }) + } + + const parsedBlocks = compact( + keyIndicatorBlocks.map(parseRawBlocksToEnrichedBlocks) + ) as EnrichedBlockKeyIndicator[] + + if (parsedBlocks.length <= 1) { + const message = + parsedBlocks.length === 0 + ? "chart-book contains no key-indicator blocks" + : "chart-book contains only one key-indicator block" + warnings.push({ + message, + isWarning: true, + }) + } + + return { + type: "chart-book", + blocks: parsedBlocks, + parseErrors: warnings, + } +} diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index 0d843b4efec..d57c2e43a4b 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -106,6 +106,16 @@ export type EnrichedBlockKeyIndicator = { source?: string } & EnrichedBlockWithParseErrors +export type RawBlockChartBook = { + type: "chart-book" + value: OwidRawGdocBlock[] +} + +export type EnrichedBlockChartBook = { + type: "chart-book" + blocks: EnrichedBlockKeyIndicator[] +} & EnrichedBlockWithParseErrors + export type RawBlockScroller = { type: "scroller" value: OwidRawGdocBlock[] | ArchieMLUnexpectedNonObjectValue @@ -744,6 +754,7 @@ export type OwidRawGdocBlock = | RawBlockTable | RawBlockBlockquote | RawBlockKeyIndicator + | RawBlockChartBook export type OwidEnrichedGdocBlock = | EnrichedBlockAllCharts @@ -782,3 +793,4 @@ export type OwidEnrichedGdocBlock = | EnrichedBlockTable | EnrichedBlockBlockquote | EnrichedBlockKeyIndicator + | EnrichedBlockChartBook diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index f406dfb2afd..bd12e7f337a 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -11,6 +11,7 @@ import { RawBlockText, RefDictionary, } from "./ArchieMlComponents.js" +import { OwidVariableWithSourceAndDimension } from "../OwidVariable.js" export enum OwidGdocPublicationContext { unlisted = "unlisted", diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index 74be08055e9..00002c8b61d 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -192,6 +192,7 @@ export { type RawBlockTopicPageIntro, type RawBlockUrl, type RawBlockKeyIndicator, + type RawBlockChartBook, tableTemplates, type TableTemplate, tableSizes, @@ -250,6 +251,7 @@ export { type EnrichedBlockTableRow, type EnrichedBlockTableCell, type EnrichedBlockKeyIndicator, + type EnrichedBlockChartBook, type RawBlockResearchAndWritingRow, } from "./gdocTypes/ArchieMlComponents.js" export { diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index 96c93b53ddc..d9d9570e4f3 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1624,6 +1624,12 @@ export function traverseEnrichedBlocks( traverseEnrichedBlocks(node, callback, spanCallback) }) }) + .with({ type: "chart-book" }, (chartBook) => { + callback(chartBook) + chartBook.blocks.forEach((node) => + traverseEnrichedBlocks(node, callback, spanCallback) + ) + }) .with( { type: "key-indicator", diff --git a/site/gdocs/components/ArticleBlock.tsx b/site/gdocs/components/ArticleBlock.tsx index 925733200d3..7815f1287c4 100644 --- a/site/gdocs/components/ArticleBlock.tsx +++ b/site/gdocs/components/ArticleBlock.tsx @@ -35,6 +35,7 @@ import { AllCharts } from "./AllCharts.js" import Video from "./Video.js" import { Table } from "./Table.js" import KeyIndicator from "./KeyIndicator.js" +import ChartBook from "./ChartBook.js" export type Container = | "default" @@ -58,6 +59,7 @@ const layouts: { [key in Container]: Layouts} = { ["all-charts"]: "col-start-2 span-cols-12", ["aside-left"]: "col-start-2 span-cols-3 span-md-cols-10 col-md-start-3", ["aside-right"]: "col-start-11 span-cols-3 span-md-cols-10 col-md-start-3", + ["chart-book"]: "col-start-2 span-cols-12", ["chart-story"]: "col-start-4 span-cols-8 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["chart"]: "col-start-4 span-cols-8 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["default"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", @@ -628,6 +630,9 @@ export default function ArticleBlock({ .with({ type: "key-indicator" }, (block) => ( )) + .with({ type: "chart-book" }, (block) => ( + + )) .exhaustive() return ( diff --git a/site/gdocs/components/ChartBook.scss b/site/gdocs/components/ChartBook.scss new file mode 100644 index 00000000000..ebef2b924ff --- /dev/null +++ b/site/gdocs/components/ChartBook.scss @@ -0,0 +1,94 @@ +.chart-book { + $padding: 24px; + $icon-width: 24px; + $duration: 0.4s; // TODO + + margin: 0 (-$padding); + + .chart-book__header { + padding: $padding; + } + + .accordion-item { + background-color: whitesmoke; + } + + .accordion-item + .accordion-item { + margin-top: 4px; + } + + .accordion-item__header { + width: 100%; + text-align: left; + padding: $padding; + } + + // .accordion-item__header > *:nth-last-child(2) { + // margin-right: $icon-width; + // } + + // .accordion-item__header > *:last-child { + // position: absolute; + // width: $icon-width; + // top: $padding; + // right: $padding; + // text-align: right; + // } + + .accordion-item__content { + padding: 0 $padding; + overflow: hidden; + transition: height $duration cubic-bezier(0.76, 0, 0.24, 1); + } + + h3 { + margin-top: 0; + } + + .spacer { + width: 100%; + height: $padding; + } + + .key-indicator-header__title { + @include body-2-semibold; + color: $blue-90; + } + + .key-indicator-header__metadata { + @include body-3-medium; + color: #60758c; + opacity: 0; + transition: opacity 0.2s; // TODO + } + + .key-indicator-header__metadata.visible { + opacity: 1 !important; + transition: opacity 0.2s $duration; // TODO + } + + .metadata-entry + .metadata-entry { + margin-top: 8px; + } + + .metadata-entry__title { + @include body-3-medium; + color: $blue-50; + } + + .metadata-entry__value { + @include body-3-medium; + color: $blue-90; + } + + button { + border: 0; + padding: 0; + cursor: pointer; + background-color: transparent; + } + + button[disabled] { + cursor: default; + } +} diff --git a/site/gdocs/components/ChartBook.tsx b/site/gdocs/components/ChartBook.tsx new file mode 100644 index 00000000000..90cd1407b2d --- /dev/null +++ b/site/gdocs/components/ChartBook.tsx @@ -0,0 +1,221 @@ +import React, { useEffect, useRef, useState } from "react" +import cx from "classnames" +import { + EnrichedBlockChartBook, + EnrichedBlockKeyIndicator, +} from "@ourworldindata/types" +import { urlToSlug } from "@ourworldindata/utils" +import Chart from "./Chart.js" +import { useLinkedChart, useLinkedIndicator } from "../utils.js" + +export default function ChartBook({ + d, + className, +}: { + d: EnrichedBlockChartBook + className?: string +}) { + const slugs = d.blocks.map((b: EnrichedBlockKeyIndicator) => + urlToSlug(b.datapageUrl) + ) + + const [isBlockOpen, setBlockOpen] = useState( + slugs.map((_: string, index: number) => index === 0) // the first block is open by default + ) + + return ( +
+ {d.blocks.map( + (block: EnrichedBlockKeyIndicator, blockIndex: number) => ( + { + // open block, close all others + const updated = slugs.map(() => false) + updated[blockIndex] = true + setBlockOpen(updated) + }} + close={() => { + // close block, leave others as they are + const updated = [...isBlockOpen] + updated[blockIndex] = false + setBlockOpen(updated) + }} + header={ + + } + > + + + ) + )} +
+ ) +} + +function AccordionItem({ + isOpen, + open, + close, + header, + children, +}: { + isOpen: boolean + open: () => void + close: () => void + header: React.ReactNode + children: React.ReactNode +}) { + const { ref, height } = useHeight() + + return ( +
+ +
+ {children} +
+
+
+ ) +} + +function KeyIndicatorHeader({ + block, + showMetadata = true, +}: { + block: EnrichedBlockKeyIndicator + showMetadata?: boolean +}) { + const { linkedChart } = useLinkedChart(block.datapageUrl) + const { linkedIndicator } = useLinkedIndicator( + linkedChart?.indicatorId ?? 0 + ) + + if (!linkedChart) return null + if (!linkedIndicator) return null + + return ( +
+
+ {linkedIndicator.metadata?.presentation?.titlePublic} +
+
+ {linkedIndicator.metadata?.origins?.[0].title ?? + "Placeholder title"}{" "} + | 1967-2021 +
+
+ ) +} + +function KeyIndicatorContent({ block }: { block: EnrichedBlockKeyIndicator }) { + const { linkedChart } = useLinkedChart(block.datapageUrl) + const { linkedIndicator } = useLinkedIndicator( + linkedChart?.indicatorId ?? 0 + ) + + if (!linkedChart) return null + if (!linkedIndicator) return null + + return ( +
+
+
+
+
Source
+
+ {linkedIndicator.metadata?.origins?.[0].title ?? + "Placeholder title"} +
+
+
+
Date range
+
1967-2021
+
+
+
+ Last updated +
+
+ May 25, 2022 +
+
+
+

+ {linkedIndicator.metadata?.description} +

+
+ +
+ ) +} + +const useHeight = () => { + const ref = useRef(null) + + const [height, setHeight] = useState(0) + + useEffect(() => { + const element = ref.current as HTMLDivElement + + const resizeObserver = new ResizeObserver( + (entries: ResizeObserverEntry[]) => { + if (!Array.isArray(entries)) return + if (entries.length === 0) return + + const entry = entries[0] + setHeight(entry.target.scrollHeight) + } + ) + + resizeObserver.observe(element) + + return () => resizeObserver.unobserve(element) + }, []) + + return { ref, height } +} diff --git a/site/owid.scss b/site/owid.scss index 6be7a3a8209..2e6eb988249 100644 --- a/site/owid.scss +++ b/site/owid.scss @@ -95,6 +95,7 @@ @import "./gdocs/components/ResearchAndWriting.scss"; @import "./gdocs/components/Chart.scss"; @import "./gdocs/components/KeyIndicator.scss"; +@import "./gdocs/components/ChartBook.scss"; @import "./DataPage.scss"; @import "./DataPageContent.scss"; @import "./detailsOnDemand.scss"; From 88ad3a11746ecfe93798820f417890353e069ae6 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Wed, 24 Jan 2024 09:52:01 +0000 Subject: [PATCH 26/51] :hammer: (gdocs) rename chart-book to key-indicator-collection --- db/model/Gdoc/GdocBase.ts | 4 +-- db/model/Gdoc/enrichedToMarkdown.ts | 13 ++++++---- db/model/Gdoc/enrichedToRaw.ts | 15 ++++++----- db/model/Gdoc/exampleEnrichedBlocks.ts | 4 +-- db/model/Gdoc/rawToArchie.ts | 13 ++++++---- db/model/Gdoc/rawToEnriched.ts | 26 +++++++++++-------- .../types/src/gdocTypes/ArchieMlComponents.ts | 12 ++++----- packages/@ourworldindata/types/src/index.ts | 4 +-- packages/@ourworldindata/utils/src/Util.ts | 15 ++++++----- site/gdocs/components/ArticleBlock.tsx | 11 +++++--- ...tBook.scss => KeyIndicatorCollection.scss} | 6 +---- ...artBook.tsx => KeyIndicatorCollection.tsx} | 8 +++--- site/owid.scss | 2 +- 13 files changed, 74 insertions(+), 59 deletions(-) rename site/gdocs/components/{ChartBook.scss => KeyIndicatorCollection.scss} (95%) rename site/gdocs/components/{ChartBook.tsx => KeyIndicatorCollection.tsx} (97%) diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index c48156e4c16..9304851d0d0 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -530,6 +530,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { "horizontal-rule", "html", "image", + "key-indicator-collection", "list", "missing-data", "numbered-list", @@ -541,8 +542,7 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { "sticky-left", "sticky-right", "table", - "text", - "chart-book" + "text" ), }, () => [] diff --git a/db/model/Gdoc/enrichedToMarkdown.ts b/db/model/Gdoc/enrichedToMarkdown.ts index c6322e81c4c..56bcf051a46 100644 --- a/db/model/Gdoc/enrichedToMarkdown.ts +++ b/db/model/Gdoc/enrichedToMarkdown.ts @@ -1,3 +1,4 @@ +import { EnrichedBlockKeyIndicator } from "@ourworldindata/types" import { OwidEnrichedGdocBlock, Span, @@ -283,11 +284,13 @@ ${links}` exportComponents ) ) - .with({ type: "chart-book" }, (b): string | undefined => { - const keyIndicators = b.blocks.map((keyIndicatorBlock) => - enrichedBlockToMarkdown(keyIndicatorBlock, exportComponents) - ) - return `\n${keyIndicators.join("\n")}\n` + .with({ type: "key-indicator-collection" }, (b): string | undefined => { + const keyIndicators = b.blocks + .map((keyIndicatorBlock: EnrichedBlockKeyIndicator) => + enrichedBlockToMarkdown(keyIndicatorBlock, exportComponents) + ) + .join("\n") + return `\n${keyIndicators}\n` }) .exhaustive() } diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index 507cdc3dd6b..118d511ba7a 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -37,7 +37,7 @@ import { RawBlockTable, RawBlockBlockquote, RawBlockKeyIndicator, - RawBlockChartBook, + RawBlockKeyIndicatorCollection, } from "@ourworldindata/types" import { spanToHtmlString } from "./gdocUtils.js" import { match, P } from "ts-pattern" @@ -449,11 +449,14 @@ export function enrichedBlockToRawBlock( }, } }) - .with({ type: "chart-book" }, (b): RawBlockChartBook => { - return { - type: "chart-book", - value: b.blocks.map(enrichedBlockToRawBlock), + .with( + { type: "key-indicator-collection" }, + (b): RawBlockKeyIndicatorCollection => { + return { + type: "key-indicator-collection", + value: b.blocks.map(enrichedBlockToRawBlock), + } } - }) + ) .exhaustive() } diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index dff799e810f..6d946e06e67 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -542,8 +542,8 @@ export const enrichedBlockExamples: Record< blurb: [enrichedBlockText], parseErrors: [], }, - "chart-book": { - type: "chart-book", + "key-indicator-collection": { + type: "key-indicator-collection", blocks: [ { type: "key-indicator", diff --git a/db/model/Gdoc/rawToArchie.ts b/db/model/Gdoc/rawToArchie.ts index b86068c1de7..d11e58266c3 100644 --- a/db/model/Gdoc/rawToArchie.ts +++ b/db/model/Gdoc/rawToArchie.ts @@ -36,7 +36,7 @@ import { RawBlockTableRow, RawBlockBlockquote, RawBlockKeyIndicator, - RawBlockChartBook, + RawBlockKeyIndicatorCollection, } from "@ourworldindata/types" import { isArray } from "@ourworldindata/utils" import { match } from "ts-pattern" @@ -641,10 +641,10 @@ function* rawBlockKeyIndicatorToArchieMLString( yield "{}" } -function* rawBlockChartBookToArchieMLString( - block: RawBlockChartBook +function* rawBlockKeyIndicatorCollectionToArchieMLString( + block: RawBlockKeyIndicatorCollection ): Generator { - yield "[.+chart-book]" + yield "[.+key-indicator-collection]" if (typeof block.value !== "string") { for (const b of block.value) yield* OwidRawGdocBlockToArchieMLStringGenerator(b) @@ -717,7 +717,10 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator( .with({ type: "table-row" }, rawBlockRowToArchieMLString) .with({ type: "blockquote" }, rawBlockBlockquoteToArchieMLString) .with({ type: "key-indicator" }, rawBlockKeyIndicatorToArchieMLString) - .with({ type: "chart-book" }, rawBlockChartBookToArchieMLString) + .with( + { type: "key-indicator-collection" }, + rawBlockKeyIndicatorCollectionToArchieMLString + ) .exhaustive() yield* content } diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index 6c905764cce..b8312ce2141 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -101,8 +101,8 @@ import { tableTemplates, RawBlockBlockquote, EnrichedBlockBlockquote, - RawBlockChartBook, - EnrichedBlockChartBook, + RawBlockKeyIndicatorCollection, + EnrichedBlockKeyIndicatorCollection, } from "@ourworldindata/types" import { traverseEnrichedSpan, @@ -201,7 +201,7 @@ export function parseRawBlocksToEnrichedBlocks( .with({ type: "entry-summary" }, parseEntrySummary) .with({ type: "table" }, parseTable) .with({ type: "key-indicator" }, parseKeyIndicator) - .with({ type: "chart-book" }, parseChartBook) + .with({ type: "key-indicator-collection" }, parseKeyIndicatorCollection) .exhaustive() } @@ -1946,9 +1946,13 @@ const parseKeyIndicator = ( }) as EnrichedBlockKeyIndicator } -function parseChartBook(raw: RawBlockChartBook): EnrichedBlockChartBook { - const createError = (error: ParseError): EnrichedBlockChartBook => ({ - type: "chart-book", +function parseKeyIndicatorCollection( + raw: RawBlockKeyIndicatorCollection +): EnrichedBlockKeyIndicatorCollection { + const createError = ( + error: ParseError + ): EnrichedBlockKeyIndicatorCollection => ({ + type: "key-indicator-collection", blocks: [], parseErrors: [error], }) @@ -1958,7 +1962,7 @@ function parseChartBook(raw: RawBlockChartBook): EnrichedBlockChartBook { if (!Array.isArray(raw.value)) { return createError({ message: - "chart-book is not a freeform array. Make sure you've written [.+chart-book]", + "key-indicator-collection is not a freeform array. Make sure you've written [.+key-indicator-collection]", }) } @@ -1970,7 +1974,7 @@ function parseChartBook(raw: RawBlockChartBook): EnrichedBlockChartBook { if (keyIndicatorBlocks.length < blocks.length) { warnings.push({ message: - "chart-book contains blocks that are not key-indicators blocks", + "key-indicator-collection contains blocks that are not key-indicators blocks", isWarning: true, }) } @@ -1982,8 +1986,8 @@ function parseChartBook(raw: RawBlockChartBook): EnrichedBlockChartBook { if (parsedBlocks.length <= 1) { const message = parsedBlocks.length === 0 - ? "chart-book contains no key-indicator blocks" - : "chart-book contains only one key-indicator block" + ? "key-indicator-collection contains no key-indicator blocks" + : "key-indicator-collection contains only one key-indicator block" warnings.push({ message, isWarning: true, @@ -1991,7 +1995,7 @@ function parseChartBook(raw: RawBlockChartBook): EnrichedBlockChartBook { } return { - type: "chart-book", + type: "key-indicator-collection", blocks: parsedBlocks, parseErrors: warnings, } diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index d57c2e43a4b..a7c84db438f 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -106,13 +106,13 @@ export type EnrichedBlockKeyIndicator = { source?: string } & EnrichedBlockWithParseErrors -export type RawBlockChartBook = { - type: "chart-book" +export type RawBlockKeyIndicatorCollection = { + type: "key-indicator-collection" value: OwidRawGdocBlock[] } -export type EnrichedBlockChartBook = { - type: "chart-book" +export type EnrichedBlockKeyIndicatorCollection = { + type: "key-indicator-collection" blocks: EnrichedBlockKeyIndicator[] } & EnrichedBlockWithParseErrors @@ -754,7 +754,7 @@ export type OwidRawGdocBlock = | RawBlockTable | RawBlockBlockquote | RawBlockKeyIndicator - | RawBlockChartBook + | RawBlockKeyIndicatorCollection export type OwidEnrichedGdocBlock = | EnrichedBlockAllCharts @@ -793,4 +793,4 @@ export type OwidEnrichedGdocBlock = | EnrichedBlockTable | EnrichedBlockBlockquote | EnrichedBlockKeyIndicator - | EnrichedBlockChartBook + | EnrichedBlockKeyIndicatorCollection diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index 00002c8b61d..2e11236e54d 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -192,7 +192,7 @@ export { type RawBlockTopicPageIntro, type RawBlockUrl, type RawBlockKeyIndicator, - type RawBlockChartBook, + type RawBlockKeyIndicatorCollection, tableTemplates, type TableTemplate, tableSizes, @@ -251,7 +251,7 @@ export { type EnrichedBlockTableRow, type EnrichedBlockTableCell, type EnrichedBlockKeyIndicator, - type EnrichedBlockChartBook, + type EnrichedBlockKeyIndicatorCollection, type RawBlockResearchAndWritingRow, } from "./gdocTypes/ArchieMlComponents.js" export { diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index d9d9570e4f3..aab01f30349 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1624,12 +1624,15 @@ export function traverseEnrichedBlocks( traverseEnrichedBlocks(node, callback, spanCallback) }) }) - .with({ type: "chart-book" }, (chartBook) => { - callback(chartBook) - chartBook.blocks.forEach((node) => - traverseEnrichedBlocks(node, callback, spanCallback) - ) - }) + .with( + { type: "key-indicator-collection" }, + (keyIndicatorCollection) => { + callback(keyIndicatorCollection) + keyIndicatorCollection.blocks.forEach((node) => + traverseEnrichedBlocks(node, callback, spanCallback) + ) + } + ) .with( { type: "key-indicator", diff --git a/site/gdocs/components/ArticleBlock.tsx b/site/gdocs/components/ArticleBlock.tsx index 7815f1287c4..e82eb27bb27 100644 --- a/site/gdocs/components/ArticleBlock.tsx +++ b/site/gdocs/components/ArticleBlock.tsx @@ -35,7 +35,7 @@ import { AllCharts } from "./AllCharts.js" import Video from "./Video.js" import { Table } from "./Table.js" import KeyIndicator from "./KeyIndicator.js" -import ChartBook from "./ChartBook.js" +import KeyIndicatorCollection from "./KeyIndicatorCollection.js" export type Container = | "default" @@ -59,7 +59,6 @@ const layouts: { [key in Container]: Layouts} = { ["all-charts"]: "col-start-2 span-cols-12", ["aside-left"]: "col-start-2 span-cols-3 span-md-cols-10 col-md-start-3", ["aside-right"]: "col-start-11 span-cols-3 span-md-cols-10 col-md-start-3", - ["chart-book"]: "col-start-2 span-cols-12", ["chart-story"]: "col-start-4 span-cols-8 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["chart"]: "col-start-4 span-cols-8 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["default"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", @@ -73,6 +72,7 @@ const layouts: { [key in Container]: Layouts} = { ["image--wide"]: "col-start-4 span-cols-8 col-md-start-2 span-md-cols-12", ["image-caption"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["key-indicator"]: "col-start-2 span-cols-12", + ["key-indicator-collection"]: "col-start-2 span-cols-12", ["key-insights"]: "col-start-2 span-cols-12", ["list"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", ["numbered-list"]: "col-start-5 span-cols-6 col-md-start-3 span-md-cols-10 span-sm-cols-12 col-sm-start-2", @@ -630,8 +630,11 @@ export default function ArticleBlock({ .with({ type: "key-indicator" }, (block) => ( )) - .with({ type: "chart-book" }, (block) => ( - + .with({ type: "key-indicator-collection" }, (block) => ( + )) .exhaustive() diff --git a/site/gdocs/components/ChartBook.scss b/site/gdocs/components/KeyIndicatorCollection.scss similarity index 95% rename from site/gdocs/components/ChartBook.scss rename to site/gdocs/components/KeyIndicatorCollection.scss index ebef2b924ff..7fe0cadb78a 100644 --- a/site/gdocs/components/ChartBook.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -1,14 +1,10 @@ -.chart-book { +.key-indicator-collection { $padding: 24px; $icon-width: 24px; $duration: 0.4s; // TODO margin: 0 (-$padding); - .chart-book__header { - padding: $padding; - } - .accordion-item { background-color: whitesmoke; } diff --git a/site/gdocs/components/ChartBook.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx similarity index 97% rename from site/gdocs/components/ChartBook.tsx rename to site/gdocs/components/KeyIndicatorCollection.tsx index 90cd1407b2d..b1c65dff8f7 100644 --- a/site/gdocs/components/ChartBook.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -1,18 +1,18 @@ import React, { useEffect, useRef, useState } from "react" import cx from "classnames" import { - EnrichedBlockChartBook, + EnrichedBlockKeyIndicatorCollection, EnrichedBlockKeyIndicator, } from "@ourworldindata/types" import { urlToSlug } from "@ourworldindata/utils" import Chart from "./Chart.js" import { useLinkedChart, useLinkedIndicator } from "../utils.js" -export default function ChartBook({ +export default function KeyIndicatorCollection({ d, className, }: { - d: EnrichedBlockChartBook + d: EnrichedBlockKeyIndicatorCollection className?: string }) { const slugs = d.blocks.map((b: EnrichedBlockKeyIndicator) => @@ -24,7 +24,7 @@ export default function ChartBook({ ) return ( -
+
{d.blocks.map( (block: EnrichedBlockKeyIndicator, blockIndex: number) => ( Date: Thu, 25 Jan 2024 11:43:31 +0000 Subject: [PATCH 27/51] :hammer: (gdocs) re-use key-indicator component --- .../components/KeyIndicatorCollection.scss | 16 ++- .../components/KeyIndicatorCollection.tsx | 103 +++++------------- 2 files changed, 40 insertions(+), 79 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index 7fe0cadb78a..96c0335a099 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -6,7 +6,7 @@ margin: 0 (-$padding); .accordion-item { - background-color: whitesmoke; + background-color: $blue-10; } .accordion-item + .accordion-item { @@ -41,11 +41,6 @@ margin-top: 0; } - .spacer { - width: 100%; - height: $padding; - } - .key-indicator-header__title { @include body-2-semibold; color: $blue-90; @@ -87,4 +82,13 @@ button[disabled] { cursor: default; } + + // update styles + .key-indicator { + margin-bottom: 0; + padding-top: 0; + .indicator-title { + display: none; + } + } } diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index b1c65dff8f7..46b9476c4da 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -1,12 +1,15 @@ import React, { useEffect, useRef, useState } from "react" import cx from "classnames" + import { EnrichedBlockKeyIndicatorCollection, EnrichedBlockKeyIndicator, } from "@ourworldindata/types" import { urlToSlug } from "@ourworldindata/utils" -import Chart from "./Chart.js" +import { makeDateRange, makeSource } from "@ourworldindata/components" + import { useLinkedChart, useLinkedIndicator } from "../utils.js" +import KeyIndicator from "./KeyIndicator.js" export default function KeyIndicatorCollection({ d, @@ -49,7 +52,7 @@ export default function KeyIndicatorCollection({ /> } > - + ) )} @@ -95,7 +98,6 @@ function AccordionItem({ }} > {children} -
) @@ -116,80 +118,35 @@ function KeyIndicatorHeader({ if (!linkedChart) return null if (!linkedIndicator) return null + const source = makeSource({ + attribution: linkedIndicator.attributionUnshortened, + hideProcessingLevel: true, + }) + const dateRange = makeDateRange({ + dateRange: linkedIndicator.dateRange, + }) + return (
-
- {linkedIndicator.metadata?.presentation?.titlePublic} +
+ {linkedIndicator.title}
-
- {linkedIndicator.metadata?.origins?.[0].title ?? - "Placeholder title"}{" "} - | 1967-2021 -
-
- ) -} - -function KeyIndicatorContent({ block }: { block: EnrichedBlockKeyIndicator }) { - const { linkedChart } = useLinkedChart(block.datapageUrl) - const { linkedIndicator } = useLinkedIndicator( - linkedChart?.indicatorId ?? 0 - ) - - if (!linkedChart) return null - if (!linkedIndicator) return null - - return ( -
-
-
-
-
Source
-
- {linkedIndicator.metadata?.origins?.[0].title ?? - "Placeholder title"} -
-
-
-
Date range
-
1967-2021
-
-
-
- Last updated -
-
- May 25, 2022 -
-
+ {(source || dateRange) && ( +
+ {source} + {source && " | "} + {dateRange}
-

- {linkedIndicator.metadata?.description} -

-
- + )}
) } From 2e01fa3c47869b45c240f17c04907e8835098b8b Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Thu, 25 Jan 2024 11:52:08 +0000 Subject: [PATCH 28/51] :lipstick: (gdocs) remove unused import --- packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index bd12e7f337a..f406dfb2afd 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -11,7 +11,6 @@ import { RawBlockText, RefDictionary, } from "./ArchieMlComponents.js" -import { OwidVariableWithSourceAndDimension } from "../OwidVariable.js" export enum OwidGdocPublicationContext { unlisted = "unlisted", From 7bfa6fd116d6f5e3ac7865831df510b9a78b172b Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 29 Jan 2024 09:29:27 +0000 Subject: [PATCH 29/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20implement=20key=20i?= =?UTF-8?q?ndicator=20collectin=20designs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.scss | 63 +++++++++---------- .../components/KeyIndicatorCollection.tsx | 12 +++- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index 96c0335a099..bd92a384acd 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -1,44 +1,32 @@ .key-indicator-collection { - $padding: 24px; - $icon-width: 24px; - $duration: 0.4s; // TODO + $duration: 0.4s; - margin: 0 (-$padding); + margin: 0 -24px; .accordion-item { background-color: $blue-10; } .accordion-item + .accordion-item { - margin-top: 4px; + margin-top: 1px; } .accordion-item__header { width: 100%; text-align: left; - padding: $padding; + padding: 12px 24px; } - // .accordion-item__header > *:nth-last-child(2) { - // margin-right: $icon-width; - // } - - // .accordion-item__header > *:last-child { - // position: absolute; - // width: $icon-width; - // top: $padding; - // right: $padding; - // text-align: right; - // } - .accordion-item__content { - padding: 0 $padding; + padding: 0 24px; overflow: hidden; transition: height $duration cubic-bezier(0.76, 0, 0.24, 1); } - h3 { - margin-top: 0; + .key-indicator-header { + @include sm-only { + align-items: center; + } } .key-indicator-header__title { @@ -50,26 +38,27 @@ @include body-3-medium; color: #60758c; opacity: 0; - transition: opacity 0.2s; // TODO - } + transition: opacity 0.2s; - .key-indicator-header__metadata.visible { - opacity: 1 !important; - transition: opacity 0.2s $duration; // TODO + @include sm-only { + display: none; + } } - .metadata-entry + .metadata-entry { - margin-top: 8px; - } + .key-indicator-header__icon { + color: $blue-90; + font-size: 0.875rem; + font-weight: 900; + text-align: right; - .metadata-entry__title { - @include body-3-medium; - color: $blue-50; + @include sm-up { + display: none; + } } - .metadata-entry__value { - @include body-3-medium; - color: $blue-90; + .key-indicator-header__metadata.visible { + opacity: 1 !important; + transition: opacity 0.2s $duration; } button { @@ -83,6 +72,10 @@ cursor: default; } + h3 { + margin-top: 0; + } + // update styles .key-indicator { margin-bottom: 0; diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index 46b9476c4da..54bcd24cd3c 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -1,5 +1,7 @@ import React, { useEffect, useRef, useState } from "react" import cx from "classnames" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js" +import { faPlus } from "@fortawesome/free-solid-svg-icons" import { EnrichedBlockKeyIndicatorCollection, @@ -49,6 +51,7 @@ export default function KeyIndicatorCollection({ } > @@ -106,9 +109,11 @@ function AccordionItem({ function KeyIndicatorHeader({ block, showMetadata = true, + showIcon = false, }: { block: EnrichedBlockKeyIndicator showMetadata?: boolean + showIcon?: boolean }) { const { linkedChart } = useLinkedChart(block.datapageUrl) const { linkedIndicator } = useLinkedIndicator( @@ -128,7 +133,7 @@ function KeyIndicatorHeader({ return (
-
+
{linkedIndicator.title}
{(source || dateRange) && ( @@ -147,6 +152,11 @@ function KeyIndicatorHeader({ {dateRange}
)} + {showIcon && ( +
+ +
+ )}
) } From eef0d274d7a8e256ff655f2c7bac92422444b3cf Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 29 Jan 2024 13:53:31 +0000 Subject: [PATCH 30/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20show=20short=20attr?= =?UTF-8?q?ibution=20in=20the=20accordion=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index 54bcd24cd3c..51192e2d105 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -7,8 +7,12 @@ import { EnrichedBlockKeyIndicatorCollection, EnrichedBlockKeyIndicator, } from "@ourworldindata/types" -import { urlToSlug } from "@ourworldindata/utils" -import { makeDateRange, makeSource } from "@ourworldindata/components" +import { + capitalize, + joinTitleFragments, + urlToSlug, +} from "@ourworldindata/utils" +import { makeDateRange } from "@ourworldindata/components" import { useLinkedChart, useLinkedIndicator } from "../utils.js" import KeyIndicator from "./KeyIndicator.js" @@ -123,10 +127,12 @@ function KeyIndicatorHeader({ if (!linkedChart) return null if (!linkedIndicator) return null - const source = makeSource({ - attribution: linkedIndicator.attributionUnshortened, - hideProcessingLevel: true, - }) + const source = capitalize( + joinTitleFragments( + linkedIndicator.attributionShort, + linkedIndicator.titleVariant + ) + ) const dateRange = makeDateRange({ dateRange: linkedIndicator.dateRange, }) From 64c413f5b678f9507112b0f563f666dc82898888 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 30 Jan 2024 08:41:09 +0000 Subject: [PATCH 31/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20scroll=20into=20vie?= =?UTF-8?q?w=20if=20not=20visible=20after=20animation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.scss | 1 + .../components/KeyIndicatorCollection.tsx | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index bd92a384acd..aadd17d34e9 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -1,4 +1,5 @@ .key-indicator-collection { + // keep in sync with HEIGHT_ANIMATION_DURATION_IN_SECONDS in KeyIndicatorCollection.tsx $duration: 0.4s; margin: 0 -24px; diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index 51192e2d105..7b62adb2739 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -17,6 +17,9 @@ import { makeDateRange } from "@ourworldindata/components" import { useLinkedChart, useLinkedIndicator } from "../utils.js" import KeyIndicator from "./KeyIndicator.js" +// keep in sync with $duration in KeyIndicatorCollection.scss +const HEIGHT_ANIMATION_DURATION_IN_SECONDS = 0.4 + export default function KeyIndicatorCollection({ d, className, @@ -80,10 +83,11 @@ function AccordionItem({ header: React.ReactNode children: React.ReactNode }) { - const { ref, height } = useHeight() + const ref = useRef(null) + const { ref: contentRef, height } = useHeight() return ( -
+
{ return { ref, height } } + +function isElementFullyVisible(element: HTMLElement): boolean { + const bbox = element.getBoundingClientRect() + const viewHeight = Math.max( + document.documentElement.clientHeight, + window.innerHeight + ) + return bbox.top >= 0 && bbox.bottom <= viewHeight +} From fe6ba53ae9f1d169ca2ed0ae6820a9a9e186e962 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 30 Jan 2024 09:26:17 +0000 Subject: [PATCH 32/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20improve=20a11y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.scss | 8 +- .../components/KeyIndicatorCollection.tsx | 125 ++++++++++-------- 2 files changed, 77 insertions(+), 56 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index aadd17d34e9..3d036d6d5bb 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -12,6 +12,10 @@ margin-top: 1px; } + .accordion-item h3 { + margin: 0; + } + .accordion-item__header { width: 100%; text-align: left; @@ -73,10 +77,6 @@ cursor: default; } - h3 { - margin-top: 0; - } - // update styles .key-indicator { margin-bottom: 0; diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index 7b62adb2739..ab3b6828a5a 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -38,45 +38,54 @@ export default function KeyIndicatorCollection({ return (
{d.blocks.map( - (block: EnrichedBlockKeyIndicator, blockIndex: number) => ( - { - // open block, close all others - const updated = slugs.map(() => false) - updated[blockIndex] = true - setBlockOpen(updated) - }} - close={() => { - // close block, leave others as they are - const updated = [...isBlockOpen] - updated[blockIndex] = false - setBlockOpen(updated) - }} - header={ - - } - > - - - ) + (block: EnrichedBlockKeyIndicator, blockIndex: number) => { + const isOpen = isBlockOpen[blockIndex] + const slug = urlToSlug(block.datapageUrl) + + return ( + { + // open block, close all others + const updated = slugs.map(() => false) + updated[blockIndex] = true + setBlockOpen(updated) + }} + close={() => { + // close block, leave others as they are + const updated = [...isBlockOpen] + updated[blockIndex] = false + setBlockOpen(updated) + }} + header={ + + } + > + + + ) + } )}
) } function AccordionItem({ + id, isOpen, open, close, header, children, }: { + id: string isOpen: boolean open: () => void close: () => void @@ -86,39 +95,51 @@ function AccordionItem({ const ref = useRef(null) const { ref: contentRef, height } = useHeight() + const headerId = `${id}_header` + const contentId = `${id}_content` + return (
- +

+ +

{children}
From 059dbeb3f354e04c3f93c979ac939dd33591533c Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 30 Jan 2024 10:09:33 +0000 Subject: [PATCH 33/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20update=20color?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.scss | 4 ++++ .../components/KeyIndicatorCollection.tsx | 21 ++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index 3d036d6d5bb..2b41219010f 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -39,6 +39,10 @@ color: $blue-90; } + .key-indicator-header--content-visible .key-indicator-header__title { + color: $blue-60; + } + .key-indicator-header__metadata { @include body-3-medium; color: #60758c; diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index ab3b6828a5a..fcb555e0312 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -63,8 +63,7 @@ export default function KeyIndicatorCollection({ header={ } > @@ -116,7 +115,7 @@ function AccordionItem({ setTimeout(() => { if (!ref.current) return if (!isElementFullyVisible(ref.current)) { - ref.current?.scrollIntoView({ + ref.current.scrollIntoView({ behavior: "smooth", }) } @@ -149,12 +148,10 @@ function AccordionItem({ function KeyIndicatorHeader({ block, - showMetadata = true, - showIcon = false, + isContentVisible, }: { block: EnrichedBlockKeyIndicator - showMetadata?: boolean - showIcon?: boolean + isContentVisible?: boolean }) { const { linkedChart } = useLinkedChart(block.datapageUrl) const { linkedIndicator } = useLinkedIndicator( @@ -175,7 +172,11 @@ function KeyIndicatorHeader({ }) return ( -
+
{linkedIndicator.title}
@@ -185,7 +186,7 @@ function KeyIndicatorHeader({ "key-indicator-header__metadata", "col-start-5 span-cols-8", { - visible: showMetadata, + visible: !isContentVisible, } )} style={{ opacity: 0 }} @@ -195,7 +196,7 @@ function KeyIndicatorHeader({ {dateRange}
)} - {showIcon && ( + {!isContentVisible && (
From 72fed2fae9a4c9fc9d3b25564e4df51114108750 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 30 Jan 2024 10:20:03 +0000 Subject: [PATCH 34/51] =?UTF-8?q?=F0=9F=90=9B=20(gdocs)=20fix=20resizing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicatorCollection.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index fcb555e0312..8a397e9ace0 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -132,7 +132,6 @@ function AccordionItem({
- {children} +
{children}
) From 51db2ce5bd2e7aebcc6bcd1af82d02f3c08e2666 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 30 Jan 2024 14:43:51 +0000 Subject: [PATCH 35/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20add=20hover=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.scss | 30 +++++++++++++++++-- .../components/KeyIndicatorCollection.tsx | 7 +++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index 2b41219010f..8139271cd18 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -5,11 +5,27 @@ margin: 0 -24px; .accordion-item { - background-color: $blue-10; + background-color: $white; + + @include sm-only { + background-color: $blue-10; + } } .accordion-item + .accordion-item { - margin-top: 1px; + border-top: 1px solid $blue-10; + + @include sm-only { + border-top-color: $white; + } + } + + .accordion-item--open { + background-color: $blue-10; + } + + :not(.accordion-item--open):hover { + background-color: $blue-10; } .accordion-item h3 { @@ -29,6 +45,8 @@ } .key-indicator-header { + align-items: center; + @include sm-only { align-items: center; } @@ -61,10 +79,16 @@ text-align: right; @include sm-up { - display: none; + opacity: 0; + transition: opacity 0.2s; } } + :not(.accordion-item--open):hover .key-indicator-header__icon { + opacity: 1; + transition: opacity 0s; + } + .key-indicator-header__metadata.visible { opacity: 1 !important; transition: opacity 0.2s $duration; diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index 8a397e9ace0..38a9e782873 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -98,7 +98,10 @@ function AccordionItem({ const contentId = `${id}_content` return ( -
+

+ {!isOpen && ( + + {header} + + )}
- {linkedIndicator.title} + {linkedIndicator.title} {source && ( {source} @@ -190,7 +199,10 @@ function KeyIndicatorHeader({
{!isContentVisible && (
+ {/* desktop */} + {/* mobile */} +
)}
From 9c9b7355ffd0ffcbab648647e4b8a14a7d84a3e0 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Fri, 2 Feb 2024 17:22:59 +0100 Subject: [PATCH 39/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20add=20chart=20icon?= =?UTF-8?q?=20for=20key=20indicators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/GdocBase.ts | 3 + .../types/src/gdocTypes/Gdoc.ts | 3 +- .../components/KeyIndicatorCollection.scss | 66 ++++++++++--------- .../components/KeyIndicatorCollection.tsx | 55 +++++++++++++--- 4 files changed, 86 insertions(+), 41 deletions(-) diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 9304851d0d0..2eb05ff8645 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -36,6 +36,7 @@ import { OwidGdocMinimalPostInterface, urlToSlug, grabMetadataForGdocLinkedIndicator, + GrapherTabOption, } from "@ourworldindata/utils" import { BAKED_GRAPHER_URL } from "../../../settings/serverSettings.js" import { google } from "googleapis" @@ -583,11 +584,13 @@ export class GdocBase extends BaseEntity implements OwidGdocBaseInterface { if (!chart) return const resolvedSlug = chart.config.slug ?? "" const resolvedTitle = chart.config.title ?? "" + const tab = chart.config.tab ?? GrapherTabOption.chart const datapageIndicator = await getVariableOfDatapageIfApplicable(chart.config) const linkedChart: LinkedChart = { originalSlug, title: resolvedTitle, + tab, resolvedUrl: `${BAKED_GRAPHER_URL}/${resolvedSlug}`, thumbnail: `${BAKED_GRAPHER_EXPORTS_BASE_URL}/${resolvedSlug}.svg`, indicatorId: datapageIndicator?.id, diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index f406dfb2afd..5ec0af59399 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -1,5 +1,5 @@ import { Tag } from "../domainTypes/Tag.js" -import { RelatedChart } from "../grapherTypes/GrapherTypes.js" +import { GrapherTabOption, RelatedChart } from "../grapherTypes/GrapherTypes.js" import { BreadcrumbItem } from "../domainTypes/Site.js" import { TocHeadingWithTitleSupertitle } from "../domainTypes/Toc.js" import { ImageMetadata } from "./Image.js" @@ -22,6 +22,7 @@ export interface LinkedChart { originalSlug: string resolvedUrl: string title: string + tab?: GrapherTabOption thumbnail?: string indicatorId?: number // in case of a datapage } diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index 6fde96c5430..645988e0a09 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -1,22 +1,13 @@ .key-indicator-collection { - // keep in sync with HEIGHT_ANIMATION_DURATION_IN_SECONDS in KeyIndicatorCollection.tsx - $duration: 0.4s; - $border: $blue-10; + $duration: 0.4s; // keep in sync with HEIGHT_ANIMATION_DURATION_IN_SECONDS in KeyIndicatorCollection.tsx + $border: $blue-20; margin: 0 -24px; - .accordion-item--closed + .accordion-item--closed { + .accordion-item + .accordion-item { border-top: 1px solid $border; } - .accordion-item--closed:first-of-type { - border-top: 1px solid $border; - } - - .accordion-item--closed:last-of-type { - border-bottom: 1px solid $border; - } - .accordion-item h3 { margin: 0; } @@ -25,36 +16,26 @@ .accordion-item__link-mobile { display: block; width: 100%; - background-color: $white; text-align: left; } .accordion-item__button { + background-color: $blue-5; padding: 16px 24px; } .accordion-item__link-mobile { + background-color: $white; padding: 12px 24px; line-height: 1.2; } - .accordion-item__link-mobile:active .title { - text-decoration: underline; - } - .accordion-item--open .accordion-item__button { background-color: $blue-20; } .accordion-item--closed .accordion-item__button:hover { - background-color: $blue-5; - } - - .accordion-item--closed - .accordion-item__button:hover - .key-indicator-header__icon { - opacity: 1; - transition: opacity 0s; + background-color: $blue-20; } .accordion-item__content { @@ -63,27 +44,44 @@ transition: height $duration cubic-bezier(0.76, 0, 0.24, 1); } + .key-indicator-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + } + + .key-indicator-header__tab-icon { + font-size: 0.8125rem; + color: $blue-50; + margin-right: 8px; + vertical-align: baseline; + } + .key-indicator-header__title { @include body-2-semibold; color: $blue-90; } + .accordion-item__link-mobile:active .key-indicator-header__title { + text-decoration: underline; + text-underline-offset: 4px; + } + .key-indicator-header__source { @include body-3-medium; display: inline-block; - margin-left: 8px; color: $blue-50; + margin-left: 8px; } .key-indicator-header__icon { color: $blue-90; font-size: 0.875rem; font-weight: 900; - text-align: right; - @include sm-up { - opacity: 0; - transition: opacity 0.2s; + @include sm-only { + font-size: 0.75rem; } } @@ -103,7 +101,7 @@ display: none; } - svg[data-icon="arrow-right"] { + .key-indicator-header__icon[data-icon="arrow-right"] { display: none; } } @@ -139,7 +137,11 @@ margin-top: 2px; } - svg[data-icon="plus"] { + .key-indicator-header__tab-icon { + display: none; + } + + .key-indicator-header__icon[data-icon="plus"] { display: none; } } diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index 90b5d534b1b..ec0f32f6c3f 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -1,13 +1,22 @@ import React, { useEffect, useRef, useState } from "react" import cx from "classnames" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js" -import { faArrowRight, faPlus } from "@fortawesome/free-solid-svg-icons" +import { + faArrowRight, + faPlus, + faEarthAmericas, + faChartLine, + faTable, + IconDefinition, +} from "@fortawesome/free-solid-svg-icons" import { EnrichedBlockKeyIndicatorCollection, EnrichedBlockKeyIndicator, + GrapherTabOption, } from "@ourworldindata/types" import { + Url, capitalize, joinTitleFragments, urlToSlug, @@ -19,6 +28,12 @@ import KeyIndicator from "./KeyIndicator.js" // keep in sync with $duration in KeyIndicatorCollection.scss const HEIGHT_ANIMATION_DURATION_IN_SECONDS = 0.4 +const tabIconMap: Record = { + [GrapherTabOption.chart]: faChartLine, + [GrapherTabOption.map]: faEarthAmericas, + [GrapherTabOption.table]: faTable, +} + export default function KeyIndicatorCollection({ d, className, @@ -45,7 +60,7 @@ export default function KeyIndicatorCollection({ { // open block, close all others @@ -174,6 +189,14 @@ function KeyIndicatorHeader({ if (!linkedChart) return null if (!linkedIndicator) return null + const { queryParams } = Url.fromURL(linkedChart.resolvedUrl) + const tabFromQueryParams = + queryParams.tab && isValidGrapherTab(queryParams.tab) + ? queryParams.tab + : undefined + const activeTab = + tabFromQueryParams || linkedChart.tab || GrapherTabOption.chart + const source = block.source || capitalize( @@ -185,12 +208,18 @@ function KeyIndicatorHeader({ return (
-
- {linkedIndicator.title} +
+ + + {linkedIndicator.title} + {source && ( {source} @@ -198,11 +227,17 @@ function KeyIndicatorHeader({ )}
{!isContentVisible && ( -
+
{/* desktop */} - + {/* mobile */} - +
)}
@@ -252,3 +287,7 @@ function isElementAtTopOfViewport(element: HTMLElement): boolean { ) return bbox.top >= 0 && bbox.top < 0.33 * viewHeight } + +function isValidGrapherTab(tab: string): tab is GrapherTabOption { + return Object.values(GrapherTabOption).includes(tab as GrapherTabOption) +} From 1a9eb6768a34da33cf2d8b837647a3147007ab67 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Fri, 2 Feb 2024 17:06:23 +0000 Subject: [PATCH 40/51] =?UTF-8?q?=F0=9F=90=9D=20(gdocs)=20fix=20issues=20a?= =?UTF-8?q?fter=20rebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/exampleEnrichedBlocks.ts | 2 ++ site/gdocs/components/KeyIndicatorCollection.tsx | 16 ++-------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index 6d946e06e67..c2a15a05626 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -549,6 +549,7 @@ export const enrichedBlockExamples: Record< type: "key-indicator", datapageUrl: "https://ourworldindata.org/grapher/life-expectancy", + title: "How did people's life expectancy change over time?", blurb: [enrichedBlockText], parseErrors: [], }, @@ -556,6 +557,7 @@ export const enrichedBlockExamples: Record< type: "key-indicator", datapageUrl: "https://ourworldindata.org/grapher/share-of-population-in-extreme-poverty", + title: "What share of the population is living in extreme poverty?", blurb: [enrichedBlockText], parseErrors: [], }, diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index ec0f32f6c3f..c74d30cd5ce 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -15,12 +15,7 @@ import { EnrichedBlockKeyIndicator, GrapherTabOption, } from "@ourworldindata/types" -import { - Url, - capitalize, - joinTitleFragments, - urlToSlug, -} from "@ourworldindata/utils" +import { Url, capitalize, urlToSlug } from "@ourworldindata/utils" import { useLinkedChart, useLinkedIndicator } from "../utils.js" import KeyIndicator from "./KeyIndicator.js" @@ -197,14 +192,7 @@ function KeyIndicatorHeader({ const activeTab = tabFromQueryParams || linkedChart.tab || GrapherTabOption.chart - const source = - block.source || - capitalize( - joinTitleFragments( - linkedIndicator.attributionShort, - linkedIndicator.titleVariant - ) - ) + const source = capitalize(block.source || linkedIndicator.source) return (
Date: Mon, 5 Feb 2024 11:24:37 +0000 Subject: [PATCH 41/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20minor=20css=20up?= =?UTF-8?q?dates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.scss | 111 +++++++++++------- .../components/KeyIndicatorCollection.tsx | 8 +- 2 files changed, 72 insertions(+), 47 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index 645988e0a09..5cd56dcc6b9 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -1,11 +1,12 @@ .key-indicator-collection { $duration: 0.4s; // keep in sync with HEIGHT_ANIMATION_DURATION_IN_SECONDS in KeyIndicatorCollection.tsx - $border: $blue-20; - margin: 0 -24px; + --border: #{$blue-20}; + + margin: 24px -24px; .accordion-item + .accordion-item { - border-top: 1px solid $border; + border-top: 1px solid var(--border); } .accordion-item h3 { @@ -22,16 +23,36 @@ .accordion-item__button { background-color: $blue-5; padding: 16px 24px; + + @include sm-only { + padding-bottom: 0; + } + } + + // on desktop, show a clickable header that opens the accordion + .accordion-item--closed .accordion-item__button { + @include sm-only { + display: none; + } } + // on mobile, show an anchor that links to the datapage .accordion-item__link-mobile { background-color: $white; padding: 12px 24px; line-height: 1.2; + + @include sm-up { + display: none; + } } .accordion-item--open .accordion-item__button { background-color: $blue-20; + + @include sm-only { + background-color: $blue-5; + } } .accordion-item--closed .accordion-item__button:hover { @@ -47,7 +68,6 @@ .key-indicator-header { display: flex; justify-content: space-between; - align-items: center; gap: 8px; } @@ -56,14 +76,30 @@ color: $blue-50; margin-right: 8px; vertical-align: baseline; + + @include sm-only { + display: none; + } } .key-indicator-header__title { @include body-2-semibold; color: $blue-90; + margin-right: 8px; + + @include sm-only { + line-height: 1.2; + } + } + + .accordion-item--closed .key-indicator-header__title { + @include sm-only { + margin-left: 0; + } } - .accordion-item__link-mobile:active .key-indicator-header__title { + .accordion-item__link-mobile:active:not(:hover) + .key-indicator-header__title { text-decoration: underline; text-underline-offset: 4px; } @@ -72,7 +108,13 @@ @include body-3-medium; display: inline-block; color: $blue-50; - margin-left: 8px; + } + + .accordion-item--closed .key-indicator-header__source { + @include sm-only { + display: block; + margin-top: 2px; + } } .key-indicator-header__icon { @@ -85,6 +127,20 @@ } } + // hidden on mobile + .key-indicator-header__icon[data-icon="plus"] { + @include sm-only { + display: none; + } + } + + // hidden on desktop + .key-indicator-header__icon[data-icon="arrow-right"] { + @include sm-up { + display: none; + } + } + button { border: 0; padding: 0; @@ -96,58 +152,29 @@ cursor: default; } - @include sm-up { - .accordion-item__link-mobile { - display: none; - } - - .key-indicator-header__icon[data-icon="arrow-right"] { - display: none; - } - } - @include sm-only { + --border: #{$blue-10}; + .accordion-item:first-of-type::after { content: "More featured indicators"; @include h5-black-caps; display: block; - margin: 24px 24px 16px 24px; + margin: 24px 24px 16px; color: $blue-60; } .accordion-item:nth-of-type(2) { - border-top: 1px solid $border; + border-top: 1px solid var(--border); } - .accordion-item__button { - padding-bottom: 0; - } - - .accordion-item--closed .accordion-item__button { - display: none; - } - - .accordion-item--open .accordion-item__button { - background-color: $blue-5; - } - - .accordion-item--closed .key-indicator-header__source { - display: block; - margin-left: 0; - margin-top: 2px; - } - - .key-indicator-header__tab-icon { - display: none; - } - - .key-indicator-header__icon[data-icon="plus"] { - display: none; + .accordion-item:last-of-type { + border-bottom: 1px solid var(--border); } } // update styles .key-indicator { + margin-top: 0; margin-bottom: 0; .indicator-title { display: none; diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index c74d30cd5ce..643594b52d1 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -114,6 +114,7 @@ function AccordionItem({ "accordion-item--closed": !isOpen, })} > + {/* desktop */}

+ {/* mobile */} {!isOpen && ( +
Date: Mon, 5 Feb 2024 12:54:15 +0000 Subject: [PATCH 42/51] =?UTF-8?q?=F0=9F=90=9B=20(gdocs)=20show=20correct?= =?UTF-8?q?=20tab=20icon=20on=20baked=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- baker/SiteBaker.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index 88ba28e54ec..dd972675ef6 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -57,6 +57,7 @@ import { OwidGdocMinimalPostInterface, excludeUndefined, grabMetadataForGdocLinkedIndicator, + GrapherTabOption, } from "@ourworldindata/utils" import { execWrapper } from "../db/execWrapper.js" @@ -324,11 +325,13 @@ export class SiteBaker { const publishedChartsRaw = await Chart.mapSlugsToConfigs() const publishedCharts: LinkedChart[] = await Promise.all( publishedChartsRaw.map(async (chart) => { + const tab = chart.config.tab ?? GrapherTabOption.chart const datapageIndicator = await getVariableOfDatapageIfApplicable(chart.config) return { originalSlug: chart.slug, resolvedUrl: `${BAKED_GRAPHER_URL}/${chart.config.slug}`, + tab, queryString: "", title: chart.config.title || "", thumbnail: `${BAKED_GRAPHER_EXPORTS_BASE_URL}/${chart.config.slug}.svg`, From 13e40ef775ddb4718f5325cbf85a86cf3aa502bb Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 5 Feb 2024 13:00:01 +0000 Subject: [PATCH 43/51] =?UTF-8?q?=F0=9F=90=9D=20(gdocs)=20prettier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- baker/SiteBaker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index dd972675ef6..c545bb0cf15 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -325,7 +325,7 @@ export class SiteBaker { const publishedChartsRaw = await Chart.mapSlugsToConfigs() const publishedCharts: LinkedChart[] = await Promise.all( publishedChartsRaw.map(async (chart) => { - const tab = chart.config.tab ?? GrapherTabOption.chart + const tab = chart.config.tab ?? GrapherTabOption.chart const datapageIndicator = await getVariableOfDatapageIfApplicable(chart.config) return { From 60f2ab4debd7affd647a79a580204f4951c33415 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 5 Feb 2024 15:01:16 +0000 Subject: [PATCH 44/51] =?UTF-8?q?=F0=9F=90=9D=20(gdocs)=20fix=20rebase=20i?= =?UTF-8?q?ssue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicatorCollection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index 643594b52d1..dadd4eacfec 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -194,7 +194,7 @@ function KeyIndicatorHeader({ const activeTab = tabFromQueryParams || linkedChart.tab || GrapherTabOption.chart - const source = capitalize(block.source || linkedIndicator.source) + const source = capitalize(block.source || linkedIndicator.attributionShort) return (
From fcfd2a0e0e50b2ada0fc61a2cc458a9f38fe59b0 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Mon, 5 Feb 2024 15:03:37 +0000 Subject: [PATCH 45/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20improve=20error=20h?= =?UTF-8?q?andling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/model/Gdoc/rawToEnriched.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index b8312ce2141..67a8e0eba77 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -1950,11 +1950,12 @@ function parseKeyIndicatorCollection( raw: RawBlockKeyIndicatorCollection ): EnrichedBlockKeyIndicatorCollection { const createError = ( - error: ParseError + error: ParseError, + warnings: ParseError[] = [] ): EnrichedBlockKeyIndicatorCollection => ({ type: "key-indicator-collection", blocks: [], - parseErrors: [error], + parseErrors: [error, ...warnings], }) const warnings = [] @@ -1983,20 +1984,30 @@ function parseKeyIndicatorCollection( keyIndicatorBlocks.map(parseRawBlocksToEnrichedBlocks) ) as EnrichedBlockKeyIndicator[] - if (parsedBlocks.length <= 1) { - const message = - parsedBlocks.length === 0 - ? "key-indicator-collection contains no key-indicator blocks" - : "key-indicator-collection contains only one key-indicator block" + const validBlocks = parsedBlocks.filter( + (block) => + block.parseErrors.filter((error) => !error.isWarning).length === 0 + ) + + if (validBlocks.length < parsedBlocks.length) { warnings.push({ - message, + message: + "key-indicator-collection contains at least one invalid key-indicators block", isWarning: true, }) } + if (validBlocks.length <= 1) { + const message = + validBlocks.length === 0 + ? "key-indicator-collection contains no valid key-indicator blocks" + : "key-indicator-collection contains only one valid key-indicator block" + return createError({ message }, warnings) + } + return { type: "key-indicator-collection", - blocks: parsedBlocks, + blocks: validBlocks, parseErrors: warnings, } } From a6258296a9fc80b856fb1b1fab855e8182e5c142 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 6 Feb 2024 10:39:19 +0000 Subject: [PATCH 46/51] =?UTF-8?q?=E2=9C=A8=20(gdocs)=20show=20chart/map=20?= =?UTF-8?q?icon=20on=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.scss | 10 +++++----- .../components/KeyIndicatorCollection.tsx | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index 5cd56dcc6b9..d029566b572 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -71,15 +71,15 @@ gap: 8px; } + .key-indicator-header__left { + display: flex; + align-items: baseline; + } + .key-indicator-header__tab-icon { font-size: 0.8125rem; color: $blue-50; margin-right: 8px; - vertical-align: baseline; - - @include sm-only { - display: none; - } } .key-indicator-header__title { diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index dadd4eacfec..9f194b8b264 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -198,19 +198,21 @@ function KeyIndicatorHeader({ return (
-
+
- - {linkedIndicator.title} - - {source && ( - - {source} +
+ + {linkedIndicator.title} - )} + {source && ( + + {source} + + )} +
{!isContentVisible && (
From ce56db7d24578ab15e6aaa6e9d3de28602f2cfba Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 6 Feb 2024 13:02:54 +0000 Subject: [PATCH 47/51] =?UTF-8?q?=F0=9F=90=9B=20(gdocs)=20link=20to=20the?= =?UTF-8?q?=20given=20datapage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.tsx | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.tsx b/site/gdocs/components/KeyIndicatorCollection.tsx index 9f194b8b264..47202ddc614 100644 --- a/site/gdocs/components/KeyIndicatorCollection.tsx +++ b/site/gdocs/components/KeyIndicatorCollection.tsx @@ -48,8 +48,8 @@ export default function KeyIndicatorCollection({
{d.blocks.map( (block: EnrichedBlockKeyIndicator, blockIndex: number) => { + const slug = slugs[blockIndex] const isOpen = isBlockOpen[blockIndex] - const slug = urlToSlug(block.datapageUrl) return ( } + mobileHeader={ + + + + } > @@ -91,6 +99,7 @@ function AccordionItem({ open, close, header, + mobileHeader, children, }: { id: string @@ -98,6 +107,7 @@ function AccordionItem({ open: () => void close: () => void header: React.ReactNode + mobileHeader: React.ReactNode children: React.ReactNode }) { const ref = useRef(null) @@ -148,14 +158,7 @@ function AccordionItem({ {/* mobile */} - {!isOpen && ( - - {header} - - )} + {!isOpen && mobileHeader}
+ {children} + + ) +} + const useHeight = () => { const ref = useRef(null) From 40664fc4a49af14768ea247398485930777363b7 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Wed, 7 Feb 2024 14:40:56 +0000 Subject: [PATCH 48/51] =?UTF-8?q?=F0=9F=90=9D=20(gdocs)=20fix=20rebase=20i?= =?UTF-8?q?ssue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/gdocs/components/KeyIndicatorCollection.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index d029566b572..53127733202 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -176,7 +176,7 @@ .key-indicator { margin-top: 0; margin-bottom: 0; - .indicator-title { + .indicator-metadata { display: none; } } From 9de5b77c8b1cdbad7b3ddc0f23ed4fd06a52f611 Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Wed, 7 Feb 2024 19:19:01 +0000 Subject: [PATCH 49/51] =?UTF-8?q?=F0=9F=92=84=20(gdocs)=20use=20--grid-gap?= =?UTF-8?q?=20for=20horizontal=20padding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/KeyIndicatorCollection.scss | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/site/gdocs/components/KeyIndicatorCollection.scss b/site/gdocs/components/KeyIndicatorCollection.scss index 53127733202..595e6e74f7a 100644 --- a/site/gdocs/components/KeyIndicatorCollection.scss +++ b/site/gdocs/components/KeyIndicatorCollection.scss @@ -1,9 +1,10 @@ .key-indicator-collection { $duration: 0.4s; // keep in sync with HEIGHT_ANIMATION_DURATION_IN_SECONDS in KeyIndicatorCollection.tsx + --padding: var(--grid-gap, 24px); --border: #{$blue-20}; - margin: 24px -24px; + margin: 24px calc(-1 * var(--padding)); .accordion-item + .accordion-item { border-top: 1px solid var(--border); @@ -22,7 +23,7 @@ .accordion-item__button { background-color: $blue-5; - padding: 16px 24px; + padding: 16px var(--padding); @include sm-only { padding-bottom: 0; @@ -39,7 +40,7 @@ // on mobile, show an anchor that links to the datapage .accordion-item__link-mobile { background-color: $white; - padding: 12px 24px; + padding: 12px 0; line-height: 1.2; @include sm-up { @@ -60,7 +61,6 @@ } .accordion-item__content { - padding: 0 24px; overflow: hidden; transition: height $duration cubic-bezier(0.76, 0, 0.24, 1); } @@ -159,7 +159,7 @@ content: "More featured indicators"; @include h5-black-caps; display: block; - margin: 24px 24px 16px; + margin: 24px var(--padding) 16px; color: $blue-60; } @@ -170,12 +170,15 @@ .accordion-item:last-of-type { border-bottom: 1px solid var(--border); } + + .accordion-item--closed { + margin: 0 var(--padding); + } } - // update styles + // overwrite styles .key-indicator { - margin-top: 0; - margin-bottom: 0; + margin: 0; .indicator-metadata { display: none; } From f7a1cb62fd76db0f2394228f41ae70bf0511a99d Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Thu, 15 Feb 2024 20:04:17 +0000 Subject: [PATCH 50/51] =?UTF-8?q?=E2=9C=A8=20update=20mobile=20styles=20fo?= =?UTF-8?q?r=20key-indicator-collection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@ourworldindata/utils/src/Util.ts | 18 +++++++++--------- site/gdocs/components/KeyIndicator.scss | 10 ++++++++++ site/gdocs/components/KeyIndicator.tsx | 2 +- .../components/KeyIndicatorCollection.scss | 5 ++++- .../components/KeyIndicatorCollection.tsx | 2 +- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index aab01f30349..aa7adf012b8 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1624,15 +1624,6 @@ export function traverseEnrichedBlocks( traverseEnrichedBlocks(node, callback, spanCallback) }) }) - .with( - { type: "key-indicator-collection" }, - (keyIndicatorCollection) => { - callback(keyIndicatorCollection) - keyIndicatorCollection.blocks.forEach((node) => - traverseEnrichedBlocks(node, callback, spanCallback) - ) - } - ) .with( { type: "key-indicator", @@ -1644,6 +1635,15 @@ export function traverseEnrichedBlocks( }) } ) + .with( + { type: "key-indicator-collection" }, + (keyIndicatorCollection) => { + callback(keyIndicatorCollection) + keyIndicatorCollection.blocks.forEach((node) => + traverseEnrichedBlocks(node, callback, spanCallback) + ) + } + ) .with( { type: P.union( diff --git a/site/gdocs/components/KeyIndicator.scss b/site/gdocs/components/KeyIndicator.scss index 0f0c40201a7..cfd4c66f465 100644 --- a/site/gdocs/components/KeyIndicator.scss +++ b/site/gdocs/components/KeyIndicator.scss @@ -14,6 +14,9 @@ .indicator-metadata { margin-bottom: 16px; + @include sm-only { + display: none; + } } .indicator-title { @@ -119,4 +122,11 @@ display: none; } } + + .key-indicator-chart { + @include sm-only { + order: -1; + margin-bottom: 16px; + } + } } diff --git a/site/gdocs/components/KeyIndicator.tsx b/site/gdocs/components/KeyIndicator.tsx index 881c750a26e..deea95bc005 100644 --- a/site/gdocs/components/KeyIndicator.tsx +++ b/site/gdocs/components/KeyIndicator.tsx @@ -54,7 +54,7 @@ export default function KeyIndicator({
{/* desktop */} -

+

{d.title}

-
- {d.blurb.map((textBlock: EnrichedBlockText, i: number) => ( +
+ {d.text.map((textBlock: EnrichedBlockText, i: number) => ( ))}