Skip to content

Commit

Permalink
feat: Markup parser
Browse files Browse the repository at this point in the history
  • Loading branch information
renoirb committed Jun 19, 2023
1 parent 1faca00 commit 1359165
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 5 deletions.
134 changes: 134 additions & 0 deletions js/bt-un-markup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { ContextRequestEvent } from './context.js'
import { ContextRequest_TransformMakup } from './context-markup.js'


const SVG_SKELETON_SPINNER = `
<svg
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB .75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style>
<path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/>
<path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/>
</svg>
`

/**
* Take markup language (e.g. Markdown, Rst), and get it transformed into HTML.
*/
class UnMarkupElement extends HTMLElement {
constructor() {
super()
const shadowRoot = this.attachShadow({ mode: 'open' })
const template = document.createElement('template')
template.innerHTML = `
<style>
:host {
display: block;
}
#markup-loading {
width: 100%;
}
#markup-source {
display: none;
}
.is-not-transformed #markup-loading {
display: block;
}
.is-not-transformed #markup-transformed {
display: none;
}
.is-transformed #markup-loading {
display: none;
}
.is-transformed #markup-transformed {
display: block;
}
</style>
<div class="disposition-parent">
<div
id="markup-viewer"
part="markup-viewer"
class="disposition-item is-not-transformed"
>
<div
id="markup-loading"
part="markup-loading"
>
<slot name="skeleton">
${SVG_SKELETON_SPINNER}
</slot>
</div>
<div id="markup-transformed"></div>
</div>
<div
id="markup-source"
part="markup-source"
class="disposition-item"
>
<pre>
<slot></slot>
</pre>
</div>
</div>
`
const innerHtml = template.content.cloneNode(true)
shadowRoot.appendChild(innerHtml)
let slot = this.shadowRoot.querySelector('slot:not([name])')
slot.addEventListener('slotchange', this._onSlotChange)
}

_applyTransformedMarkup = (html = '') => {
const transformed = html !== ''
const elMarkupViewer = this.shadowRoot.querySelector('#markup-viewer')
const elTransformed = this.shadowRoot.querySelector(
'#markup-transformed',
)
if (transformed) {
elTransformed.innerHTML = html
elMarkupViewer.classList.remove('is-not-transformed')
elMarkupViewer.classList.add('is-transformed')
} else {
elTransformed.innerHTML = ''
elMarkupViewer.classList.add('is-not-transformed')
elMarkupViewer.classList.remove('is-transformed')
}
}

/**
* Listen on changes only on default slot, that's the trigger to ask for HTML.
*/
_onSlotChange = (_event /*: HTMLElementEventMap['slotchange'] */) => {
this.dispatchEvent(
new ContextRequestEvent(
ContextRequest_TransformMakup,
this._onContextResponse_UnMarkup,
),
)
}

_onContextResponse_UnMarkup = ({ html = '' }) => {
this._applyTransformedMarkup(html)
}
}


export default UnMarkupElement
61 changes: 61 additions & 0 deletions js/context-markup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Context Request event for signaling Markdown needing to be parsed.
*/
export const ContextRequest_TransformMakup = 'transform-markup-context'


export const isContextRequest_TransformMakup = (event) =>
event.context === ContextRequest_TransformMakup


export const assertContextRequest_TransformMakup = (event) => {
if (!isContextRequest_TransformMakup(event)) {
const message = `Unexpected error, we expected a "ContextRequest_TransformMakup" context event`
throw new Error(message)
}
}


export const markdownReplaceYouTubeShortCodes = (markdownText) => markdownText.replace(
// replace any youtube shortcodes
/{{<\s*youtube\s+([^\s>}]+)\s*>}}/g,
'<iframe width="848" height="477" src="https://www.youtube.com/embed/$1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
)


export class TransformMarkupSource {

frontMatterString /*: string */ = ''

markup /*: string */ = ''

frontMatter /*?: Record<string, unknown> */ = void 0

html /*?: string */ = void 0

get [Symbol.toStringTag]() {
return 'TransformMarkupSource';
}

constructor(source /*: string */ = '') {
const isThreeDashesLine = line => /^---$/.test(line)
const lines = source.split('\n')
// Line numbers of frontMatter.
const [fmBeginLn, fmEndLn] = lines.map((ln, lnNbr) => isThreeDashesLine(ln) ? lnNbr : false).filter(i => i)
// Probably bogus
this.frontMatterString = lines.slice(fmBeginLn + 1, fmEndLn).join('\n')
this.markup = lines.slice(fmEndLn + 1).join('\n')
}
}


export const getFromContext_TransformMakup = (event) => {
assertContextRequest_TransformMakup(event)
const innerHTML = event.originalTarget.innerHTML
return new TransformMarkupSource(innerHTML)
}


export const isValidContextResponse_TransformMakup = (payload) => {
return /TransformMarkupSource/.test('' + payload)
}
15 changes: 15 additions & 0 deletions js/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

const DEP_SHOWDOWN_VERSION = '2.1.0'
const IMPORT_DEP_SHOWDOWN = `https://ga.jspm.io/npm:showdown@${DEP_SHOWDOWN_VERSION}/dist/showdown.js`

/**
* By default, markup is in Markdown, but could be in another.
*/
export const markupParser = async (opts = {}) => {
const imported = await import(IMPORT_DEP_SHOWDOWN)
const showdown = imported?.default
const converter = new showdown.Converter({ tables: true, simplifiedAutoLink: true, ...opts })
converter.setOption('openLinksInNewWindow', true)

return converter
}
28 changes: 27 additions & 1 deletion js/init.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import dayjs from 'https://esm.archive.org/dayjs'
import yml from 'https://esm.archive.org/js-yaml'
import { isValidCustomElement, registerCustomElement } from './utils-wc.js'
import {
getFromContext_DateConversion,
isContextRequest_DateConveresion,
isValidContextResponse_DateConversion,
} from './context-date-conversion.js'
import {
getFromContext_TransformMakup,
isContextRequest_TransformMakup,
isValidContextResponse_TransformMakup,
markdownReplaceYouTubeShortCodes,
} from './context-markup.js'
import { markupParser } from './defaults.js'

/**
* Our own components, but not loading them just yet.
*/
const OUR_COMPONENTS = [
['bt-time', './bt-date-time.js'],
['bt-un-markup', './bt-un-markup.js'],
]


const handleContextRequest_DateConversion = (event) => {
const test = isContextRequest_DateConveresion(event)
if (isContextRequest_DateConveresion(event)) {
event.stopPropagation()
const { date, dateHumanFormat } = getFromContext_DateConversion(event)
Expand Down Expand Up @@ -45,10 +54,27 @@ const main = async (realm, { components = [] }) => {
}
}

// Make this dynamic, based on configured markup parser
const contentParser = await markupParser()

const handleContextRequest_TransformMarkup = (event) => {
if (isContextRequest_TransformMakup(event)) {
event.stopPropagation()
const obj = getFromContext_TransformMakup(event)
const { frontMatterString, markup } = obj
const html = contentParser.makeHtml(markdownReplaceYouTubeShortCodes(markup))
const frontMatter = yml.load(frontMatterString)
Reflect.set(obj, 'html', html)
Reflect.set(obj, 'frontMatter', frontMatter)
isValidContextResponse_TransformMakup(obj) && event.callback(obj)
}
}

// TODO Make this configurable(?)
realm.document.addEventListener('context-request', (event) => {
// ... and others.
handleContextRequest_DateConversion(event)
handleContextRequest_TransformMarkup(event)
})
}

Expand Down
7 changes: 3 additions & 4 deletions theme/future-imperfect/bt-post-full.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ customElements.define('bt-post-full', class extends LitElement {

render() {
const post = url2post(this.url)
const body = markdown_to_html(post.body_raw)

const key = new URL(post.url).pathname.replace(/^\/+/, '').replace(/\/+$/, '') // xxx
// console.error({key})
Expand Down Expand Up @@ -59,9 +58,9 @@ customElements.define('bt-post-full', class extends LitElement {
</div>
<featured-image url=${this.url} single=true></featured-image>`}
<div>
${unsafeHTML(body)}
</div>
<bt-un-markup>
${unsafeHTML(post.body_raw)}
</bt-un-markup>
${post.type === 'post' ? html`
<footer>
Expand Down

0 comments on commit 1359165

Please sign in to comment.