From 8fe00360b1e68a6eb8a9535d38a45b9a571552b8 Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Tue, 5 Dec 2023 18:10:02 +0100 Subject: [PATCH 1/4] :tada: add citation to sources tab, tweak citation and some related styles --- .../src/DataCitation/DataCitation.tsx | 40 ++++++ .../IndicatorProcessing.scss | 25 +++- .../@ourworldindata/components/src/index.ts | 2 + .../grapher/src/modal/SourcesModal.tsx | 31 +++++ packages/@ourworldindata/utils/src/index.ts | 2 + .../utils/src/metadataHelpers.ts | 83 +++++++++++- site/DataPageV2Content.tsx | 121 +++++------------- 7 files changed, 211 insertions(+), 93 deletions(-) create mode 100644 packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx diff --git a/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx b/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx new file mode 100644 index 00000000000..466d0d69032 --- /dev/null +++ b/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx @@ -0,0 +1,40 @@ +import React from "react" +import { CodeSnippet } from "../CodeSnippet/CodeSnippet.js" + +export const DataCitation = (props: { + citationShort: string + citationLong: string +}) => { + return ( + <> + {props.citationShort && ( + <> +

+ In-line citation +
+ If you have limited space (e.g. in data visualizations, + on social media), you can use this abbreviated in-line + citation: +

+ + + )} + {props.citationLong && ( + <> +

+ Full citation +

+ + + )} + + ) +} diff --git a/packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss b/packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss index e2b43a3efe3..22c40e208cd 100644 --- a/packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss +++ b/packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss @@ -56,6 +56,7 @@ margin: 0; font-size: var(--content-size); + ol, ul { margin-left: 1em; @@ -67,6 +68,26 @@ &:last-child { margin-bottom: 0; } + p { + margin: 0; + } + } + } + + ol { + margin-left: 1em; + + li { + margin: 0.5em 0; // half of the default margin for ordered lists, otherwise like unordered lists above + &:first-child { + margin-top: 0; + } + &:last-child { + margin-bottom: 0; + } + p { + margin: 0; + } } } @@ -77,10 +98,6 @@ > *:last-child { margin-bottom: 0; } - - ul li p { - margin: 0; - } } a { diff --git a/packages/@ourworldindata/components/src/index.ts b/packages/@ourworldindata/components/src/index.ts index 35b288d6d40..d5e088b7264 100644 --- a/packages/@ourworldindata/components/src/index.ts +++ b/packages/@ourworldindata/components/src/index.ts @@ -38,6 +38,8 @@ export { renderCodeSnippets, } from "./CodeSnippet/CodeSnippet.js" +export { DataCitation } from "./DataCitation/DataCitation.js" + export { DATAPAGE_ABOUT_THIS_DATA_SECTION_ID, DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID, diff --git a/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx b/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx index 92efa2b287b..b34e2490287 100644 --- a/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx +++ b/packages/@ourworldindata/grapher/src/modal/SourcesModal.tsx @@ -13,11 +13,14 @@ import { OwidSource, IndicatorTitleWithFragments, joinTitleFragments, + getCitationShort, + getCitationLong, } from "@ourworldindata/utils" import { IndicatorSources, IndicatorProcessing, SimpleMarkdownText, + DataCitation, } from "@ourworldindata/components" import React from "react" import { action, computed } from "mobx" @@ -282,6 +285,27 @@ export class Source extends React.Component<{ return { ...this.column.def, source: this.column.source } } + @computed get citationShort(): string { + return getCitationShort( + this.def.origins ?? [], + getAttributionFragmentsFromVariable(this.def), + this.def.owidProcessingLevel + ) + } + + @computed get citationLong(): string { + return getCitationLong( + this.title, + this.def.origins ?? [], + this.source, + getAttributionFragmentsFromVariable(this.def), + this.def.presentation?.attributionShort, + this.def.presentation?.titleVariant, + this.def.owidProcessingLevel, + undefined + ) + } + @computed private get source(): OwidSource { return this.def.source ?? {} } @@ -426,6 +450,13 @@ export class Source extends React.Component<{ +

+ How to cite this data: +

+ ) } diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index 1c8395c6b17..ca8413a12c9 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -382,6 +382,8 @@ export { splitSourceTextIntoFragments, prepareSourcesForDisplay, formatSourceDate, + getCitationLong, + getCitationShort, } from "./metadataHelpers.js" export { diff --git a/packages/@ourworldindata/utils/src/metadataHelpers.ts b/packages/@ourworldindata/utils/src/metadataHelpers.ts index 46bcbbfd531..1b057e49e6d 100644 --- a/packages/@ourworldindata/utils/src/metadataHelpers.ts +++ b/packages/@ourworldindata/utils/src/metadataHelpers.ts @@ -1,8 +1,13 @@ import { OwidOrigin } from "./OwidOrigin" -import { OwidProcessingLevel, OwidVariableWithSource } from "./OwidVariable" +import { + IndicatorTitleWithFragments, + OwidProcessingLevel, + OwidVariableWithSource, +} from "./OwidVariable" import { DisplaySource } from "./owidTypes" import { compact, uniq, last, excludeUndefined } from "./Util" import dayjs from "./dayjs.js" +import { OwidSource } from "./OwidSource.js" export function getOriginAttributionFragments( origins: OwidOrigin[] | undefined @@ -184,6 +189,82 @@ export const prepareSourcesForDisplay = ( return sourcesForDisplay } +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 "" +} +export const getCitationShort = ( + origins: OwidOrigin[], + attributions: string[], + owidProcessingLevel: OwidProcessingLevel | undefined +) => { + const producersWithYear = uniq( + origins.map((o) => `${o.producer}${getYearSuffixFromOrigin(o)}`) + ) + const processingLevelPhrase = + getPhraseForProcessingLevel(owidProcessingLevel) + + const attributionFragments = attributions ?? producersWithYear + const attributionPotentiallyShortened = + attributionFragments.length > 3 + ? `${attributionFragments[0]} and other sources` + : attributionFragments.join("; ") + + return `${attributionPotentiallyShortened} – ${processingLevelPhrase} by Our World in Data` +} + +export const getCitationLong = ( + indicatorTitle: IndicatorTitleWithFragments, + origins: OwidOrigin[], + source: OwidSource | undefined, + attributions: string[], + attributionShort: string | undefined, + titleVariant: string | undefined, + owidProcessingLevel: OwidProcessingLevel | undefined, + canonicalUrl: string | undefined +) => { + const sourceShortName = + attributionShort && titleVariant + ? `${attributionShort} – ${titleVariant}` + : attributionShort || titleVariant + const producersWithYear = uniq( + origins.map((o) => `${o.producer}${getYearSuffixFromOrigin(o)}`) + ) + const processingLevelPhrase = + getPhraseForProcessingLevel(owidProcessingLevel) + + const attributionFragments = attributions ?? producersWithYear + const attributionUnshortened = attributionFragments.join("; ") + const citationLonger = `${attributionUnshortened} – ${processingLevelPhrase} by Our World in Data` + const titleWithOptionalFragments = excludeUndefined([ + indicatorTitle.title, + sourceShortName, + ]).join(" – ") + const originsLong = uniq( + origins.map( + (o) => + `${o.producer}, “${o.title ?? o.titleSnapshot}${ + o.versionProducer ? " " + o.versionProducer : "" + }”` + ) + ).join("; ") + const today = dayjs().format("MMMM D, YYYY") + return excludeUndefined([ + `${citationLonger}.`, + `“${titleWithOptionalFragments}” [dataset].`, + originsLong + ? `${originsLong} [original data].` + : source?.name + ? `${source?.name} [original data].` + : undefined, + canonicalUrl ? `Retrieved ${today} from ${canonicalUrl}` : undefined, + ]).join(" ") +} export const formatSourceDate = ( date: string | undefined, format: string diff --git a/site/DataPageV2Content.tsx b/site/DataPageV2Content.tsx index 9dc392b7b45..4e7e39e8a2f 100644 --- a/site/DataPageV2Content.tsx +++ b/site/DataPageV2Content.tsx @@ -23,6 +23,7 @@ import { makeUnitConversionFactor, makeLinks, HtmlOrSimpleMarkdownText, + DataCitation, } from "@ourworldindata/components" import ReactDOM from "react-dom" import { GrapherWithFallback } from "./GrapherWithFallback.js" @@ -33,13 +34,14 @@ import { uniq, formatAuthors, intersection, - getPhraseForProcessingLevel, prepareSourcesForDisplay, DataPageRelatedResearch, isEmpty, excludeUndefined, OwidOrigin, DataPageDataV2, + getCitationShort, + getCitationLong, joinTitleFragments, } from "@ourworldindata/utils" import { AttachmentsContext, DocumentContext } from "./gdocs/OwidGdoc.js" @@ -142,40 +144,23 @@ export const DataPageV2Content = ({ ) const attributionFragments = datapageData.attributions ?? producersWithYear - const attributionPotentiallyShortened = - attributionFragments.length > 3 - ? `${attributionFragments[0]} and other sources` - : attributionFragments.join("; ") const attributionUnshortened = attributionFragments.join("; ") - const processingLevelPhrase = getPhraseForProcessingLevel( + const citationShort = getCitationShort( + datapageData.origins, + datapageData.attributions, datapageData.owidProcessingLevel ) - const citationShort = `${attributionPotentiallyShortened} – ${processingLevelPhrase} by Our World in Data` - const citationLonger = `${attributionUnshortened} – ${processingLevelPhrase} by Our World in Data` - const originsLong = uniq( - datapageData.origins.map( - (o) => - `${o.producer}, ${o.title ?? o.titleSnapshot}${ - o.versionProducer ? " " + o.versionProducer : "" - }` - ) - ).join("; ") - const today = dayjs().format("MMMM D, YYYY") const currentYear = dayjs().year() - const titleWithOptionalFragments = excludeUndefined([ - datapageData.title.title, - titleFragments, - ]).join(" – ") - const citationLong = excludeUndefined([ - `${citationLonger}.`, - `${titleWithOptionalFragments} [dataset].`, - originsLong - ? `${originsLong} [original data].` - : datapageData.source?.name - ? `${datapageData.source?.name} [original data].` - : undefined, - `Retrieved ${today} from ${canonicalUrl}`, - ]).join(" ") + const citationLong = getCitationLong( + datapageData.title, + datapageData.origins, + datapageData.source, + datapageData.attributions, + datapageData.attributionShort, + datapageData.titleVariant, + datapageData.owidProcessingLevel, + canonicalUrl + ) const { linkedDocuments = {}, @@ -673,6 +658,7 @@ export const DataPageV2Content = ({ + {(citationShort || citationLong || citationDatapage) && ( @@ -681,63 +667,6 @@ export const DataPageV2Content = ({ Citations
- {(citationShort || - citationLong) && ( -
-
- How to cite this data -
- {citationShort && ( - <> -

- - In-line - citation - -
- If you have - limited space - (e.g. in data - visualizations, - on social - media), you can - use this - abbreviated - in-line - citation: -

- - - )} - {citationLong && ( - <> -

- - Full - citation - -

- - - )} -
- )} {citationDatapage && (
@@ -759,6 +688,22 @@ export const DataPageV2Content = ({ />
)} +
+
+ How to cite this data +
+ {(citationShort || + citationLong) && ( + + )} +
)} From a25684973364e949fd257b6e0e6a96ce76826a83 Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Tue, 5 Dec 2023 18:26:40 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9D=20eslint=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@ourworldindata/utils/src/metadataHelpers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@ourworldindata/utils/src/metadataHelpers.ts b/packages/@ourworldindata/utils/src/metadataHelpers.ts index 1b057e49e6d..6d3350e8d76 100644 --- a/packages/@ourworldindata/utils/src/metadataHelpers.ts +++ b/packages/@ourworldindata/utils/src/metadataHelpers.ts @@ -189,7 +189,7 @@ export const prepareSourcesForDisplay = ( return sourcesForDisplay } -const getYearSuffixFromOrigin = (o: OwidOrigin) => { +const getYearSuffixFromOrigin = (o: OwidOrigin): string => { const year = o.dateAccessed ? dayjs(o.dateAccessed, ["YYYY-MM-DD", "YYYY"]).year() : o.datePublished @@ -202,7 +202,7 @@ export const getCitationShort = ( origins: OwidOrigin[], attributions: string[], owidProcessingLevel: OwidProcessingLevel | undefined -) => { +): string => { const producersWithYear = uniq( origins.map((o) => `${o.producer}${getYearSuffixFromOrigin(o)}`) ) @@ -227,7 +227,7 @@ export const getCitationLong = ( titleVariant: string | undefined, owidProcessingLevel: OwidProcessingLevel | undefined, canonicalUrl: string | undefined -) => { +): string => { const sourceShortName = attributionShort && titleVariant ? `${attributionShort} – ${titleVariant}` From d913797ce7b021c5e86ff3800e8fae89b2aa0a76 Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Tue, 26 Dec 2023 19:36:34 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=92=84=20fix=20style=20bleed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@ourworldindata/grapher/src/modal/SourcesModal.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss b/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss index c1611d1e5ea..cece9597102 100644 --- a/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss +++ b/packages/@ourworldindata/grapher/src/modal/SourcesModal.scss @@ -125,6 +125,14 @@ margin-right: $tab-gap; } + .citation__paragraph { + color: $light-text; + } + + .citation__type { + color: $dark-text; + } + .indicator-processing .indicator-processing__link { @include body-3-medium; display: inline-block; From ddb4c0392d7e6eaa7d36de5e723371974d1d472e Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Tue, 26 Dec 2023 20:45:01 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=90=9D=20remove=20"on=20social=20medi?= =?UTF-8?q?a"=20text=20fragment=20in=20citation=20guidance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/src/DataCitation/DataCitation.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx b/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx index 466d0d69032..db1009fb12c 100644 --- a/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx +++ b/packages/@ourworldindata/components/src/DataCitation/DataCitation.tsx @@ -12,9 +12,8 @@ export const DataCitation = (props: {

In-line citation
- If you have limited space (e.g. in data visualizations, - on social media), you can use this abbreviated in-line - citation: + If you have limited space (e.g. in data visualizations), + you can use this abbreviated in-line citation: