diff options
author | Robin Appelman <robin@icewind.nl> | 2017-08-28 17:37:02 +0300 |
---|---|---|
committer | Robin Appelman <robin@icewind.nl> | 2017-08-28 18:40:21 +0300 |
commit | 8322441915a9baa85d99b663714ba65ba414f782 (patch) | |
tree | 1c123c62c25eac33bf02fde32d79f56d86a3dc44 /js | |
parent | 6482a0f64a967b068bec9f13218318fa23da361a (diff) |
video plugin
Signed-off-by: Robin Appelman <robin@icewind.nl>
Diffstat (limited to 'js')
-rw-r--r-- | js/MermaidPlugin.ts | 6 | ||||
-rw-r--r-- | js/Renderer.ts | 4 | ||||
-rw-r--r-- | js/VideoPlugin.ts | 148 |
3 files changed, 154 insertions, 4 deletions
diff --git a/js/MermaidPlugin.ts b/js/MermaidPlugin.ts index 695c2ba..657b111 100644 --- a/js/MermaidPlugin.ts +++ b/js/MermaidPlugin.ts @@ -42,12 +42,12 @@ export const MermaidPlugin = (md: MarkdownIt.MarkdownIt) => { const token = tokens[idx]; const code = token.content.trim(); if (token.info === 'mermaid') { - return mermaidChart(code) + return mermaidChart(code); } const firstLine = code.split(/\n/)[0].trim(); if (firstLine === 'gantt' || firstLine === 'sequenceDiagram' || firstLine.match(/^graph (?:TB|BT|RL|LR|TD);?$/)) { - return mermaidChart(code) + return mermaidChart(code); } - return originalRenderer(tokens, idx, options, env, slf) + return originalRenderer(tokens, idx, options, env, slf); } };
\ No newline at end of file diff --git a/js/Renderer.ts b/js/Renderer.ts index 7b0aba8..1b60d57 100644 --- a/js/Renderer.ts +++ b/js/Renderer.ts @@ -4,6 +4,7 @@ import {CheckboxPlugin} from './CheckboxPlugin'; import * as AnchorPlugin from 'markdown-it-anchor'; import * as slugify from 'slugify'; import * as TOCPlugin from 'markdown-it-table-of-contents'; +import VideoPlugin from './VideoPlugin'; import 'katex/dist/katex.min.css'; import 'highlight.js/styles/github.css'; @@ -81,6 +82,7 @@ export class Renderer { this.md.use(TOCPlugin, { slugify: slugifyHeading }); + this.md.use(VideoPlugin); this.md.use(iterator, 'url_new_win', 'link_open', (tokens: MarkdownIt.Token[], idx: number) => { const href = tokens[idx].attrGet('href') as string; if (href[0] !== '#') { @@ -111,7 +113,7 @@ export class Renderer { } getImageUrl(path: string): string { - if (!path) { + if (!path || path.indexOf('.') === -1) { return path; } if (path.substr(0, 7) === 'http://' || path.substr(0, 8) === 'https://' || path.substr(0, 3) === '://') { diff --git a/js/VideoPlugin.ts b/js/VideoPlugin.ts new file mode 100644 index 0000000..3a72e4f --- /dev/null +++ b/js/VideoPlugin.ts @@ -0,0 +1,148 @@ +// based on https://github.com/brianjgeiger/markdown-it-video + +import {default as MarkdownIt, Token} from "markdown-it"; + +type VideoService = 'youtube' | 'vimeo' | 'vine' | 'prezi'; + +function isVideoService(service: string): service is VideoService { + return ['youtube', 'vimeo', 'vine', 'prezi'].indexOf(service) !== -1; +} + +const youtubeRegex = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/; + +function youtubeParser(url: string): string { + const match = url.match(youtubeRegex); + return match && match[7].length === 11 ? match[7] : url; +} + +/*eslint-disable max-len */ +const vimeoRegex = /https?:\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|album\/(\d+)\/video\/|)(\d+)(?:$|\/|\?)/; + +/*eslint-enable max-len */ +function vimeoParser(url: string): string { + const match = url.match(vimeoRegex); + return match && typeof match[3] === 'string' ? match[3] : url; +} + +const vineRegex = /^http(?:s?):\/\/(?:www\.)?vine\.co\/v\/([a-zA-Z0-9]{1,13}).*/; + +function vineParser(url: string): string { + const match = url.match(vineRegex); + return match && match[1].length === 11 ? match[1] : url; +} + +const preziRegex = /^https:\/\/prezi.com\/(.[^/]+)/; + +function preziParser(url: string): string { + const match = url.match(preziRegex); + return match ? match[1] : url; +} + +function getVideoId(service: string, url: string): string | false { + const serviceLower = service.toLowerCase(); + if (serviceLower === 'youtube') { + return youtubeParser(url); + } else if (serviceLower === 'vimeo') { + return vimeoParser(url); + } else if (serviceLower === 'vine') { + return vineParser(url); + } else if (serviceLower === 'prezi') { + return preziParser(url); + } + return false; +} + +function videoUrl(service: VideoService, videoID: string, options: VideoOptions): string { + switch (service) { + case 'youtube': + return '//www.youtube.com/embed/' + videoID; + case 'vimeo': + return '//player.vimeo.com/video/' + videoID; + case 'vine': + return '//vine.co/v/' + videoID + '/embed/' + options.vine.embed; + case 'prezi': + return 'https://prezi.com/embed/' + videoID + + '/?bgcolor=ffffff&lock_to_path=0&autoplay=0&autohide_ctrls=0&' + + 'landing_data=bHVZZmNaNDBIWnNjdEVENDRhZDFNZGNIUE43MHdLNWpsdFJLb2ZHanI5N1lQVHkxSHFxazZ0UUNCRHloSXZROHh3PT0&' + + 'landing_sign=1kD6c0N6aYpMUS0wxnQjxzSqZlEB8qNFdxtdjYhwSuI'; + } +} + +function isEmbeddedVideo(url: string) { + return url.match(/\.(webm|mp4|ogv)/) !== null; +} + +function renderVideo(md: MarkdownIt.MarkdownIt, options: VideoOptions) { + const altTokenizer = md.renderer['renderInlineAsText']; + return (tokens: Token[], idx: number, env) => { + const token = tokens[idx]; + const url = token.attrGet('src'); + if (!url) { + return false; + } + token.attrs[token.attrIndex('alt')][1] = altTokenizer(token.children, options, env); + const alt = token.attrGet('alt'); + if (alt && isVideoService(alt)) { + return renderVideoService(md, options, alt, url); + } + console.log(url, isEmbeddedVideo(url)); + if (isEmbeddedVideo(url)) { + return renderEmbededVideo(md, options, url); + } + + return false; + } +} + +function renderVideoService(md: MarkdownIt.MarkdownIt, options: VideoOptions, service: VideoService, url: string): string | false { + const videoID = getVideoId(service, url as string); + if (!videoID) { + return false; + } + const escapedVideoId = md.utils.escapeHtml(videoID); + const lowerService = service.toLowerCase() as VideoService; + const height = options[lowerService].width / 16 * 9; + return videoID === '' ? '' : + '<div class="embed-responsive"><iframe class="embed-responsive-item" id="' + + service + 'player" type="text/html" width="' + (options[lowerService].width) + + '" height="' + (height) + + '" src="' + videoUrl(lowerService, escapedVideoId, options) + + '" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>'; +} + +function renderEmbededVideo(md: MarkdownIt.MarkdownIt, options: VideoOptions, url: string) { + url = md.utils.escapeHtml(url); + return `<div class="embed-responsive"><video controls="controls" src="${url}"/></div>`; +} + +export interface VideoServiceOptions { + width: number; +} + +export interface VineOptions extends VideoServiceOptions { + embed: string; +} + +export interface VideoOptions { + youtube: VideoServiceOptions; + vimeo: VideoServiceOptions; + vine: VineOptions; + prezi: VideoServiceOptions; +} + +const defaults: VideoOptions = { + youtube: {width: 640}, + vimeo: {width: 500}, + vine: {width: 600, embed: 'simple'}, + prezi: {width: 550} +}; + +export default function VideoPlugin(md: MarkdownIt.MarkdownIt, options: VideoOptions) { + const originalRenderer = md.renderer.rules.image; + md.renderer.rules.image = (tokens: Token[], idx: number, options: VideoOptions, env, slf) => { + options = $.extend(defaults, options); + console.log(JSON.stringify(tokens[idx])); + const videoResult = renderVideo(md, options)(tokens, idx, env); + return videoResult || originalRenderer(tokens, idx, options, env, slf) + }; +}
\ No newline at end of file |