Adds Markdown component and scrubs html directives per #148

This commit is contained in:
Jon McClure 2024-04-08 18:36:18 +01:00
parent 653cce5c71
commit 1edd479b01
14 changed files with 181 additions and 32 deletions

View file

@ -1,5 +1,6 @@
<!-- @component `BodyText` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-bodytext--default) -->
<script lang="ts">
import Markdown from '../Markdown/Markdown.svelte';
/**
* A markdown text string.
* @type {string}
@ -14,12 +15,9 @@
/** Add an id to the block tag to target it with custom CSS. */
export let id: string = '';
import { marked } from 'marked';
import Block from '../Block/Block.svelte';
</script>
<Block id="{id}" class="fmy-6 {cls}">
{#if text}
{@html marked.parse(text)}
{/if}
<Markdown source="{text}" />
</Block>

View file

@ -4,12 +4,12 @@
/**
* Title of the note item
*/
title: String;
title: string;
/**
* Contents of the note as a markdown string
* @required
*/
text: String;
text: string;
}
/**
@ -18,15 +18,19 @@
*/
export let notes: EndNote[] = [];
import { marked } from 'marked';
import Block from '../Block/Block.svelte';
import Markdown from '../Markdown/Markdown.svelte';
</script>
<Block class="notes fmt-6 fmb-8">
{#if notes}
{#each notes as note}
<div class="note-title">{@html marked.parse(note.title)}</div>
<div class="note-content">{@html marked.parse(note.text)}</div>
<div class="note-title">
<Markdown source="{note.title}" />
</div>
<div class="note-content">
<Markdown source="{note.text}" />
</div>
{/each}
{/if}
</Block>

View file

@ -70,7 +70,7 @@
import TextBlock from './TextBlock.svelte';
import Block from '../Block/Block.svelte';
import PaddingReset from '../PaddingReset/PaddingReset.svelte';
import { marked } from 'marked';
import Markdown from '../Markdown/Markdown.svelte';
</script>
<Block
@ -93,7 +93,7 @@
<TextBlock width="{textWidth}">
<h3>{title}</h3>
{#if description}
{@html marked(description)}
<Markdown source="{description}" />
{/if}
</TextBlock>
</PaddingReset>
@ -108,7 +108,7 @@
<!-- Custom ARIA markup -->
<slot name="aria" />
{:else}
{@html marked(ariaDescription)}
<Markdown source="{ariaDescription}" />
{/if}
</div>
{/if}
@ -123,7 +123,7 @@
<PaddingReset containerIsFluid="{width === 'fluid'}">
<TextBlock width="{textWidth}">
<aside>
{@html marked(notes)}
<Markdown source="{notes}" />
</aside>
</TextBlock>
</PaddingReset>

View file

@ -50,7 +50,7 @@
import Block from '../Block/Block.svelte';
import Byline from '../Byline/Byline.svelte';
import { marked } from 'marked';
import Markdown from '../Markdown/Markdown.svelte';
let hedClass;
$: {
@ -94,7 +94,9 @@
<!-- Headline named slot -->
<slot name="hed" />
{:else}
<h1 class="{hedClass}">{@html marked.parseInline(hed)}</h1>
<h1 class="{hedClass}">
<Markdown source="{hed}" parseInline />
</h1>
{/if}
{#if $$slots.dek}
<!-- Dek named slot-->
@ -103,7 +105,7 @@
</div>
{:else if dek}
<div class="dek fmx-auto fmb-6">
{@html marked(dek)}
<Markdown source="{dek}" />
</div>
{/if}
</div>

View file

@ -43,7 +43,7 @@
export let theme: Theme = 'light';
import Block from '../Block/Block.svelte';
import { marked } from 'marked';
import Markdown from '../Markdown/Markdown.svelte';
</script>
<aside class="infobox {theme}">
@ -58,7 +58,9 @@
<slot name="header" />
</div>
{:else if title}
<div class="header fmb-2">{@html marked(title)}</div>
<div class="header fmb-2">
<Markdown source="{title}" />
</div>
{/if}
{#if $$slots.body}
@ -67,7 +69,9 @@
<slot name="body" />
</div>
{:else}
<div class="body">{@html marked(text)}</div>
<div class="body">
<Markdown source="{text}" />
</div>
{/if}
{#if $$slots.footer}
@ -76,7 +80,9 @@
<slot name="footer" />
</div>
{:else if notes}
<div class="footer fmt-2">{@html marked(notes)}</div>
<div class="footer fmt-2">
<Markdown source="{notes}" />
</div>
{/if}
</Block>
</aside>

View file

@ -0,0 +1,31 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
import Markdown from './Markdown.svelte';
import { withComponentDocs } from '$docs/utils/withParams.js';
import Block from '../Block/Block.svelte';
const metaProps = {
...withComponentDocs(componentDocs),
};
</script>
<Meta title="Components/Markdown" component="{Markdown}" {...metaProps} />
<Template let:args>
<Block>
<Markdown {...args} />
</Block>
</Template>
<Story
name="Default"
args="{{
source: 'This is *some* text in **markdown**.',
}}"
/>

View file

@ -0,0 +1,42 @@
<!-- @component `Markdown` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-Markdown--default) -->
<script lang="ts">
import type { Action } from 'svelte/action';
import { marked } from 'marked';
import { staticMarkdown } from './stores';
/** A Markdown formatted string */
export let source = '';
/** Parse markdown inline, i.e., without wrapping it in paragraph tags */
export let parseInline = false;
$: markdown = parseInline ? marked.parseInline(source) : marked.parse(source);
const setInnerHTML: Action<HTMLElement, string> = (node, html) => {
node.innerHTML = html;
return {
update(html) {
node.innerHTML = html;
},
destroy() {
node.innerHTML = '';
},
};
};
</script>
{#if source}
{#if $staticMarkdown}
<div>
{@html markdown}
</div>
{:else}
<div use:setInnerHTML="{markdown}"></div>
{/if}
{/if}
<style>
div {
display: contents;
}
</style>

View file

@ -0,0 +1,25 @@
import { writable } from 'svelte/store';
/**
* Set to `false` in the browser to ensure Markdown content correctly updates
* when a SvelteKit app hyrates.
*
* @example
* ```javascript
* // +layout.js
* import { staticMarkdown } from '@reuters-graphics/graphics-components';
* import { building } from '$app/environment';
*
* export const load = async() => {
* // Set the store with the value of building.
* staticMarkdown.set(building);
*
* // Markdown using this content will correctly refresh when
* // a reader loads your page.
* const content = await fetchPageContent();
*
* return { content };
* }
* ```
*/
export const staticMarkdown = writable(true);

View file

@ -0,0 +1,39 @@
The Markdown component renders markdown into HTML. That's it!
---
```svelte
<script>
import { Markdown } from '@reuters-graphics/graphics-components';
</script>
<Markdown source="{'My markdown **string**!'}" />
```
... well, almost.
Owing to a weird quirk of Svelte's [`@html`](https://svelte.dev/docs/special-tags#html) directive (see [this issue](https://github.com/reuters-graphics/graphics-components/issues/148)), if you want your resulting HTML to be dynamic — e.g., update after a SvelteKit app [hydrates](https://kit.svelte.dev/docs/glossary#hydration) — then you may need to set the included `$staticMarkdown` store to `false` in the browser.
For example, if you're refreshing some data with markdown strings in a SvelteKit project using a [load function](https://kit.svelte.dev/docs/load), set the store to reflect the [`building`](https://kit.svelte.dev/docs/modules#$app-environment-building) variable, which will correctly [prerender](https://kit.svelte.dev/docs/glossary#prerendering) your markdown content AND update it after fresh data is fetched in the browser.
```javascript
// +layout.js
import { staticMarkdown } from '@reuters-graphics/graphics-components';
import { building } from '$app/environment';
/** @type {import('./$types').LayoutLoad} */
export const load = async () => {
// Set the staticMarkdown store with the value of building.
staticMarkdown.set(building);
// Now this content will correctly refresh when a reader loads your page.
const resp = await fetch(
'https://graphics.thomsonreuters.com/data/my-page-content.json'
);
const content = await resp.json();
return { content };
};
```
If you're not updating your markdown content as above, you can safely leave the `$staticMarkdown` store alone and your page will do the right thing.

View file

@ -57,7 +57,7 @@
import Block from '../Block/Block.svelte';
import PaddingReset from '../PaddingReset/PaddingReset.svelte';
import { marked } from 'marked';
import Markdown from '../Markdown/Markdown.svelte';
let containerWidth;
@ -123,7 +123,7 @@
{#each row as img, i}
{#if img.caption}
<div id="{id}-figure-{ri}-{i}" class="caption">
{@html marked(img.caption)}
<Markdown source="{img.caption}" />
</div>
{/if}
{/each}

View file

@ -4,8 +4,8 @@
export let step: ScrollerStep;
export let index: number;
import { marked } from 'marked';
import Block from '../../Block/Block.svelte';
import Markdown from '../../Markdown/Markdown.svelte';
</script>
{#if step.foreground === '' || !step.foreground}
@ -14,18 +14,18 @@
{#if typeof step.altText === 'string'}
<div class="background-alt-text visually-hidden">
{@html marked.parse(step.altText)}
<Markdown source="{step.altText}" />
</div>
{/if}
{:else if typeof step.foreground === 'string'}
<Block class="body-text step-{index + 1}">
<div class="embedded-foreground step-{index + 1}">
{@html marked.parse(step.foreground)}
<Markdown source="{step.foreground}" />
</div>
{#if typeof step.altText === 'string'}
<div class="background-alt-text visually-hidden">
{@html marked.parse(step.altText)}
<Markdown source="{step.altText}" />
</div>
{/if}
</Block>

View file

@ -3,7 +3,7 @@
export let steps: ScrollerStep[] = [];
import { marked } from 'marked';
import Markdown from '../Markdown/Markdown.svelte';
</script>
{#each steps as step, i}
@ -16,13 +16,13 @@
<div class="empty-step-foreground"></div>
{#if typeof step.altText === 'string'}
<div class="background-alt-text visually-hidden">
{@html marked.parse(step.altText)}
<Markdown source="{step.altText}" />
</div>
{/if}
{:else}
<div class="step-foreground w-full">
{#if typeof step.foreground === 'string'}
{@html marked.parse(step.foreground)}
<Markdown source="{step.foreground}" />
{:else}
<svelte:component
this="{step.foreground}"
@ -32,7 +32,7 @@
</div>
{#if typeof step.altText === 'string'}
<div class="background-alt-text visually-hidden">
{@html marked.parse(step.altText)}
<Markdown source="{step.altText}" />
</div>
{/if}
{/if}

View file

@ -39,7 +39,7 @@
import Block from '../Block/Block.svelte';
import Fa from 'svelte-fa/src/fa.svelte';
import { faLink } from '@fortawesome/free-solid-svg-icons';
import { marked } from 'marked';
import Markdown from '../Markdown/Markdown.svelte';
</script>
<Block width="normal" id="{id}" class="simple-timeline-container fmy-6 {cls}">
@ -76,7 +76,7 @@
</div>
{/if}
{#if event.context}
{@html marked(event.context)}
<Markdown source="{event.context}" />
{/if}
</div>
{/each}

View file

@ -24,11 +24,13 @@ export { default as HeroHeadline } from './components/HeroHeadline/Hero.svelte';
export { default as EndNotes } from './components/EndNotes/EndNotes.svelte';
export { default as InfoBox } from './components/InfoBox/InfoBox.svelte';
export { default as InlineAd } from './components/AdSlot/InlineAd.svelte';
export { default as Markdown } from './components/Markdown/Markdown.svelte';
export { default as PaddingReset } from './components/PaddingReset/PaddingReset.svelte';
export { default as PhotoCarousel } from './components/PhotoCarousel/PhotoCarousel.svelte';
export { default as PhotoPack } from './components/PhotoPack/PhotoPack.svelte';
export { default as PymChild } from './components/PymChild/PymChild.svelte';
export { pymChildStore } from './components/PymChild/stores.js';
export { staticMarkdown } from './components/Markdown/stores.js';
export { default as ReferralBlock } from './components/ReferralBlock/ReferralBlock.svelte';
export { default as ReutersGraphicsLogo } from './components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte';
export { default as ReutersLogo } from './components/ReutersLogo/ReutersLogo.svelte';