Skip to content

Commit

Permalink
Add Instagram plugin (#55)
Browse files Browse the repository at this point in the history
* Add instagram plugin

* Fixes based on review

* Update wording

* Fixes based on review

---------

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

- (minor) Add Instagram embeds
- (minor) Add Vimeo embeds


Expand Down
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,57 @@ Set this property to `false` to disable this plugin.
_No options are available for this plugin._
</details>

### instagram

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

The basic syntax is `[instagram <post>]`. E.g. `[instagram https://www.instagram.com/p/CkQuv3_LRgS]`.
After the post, assorted space-separated flags can be added (in any combination/order):

- Add `caption` to include caption under the post.
- Add `left`, `center`, or `right` to set the alignment of the embed (default is `left`).
- Add any set of digits to set the width of the embed (in pixels, between 326 and 550, default is 326 as set by Instagram's embed.js).

If two or more alignments are selected, `left` will be preferred, followed by `center`, then `right`.

If a width outside the range of 326-550 is selected, a clamped value will be used.

**Example Markdown input:**

[instagram https://www.instagram.com/p/CkQuv3_LRgS]

[instagram https://www.instagram.com/p/CkQuv3_LRgS left caption 400]

**Example HTML output:**

<div class="instagram">
<blockquote class="instagram-media"
data-instgrm-permalink="https://www.instagram.com/p/CkQuv3_LRgS"
data-instgrm-version="14">
<a href="https://instagram.com/p/CkQuv3_LRgS">View post</a>
</blockquote>
</div>

<div class="instagram" align="left">
<blockquote class="instagram-media"
style="width: 400px;"
data-instgrm-permalink="https://www.instagram.com/p/CkQuv3_LRgS"
data-instgrm-version="14"
data-instgrm-captioned>
<a href="https://instagram.com/p/CkQuv3_LRgS">View post</a>
</blockquote>
</div>
<script async defer src="https://www.instagram.com/embed.js" type="text/javascript" onload="window.instgrm && window.instgrm.Embeds.process()"></script>

**Options:**

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

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

### underline

<details>
Expand Down
12 changes: 12 additions & 0 deletions fixtures/full-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,18 @@ Like a few other embeds, you can also pass optional flags to customize the embed
- Pass `light` or `dark` to switch the theme of the embed (e.g. `[twitter https://twitter.com/MattIPv4/status/1576415168426573825 dark]`)
- Pass `left`, `center`, or `right` to align the embed (e.g. `[twitter https://twitter.com/MattIPv4/status/1576415168426573825 left]`)

### Instagram

You can also embed a post from Instagram by passing the URL for the post:

[instagram https://www.instagram.com/p/CkQuv3_LRgS]

Like a few other embeds, you can also pass optional flags to customize the embed:

- Pass any integer value (between 326 and 550) to set a custom width for the embed (e.g. `[instagram https://www.instagram.com/p/CkQuv3_LRgS 400]`)
- Add `left`, `center`, or `right` to set the alignment of the embed (default is `left`).
- Pass `caption` to include caption under the post (e.g. `[instagram https://www.instagram.com/p/CkQuv3_LRgS caption]`)


## Step 6 — Tutorials

Expand Down
14 changes: 14 additions & 0 deletions fixtures/full-output.html
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,19 @@ <h3 id="twitter"><a class="hash-anchor" href="#twitter" aria-hidden="true"></a>T
<li>Pass <code>light</code> or <code>dark</code> to switch the theme of the embed (e.g. <code>[twitter https://twitter.com/MattIPv4/status/1576415168426573825 dark]</code>)</li>
<li>Pass <code>left</code>, <code>center</code>, or <code>right</code> to align the embed (e.g. <code>[twitter https://twitter.com/MattIPv4/status/1576415168426573825 left]</code>)</li>
</ul>
<h3 id="instagram"><a class="hash-anchor" href="#instagram" aria-hidden="true"></a>Instagram</h3>
<p>You can also embed a post from Instagram by passing the URL for the post:</p>
<div class="instagram">
<blockquote class="instagram-media" data-instgrm-permalink="https://www.instagram.com/p/CkQuv3_LRgS" data-instgrm-version="14">
<a href="https://instagram.com/p/CkQuv3_LRgS">View post</a>
</blockquote>
</div>
<p>Like a few other embeds, you can also pass optional flags to customize the embed:</p>
<ul>
<li>Pass any integer value (between 326 and 550) to set a custom width for the embed (e.g. <code>[instagram https://www.instagram.com/p/CkQuv3_LRgS 400]</code>)</li>
<li>Add <code>left</code>, <code>center</code>, or <code>right</code> to set the alignment of the embed (default is <code>left</code>).</li>
<li>Pass <code>caption</code> to include caption under the post (e.g. <code>[instagram https://www.instagram.com/p/CkQuv3_LRgS caption]</code>)</li>
</ul>
<h2 id="step-6-tutorials"><a class="hash-anchor" href="#step-6-tutorials" aria-hidden="true"></a>Step 6 — Tutorials</h2>
<p>Certain features of our Markdown engine are designed specifically for our tutorial content-types.
These may not be enabled in all contexts in the DigitalOcean community, but are enabled by default in the do-markdownit plugin.</p>
Expand All @@ -357,3 +370,4 @@ <h2 id="conclusion"><a class="hash-anchor" href="#conclusion" aria-hidden="true"
<script async defer src="https://static.codepen.io/assets/embed/ei.js" type="text/javascript"></script>
<script async defer src="https://cdn.jsdelivr.net/gh/ireade/[email protected]/public/caniuse-embed.min.js" type="text/javascript"></script>
<script async defer src="https://platform.twitter.com/widgets.js" type="text/javascript"></script>
<script async defer src="https://www.instagram.com/embed.js" type="text/javascript" onload="window.instgrm && window.instgrm.Embeds.process()"></script>
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const safeObject = require('./util/safe_object');
* @property {false} [wistia] Disable Wistia embeds.
* @property {false} [vimeo] Disable Vimeo embeds.
* @property {false} [twitter] Disable Twitter embeds.
* @property {false} [instagram] Disable Instagram 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.
* @property {false|import('./modifiers/fence_secondary_label').FenceSecondaryLabelOptions} [fence_secondary_label] Disable fence secondary labels, or set options for the feature.
Expand Down Expand Up @@ -149,6 +150,10 @@ module.exports = (md, options) => {
md.use(require('./rules/embeds/twitter'), safeObject(optsObj.twitter));
}

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

// Register modifiers

if (optsObj.underline !== false) {
Expand Down
175 changes: 175 additions & 0 deletions rules/embeds/instagram.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
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/instagram
*/

const safeObject = require('../../util/safe_object');

/**
* Add support for [Instagram](https://instagram.com/) embeds in Markdown, as block syntax.
*
* The basic syntax is `[instagram <post>]`. E.g. `[instagram https://www.instagram.com/p/CkQuv3_LRgS]`.
* After the post, assorted space-separated flags can be added (in any combination/order):
*
* - Add `caption` to include caption under the post.
* - Add `left`, `center`, or `right` to set the alignment of the embed (default is `left`).
* - Add any set of digits to set the width of the embed (in pixels, between 326 and 550, default is 326 as set by Instagram's embed.js).
*
* If two or more alignments are selected, `left` will be preferred, followed by `center`, then `right`.
*
* If a width outside the range of 326-550 is selected, a clamped value will be used.
*
* @example
* [instagram https://www.instagram.com/p/CkQuv3_LRgS]
*
* [instagram https://www.instagram.com/p/CkQuv3_LRgS left caption 400]
*
* <div class="instagram">
* <blockquote class="instagram-media"
* data-instgrm-permalink="https://www.instagram.com/p/CkQuv3_LRgS"
* data-instgrm-version="14">
* <a href="https://instagram.com/p/CkQuv3_LRgS">View post</a>
* </blockquote>
* </div>
*
* <div class="instagram" align="left">
* <blockquote class="instagram-media"
* style="width: 400px;"
* data-instgrm-permalink="https://www.instagram.com/p/CkQuv3_LRgS"
* data-instgrm-version="14"
* data-instgrm-captioned>
* <a href="https://instagram.com/p/CkQuv3_LRgS">View post</a>
* </blockquote>
* </div>
* <script async defer src="https://www.instagram.com/embed.js" type="text/javascript" onload="window.instgrm && window.instgrm.Embeds.process()></script>
*
* @type {import('markdown-it').PluginSimple}
*/
module.exports = md => {
/**
* Parsing rule for Instagram markup.
*
* @type {import('markdown-it/lib/parser_block').RuleBlock}
* @private
*/
const instagramRule = (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 < 13) return false; // [instagram a]
if (currentLine.slice(0, 11) !== '[instagram ') return false;
if (currentLine[currentLine.length - 1] !== ']') return false;

// Check for Instagram match
// https://www.instagram.com/p/<post> (treat everything prior to <post> as optional, ish)
const alignment = [ 'left', 'center', 'right' ];
const match = currentLine.match(`^\\[instagram (?:(?:(?:(?:https?:)?\\/\\/)?(?:www\\.)?instagram\\.com)?\\/)?(?:(?:\\w+)\\/)?(\\w+)((?: (?:${alignment.join('|')}|caption|\\d+))*)\\]$`);
if (!match) return false;

// Get the user
const post = match[1];
if (!post) return false;

// Get the raw flags
const flags = match[2];

// Get the width
const widthMatch = flags.match(/\d+/);
const width = widthMatch ? Math.max(Math.min(Number(widthMatch[0]), 550), 326) : 0;

// Get the caption
const showCaption = flags.includes('caption');

// Get the alignment
const align = alignment.find(t => flags.includes(t)) || 'center';

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

// Add token to state
const token = state.push('instagram', 'instagram', 0);
token.block = true;
token.markup = match[0];
token.instagram = { post, width, showCaption, align };

// Track that we need the script
state.env.instagram = safeObject(state.env.instagram);
state.env.instagram.tokenized = true;

// Done
return true;
};

md.block.ruler.before('paragraph', 'instagram', instagramRule);

/**
* Parsing rule to inject the Instagram script.
*
* @type {import('markdown-it').RuleCore}
* @private
*/
const instagramScriptRule = state => {
if (state.inlineMode) return;

// Check if we need to inject the script
if (state.env.instagram && state.env.instagram.tokenized && !state.env.instagram.injected) {
// Set that we've injected it
state.env.instagram.injected = true;

// Inject the token
const token = new state.Token('html_block', '', 0);
token.content = '<script async defer src="https://www.instagram.com/embed.js" type="text/javascript" onload="window.instgrm && window.instgrm.Embeds.process()"></script>\n';
state.tokens.push(token);
}
};

md.core.ruler.push('instagram_script', instagramScriptRule);

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

// Construct the attrs
const attrCaption = token.instagram.showCaption ? ' data-instgrm-captioned' : '';
const attrAlign = token.instagram.align !== 'center' ? ` align="${md.utils.escapeHtml(token.instagram.align)}"` : '';

// Escape some HTML
const post = md.utils.escapeHtml(token.instagram.post);
const width = md.utils.escapeHtml(token.instagram.width);

// Return the HTML
return `<div class="instagram"${attrAlign}>
<blockquote class="instagram-media" ${width ? `style="width: ${width}px;" ` : ''}data-instgrm-permalink="https://www.instagram.com/p/${post}" data-instgrm-version="14"${attrCaption}>
<a href="https://instagram.com/p/${post}">View post</a>
</blockquote>
</div>\n`;
};
};
Loading

0 comments on commit 30c6c76

Please sign in to comment.