Skip to content

Commit

Permalink
Add vimeo embed (#54)
Browse files Browse the repository at this point in the history
* Add vimeo embed

* Update readme and changelog

* Remove extra whitespace

* Update copyright, fix twitter link, extend test cases

* Remove support for invalid URL

---------

Co-authored-by: szabi <[email protected]>
  • Loading branch information
MSzabi and MSzabi authored Mar 22, 2023
1 parent fa90178 commit 461d54b
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Any non-code changes should be prefixed with `(docs)`.
See `PUBLISH.md` for instructions on how to publish a new version.
-->

- (minor) Add Vimeo embeds


## v1.5.1 - 2f1f346

Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -661,10 +661,38 @@ Set this property to `false` to disable this plugin.
_No options are available for this plugin._
</details>


### vimeo

<details>
<summary>Add support for <a href="https://vimeo.com/">Vimeo</a> embeds in Markdown, as block syntax.</summary>

The basic syntax is `[vimeo <url>]`. E.g. `[vimeo https://player.vimeo.com/video/329272793]`.
Height and width can optionally be set using `[vimeo <url> [height] [width]]`. E.g. `[vimeo https://player.vimeo.com/video/329272793 380 560]`.
The default value for height is 270, and for width is 480.

**Example Markdown input:**

[vimeo https://player.vimeo.com/video/329272793]

**Example HTML output:**

<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="270" width="480" style="aspect-ratio: 16/9" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View vimeo video</a>
</iframe>

**Options:**

Pass options for this plugin as the `vimeo` property of the `do-markdownit` plugin options.
Set this property to `false` to disable this plugin.

_No options are available for this plugin._
</details>

### twitter

<details>
<summary>Add support for [Twitter](https://twitter.com/) embeds in Markdown, as block syntax.</summary>
<summary>Add support for <a href="https://twitter.com/">Twitter</a> embeds in Markdown, as block syntax.</summary>

The basic syntax is `[twitter <tweet>]`. E.g. `[twitter https://twitter.com/MattIPv4/status/1576415168426573825]`.
After the tweet, assorted space-separated flags can be added (in any combination/order):
Expand Down
9 changes: 9 additions & 0 deletions fixtures/full-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,15 @@ Embedding a Wistia video (id, height, width):
_As with the YouTube embed, both the width and height are optional and have the same defaults._\
_The same behaviour applies to the width/height set, with responsive scaling._

### Vimeo

Embedding a Vimeo video (url, height, width):

[vimeo https://player.vimeo.com/video/329272793 225 400]

_As with the YouTube embed, both the width and height are optional and have the same defaults._\
_The same behaviour applies to the width/height set, with responsive scaling._

### DNS

Embedding DNS record lookups (hostname, record types...):
Expand Down
7 changes: 7 additions & 0 deletions fixtures/full-output.html
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,13 @@ <h3 id="wistia"><a class="hash-anchor" href="#wistia" aria-hidden="true"></a>Wis
</iframe>
<p><em>As with the YouTube embed, both the width and height are optional and have the same defaults.</em><br>
<em>The same behaviour applies to the width/height set, with responsive scaling.</em></p>
<h3 id="vimeo"><a class="hash-anchor" href="#vimeo" aria-hidden="true"></a>Vimeo</h3>
<p>Embedding a Vimeo video (url, height, width):</p>
<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="225" width="400" style="aspect-ratio: 16/9" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
</iframe>
<p><em>As with the YouTube embed, both the width and height are optional and have the same defaults.</em><br>
<em>The same behaviour applies to the width/height set, with responsive scaling.</em></p>
<h3 id="dns"><a class="hash-anchor" href="#dns" aria-hidden="true"></a>DNS</h3>
<p>Embedding DNS record lookups (hostname, record types…):</p>
<div data-dns-tool-embed data-dns-domain="digitalocean.com" data-dns-types="A,AAAA">
Expand Down
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const safeObject = require('./util/safe_object');
* @property {false} [caniuse] Disable CanIUse embeds.
* @property {false} [youtube] Disable YouTube embeds.
* @property {false} [wistia] Disable Wistia embeds.
* @property {false} [vimeo] Disable Vimeo embeds.
* @property {false} [twitter] Disable Twitter embeds.
* @property {false} [underline] Disable underline syntax.
* @property {false|import('./modifiers/fence_label').FenceLabelOptions} [fence_label] Disable fence labels, or set options for the feature.
Expand Down Expand Up @@ -140,6 +141,10 @@ module.exports = (md, options) => {
md.use(require('./rules/embeds/wistia'), safeObject(optsObj.wistia));
}

if (optsObj.vimeo !== false) {
md.use(require('./rules/embeds/vimeo'), safeObject(optsObj.vimeo));
}

if (optsObj.twitter !== false) {
md.use(require('./rules/embeds/twitter'), safeObject(optsObj.twitter));
}
Expand Down
108 changes: 108 additions & 0 deletions rules/embeds/vimeo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
Copyright 2023 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

/**
* @module rules/embeds/vimeo
*/

const reduceFraction = require('../../util/reduce_fraction');

/**
* Add support for [Vimeo](http://player.vimeo.com) embeds in Markdown, as block syntax.
*
* The basic syntax is `[vimeo <url>]`. E.g., `[vimeo https://player.vimeo.com/video/329272793]`.
* Height and width can optionally be set using `[vimeo <url> [height] [width]]`. E.g., `[vimeo https://player.vimeo.com/video/329272793 380 560]`.
* The default value for height is 270 and for width is 480.
*
* @example
* [vimeo https://player.vimeo.com/video/329272793]
*
* <iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="270" width="480" style="aspect-ratio: 16/9" frameborder="0" allowfullscreen>
* <a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
* </iframe>
*
* @type {import('markdown-it').PluginSimple}
*/
module.exports = md => {
/**
* Parsing rule for Vimeo markup.
*
* @type {import('markdown-it/lib/parser_block').RuleBlock}
* @private
*/
const vimeoRule = (state, startLine, endLine, silent) => {
// If silent, don't replace
if (silent) return false;

// Get current string to consider (just current line)
const pos = state.bMarks[startLine] + state.tShift[startLine];
const max = state.eMarks[startLine];
const currentLine = state.src.substring(pos, max);

// Perform some non-regex checks for speed
if (currentLine.length < 9) return false; // [vimeo a]
if (currentLine.slice(0, 7) !== '[vimeo ') return false;
if (currentLine[currentLine.length - 1] !== ']') return false;

// Check for vimeo match
const match = currentLine.match(/^\[vimeo (?:(?:(?:https?:)?\/\/)?player\.vimeo\.com\/video\/)?(\d+)?(?: (\d+))?(?: (\d+))?]$/);
if (!match) return false;

// Get the id from the url
const id = match[1];
if (!id) return false;

// Get the height
const height = Number(match[2]) || 270;

// Get the width
const width = Number(match[3]) || 480;

// Update the pos for the parser
state.line = startLine + 1;

// Add token to state
const token = state.push('vimeo', 'vimeo', 0);
token.block = true;
token.markup = match[0];
token.vimeo = { id: Number(id), height, width };

// Done
return true;
};

md.block.ruler.before('paragraph', 'vimeo', vimeoRule);

/**
* Rendering rule for Vimeo markup.
*
* @type {import('markdown-it/lib/renderer').RenderRule}
* @private
*/
md.renderer.rules.vimeo = (tokens, index) => {
const token = tokens[index];

// Determine the aspect ratio
const aspectRatio = reduceFraction(token.vimeo.width, token.vimeo.height).join('/');

// Return the HTML
return `<iframe src="https://player.vimeo.com/video/${encodeURIComponent(token.vimeo.id)}" class="vimeo" height="${token.vimeo.height}" width="${token.vimeo.width}" style="aspect-ratio: ${aspectRatio}" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/${encodeURIComponent(token.vimeo.id)}" target="_blank">View Vimeo video</a>
</iframe>\n`;
};
};
88 changes: 88 additions & 0 deletions rules/embeds/vimeo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright 2023 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

const md = require('markdown-it')().use(require('./vimeo'));

it('handles vimeo embeds (not inline)', () => {
expect(md.render('[vimeo https://player.vimeo.com/video/329272793 280 560]')).toBe(`<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="280" width="560" style="aspect-ratio: 2/1" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
</iframe>
`);
});

it('handles vimeo embeds with no id (no embed)', () => {
expect(md.render('[vimeo ]')).toBe(`<p>[vimeo ]</p>
`);
});

it('handles vimeo embeds that are unclosed (no embed)', () => {
expect(md.render('[vimeo https://player.vimeo.com/video/329272793')).toBe(`<p>[vimeo https://player.vimeo.com/video/329272793</p>
`);
});

it('handles vimeo embeds with http', () => {
expect(md.render('[vimeo http://player.vimeo.com/video/329272793]')).toBe(`<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="270" width="480" style="aspect-ratio: 16/9" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
</iframe>
`);
});

it('handles vimeo embeds with no https:', () => {
expect(md.render('[vimeo //player.vimeo.com/video/329272793]')).toBe(`<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="270" width="480" style="aspect-ratio: 16/9" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
</iframe>
`);
});

it('handles vimeo embeds with no https://', () => {
expect(md.render('[vimeo player.vimeo.com/video/329272793]')).toBe(`<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="270" width="480" style="aspect-ratio: 16/9" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
</iframe>
`);
});

it('handles vimeo embeds with just the video ID', () => {
expect(md.render('[vimeo 329272793]')).toBe(`<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="270" width="480" style="aspect-ratio: 16/9" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
</iframe>
`);
});

it('handles vimeo embeds without width', () => {
expect(md.render('[vimeo https://player.vimeo.com/video/329272793 240]')).toBe(`<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="240" width="480" style="aspect-ratio: 2/1" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
</iframe>
`);
});

it('handles vimeo embeds without width or height', () => {
expect(md.render('[vimeo https://player.vimeo.com/video/329272793]')).toBe(`<iframe src="https://player.vimeo.com/video/329272793" class="vimeo" height="270" width="480" style="aspect-ratio: 16/9" frameborder="0" allowfullscreen>
<a href="https://player.vimeo.com/video/329272793" target="_blank">View Vimeo video</a>
</iframe>
`);
});

it('handles vimeo embeds attempting html injection', () => {
expect(md.render('[vimeo <script>alert();</script> 280 560]')).toBe(`<p>[vimeo &lt;script&gt;alert();&lt;/script&gt; 280 560]</p>
`);
});

it('handles vimeo embeds attempting url manipulation', () => {
expect(md.render('[vimeo a/../../b 280 560]')).toBe(`<p>[vimeo a/../../b 280 560]</p>
`);
});
23 changes: 23 additions & 0 deletions styles/_vimeo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Copyright 2023 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License") !default;
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Vimeo embeds
.vimeo {
display: block;
height: auto;
margin: 1em auto;
max-width: 100%;
}
1 change: 1 addition & 0 deletions styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ limitations under the License.
// Plugin styling
@import "youtube";
@import "wistia";
@import "vimeo";
@import "rsvp_button";
@import "terminal_button";
@import "columns";
Expand Down

0 comments on commit 461d54b

Please sign in to comment.