diff --git a/adminSiteServer/apiRouter.ts b/adminSiteServer/apiRouter.ts index 9e3d15914a4..8079aa1f503 100644 --- a/adminSiteServer/apiRouter.ts +++ b/adminSiteServer/apiRouter.ts @@ -181,6 +181,7 @@ import { import { match } from "ts-pattern" import { GdocDataInsight } from "../db/model/Gdoc/GdocDataInsight.js" import { GdocHomepage } from "../db/model/Gdoc/GdocHomepage.js" +import { GdocAbout } from "../db/model/Gdoc/GdocAbout.js" import { GdocAuthor } from "../db/model/Gdoc/GdocAuthor.js" import path from "path" import { @@ -2899,8 +2900,13 @@ getRouteNonIdempotentWithRWTransaction( async function indexAndBakeGdocIfNeccesary( trx: db.KnexReadWriteTransaction, user: Required, - prevGdoc: GdocPost | GdocDataInsight | GdocHomepage | GdocAuthor, - nextGdoc: GdocPost | GdocDataInsight | GdocHomepage | GdocAuthor + prevGdoc: + | GdocPost + | GdocDataInsight + | GdocHomepage + | GdocAbout + | GdocAuthor, + nextGdoc: GdocPost | GdocDataInsight | GdocHomepage | GdocAbout | GdocAuthor ) { const prevJson = prevGdoc.toJSON() const nextJson = nextGdoc.toJSON() diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx index 894f85fc9ed..cecd2b225c3 100644 --- a/baker/SiteBaker.tsx +++ b/baker/SiteBaker.tsx @@ -110,8 +110,10 @@ import { getRedirectsFromDb } from "../db/model/Redirect.js" import { getTombstones } from "../db/model/GdocTombstone.js" import { bakeAllMultiDimDataPages } from "./MultiDimBaker.js" import { getAllLinkedPublishedMultiDimDataPages } from "../db/model/MultiDimDataPage.js" +import { getPublicDonorNames } from "../db/model/Donor.js" type PrefetchedAttachments = { + donors: string[] linkedAuthors: LinkedAuthor[] linkedDocuments: Record imageMetadata: Record @@ -170,14 +172,14 @@ function getProgressBarTotal(bakeSteps: BakeStepConfig): number { if (bakeSteps.has("redirects")) total++ // Add a tick for the validation step that occurs when these two steps run if (bakeSteps.has("dods") && bakeSteps.has("charts")) total++ - // Add 6 ticks for prefetching attachments, which will only run if any of these steps are enabled + // Add ticks for prefetching attachments, which will only run if any of these steps are enabled if ( bakeSteps.has("gdocPosts") || bakeSteps.has("gdocTombstones") || bakeSteps.has("dataInsights") || bakeSteps.has("authors") ) { - total += 7 + total += 8 } return total } @@ -350,6 +352,12 @@ export class SiteBaker { ): Promise { if (!this._prefetchedAttachmentsCache) { console.log("Prefetching attachments...") + + const donors = await getPublicDonorNames(knex) + this.progressBar.tick({ + name: `✅ Prefetched donors`, + }) + const publishedGdocs = await getAllMinimalGdocBaseObjects(knex) const publishedGdocsDictionary = keyBy(publishedGdocs, "id") this.progressBar.tick({ @@ -456,6 +464,7 @@ export class SiteBaker { }) const prefetchedAttachments = { + donors, linkedAuthors: publishedAuthors, linkedDocuments: publishedGdocsDictionary, imageMetadata: imageMetadataDictionary, @@ -497,6 +506,7 @@ export class SiteBaker { ) return { + donors: this._prefetchedAttachmentsCache.donors, linkedDocuments, imageMetadata: pick( this._prefetchedAttachmentsCache.imageMetadata, @@ -614,6 +624,7 @@ export class SiteBaker { publishedGdoc.linkedChartSlugs.grapher, publishedGdoc.linkedChartSlugs.explorer, ]) + publishedGdoc.donors = attachments.donors publishedGdoc.linkedAuthors = attachments.linkedAuthors publishedGdoc.linkedDocuments = attachments.linkedDocuments publishedGdoc.imageMetadata = attachments.imageMetadata diff --git a/db/migration/1733228214376-CreateDonors.ts b/db/migration/1733228214376-CreateDonors.ts new file mode 100644 index 00000000000..fe142724185 --- /dev/null +++ b/db/migration/1733228214376-CreateDonors.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class CreateDonors1733228214376 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`-- sql + CREATE TABLE donors ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + originalName VARCHAR(255) NOT NULL, + shouldPublish BOOLEAN NOT NULL DEFAULT (false), + comment TEXT DEFAULT (''), + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY (originalName) + ) + `) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`-- sql + DROP TABLE donors + `) + } +} diff --git a/db/model/Donor.ts b/db/model/Donor.ts new file mode 100644 index 00000000000..898ae34e82e --- /dev/null +++ b/db/model/Donor.ts @@ -0,0 +1,13 @@ +import { DbPlainDonor, DonorsTableName } from "@ourworldindata/types" +import * as db from "../db" + +export async function getPublicDonorNames( + knex: db.KnexReadonlyTransaction +): Promise { + const donors = await knex(DonorsTableName) + .select("name") + .where({ shouldPublish: true }) + .orderBy("name") + .distinct() + return donors.map((donor) => donor.name) +} diff --git a/db/model/Gdoc/GdocAbout.ts b/db/model/Gdoc/GdocAbout.ts new file mode 100644 index 00000000000..4008a24b8cc --- /dev/null +++ b/db/model/Gdoc/GdocAbout.ts @@ -0,0 +1,37 @@ +import { + OwidGdocAboutContent, + OwidGdocAboutInterface, + OwidGdocBaseInterface, +} from "@ourworldindata/types" +import { traverseEnrichedBlock } from "@ourworldindata/utils" +import * as db from "../../db.js" +import { getPublicDonorNames } from "../Donor.js" +import { GdocBase } from "./GdocBase.js" + +export class GdocAbout extends GdocBase implements OwidGdocAboutInterface { + content!: OwidGdocAboutContent + + static create(obj: OwidGdocBaseInterface): GdocAbout { + const gdoc = new GdocAbout(undefined) + Object.assign(gdoc, obj) + return gdoc + } + + async _loadSubclassAttachments( + knex: db.KnexReadonlyTransaction + ): Promise { + let hasDonors = false + for (const enrichedBlockSource of this.enrichedBlockSources) { + for (const block of enrichedBlockSource) { + traverseEnrichedBlock(block, (block) => { + if (block.type === "donors") { + hasDonors = true + } + }) + } + } + if (hasDonors) { + this.donors = await getPublicDonorNames(knex) + } + } +} diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts index 0efb3f349e6..00666aef159 100644 --- a/db/model/Gdoc/GdocBase.ts +++ b/db/model/Gdoc/GdocBase.ts @@ -82,6 +82,7 @@ export class GdocBase implements OwidGdocBaseInterface { breadcrumbs: BreadcrumbItem[] | null = null tags: DbPlainTag[] | null = null errors: OwidGdocErrorMessage[] = [] + donors: string[] = [] imageMetadata: Record = {} linkedAuthors: LinkedAuthor[] = [] linkedCharts: Record = {} @@ -543,6 +544,7 @@ export class GdocBase implements OwidGdocBaseInterface { "aside", "blockquote", "callout", + "donors", "expandable-paragraph", "entry-summary", "gray-section", diff --git a/db/model/Gdoc/GdocFactory.ts b/db/model/Gdoc/GdocFactory.ts index 4e2ebdfb8c6..5793ac9e5af 100644 --- a/db/model/Gdoc/GdocFactory.ts +++ b/db/model/Gdoc/GdocFactory.ts @@ -44,13 +44,14 @@ import { getPublishedGdocPostsWithTags, } from "../../db.js" import { enrichedBlocksToMarkdown } from "./enrichedToMarkdown.js" +import { GdocAbout } from "./GdocAbout.js" import { GdocAuthor } from "./GdocAuthor.js" import { extractFilenamesFromBlock } from "./gdocUtils.js" import { fetchImagesFromDriveAndSyncToS3 } from "../Image.js" export function gdocFromJSON( json: Record -): GdocPost | GdocDataInsight | GdocHomepage | GdocAuthor { +): GdocPost | GdocDataInsight | GdocHomepage | GdocAbout | GdocAuthor { if (typeof json.content === "string") { json.content = JSON.parse(json.content) } @@ -72,12 +73,16 @@ export function gdocFromJSON( OwidGdocType.Article, OwidGdocType.LinearTopicPage, OwidGdocType.TopicPage, - OwidGdocType.Fragment, - OwidGdocType.AboutPage + OwidGdocType.Fragment ), // TODO: better validation here? () => GdocPost.create({ ...(json as any) }) ) + .with( + OwidGdocType.AboutPage, + // TODO: better validation here? + () => GdocAbout.create({ ...(json as any) }) + ) .with( OwidGdocType.DataInsight, // TODO: better validation here? @@ -127,7 +132,7 @@ export async function createGdocAndInsertIntoDb( export async function updateGdocContentOnly( knex: KnexReadonlyTransaction, id: string, - gdoc: GdocPost | GdocDataInsight | GdocHomepage | GdocAuthor + gdoc: GdocPost | GdocDataInsight | GdocHomepage | GdocAbout | GdocAuthor ): Promise { let markdown: string | null = gdoc.markdown try { @@ -276,7 +281,7 @@ export async function getPublishedGdocBaseObjectBySlug( export async function getAndLoadGdocBySlug( knex: KnexReadWriteTransaction, slug: string -): Promise { +): Promise { const base = await getPublishedGdocBaseObjectBySlug(knex, slug, true) if (!base) { throw new Error( @@ -291,7 +296,7 @@ export async function getAndLoadGdocById( knex: KnexReadWriteTransaction, id: string, contentSource?: GdocsContentSource -): Promise { +): Promise { const base = await getGdocBaseObjectById(knex, id, true) if (!base) throw new Error(`No Google Doc with id "${id}" found in the database`) @@ -320,7 +325,7 @@ export async function loadGdocFromGdocBase( knex: KnexReadWriteTransaction, base: OwidGdocBaseInterface, contentSource?: GdocsContentSource -): Promise { +): Promise { const type = get(base, "content.type") as unknown if (!type) throw new Error( @@ -338,11 +343,11 @@ export async function loadGdocFromGdocBase( OwidGdocType.Article, OwidGdocType.LinearTopicPage, OwidGdocType.TopicPage, - OwidGdocType.Fragment, - OwidGdocType.AboutPage + OwidGdocType.Fragment ), () => GdocPost.create(base) ) + .with(OwidGdocType.AboutPage, () => GdocAbout.create(base)) .with(OwidGdocType.DataInsight, () => GdocDataInsight.create(base)) .with(OwidGdocType.Homepage, () => GdocHomepage.create(base)) .with(OwidGdocType.Author, () => GdocAuthor.create(base)) @@ -627,7 +632,7 @@ export async function getAllGdocIndexItemsOrderedByUpdatedAt( export async function addImagesToContentGraph( trx: KnexReadWriteTransaction, - gdoc: GdocPost | GdocDataInsight | GdocHomepage | GdocAuthor + gdoc: GdocPost | GdocDataInsight | GdocHomepage | GdocAbout | GdocAuthor ): Promise { const id = gdoc.id // Deleting and recreating these is simpler than tracking orphans over the next code block diff --git a/db/model/Gdoc/enrichedToMarkdown.ts b/db/model/Gdoc/enrichedToMarkdown.ts index 70e4c9085b0..ae7a4f28498 100644 --- a/db/model/Gdoc/enrichedToMarkdown.ts +++ b/db/model/Gdoc/enrichedToMarkdown.ts @@ -127,6 +127,9 @@ ${items} exportComponents ) ) + .with({ type: "donors" }, (_): string | undefined => + markdownComponent("DonorList", {}, exportComponents) + ) .with({ type: "scroller" }, () => undefined) // Note: dropped .with( { type: "chart-story" }, diff --git a/db/model/Gdoc/enrichedToRaw.ts b/db/model/Gdoc/enrichedToRaw.ts index 35d6ffe6027..ebc84320256 100644 --- a/db/model/Gdoc/enrichedToRaw.ts +++ b/db/model/Gdoc/enrichedToRaw.ts @@ -6,6 +6,7 @@ import { RawBlockAside, RawBlockChart, RawBlockChartStory, + RawBlockDonorList, RawBlockGraySection, RawBlockHeading, RawBlockHtml, @@ -121,6 +122,13 @@ export function enrichedBlockToRawBlock( }, }) ) + .with( + { type: "donors" }, + (b): RawBlockDonorList => ({ + type: b.type, + value: b.value, + }) + ) .with( { type: "scroller" }, (b): RawBlockScroller => ({ diff --git a/db/model/Gdoc/exampleEnrichedBlocks.ts b/db/model/Gdoc/exampleEnrichedBlocks.ts index ccae879baee..b059e9ff95c 100644 --- a/db/model/Gdoc/exampleEnrichedBlocks.ts +++ b/db/model/Gdoc/exampleEnrichedBlocks.ts @@ -92,6 +92,11 @@ export const enrichedBlockExamples: Record< caption: boldLinkExampleText, parseErrors: [], }, + donors: { + type: "donors", + value: {}, + parseErrors: [], + }, scroller: { type: "scroller", blocks: [ diff --git a/db/model/Gdoc/gdocUtils.ts b/db/model/Gdoc/gdocUtils.ts index da5d2faa110..d672e9bbe6d 100644 --- a/db/model/Gdoc/gdocUtils.ts +++ b/db/model/Gdoc/gdocUtils.ts @@ -222,6 +222,7 @@ export function extractFilenamesFromBlock( "callout", "chart-story", "chart", + "donors", "entry-summary", "expandable-paragraph", "explorer-tiles", diff --git a/db/model/Gdoc/rawToArchie.ts b/db/model/Gdoc/rawToArchie.ts index fee661107db..852e42ecb43 100644 --- a/db/model/Gdoc/rawToArchie.ts +++ b/db/model/Gdoc/rawToArchie.ts @@ -5,6 +5,7 @@ import { RawBlockAside, RawBlockChart, RawBlockChartStory, + RawBlockDonorList, RawBlockGraySection, RawBlockHomepageIntro, RawBlockHorizontalRule, @@ -126,6 +127,13 @@ function* rawBlockChartToArchieMLString( yield "{}" } +function* rawBlockDonorListToArchieMLString( + _block: RawBlockDonorList +): Generator { + yield "{.donors}" + yield "{}" +} + function* rawBlockScrollerToArchieMLString( block: RawBlockScroller ): Generator { @@ -811,6 +819,7 @@ export function* OwidRawGdocBlockToArchieMLStringGenerator( .with({ type: "all-charts" }, rawBlockAllChartsToArchieMLString) .with({ type: "aside" }, rawBlockAsideToArchieMLString) .with({ type: "chart" }, rawBlockChartToArchieMLString) + .with({ type: "donors" }, rawBlockDonorListToArchieMLString) .with({ type: "scroller" }, rawBlockScrollerToArchieMLString) .with({ type: "callout" }, rawBlockCalloutToArchieMLString) .with({ type: "chart-story" }, rawBlockChartStoryToArchieMLString) diff --git a/db/model/Gdoc/rawToEnriched.ts b/db/model/Gdoc/rawToEnriched.ts index 0eaf2438c52..b964cb04364 100644 --- a/db/model/Gdoc/rawToEnriched.ts +++ b/db/model/Gdoc/rawToEnriched.ts @@ -7,6 +7,7 @@ import { EnrichedBlockCallout, EnrichedBlockChart, EnrichedBlockChartStory, + EnrichedBlockDonorList, EnrichedBlockGraySection, EnrichedBlockHeading, EnrichedBlockHorizontalRule, @@ -43,6 +44,7 @@ import { RawBlockCallout, RawBlockChart, RawBlockChartStory, + RawBlockDonorList, RawBlockGraySection, RawBlockHeading, RawBlockHtml, @@ -167,6 +169,7 @@ export function parseRawBlocksToEnrichedBlocks( .with({ type: "blockquote" }, parseBlockquote) .with({ type: "callout" }, parseCallout) .with({ type: "chart" }, parseChart) + .with({ type: "donors" }, parseDonorList) .with({ type: "scroller" }, parseScroller) .with({ type: "chart-story" }, parseChartStory) .with({ type: "image" }, parseImage) @@ -489,6 +492,14 @@ const parseChart = (raw: RawBlockChart): EnrichedBlockChart => { } } +const parseDonorList = (raw: RawBlockDonorList): EnrichedBlockDonorList => { + return { + type: "donors", + value: raw.value, + parseErrors: [], + } +} + const parseScroller = (raw: RawBlockScroller): EnrichedBlockScroller => { const createError = ( error: ParseError, diff --git a/packages/@ourworldindata/types/src/dbTypes/Donors.ts b/packages/@ourworldindata/types/src/dbTypes/Donors.ts new file mode 100644 index 00000000000..6260379d847 --- /dev/null +++ b/packages/@ourworldindata/types/src/dbTypes/Donors.ts @@ -0,0 +1,14 @@ +export const DonorsTableName = "donors" + +export type DbInsertDonor = { + name: string + originalName: string + shouldPublish: boolean + comment: string + createdAt?: Date + updatedAt?: Date +} + +export type DbPlainDonor = Required & { + id: number +} diff --git a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts index 139c67400af..8360d507d83 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/ArchieMlComponents.ts @@ -86,6 +86,16 @@ export type EnrichedBlockChart = { tabs?: ChartTabKeyword[] } & EnrichedBlockWithParseErrors +export type RawBlockDonorList = { + type: "donors" + value?: Record // dummy value to unify block shapes +} + +export type EnrichedBlockDonorList = { + type: "donors" + value?: Record // dummy value to unify block shapes +} & EnrichedBlockWithParseErrors + export type RawBlockKeyIndicatorValue = { datapageUrl?: string title?: string @@ -925,6 +935,7 @@ export type OwidRawGdocBlock = | RawBlockAside | RawBlockCallout | RawBlockChart + | RawBlockDonorList | RawBlockScroller | RawBlockChartStory | RawBlockExplorerTiles @@ -974,6 +985,7 @@ export type OwidEnrichedGdocBlock = | EnrichedBlockAside | EnrichedBlockCallout | EnrichedBlockChart + | EnrichedBlockDonorList | EnrichedBlockScroller | EnrichedBlockChartStory | EnrichedBlockExplorerTiles diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts index 4297418ddb0..a6f35022ea3 100644 --- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts +++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts @@ -214,6 +214,7 @@ export interface OwidGdocAboutContent { export interface OwidGdocAboutInterface extends OwidGdocBaseInterface { content: OwidGdocAboutContent + donors?: string[] } export type OwidGdocContent = diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts index 3ec928680a3..35934ae8bd2 100644 --- a/packages/@ourworldindata/types/src/index.ts +++ b/packages/@ourworldindata/types/src/index.ts @@ -213,6 +213,7 @@ export { type RawBlockTableRow, type RawBlockTableCell, type RawChartStoryValue, + type RawBlockDonorList, type RawRecircLink, type RawSDGGridItem, type RawBlockEntrySummary, @@ -224,6 +225,7 @@ export { type EnrichedBlockCallout, type EnrichedBlockChart, type EnrichedBlockChartStory, + type EnrichedBlockDonorList, type EnrichedBlockExpandableParagraph, type EnrichedBlockExplorerTiles, type EnrichedBlockGraySection, @@ -514,6 +516,11 @@ export { type DbInsertDatasetTag, DatasetTagsTableName, } from "./dbTypes/DatasetTags.js" +export { + type DbInsertDonor, + type DbPlainDonor, + DonorsTableName, +} from "./dbTypes/Donors.js" export { type DbPlainEntity, type DbInsertEntity, diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index 35444307cdc..d5476d8c9dc 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1708,6 +1708,7 @@ export function traverseEnrichedBlock( type: P.union( "chart-story", "chart", + "donors", "horizontal-rule", "html", "image", diff --git a/site/gdocs/OwidGdoc.tsx b/site/gdocs/OwidGdoc.tsx index b37668275a2..0380c0cf0c8 100644 --- a/site/gdocs/OwidGdoc.tsx +++ b/site/gdocs/OwidGdoc.tsx @@ -25,6 +25,7 @@ import { Author } from "./pages/Author.js" import AboutPage from "./pages/AboutPage.js" export type Attachments = { + donors?: string[] linkedAuthors?: LinkedAuthor[] linkedCharts: Record linkedIndicators: Record @@ -120,6 +121,7 @@ export function OwidGdoc({ return ( ) }) + .with({ type: "donors" }, (_block) => ( + + )) .with({ type: "scroller" }, (block) => ( donor[0]) + return ( +
+
+

(Listed in alphabetical order)

+ {Object.entries(donorsByLetter).map(([letter, donors]) => ( +
+

{letter}

+
    + {donors.map((donor) => ( +
  • + {donor} +
  • + ))} +
+
+ ))} +
+
+ ) +} diff --git a/site/gdocs/pages/AboutPage.scss b/site/gdocs/pages/AboutPage.scss index a519774bdf6..643ba07e605 100644 --- a/site/gdocs/pages/AboutPage.scss +++ b/site/gdocs/pages/AboutPage.scss @@ -1,3 +1,9 @@ +.about-page { + .footnote-container { + margin-top: 0; + } +} + .about-header { color: $blue-60; } @@ -40,6 +46,10 @@ .about-body { margin-bottom: 80px; + &:has(.article-block__donors) { + margin-bottom: 0; + } + h2 { @include h1-semibold; color: $blue-60; @@ -73,4 +83,15 @@ margin-bottom: 0; } } + + .article-block__donors { + margin-top: 32px; + padding-top: 40px; + background-color: $beige; + + @include md-down { + margin-top: 16px; + padding-top: 24px; + } + } } diff --git a/site/gdocs/pages/AboutPage.tsx b/site/gdocs/pages/AboutPage.tsx index 23ef82224fa..f81a7371518 100644 --- a/site/gdocs/pages/AboutPage.tsx +++ b/site/gdocs/pages/AboutPage.tsx @@ -17,7 +17,7 @@ const NAV_LINKS = [ export default function AboutPage({ content, slug }: OwidGdocAboutInterface) { return ( -
+

About

diff --git a/site/gdocs/utils.tsx b/site/gdocs/utils.tsx index b4ea2343544..cbeb58774ef 100644 --- a/site/gdocs/utils.tsx +++ b/site/gdocs/utils.tsx @@ -142,6 +142,11 @@ export const useImage = ( return metadata } +export function useDonors(): string[] | undefined { + const { donors } = useContext(AttachmentsContext) + return donors +} + const LinkedA = ({ span }: { span: SpanLink }): React.ReactElement => { const linkType = getLinkType(span.url) const { linkedDocument } = useLinkedDocument(span.url) diff --git a/site/owid.scss b/site/owid.scss index e4abec9ad18..58145608ab6 100644 --- a/site/owid.scss +++ b/site/owid.scss @@ -89,6 +89,7 @@ @import "./gdocs/components/topic-page.scss"; @import "./gdocs/components/DataInsightDateline.scss"; @import "./gdocs/components/DataInsightsNewsletter.scss"; +@import "./gdocs/components/Donors.scss"; @import "./gdocs/components/LatestDataInsights.scss"; @import "./gdocs/components/LatestDataInsightsBlock.scss"; @import "./gdocs/components/LinkedAuthor.scss";