Add Asciinema integration (#326)

* Added Asciinema integration

* Added CHANGELOG entry
This commit is contained in:
Erik Michelson 2020-07-16 22:14:47 +02:00 committed by GitHub
parent 1b52bac838
commit 8216958f91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 68 additions and 1 deletions

View file

@ -32,6 +32,7 @@
- Images, videos, and other non-text content is now wider in View Mode - Images, videos, and other non-text content is now wider in View Mode
- Notes may now be deleted directly from the history page - Notes may now be deleted directly from the history page
- CodiMD instances can now be branded either with a '@ <custom string>' or '@ <custom logo>' after the CodiMD logo and text - CodiMD instances can now be branded either with a '@ <custom string>' or '@ <custom logo>' after the CodiMD logo and text
- Asciinema videos may now be embedded by pasting the URL of one video into a single line
### Changed ### Changed

View file

@ -47,6 +47,9 @@ https://www.youtube.com/watch?v=KgMpKsp23yY
## Vimeo ## Vimeo
https://vimeo.com/23237102 https://vimeo.com/23237102
## Asciinema
https://asciinema.org/a/117928
## PDF ## PDF
{%pdf https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf %} {%pdf https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf %}

View file

@ -26,6 +26,7 @@ import { highlightedCode } from './markdown-it-plugins/highlighted-code'
import { linkifyExtra } from './markdown-it-plugins/linkify-extra' import { linkifyExtra } from './markdown-it-plugins/linkify-extra'
import { MarkdownItParserDebugger } from './markdown-it-plugins/parser-debugger' import { MarkdownItParserDebugger } from './markdown-it-plugins/parser-debugger'
import './markdown-renderer.scss' import './markdown-renderer.scss'
import { replaceAsciinemaLink } from './regex-plugins/replace-asciinema-link'
import { replaceGistLink } from './regex-plugins/replace-gist-link' import { replaceGistLink } from './regex-plugins/replace-gist-link'
import { replaceLegacyGistShortCode } from './regex-plugins/replace-legacy-gist-short-code' import { replaceLegacyGistShortCode } from './regex-plugins/replace-legacy-gist-short-code'
import { replaceLegacySlideshareShortCode } from './regex-plugins/replace-legacy-slideshare-short-code' import { replaceLegacySlideshareShortCode } from './regex-plugins/replace-legacy-slideshare-short-code'
@ -39,6 +40,7 @@ import { replaceQuoteExtraTime } from './regex-plugins/replace-quote-extra-time'
import { replaceVimeoLink } from './regex-plugins/replace-vimeo-link' import { replaceVimeoLink } from './regex-plugins/replace-vimeo-link'
import { replaceYouTubeLink } from './regex-plugins/replace-youtube-link' import { replaceYouTubeLink } from './regex-plugins/replace-youtube-link'
import { ComponentReplacer, SubNodeConverter } from './replace-components/ComponentReplacer' import { ComponentReplacer, SubNodeConverter } from './replace-components/ComponentReplacer'
import { AsciinemaReplacer } from './replace-components/asciinema/asciinema-replacer'
import { GistReplacer } from './replace-components/gist/gist-replacer' import { GistReplacer } from './replace-components/gist/gist-replacer'
import { HighlightedCodeReplacer } from './replace-components/highlighted-fence/highlighted-fence-replacer' import { HighlightedCodeReplacer } from './replace-components/highlighted-fence/highlighted-fence-replacer'
import { ImageReplacer } from './replace-components/image/image-replacer' import { ImageReplacer } from './replace-components/image/image-replacer'
@ -99,6 +101,7 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, cla
md.use(markdownItRegex, replaceLegacySlideshareShortCode) md.use(markdownItRegex, replaceLegacySlideshareShortCode)
md.use(markdownItRegex, replaceLegacySpeakerdeckShortCode) md.use(markdownItRegex, replaceLegacySpeakerdeckShortCode)
md.use(markdownItRegex, replacePdfShortCode) md.use(markdownItRegex, replacePdfShortCode)
md.use(markdownItRegex, replaceAsciinemaLink)
md.use(markdownItRegex, replaceYouTubeLink) md.use(markdownItRegex, replaceYouTubeLink)
md.use(markdownItRegex, replaceVimeoLink) md.use(markdownItRegex, replaceVimeoLink)
md.use(markdownItRegex, replaceGistLink) md.use(markdownItRegex, replaceGistLink)
@ -144,6 +147,7 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, cla
new GistReplacer(), new GistReplacer(),
new YoutubeReplacer(), new YoutubeReplacer(),
new VimeoReplacer(), new VimeoReplacer(),
new AsciinemaReplacer(),
new PdfReplacer(), new PdfReplacer(),
new ImageReplacer(), new ImageReplacer(),
new TocReplacer(), new TocReplacer(),

View file

@ -0,0 +1,18 @@
import { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
const protocolRegex = /(?:http(?:s)?:\/\/)?/
const domainRegex = /(?:asciinema\.org\/a\/)/
const idRegex = /(\d+)/
const tailRegex = /(?:[./?#].*)?/
const gistUrlRegex = new RegExp(`(?:${protocolRegex.source}${domainRegex.source}${idRegex.source}${tailRegex.source})`)
const linkRegex = new RegExp(`^${gistUrlRegex.source}$`, 'i')
export const replaceAsciinemaLink: RegexOptions = {
name: 'asciinema-link',
regex: linkRegex,
replace: (match) => {
// ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
// noinspection CheckTagEmptyBody
return `<codimd-asciinema id="${match}"></codimd-asciinema>`
}
}

View file

@ -0,0 +1,19 @@
import React from 'react'
import { OneClickEmbedding } from '../one-click-frame/one-click-embedding'
export interface AsciinemaFrameProps {
id: string
}
export const AsciinemaFrame: React.FC<AsciinemaFrameProps> = ({ id }) => {
return (
<OneClickEmbedding
containerClassName={'embed-responsive embed-responsive-16by9'}
previewContainerClassName={'embed-responsive-item'}
hoverIcon={'play'}
loadingImageUrl={`https://asciinema.org/a/${id}.png`}>
<iframe className='embed-responsive-item' title={`asciinema cast ${id}`}
src={`https://asciinema.org/a/${id}/embed?autoplay=1`}/>
</OneClickEmbedding>
)
}

View file

@ -0,0 +1,21 @@
import { DomElement } from 'domhandler'
import React from 'react'
import { getAttributesFromCodiMdTag } from '../codi-md-tag-utils'
import { ComponentReplacer } from '../ComponentReplacer'
import { AsciinemaFrame } from './asciinema-frame'
export class AsciinemaReplacer implements ComponentReplacer {
private counterMap: Map<string, number> = new Map<string, number>()
getReplacement (node: DomElement): React.ReactElement | undefined {
const attributes = getAttributesFromCodiMdTag(node, 'asciinema')
if (attributes && attributes.id) {
const asciinemaId = attributes.id
const count = (this.counterMap.get(asciinemaId) || 0) + 1
this.counterMap.set(asciinemaId, count)
return (
<AsciinemaFrame id={asciinemaId}/>
)
}
}
}

View file

@ -13,8 +13,9 @@ export class PossibleWiderReplacer implements ComponentReplacer {
const childIsImage = node.children[0].name === 'img' const childIsImage = node.children[0].name === 'img'
const childIsYoutube = node.children[0].name === 'codimd-youtube' const childIsYoutube = node.children[0].name === 'codimd-youtube'
const childIsVimeo = node.children[0].name === 'codimd-vimeo' const childIsVimeo = node.children[0].name === 'codimd-vimeo'
const childIsAsciinema = node.children[0].name === 'codimd-asciinema'
const childIsPDF = node.children[0].name === 'codimd-pdf' const childIsPDF = node.children[0].name === 'codimd-pdf'
if (!(childIsImage || childIsYoutube || childIsVimeo || childIsPDF)) { if (!(childIsImage || childIsYoutube || childIsVimeo || childIsAsciinema || childIsPDF)) {
return return
} }