Compare commits

...

35 Commits

Author SHA1 Message Date
jeffreytse
8ca6d144a4 release: v0.9.3 2020-10-07 18:46:06 +08:00
jeffreytse
7847cac8c4 fix: inline code corrupted in table (#27) 2020-10-07 18:41:45 +08:00
jeffreytse
6973d6ab7e docs: update README.md 2020-10-03 09:22:31 +08:00
jeffreytse
ac287926a9 release: v0.9.2 2020-09-18 12:35:17 +08:00
jeffreytse
4195a431be docs: update README.md 2020-09-16 18:40:38 +08:00
jeffreytse
e1a5bf7356 feat: support svg object for diagrams (#23,#24,#25) 2020-09-16 18:38:26 +08:00
jeffreytse
b29469b7b2 feat: add images related functions 2020-09-16 18:33:47 +08:00
jeffreytse
7d86dcbe03 docs: update README.md 2020-08-29 20:11:00 +08:00
jeffreytse
793e6591a3 chore: revise .travis.yml format 2020-08-29 20:08:23 +08:00
Dirk van Oosterbosch
9b283f62e5
docs: revise the link to the PlantUML website (#22) 2020-08-28 18:12:27 +08:00
jeffreytse
ccd9c971c1 chore: init rspec 2020-08-16 17:30:23 +08:00
jeffreytse
c15932fd45 chore: update gemspec summary 2020-08-10 16:29:48 +08:00
jeffreytse
d0e4f6d9aa release: v0.9.1 2020-08-10 16:27:34 +08:00
jeffreytse
643072fdf3 docs: update README.md 2020-08-10 16:24:28 +08:00
jeffreytse
92ee45f8b2 feat: support spotify and soundcloud 2020-08-10 11:52:51 +08:00
jeffreytse
c596868051 refactor: rename video-processor to media-processor 2020-08-09 19:06:09 +08:00
jeffreytse
de62d61a6a feat: support rendering audio link 2020-08-09 19:04:29 +08:00
jeffreytse
3852a48aba docs: update README.md 2020-08-01 11:31:24 +08:00
jeffreytse
9b7ed3a951 release: v0.9.0 2020-08-01 11:30:59 +08:00
jeffreytse
2fa6177694 feat: support mathjax v3 2020-08-01 11:22:42 +08:00
jeffreytse
c98006df64 fix: liquid filter of percent form broken (#21) 2020-07-31 21:18:16 +08:00
jeffreytse
c59b4e67f5 release: v0.8.7 2020-07-30 12:29:30 +08:00
jeffreytse
58b9d5e784 fix: wrong ext matched if path including dots 2020-07-30 12:25:48 +08:00
jeffreytse
d094789ad4 fix: link and image in same row not working (#20) 2020-07-28 11:59:32 +08:00
jeffreytse
f8f6ffe26e docs: update README.md 2020-07-26 01:05:18 +08:00
jeffreytse
44ec868188 release: v0.8.6 2020-07-26 00:47:40 +08:00
jeffreytse
2e99bcfb84 docs: update README.md 2020-07-26 00:43:56 +08:00
jeffreytse
04ff451208 feat: support table cell attribute list (#19) 2020-07-25 20:54:31 +08:00
jeffreytse
a85ec310cd fix: html inside table cell not working 2020-07-25 14:09:27 +08:00
jeffreytse
c916d10c6b release: v0.8.5 2020-07-21 10:30:25 +08:00
jeffreytse
430b992521 fix: error with permalink file extension (#18) 2020-07-21 10:23:25 +08:00
jeffreytse
e1f1aa9006 release: v0.8.4 2020-07-16 13:54:47 +08:00
jeffreytse
6d3bd946f2 fix: liquid filter tag broken issue 2020-07-16 13:05:04 +08:00
jeffreytse
8e932a4d32 fix: correct style of table cell 2020-07-16 13:00:35 +08:00
jeffreytse
006983a62e fix: emoji img css class and block display issue 2020-07-09 23:07:13 +08:00
16 changed files with 628 additions and 237 deletions

3
.rspec Normal file
View File

@ -0,0 +1,3 @@
--color
--format progress
--require spec_helper

View File

@ -1,13 +1,13 @@
language: ruby
cache: bundler
rvm:
- 2.7
- 2.3
- 2.7
- 2.3
env:
global:
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
matrix:
- JEKYLL_VERSION="~> 3.8"
- JEKYLL_VERSION="~> 3.8"
matrix:
include:
- rvm: 2.7
@ -15,8 +15,8 @@ matrix:
- rvm: 2.7
env: JEKYLL_VERSION=">= 4.0.0"
before_install:
- gem update --system
- gem install bundler
- gem update --system
- gem install bundler
before_script: bundle update
script: script/cibuild
notifications:

146
README.md
View File

@ -101,16 +101,20 @@ Spaceship is a minimalistic, powerful and extremely customizable [Jekyll](https:
- [1.3 Headerless](#headerless)
- [1.4 Cell Alignment](#cell-alignment)
- [1.5 Cell Markdown](#cell-markdown)
- [1.6 Cell Inline Attributes](#cell-inline-attributes)
- [2. MathJax Usage](#2-mathjax-usage)
- [2.1 Performance Optimization](#21-performance-optimization)
- [2.2 How to use?](#22-how-to-use)
- [3. PlantUML Usage](#3-plantuml-usage)
- [4. Mermaid Usage](#4-mermaid-usage)
- [5. Video Usage](#5-video-usage)
- [5. Media Usage](#5-media-usage)
- [5.1 Youtube Usage](#youtube-usage)
- [5.2 Vimeo Usage](#vimeo-usage)
- [5.3 DailyMotion Usage](#dailymotion-usage)
- [5.4 General Video Usage](#general-video-usage)
- [5.4 Spotify Usage](#spotify-usage)
- [5.5 SoundCloud Usage](#soundcloud-usage)
- [5.6 General Video Usage](#general-video-usage)
- [5.7 General Audio Usage](#general-audio-usage)
- [6. Hybrid HTML with Markdown](#6-hybrid-html-with-markdown)
- [7. Markdown Polyfill](#7-markdown-polyfill)
- [7.1 Escape Ordered List](#71-escape-ordered-list)
@ -142,7 +146,7 @@ plugins:
**💡 Tip:** Note that GitHub Pages runs in `safe` mode and only allows [a set of whitelisted plugins](https://pages.github.com/versions/). To use the gem in GitHub Pages, you need to build locally or use CI (e.g. [travis](https://travis-ci.org/), [github workflow](https://help.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow)) and deploy to your `gh-pages` branch.
### Additions
### Additions for Unlimited GitHub Pages
* Here is a GitHub Action named [jekyll-deploy-action](https://github.com/jeffreytse/jekyll-deploy-action) for Jekyll site deployment conveniently. 👍
* Here is a [Jekyll site](https://github.com/jeffreytse/jekyll-jeffreytse-blog) using Travis to build and deploy to GitHub Pages for your references.
@ -161,16 +165,20 @@ jekyll-spaceship:
- plantuml-processor
- mermaid-processor
- polyfill-processor
- video-processor
- media-processor
- emoji-processor
- element-processor
mathjax-processor:
src: //cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML
src:
- https://polyfill.io/v3/polyfill.min.js?features=es6
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
config:
tex2jax:
tex:
inlineMath:
- ['$','$']
- ['\(','\)']
svg:
fontCache: 'global'
plantuml-processor:
mode: default # mode value 'pre-fetch' for fetching image at building stage
css:
@ -178,7 +186,7 @@ jekyll-spaceship:
syntax:
code: 'plantuml!'
custom: ['@startuml', '@enduml']
src: http://www.plantuml.com/plantuml/png/
src: http://www.plantuml.com/plantuml/svg/
mermaid-processor:
mode: default # mode value 'pre-fetch' for fetching image at building stage
css:
@ -189,14 +197,14 @@ jekyll-spaceship:
config:
theme: default
src: https://mermaid.ink/svg/
video-processor:
media-processor:
default:
id: 'video-{id}'
class: 'video'
id: 'media-{id}'
class: 'media'
width: '100%'
height: 350
border: 0
style: 'max-width: 600px'
frameborder: 0
style: 'max-width: 600px; outline: none;'
allow: 'encrypted-media; picture-in-picture'
emoji-processor:
css:
@ -534,6 +542,61 @@ Rowspan is 4
</tbody>
</table>
#### Cell Inline Attributes
This feature is very useful for custom cell such as using inline style. (e.g., background, color, font)
The idea and syntax comes from the [Maruku](http://maruku.rubyforge.org/) package.
[](https://kramdown.gettalong.org/syntax.html#block-ials)
Following are some examples of attributes definitions (ALDs) and afterwards comes the syntax explanation:
```markdown
{:ref-name: #id .cls1 .cls2}
{:second: ref-name #id-of-other title="hallo you"}
{:other: ref-name second}
```
An ALD line has the following structure:
- a left brace, optionally preceded by up to three spaces,
- followed by a colon, the id and another colon,
- followed by attribute definitions (allowed characters are backslash-escaped closing braces or any character except a not escaped closing brace),
- followed by a closing brace and optional spaces until the end of the line.
If there is more than one ALD with the same reference name, the attribute definitions of all the ALDs are processed like they are defined in one ALD.
An inline attribute list (IAL) is used to attach attributes to another element.
Here are some examples for span IALs:
```markdown
{: #id .cls1 .cls2} <!-- #id <=> id="id", .cls1 .cls2 <=> class="cls1 cls2" -->
{: ref-name title="hallo you"}
{: ref-name class='.cls3' .cls4}
```
Here is an example for custom table cell with IAL:
```markdown
{:color-style: style="background: black;"}
{:color-style: style="color: white;"}
{:text-style: style="font-weight: 800; text-decoration: underline;"}
|: Here's an Inline Attribute Lists example :||||
| ------- | ------------------ | -------------------- | ------------------ |
|: :|: <div style="color: red;"> &lt; Normal HTML Block > </div> :|||
| ^^ | Red {: .cls style="background: orange" } |||
| ^^ IALs | Green {: #id style="background: green; color: white" } |||
| ^^ | Blue {: style="background: blue; color: white" } |||
| ^^ | Black {: color-style text-style } |||
```
Code above would be parsed as:
<img width="580px" src="https://user-images.githubusercontent.com/9413601/88461592-738afb00-ced7-11ea-9aac-3179023742b0.png" alt="IALs">
Additionally, [here](https://kramdown.gettalong.org/syntax.html#block-ials) you can learn more details about IALs.
### 2. MathJax Usage
[MathJax](http://www.mathjax.org/) is an open-source JavaScript display engine for LaTeX, MathML, and AsciiMath notation that works in all modern browsers.
@ -574,7 +637,7 @@ Code above would be parsed as:
### 3. PlantUML Usage
[PlantUML](http://plantuml.sourceforge.net/) is a component that allows to quickly write:
[PlantUML](https://plantuml.com) is a component that allows to quickly write:
- sequence diagram,
- use case diagram,
@ -645,25 +708,31 @@ Code above would be parsed as:
![Mermaid Diagram](https://user-images.githubusercontent.com/9413601/85282355-2e317300-b4be-11ea-9c30-8f9d61540d14.png)
### 5. Video Usage
### 5. Media Usage
How often did you find yourself googling "**How to embed a video in markdown?**"
How often did you find yourself googling "**How to embed a video/audio in markdown?**"
While its not possible to embed a video in markdown, the best and easiest way is to extract a frame from the video. To add videos to your markdown files easier I developped this tool for you, and it will parse the video link inside the image block automatically.
While its not possible to embed a video/audio in markdown, the best and easiest
way is to extract a frame from the video/audio. To add videos/audios to your
markdown files easier I developped this tool for you, and it will parse the
video/audio link inside the image block automatically.
**For now, these video links parsing are provided:**
**For now, these media links parsing are provided:**
- Youtube
- Vimeo
- DailyMotion
- General Video ( mp4 | avi | webm | ogg | ogv | 3gp | flv | mov ... )
- Spotify
- SoundCloud
- General Video ( mp4 | avi | ogg | ogv | webm | 3gp | flv | mov ... )
- General Audio ( mp3 | wav | ogg | mid | midi | aac | wma ... )
There are two ways to embed a video in your Jekyll blog page:
There are two ways to embed a video/audio in your Jekyll blog page:
Inline-style:
```markdown
![]({video-link})
![]({media-link})
```
Reference-style:
@ -671,10 +740,10 @@ Reference-style:
```markdown
![][{reference}]
[{reference}]: {video-link}
[{reference}]: {media-link}
```
For configuring video attributes (e.g, width, height), just adding query string to
For configuring media attributes (e.g, width, height), just adding query string to
the link as below:
```markdown
@ -707,6 +776,22 @@ the link as below:
![](https://dai.ly/x7tgcev?width=100%&height=400)
```
#### Spotify Usage
```markdown
![](http://open.spotify.com/track/4Dg5moVCTqxAb7Wr8Dq2T5)
```
<image width="600" src="https://user-images.githubusercontent.com/9413601/89762618-5d11b000-db23-11ea-81db-35cc3682b234.png">
#### SoundCloud Usage
```markdown
![](https://soundcloud.com/aviciiofficial/preview-avicii-vs-lenny)
```
<image width="600" src="https://user-images.githubusercontent.com/9413601/89762969-1c666680-db24-11ea-97e3-4340f7fac7ac.png">
#### General Video Usage
```markdown
@ -717,6 +802,15 @@ the link as below:
![](//techslides.com/demos/sample-videos/small.mp4?width=400)
```
#### General Audio Usage
```markdown
![](//www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3)
![](//www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3?autoplay=1&loop=1)
```
<image width="300" src="https://user-images.githubusercontent.com/9413601/89762143-68181080-db22-11ea-8467-e8b2a8a96ae5.png">
### 6. Hybrid HTML with Markdown
@ -872,7 +966,7 @@ Automatically adds a `target="_blank" rel="noopener noreferrer"` attribute to al
jekyll-spaceship:
element-processor:
css:
- a: # Replce all `a` tags
- a: # Replace all `a` tags
props:
class: ['(^.*$)', '\0 ext-link'] # Add `ext-link` to class by regex pattern
target: _blank # Replace `target` value to `_blank`
@ -888,9 +982,9 @@ Automatically adds `loading="lazy"` to `img` and `iframe` tags to natively load
jekyll-spaceship:
element-processor:
css:
- a: # Replce all `a` tags
- a: # Replace all `a` tags
props: #
loading: lazy # Replace `lading` value to `lazy`
loading: lazy # Replace `loading` value to `lazy`
```
In case you want to prevent loading some images/iframes lazily, add
@ -903,7 +997,7 @@ See the following examples to prevent lazy loading.
jekyll-spaceship:
element-processor:
css:
- a: # Replce all `a` tags
- a: # Replace all `a` tags
props: #
loading: eager # Replace `loading` value to `eager`
```

View File

@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
spec.version = Jekyll::Spaceship::VERSION
spec.authors = ["jeffreytse"]
spec.email = ["jeffreytse.mail@gmail.com"]
spec.summary = "A Jekyll plugin to provide powerful supports for table, mathjax, plantuml, mermaid, emoji, video, youtube, vimeo, dailymotion, etc."
spec.summary = "A Jekyll plugin to provide powerful supports for table, mathjax, plantuml, mermaid, emoji, video, audio, youtube, vimeo, dailymotion, spotify, soundcloud, etc."
spec.homepage = "https://github.com/jeffreytse/jekyll-spaceship"
spec.license = "MIT"

View File

@ -10,7 +10,7 @@ module Jekyll::Spaceship
'plantuml-processor',
'mermaid-processor',
'polyfill-processor',
'video-processor',
'media-processor',
'emoji-processor',
'element-processor'
]

View File

@ -69,7 +69,7 @@ module Jekyll::Spaceship
end
def self.ext(page)
ext = page.path.match(/\.\S+$/)
ext = page.path.match(/\.[^.]+$/)
ext.to_s.rstrip
end

View File

@ -65,7 +65,7 @@ module Jekyll::Spaceship
def initialize_exclusions
if @@_exclusions.size.zero?
self.class.exclude :code, :math
self.class.exclude :code, :math, :liquid_filter
end
@exclusions = @@_exclusions.uniq
@@_exclusions.clear
@ -116,12 +116,12 @@ module Jekyll::Spaceship
if self.respond_to? method
@page.content = self.pre_exclude @page.content
@page.content = self.send method, @page.content
@page.content = self.after_exclude @page.content
@page.content = self.post_exclude @page.content
end
else
if Type.html? output_ext
method = 'on_handle_html'
elsif css? output_ext
elsif Type.css? output_ext
method = 'on_handle_css'
end
if self.respond_to? method
@ -151,31 +151,40 @@ module Jekyll::Spaceship
logger.log file
end
def pre_exclude(content)
@exclusion_store = []
def exclusion_regexs()
regexs = []
@exclusions.each do |type|
regex = nil
if type == :code
regex = /((`+)\s*(\w*)((?:.|\n)*?)\2)/
regex = /(((?<!\\)`+)\s*(\w*)((?:.|\n)*?)\2)/
elsif type == :math
regex = /(((?<!\\)\${1,2})[^\n]*?\1)/
elsif type == :liquid_filter
regex = /((?<!\\)((\{\{[^\n]*?\}\})|(\{%[^\n]*?%\})))/
end
next if regex.nil?
regexs.push regex unless regex.nil?
end
regexs
end
def pre_exclude(content, regexs = self.exclusion_regexs())
@exclusion_store = []
regexs.each do |regex|
content.scan(regex) do |match_data|
match = match_data[0]
id = @exclusion_store.size
content = content.sub(match, "[JEKYLL@#{object_id}@#{id}]")
content = content.sub(match, "<!JEKYLL@#{object_id}@#{id}>")
@exclusion_store.push match
end
end
content
end
def after_exclude(content)
def post_exclude(content)
while @exclusion_store.size > 0
match = @exclusion_store.pop
id = @exclusion_store.size
content = content.sub("[JEKYLL@#{object_id}@#{id}]", match)
content = content.sub("<!JEKYLL@#{object_id}@#{id}>", match)
end
@exclusion_store = []
content
@ -190,5 +199,38 @@ module Jekyll::Spaceship
end
content
end
def self.fetch_img_data(url)
begin
res = Net::HTTP.get_response URI(url)
raise res.body unless res.is_a?(Net::HTTPSuccess)
content_type = res.header['Content-Type']
raise 'Unknown content type!' if content_type.nil?
content_body = res.body.force_encoding('UTF-8')
return {
'type' => content_type,
'body' => content_body
}
rescue StandardError => msg
logger.log msg
end
end
def self.make_img_tag(data)
css_class = data['class']
type = data['type']
body = data['body']
if type == 'url'
"<img class=\"#{css_class}\" src=\"#{body}\">"
elsif type.include?('svg')
body.gsub(/\<\?xml.*?\?>/, '')
.gsub(/<!--[^\0]*?-->/, '')
.sub(/<svg /, "<svg class=\"#{css_class}\" ")
else
body = Base64.encode64(body)
body = "data:#{type};base64, #{body}"
"<img class=\"#{css_class}\" src=\"#{body}\">"
end
end
end
end

View File

@ -24,15 +24,16 @@ module Jekyll::Spaceship
# escape plus sign
emoji_name = emoji.name.gsub('+', '\\\+')
css_class = self.config['css']['class']
content = content.gsub(
/(?<!\=")\s*:#{emoji_name}:\s*(?!"\s)/,
"<img class=\"\""\
"<img class=\"#{css_class}\""\
" title=\":#{emoji.name}:\""\
" alt=\":#{emoji.name}:\""\
" raw=\"#{emoji.raw}\""\
" src=\"#{config['src']}#{emoji.image_filename}\""\
" style=\"vertical-align: middle;"\
" style=\"vertical-align: middle; display: inline;"\
" max-width: 1em; visibility: hidden;\""\
" onload=\"this.style.visibility='visible'\""\
" onerror=\"this.replaceWith(this.getAttribute('raw'))\">"\

View File

@ -6,9 +6,13 @@ module Jekyll::Spaceship
class MathjaxProcessor < Processor
def self.config
{
'src' => '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML',
'src' => [
'https://polyfill.io/v3/polyfill.min.js?features=es6',
'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js',
],
'config' => {
'tex2jax' => { 'inlineMath' => [['$','$'], ['\\(','\\)']] }
'tex' => { 'inlineMath' => [['$','$'], ['\\(','\\)']] },
'svg': { 'fontCache': 'global' }
}
}
end
@ -27,8 +31,15 @@ module Jekyll::Spaceship
self.handled = true
cfg = "MathJax.Hub.Config(#{config['config'].to_json});"
head.add_child("<script src=\"#{config['src']}\">#{cfg}</script>")
# add mathjax config
cfg = config['config'].to_json
head.add_child("<script>MathJax=#{cfg}</script>")
# add mathjax dependencies
config['src'] = [config['src']] if config['src'].is_a? String
config['src'].each do |src|
head.add_child("<script src=\"#{src}\"></script>")
end
doc.to_html
end

View File

@ -0,0 +1,234 @@
# frozen_string_literal: true
require 'uri'
module Jekyll::Spaceship
class MediaProcessor < Processor
def self.config
{
'default' => {
'id' => 'media-{id}',
'class' => 'media',
'width' => '100%',
'height' => 350,
'frameborder' => 0,
'style' => 'max-width: 600px;outline: none',
'allow' => 'encrypted-media; picture-in-picture'
}
}
end
def on_handle_markdown(content)
content = handle_normal_audio(content)
content = handle_normal_video(content)
content = handle_youtube(content)
content = handle_vimeo(content)
content = handle_dailymotion(content)
content = handle_spotify(content)
content = handle_soundcloud(content)
end
# Examples:
# ![audio](//www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3)
# ![audio](//www.expample.com/examples/t-rex-roar.mp3?autoplay=true&loop=true)
def handle_normal_audio(content)
handle_media(content, {
media_type: 'audio',
host: '(https?:)?\\/\\/.*\\/',
id: '(.+?\\.(mp3|wav|ogg|mid|midi|aac|wma))',
})
end
# Examples:
# ![video](//www.html5rocks.com/en/tutorials/video/basics/devstories.webm)
# ![video](//techslides.com/demos/sample-videos/small.ogv?allow=autoplay)
# ![video](//techslides.com/demos/sample-videos/small.mp4?width=400)
def handle_normal_video(content)
handle_media(content, {
media_type: 'iframe',
host: '(https?:)?\\/\\/.*\\/',
id: '(.+?\\.(avi|mp4|webm|ogg|ogv|flv|mkv|mov|wmv|3gp|rmvb|asf))'
})
end
# Examples:
# ![youtube](https://www.youtube.com/watch?v=XA2WjJbmmoM "title")
# ![youtube](http://www.youtube.com/embed/w-m_yZCLF5Q)
# ![youtube](//youtu.be/mEP3YXaSww8?height=100%&width=400)
def handle_youtube(content)
handle_media(content, {
media_type: 'iframe',
host: '(https?:)?\\/\\/.*youtu.*',
id: '(?<=\\?v\\=|embed\\/|\\.be\\/)([a-zA-Z0-9\\_\\-]+)',
base_url: "https://www.youtube.com/embed/"
})
end
# Examples:
# ![vimeo](https://vimeo.com/263856289)
# ![vimeo](https://vimeo.com/263856289?height=100%&width=400)
def handle_vimeo(content)
handle_media(content, {
media_type: 'iframe',
host: '(https?:)?\\/\\/vimeo\\.com\\/',
id: '([0-9]+)',
base_url: "https://player.vimeo.com/video/"
})
end
# Examples:
# ![dailymotion](https://www.dailymotion.com/video/x7tgcev)
# ![dailymotion](https://dai.ly/x7tgcev?height=100%&width=400)
def handle_dailymotion(content)
handle_media(content, {
media_type: 'iframe',
host: '(https?:)?\\/\\/.*dai.?ly.*',
id: '(?<=video\\/|\\/)([a-zA-Z0-9\\_\\-]+)',
base_url: "https://www.dailymotion.com/embed/video/"
})
end
# Examples:
# ![spotify](//open.spotify.com/track/4Dg5moVCTqxAb7Wr8Dq2T5)
# ![spotify](//open.spotify.com/track/37mEkAaqCE7FXMvnlVA8pp?width=400)
def handle_spotify(content)
handle_media(content, {
media_type: 'iframe',
host: '(https?:)?\\/\\/open\\.spotify\\.com\\/track\\/',
id: '(?<=track\\/)([a-zA-Z0-9\\_\\-]+)',
base_url: "https://open.spotify.com/embed/track/",
height: 80
})
end
# Examples:
# ![soundcloud](//soundcloud.com/aviciiofficial/preview-avicii-vs-lenny)
def handle_soundcloud(content)
handle_media(content, {
media_type: 'iframe',
id_from: 'html',
host: '(https?:)?\\/\\/soundcloud\\.com\\/.+\\/[^\\?]+',
id: '(?<=soundcloud:\\/\\/sounds:)([0-9]+)',
base_url: "https://w.soundcloud.com/player/?url="\
"https%3A//api.soundcloud.com/tracks/",
height: 125,
})
end
def handle_media(content, data)
host = data[:host]
return content if content.sub(/#{host}/, '').nil?
media_type = data[:media_type]
base_url = data[:base_url]
id = data[:id_from] === 'html' ? '()' : data[:id]
url = "(#{host}#{id}\\S*)"
title = '("(.*)".*){0,1}'
# pre-handle reference-style links
regex = /(\[(.*)\]:\s*(#{url}\s*#{title}))/
content.scan regex do |match_data|
match = match_data[0]
ref_name = match_data[1]
ref_value = match_data[2]
content = content.gsub(match, '')
.gsub(/\!\[(.*)\]\s*\[#{ref_name}\]/,
"![\1](#{ref_value})")
end
# handle inline-style links
regex = /(\!\[(.*)\]\(.*#{url}\s*#{title}\))/
content.scan regex do |match_data|
url = match_data[2]
id = data[:id_from] === 'html' \
? get_id_from_html(url, data[:id]) \
: match_data[4]
title = match_data[6]
qs = url.match(/(?<=\?)(\S*?)$/)
qs = Hash[URI.decode_www_form(qs.to_s)].reject do |k, v|
next true if v == id or v == ''
end
cfg = self.config['default'].clone
cfg['id'] = qs['id'] || cfg['id']
cfg['class'] = qs['class'] || cfg['class']
cfg['style'] = qs['style'] || cfg['style']
cfg['id'] = cfg['id'].gsub('{id}', id)
cfg['class'] = cfg['class'].gsub('{id}', id)
cfg['src'] = URI(base_url ? "#{base_url}#{id}" : url).tap do |v|
v.query = URI.encode_www_form(qs) if qs.size > 0
end
case media_type
when 'audio'
cfg['autoplay'] = qs['autoplay'] || data[:autoplay] || cfg['autoplay']
cfg['loop'] = qs['loop'] || data[:loop] || cfg['loop']
cfg['style'] += ';display: none;' if qs['hidden']
content = handle_audio(content, { target: match_data[0], cfg: cfg })
when 'iframe'
cfg['title'] = title
cfg['width'] = qs['width'] || data[:width] || cfg['width']
cfg['height'] = qs['height'] || data[:height] || cfg['height']
cfg['frameborder'] = qs['frameborder'] || cfg['frameborder']
cfg['allow'] ||= cfg['allow']
content = handle_iframe(content, { target: match_data[0], cfg: cfg })
end
self.handled = true
end
content
end
def handle_audio(content, data)
cfg = data[:cfg]
html = "<audio"\
" id=\"#{cfg['id']}\""\
" class=\"#{cfg['class']}\""\
" #{cfg['autoplay'] ? 'autoplay' : ''}"\
" #{cfg['loop'] ? 'loop' : ''}"\
" src=\"#{cfg['src']}\""\
" style=\"#{cfg['style']}\""\
" controls>" \
"<p> Your browser doesn't support HTML5 audio."\
" Here is a <a href=\"#{cfg['src']}\">link to download the audio</a>"\
"instead. </p>"\
"</audio>"
content.gsub(data[:target], html)
end
def handle_iframe(content, data)
cfg = data[:cfg]
html = "<iframe"\
" id=\"#{cfg['id']}\""\
" class=\"#{cfg['class']}\""\
" src=\"#{cfg['src']}\""\
" title=\"#{cfg['title']}\""\
" width=\"#{cfg['width']}\""\
" height=\"#{cfg['height']}\""\
" style=\"#{cfg['style']}\""\
" allow=\"#{cfg['allow']}\""\
" frameborder=\"#{cfg['frameborder']}\""\
" allowfullscreen>"\
"</iframe>"
content.gsub(data[:target], html)
end
def get_id_from_html(url, pattern)
id = ''
begin
url = 'https:' + url if url.start_with? '//'
res = Net::HTTP.get_response URI(url)
raise res.body unless res.is_a?(Net::HTTPSuccess)
res.body.match pattern do |match_data|
id = match_data[0]
break
end
rescue StandardError => msg
data = url
logger.log msg
end
id
end
end
end

View File

@ -62,18 +62,20 @@ module Jekyll::Spaceship
def handle_mermaid(code)
# encode to UTF-8
code = code.encode('UTF-8')
url = get_url(code)
# render mode
case self.config['mode']
when 'pre-fetch'
url = self.get_mermaid_img_data(url)
data = self.class.fetch_img_data(url)
end
if data.nil?
data = { 'type' => 'url', 'body' => url }
end
# return img tag
css_class = self.config['css']['class']
"<img class=\"#{css_class}\" src=\"#{url}\">"
data['class'] = self.config['css']['class']
self.class.make_img_tag(data)
end
def get_url(code)
@ -96,21 +98,5 @@ module Jekyll::Spaceship
raise "No supported src ! #{src}"
end
end
def get_mermaid_img_data(url)
data = ''
begin
res = Net::HTTP.get_response URI(url)
raise res.body unless res.is_a?(Net::HTTPSuccess)
data = Base64.encode64(res.body)
content_type = res.header['Content-Type']
raise 'Unknown content type!' if content_type.nil?
data = "data:#{content_type};base64, #{data}"
rescue StandardError => msg
data = url
logger.log msg
end
data
end
end
end

View File

@ -17,7 +17,7 @@ module Jekyll::Spaceship
'css' => {
'class' => 'plantuml'
},
'src' => 'http://www.plantuml.com/plantuml/png/'
'src' => 'http://www.plantuml.com/plantuml/svg/'
}
end
@ -59,18 +59,20 @@ module Jekyll::Spaceship
def handle_plantuml(code)
# wrap plantuml code
code = "@startuml#{code}@enduml".encode('UTF-8')
url = get_url(code)
url = self.get_url(code)
# render mode
case self.config['mode']
when 'pre-fetch'
url = self.get_plantuml_img_data(url)
data = self.class.fetch_img_data(url)
end
if data.nil?
data = { 'type' => 'url', 'body' => url }
end
# return img tag
css_class = self.config['css']['class']
"<img class=\"#{css_class}\" src=\"#{url}\">"
data['class'] = self.config['css']['class']
self.class.make_img_tag(data)
end
def get_url(code)
@ -87,21 +89,5 @@ module Jekyll::Spaceship
raise "No supported src ! #{src}"
end
end
def get_plantuml_img_data(url)
data = ''
begin
res = Net::HTTP.get_response URI(url)
raise res.body unless res.is_a?(Net::HTTPSuccess)
data = Base64.encode64(res.body)
content_type = res.header['Content-Type']
raise 'Unknown content type!' if content_type.nil?
data = "data:#{content_type};base64, #{data}"
rescue StandardError => msg
data = url
logger.log msg
end
data
end
end
end

View File

@ -5,6 +5,9 @@ require "nokogiri"
module Jekyll::Spaceship
class TableProcessor < Processor
ATTR_LIST_PATTERN = /((?<!\\)\{:(?:([A-Za-z]\S*):)?(.*?)(?<!\\)\})/
ATTR_LIST_REFS = {}
def on_handle_markdown(content)
# pre-handle reference-style links
references = {}
@ -16,7 +19,9 @@ module Jekyll::Spaceship
if references.size > 0
content.scan(/[^\n]*(?<!\\)\|[^\n]*/) do |result|
references.each do |key, val|
replace = result.gsub(/\[([^\n]*)\]\s*\[#{key}\]/, "[\1](#{val})")
replace = result.gsub(
/\[([^\n\]]*?)\]\s*\[#{key}\]/,
"[\1](#{val})")
next if result == replace
content = content.gsub(result, replace)
end
@ -42,6 +47,19 @@ module Jekyll::Spaceship
next if result == replace
content = content.gsub(result, replace)
end
# pre-handle attribute list (AL)
ATTR_LIST_REFS.clear()
content.scan(ATTR_LIST_PATTERN) do |result|
ref = result[1]
list = result[2]
next if ref.nil?
if ATTR_LIST_REFS.has_key? ref
ATTR_LIST_REFS[ref] += list
else
ATTR_LIST_REFS[ref] = list
end
end
content
end
@ -69,6 +87,7 @@ module Jekyll::Spaceship
handle_multi_rows(data)
handle_text_align(data)
handle_rowspan(data)
handle_attr_list(data)
end
end
rows.each do |row|
@ -161,7 +180,7 @@ module Jekyll::Spaceship
if scope.table.multi_row_cells != cells and scope.table.multi_row_start
for i in 0...scope.table.multi_row_cells.count do
multi_row_cell = scope.table.multi_row_cells[i]
multi_row_cell.inner_html += "<br>#{cells[i].inner_html}"
multi_row_cell.inner_html += "\n<br>\n#{cells[i].inner_html}"
end
row.remove
end
@ -186,7 +205,7 @@ module Jekyll::Spaceship
span_cell = scope.table.span_row_cells[scope.row.col_index]
if span_cell and cell.content.match(/^\s*\^{2}/)
cell.content = cell.content.gsub(/^\s*\^{2}/, '')
span_cell.inner_html += "<br>#{cell.inner_html}"
span_cell.inner_html += "\n<br>\n#{cell.inner_html}"
rowspan = span_cell.get_attribute('rowspan') || 1
rowspan = rowspan.to_i + 1
span_cell.set_attribute('rowspan', "#{rowspan}")
@ -235,15 +254,69 @@ module Jekyll::Spaceship
cell.set_attribute('style', style)
end
# Examples:
# {:ref-name: .cls1 title="hello" }
# {: #id ref-name data="world" }
# {: #id title="hello" }
# {: .cls style="color: #333" }
def handle_attr_list(data)
cell = data.cell
content = cell.inner_html
# inline attribute list(IAL) handler
ial_handler = ->(list) do
list.scan(/(\S+)=("|')(.*?)\2|(\S+)/) do |attr|
key = attr[0]
val = attr[2]
single = attr[3]
if !key.nil?
val = (cell.get_attribute(key) || '') + val
cell.set_attribute(key, val)
elsif !single.nil?
if single.start_with? '#'
key = 'id'
val = single[1..-1]
elsif single.start_with? '.'
key = 'class'
val = cell.get_attribute(key) || ''
val += (val.size.zero? ? '' : ' ') + single[1..-1]
elsif ATTR_LIST_REFS.has_key? single
ial_handler.call ATTR_LIST_REFS[single]
end
unless key.nil?
cell.set_attribute(key, val)
end
end
end
end
# handle attribute list
content.scan(ATTR_LIST_PATTERN) do |result|
ref = result[1]
list = result[2]
# handle inline attribute list
ial_handler.call list if ref.nil?
# remove attr_list
content = content.sub(result[0], '')
end
cell.inner_html = content
end
def handle_format(data)
cell = data.cell
cvter = self.converter('markdown')
return if cvter.nil?
content = cell.inner_html
content = self.pre_exclude(content, [/(\<code.*\>.*\<\/code\>)/])
.gsub(/(?<!\\)\|/, '\\|')
.gsub(/^\s+|\s+$/, '')
.gsub(/&lt;/, '<')
.gsub(/&gt;/, '>')
content = self.post_exclude(content)
content = cvter.convert(content)
cell.inner_html = Nokogiri::HTML.fragment(content)
content = Nokogiri::HTML.fragment(content)
if content.children.first&.name == 'p'
content = content.children
end
cell.inner_html = content.inner_html
end
end
end

View File

@ -1,139 +0,0 @@
# frozen_string_literal: true
require 'uri'
module Jekyll::Spaceship
class VideoProcessor < Processor
def self.config
{
'default' => {
'id' => 'video-{id}',
'class' => 'video',
'width' => '100%',
'height' => 350,
'border' => 0,
'style' => 'max-width: 600px',
'allow' => 'encrypted-media; picture-in-picture',
}
}
end
def on_handle_markdown(content)
content = handle_normal_video(content)
content = handle_youtube(content)
content = handle_vimeo(content)
content = handle_dailymotion(content)
end
# Examples:
# ![video](//www.html5rocks.com/en/tutorials/video/basics/devstories.webm)
# ![video](//techslides.com/demos/sample-videos/small.ogv?allow=autoplay)
# ![video](//techslides.com/demos/sample-videos/small.mp4?width=400)
def handle_normal_video(content)
handle_video(content, {
host: '(https?:)?\\/\\/.*\\/',
id: '(.+?\\.(avi|mp4|webm|ogg|ogv|flv|mkv|mov|wmv|3gp|rmvb|asf))',
})
end
# Examples:
# ![youtube](https://www.youtube.com/watch?v=XA2WjJbmmoM "title")
# ![youtube](http://www.youtube.com/embed/w-m_yZCLF5Q)
# ![youtube](//youtu.be/mEP3YXaSww8?height=100%&width=400)
def handle_youtube(content)
handle_video(content, {
host: '(https?:)?\\/\\/.*youtu.*',
id: '(?<=\\?v\\=|embed\\/|\\.be\\/)([a-zA-Z0-9\\_\\-]+)',
iframe_url: "https://www.youtube.com/embed/"
})
end
# Examples:
# ![vimeo](https://vimeo.com/263856289)
# ![vimeo](https://vimeo.com/263856289?height=100%&width=400)
def handle_vimeo(content)
handle_video(content, {
host: '(https?:)?\\/\\/vimeo\\.com\\/',
id: '([0-9]+)',
iframe_url: "https://player.vimeo.com/video/"
})
end
# Examples:
# ![dailymotion](https://www.dailymotion.com/video/x7tgcev)
# ![dailymotion](https://dai.ly/x7tgcev?height=100%&width=400)
def handle_dailymotion(content)
handle_video(content, {
host: '(https?:)?\\/\\/.*dai.?ly.*',
id: '(?<=video\\/|\\/)([a-zA-Z0-9\\_\\-]+)',
iframe_url: "https://www.dailymotion.com/embed/video/"
})
end
def handle_video(content, data)
host = data[:host]
return content if content.sub(/#{host}/, '').nil?
iframe_url = data[:iframe_url]
id = data[:id]
url = "(#{host}#{id}\\S*)"
title = '("(.*)".*){0,1}'
# pre-handle reference-style links
regex = /(\[(.*)\]:\s*(#{url}\s*#{title}))/
content.scan regex do |match_data|
match = match_data[0]
ref_name = match_data[1]
ref_value = match_data[2]
content = content.gsub(match, '')
.gsub(/\!\[(.*)\]\s*\[#{ref_name}\]/,
"![\1](#{ref_value})")
end
# handle inline-style links
regex = /(\!\[(.*)\]\(.*#{url}\s*#{title}\))/
content.scan regex do |match_data|
url = match_data[2]
id = match_data[4]
title = match_data[6]
qs = url.match(/(?<=\?)(\S*?)$/)
qs = Hash[URI.decode_www_form(qs.to_s)].reject do |k, v|
next true if v == id or v == ''
end
default = self.config['default']
css_id = qs['id'] || default['id']
css_class = qs['class'] || default['class']
width = qs['width'] || data[:width] || default['width']
height = qs['height'] || data[:height] || default['height']
frameborder = qs['frameborder'] || default['border']
style = qs['style'] || default['style']
allow = qs['allow'] || default['allow']
css_id = css_id.gsub('{id}', id)
css_class = css_class.gsub('{id}', id)
url = URI(iframe_url ? "#{iframe_url}#{id}" : url).tap do |v|
v.query = URI.encode_www_form(qs) if qs.size > 0
end
html = "<iframe"\
" id=\"#{css_id}\""\
" class=\"#{css_class}\""\
" src=\"#{url}\""\
" title=\"#{title}\""\
" width=\"#{width}\""\
" height=\"#{height}\""\
" style=\"#{style}\""\
" allow=\"#{allow}\""\
" frameborder=\"#{frameborder}\""\
" allowfullscreen>" \
"</iframe>"
content = content.gsub(match_data[0], html)
self.handled = true
end
content
end
end
end

View File

@ -2,6 +2,6 @@
module Jekyll
module Spaceship
VERSION = "0.8.3"
VERSION = "0.9.3"
end
end

100
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,100 @@
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
# have no way to turn it off -- the option exists only for backwards
# compatibility in RSpec 3). It causes shared context metadata to be
# inherited by the metadata hash of host groups and examples, rather than
# triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# This allows you to limit a spec run to individual examples or groups
# you care about by tagging them with `:focus` metadata. When nothing
# is tagged with `:focus`, all examples get run. RSpec also provides
# aliases for `it`, `describe`, and `context` that include `:focus`
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
config.filter_run_when_matching :focus
# Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. We recommend
# you configure your source control system to ignore this file.
config.example_status_persistence_file_path = "spec/examples.txt"
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
config.warnings = true
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = "doc"
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
=end
end