Skip to content

Commit

Permalink
Merge pull request #2885 from owid/blockquote-component
Browse files Browse the repository at this point in the history
Blockquote component
  • Loading branch information
ikesau authored Nov 2, 2023
2 parents f3d12b0 + c6bb9d7 commit fd9cff6
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 41 deletions.
1 change: 1 addition & 0 deletions db/model/Gdoc/Gdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ export class Gdoc extends BaseEntity implements OwidGdocInterface {
"additional-charts",
"align",
"aside",
"blockquote",
"callout",
"expandable-paragraph",
"entry-summary",
Expand Down
13 changes: 13 additions & 0 deletions db/model/Gdoc/enrichedToRaw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
RawBlockEntrySummary,
RawBlockVideo,
RawBlockTable,
RawBlockBlockquote,
} from "@ourworldindata/utils"
import { spanToHtmlString } from "./gdocUtils.js"
import { match, P } from "ts-pattern"
Expand Down Expand Up @@ -420,5 +421,17 @@ export function enrichedBlockToRawBlock(
},
}
})
.with({ type: "blockquote" }, (b): RawBlockBlockquote => {
return {
type: "blockquote",
value: {
text: b.text.map((enriched) => ({
type: "text",
value: spansToHtmlText(enriched.value),
})),
citation: b.citation,
},
}
})
.exhaustive()
}
6 changes: 6 additions & 0 deletions db/model/Gdoc/exampleEnrichedBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,4 +529,10 @@ export const enrichedBlockExamples: Record<
],
parseErrors: [],
},
blockquote: {
type: "blockquote",
text: [enrichedBlockText],
citation: "Max Roser",
parseErrors: [],
},
}
52 changes: 11 additions & 41 deletions db/model/Gdoc/htmlToEnriched.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
SpanSimpleText,
OwidEnrichedGdocBlock,
EnrichedBlockImage,
EnrichedBlockPullQuote,
EnrichedBlockHeading,
EnrichedBlockChart,
EnrichedBlockHtml,
Expand All @@ -34,6 +33,7 @@ import {
EnrichedBlockExpandableParagraph,
EnrichedBlockGraySection,
EnrichedBlockStickyRightContainer,
EnrichedBlockBlockquote,
} from "@ourworldindata/utils"
import { match, P } from "ts-pattern"
import {
Expand Down Expand Up @@ -822,19 +822,21 @@ function cheerioToArchieML(
.with({ tagName: "address" }, unwrapElementWithContext)
.with(
{ tagName: "blockquote" },
(): BlockParseResult<EnrichedBlockPullQuote> => {
const spansResult = getSimpleTextSpansFromChildren(
element,
context
)
(): BlockParseResult<EnrichedBlockBlockquote> => {
const spansResult = getSpansFromChildren(element, context)

return {
errors: spansResult.errors,
content: [
{
type: "pull-quote",
// TODO: this is incomplete - needs to match to all text-ish elements like StructuredText
text: spansResult.content,
type: "blockquote",
text: [
{
type: "text",
value: spansResult.content,
parseErrors: [],
},
],
parseErrors: [],
},
],
Expand Down Expand Up @@ -1385,38 +1387,6 @@ function findCheerioElementRecursive(
return undefined
}

function getSimpleSpans(spans: Span[]): [SpanSimpleText[], Span[]] {
return partition(
spans,
(span: Span): span is SpanSimpleText =>
span.spanType === "span-simple-text"
)
}

function getSimpleTextSpansFromChildren(
element: CheerioElement,
context: ParseContext
): BlockParseResult<SpanSimpleText> {
const spansResult = getSpansFromChildren(element, context)
const [simpleSpans, otherSpans] = getSimpleSpans(spansResult.content)
const errors =
otherSpans.length === 0
? spansResult.errors
: [
...spansResult.errors,
{
name: "expected only plain text" as const,
details: `suppressed tags: ${otherSpans
.map((s) => s.spanType)
.join(", ")}`,
},
]
return {
errors,
content: simpleSpans,
}
}

function getSpansFromChildren(
element: CheerioElement,
context: ParseContext
Expand Down
17 changes: 17 additions & 0 deletions db/model/Gdoc/rawToArchie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
isArray,
RawBlockTable,
RawBlockTableRow,
RawBlockBlockquote,
} from "@ourworldindata/utils"
import { match } from "ts-pattern"

Expand Down Expand Up @@ -586,6 +587,21 @@ function* rawBlockRowToArchieMLString(
yield "{}"
}

function* rawBlockBlockquoteToArchieMLString(
blockquote: RawBlockBlockquote
): Generator<string, void, undefined> {
yield "{.blockquote}"
yield* propertyToArchieMLString("citation", blockquote.value)
if (blockquote.value.text) {
yield "[.+text]"
for (const textBlock of blockquote.value.text) {
yield* OwidRawGdocBlockToArchieMLStringGenerator(textBlock)
}
yield "[]"
}
yield "{}"
}

function* rawBlockTableToArchieMLString(
block: RawBlockTable
): Generator<string, void, undefined> {
Expand Down Expand Up @@ -666,6 +682,7 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator(
.with({ type: "entry-summary" }, rawBlockEntrySummaryToArchieMLString)
.with({ type: "table" }, rawBlockTableToArchieMLString)
.with({ type: "table-row" }, rawBlockRowToArchieMLString)
.with({ type: "blockquote" }, rawBlockBlockquoteToArchieMLString)
.exhaustive()
yield* content
}
Expand Down
47 changes: 47 additions & 0 deletions db/model/Gdoc/rawToEnriched.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ import {
EnrichedBlockTableCell,
tableSizes,
tableTemplates,
RawBlockBlockquote,
EnrichedBlockBlockquote,
} from "@ourworldindata/utils"
import { checkIsInternalLink } from "@ourworldindata/components"
import {
Expand All @@ -130,6 +132,7 @@ export function parseRawBlocksToEnrichedBlocks(
.with({ type: "all-charts" }, parseAllCharts)
.with({ type: "additional-charts" }, parseAdditionalCharts)
.with({ type: "aside" }, parseAside)
.with({ type: "blockquote" }, parseBlockquote)
.with({ type: "callout" }, parseCallout)
.with({ type: "chart" }, parseChart)
.with({ type: "scroller" }, parseScroller)
Expand Down Expand Up @@ -306,6 +309,50 @@ const parseAside = (raw: RawBlockAside): EnrichedBlockAside => {
}
}

const parseBlockquote = (raw: RawBlockBlockquote): EnrichedBlockBlockquote => {
const createError = (error: ParseError): EnrichedBlockBlockquote => ({
type: "blockquote",
text: [],
parseErrors: [error],
})

if (
typeof raw.value.citation !== "undefined" &&
typeof raw.value.citation !== "string"
) {
return createError({
message: "Citation is not a string",
})
}
// citation might not be a URL, in which case this is a no-op
// but if it is a URL, Gdocs may have wrapped it in an <a> tag which we want to remove
const citation = extractUrl(raw.value.citation)
// Enforcing http prefix for URLs so that the UI component can easily decide whether to use it in a cite attribute or <cite> tag
if (citation.includes("www.") && !citation.startsWith("http")) {
return createError({
message:
"Citation is a URL but is missing the http:// or https:// prefix",
})
}

if (!isArray(raw.value.text))
return createError({
message:
"Text is not a freeform array. Make sure you've written [.+text]",
})

const parsedText = raw.value.text.map(parseText)

const parsedTextErrors = parsedText.flatMap((block) => block.parseErrors)

return {
type: "blockquote",
text: parsedText,
citation,
parseErrors: parsedTextErrors,
}
}

const parseChart = (raw: RawBlockChart): EnrichedBlockChart => {
const createError = (
error: ParseError,
Expand Down
6 changes: 6 additions & 0 deletions packages/@ourworldindata/utils/src/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,12 @@ export function traverseEnrichedBlocks(
})
})
})
.with({ type: "blockquote" }, (blockquote) => {
callback(blockquote)
blockquote.text.forEach((node) => {
traverseEnrichedBlocks(node, callback, spanCallback)
})
})
.with(
{
type: P.union(
Expand Down
2 changes: 2 additions & 0 deletions packages/@ourworldindata/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export {
type EnrichedBlockAllCharts,
type EnrichedBlockAdditionalCharts,
type EnrichedBlockAside,
type EnrichedBlockBlockquote,
type EnrichedBlockCallout,
type EnrichedBlockChart,
type EnrichedBlockChartStory,
Expand Down Expand Up @@ -126,6 +127,7 @@ export {
type RawBlockAllCharts,
type RawBlockAdditionalCharts,
type RawBlockAside,
type RawBlockBlockquote,
type RawBlockCallout,
type RawBlockChart,
type RawBlockChartStory,
Expand Down
16 changes: 16 additions & 0 deletions packages/@ourworldindata/utils/src/owidTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,20 @@ export interface EnrichedBlockTableCell {
content: OwidEnrichedGdocBlock[]
}

export type RawBlockBlockquote = {
type: "blockquote"
value: {
text?: RawBlockText[]
citation?: string
}
}

export type EnrichedBlockBlockquote = {
type: "blockquote"
text: EnrichedBlockText[]
citation?: string
} & EnrichedBlockWithParseErrors

export type Ref = {
id: string
// Can be -1
Expand Down Expand Up @@ -1273,6 +1287,7 @@ export type OwidRawGdocBlock =
| RawBlockAlign
| RawBlockEntrySummary
| RawBlockTable
| RawBlockBlockquote

export type OwidEnrichedGdocBlock =
| EnrichedBlockAllCharts
Expand Down Expand Up @@ -1309,6 +1324,7 @@ export type OwidEnrichedGdocBlock =
| EnrichedBlockAlign
| EnrichedBlockEntrySummary
| EnrichedBlockTable
| EnrichedBlockBlockquote

export enum OwidGdocPublicationContext {
unlisted = "unlisted",
Expand Down
33 changes: 33 additions & 0 deletions site/gdocs/ArticleBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,39 @@ export default function ArticleBlock({
{...block}
/>
))
.with({ type: "blockquote" }, (block) => {
// If the citation exists and is a URL, it uses the cite attribute
// If the citation exists and is not a URL, it uses the footer
// Otherwise, we show nothing for cases where the citation is written in the surrounding text
const isCitationAUrl = Boolean(
block.citation && block.citation.startsWith("http")
)
const shouldShowCitationInFooter = block.citation && !isCitationAUrl
const blockquoteProps = isCitationAUrl
? { cite: block.citation }
: {}

return (
<blockquote
className={cx(getLayout("blockquote", containerType))}
{...blockquoteProps}
>
{block.text.map((textBlock, i) => (
<Paragraph
className="article-block__text"
d={textBlock}
key={i}
/>
))}

{shouldShowCitationInFooter ? (
<footer>
<cite>{block.citation}</cite>
</footer>
) : null}
</blockquote>
)
})
.exhaustive()

return (
Expand Down
20 changes: 20 additions & 0 deletions site/gdocs/centered-article.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ $banner-height: 200px;
margin-top: 0;
}

.article-block__blockquote {
margin-top: 0;
color: $blue-60;
p {
font-style: italic;
&:last-of-type {
margin-bottom: 0px;
}
}
a {
color: $blue-60;
}
footer {
margin-top: 8px;
}
cite {
font-style: normal;
}
}

.article-block__heading {
a.deep-link {
position: absolute;
Expand Down

0 comments on commit fd9cff6

Please sign in to comment.