Adds Markdown component and scrubs html directives per #148
This commit is contained in:
parent
653cce5c71
commit
1edd479b01
14 changed files with 181 additions and 32 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
31
src/components/Markdown/Markdown.stories.svelte
Normal file
31
src/components/Markdown/Markdown.stories.svelte
Normal 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**.',
|
||||
}}"
|
||||
/>
|
||||
42
src/components/Markdown/Markdown.svelte
Normal file
42
src/components/Markdown/Markdown.svelte
Normal 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>
|
||||
25
src/components/Markdown/stores.ts
Normal file
25
src/components/Markdown/stores.ts
Normal 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);
|
||||
39
src/components/Markdown/stories/docs/component.md
Normal file
39
src/components/Markdown/stories/docs/component.md
Normal 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.
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Reference in a new issue