pmwiki-gist-embed/gist-embed.php
2021-12-11 20:39:11 -05:00

264 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php if (!defined('PmWiki')) exit();
/** \pastebin-embed.php
* \Copyright 2017-2021 Said Achmiz
* \Licensed under the MIT License
* \brief Embed Gists in a wikipage.
*/
$RecipeInfo['GistEmbed']['Version'] = '2018-10-19';
## (:gist-embed:)
Markup('gist-embed', '<fulltext', '/\\(:gist-embed\\s+(.+?)\\s*:\\)/', 'GistEmbed');
SDV($GistEmbedHighlightStyle, "background-color: yellow;");
function GistEmbed($m) {
static $id = 1;
## Parse arguments to the markup.
$parsed = ParseArgs($m[1]);
## These are the "bare" arguments (ones which don't require a key, just value(s)).
$args = $parsed[''];
$gist_id = $args[0];
$noJS = in_array('no-js', $args);
$noFooter = in_array('nofooter', $args);
$noLineNumbers = in_array('nolinenums', $args);
$raw = in_array('raw', $args);
$noPre = in_array('no-pre', $args);
## Check whether specific files are being specified.
$files = array();
if ($args[1] && !in_array($args[1], array('no-js', 'nofooter', 'nolinenums', 'raw', 'no-pre')))
$files = explode(',',$args[1]);
## Convert the comma-delimited line ranges to an array containing each line to be
## included as values.
## Note that the line numbers will be zero-indexed (for use with raw text, etc.).
$line_ranges = $parsed['lines'] ? explode(',', $parsed['lines']) : array();
$line_numbers = array();
$to_end_from = -1;
foreach ($line_ranges as $key => $line_range) {
if (preg_match("/([0-9]+)[-]([0-9]+)/", $line_range, $m)) {
$line_numbers = array_merge($line_numbers, range(--$m[1],--$m[2]));
} else if (preg_match("/([0-9]+)[-]$/", $line_range, $m)) {
$line_numbers[] = $to_end_from = --$m[1];
} else {
$line_numbers[] = --$line_range;
}
}
## Same thing, but for highlighted line ranges.
$hl_line_ranges = $parsed['hl'] ? explode(',', $parsed['hl']) : array();
$hl_line_numbers = array();
$hl_to_end_from = -1;
foreach ($hl_line_ranges as $key => $hl_line_range) {
if (preg_match("/([0-9]+)[-]([0-9]+)/", $hl_line_range, $m)) {
$hl_line_numbers = array_merge($hl_line_numbers, range(--$m[1],--$m[2]));
} else if (preg_match("/([0-9]+)[-]$/", $hl_line_range, $m)) {
$hl_line_numbers[] = $hl_to_end_from = --$m[1];
} else {
$hl_line_numbers[] = --$hl_line_range;
}
}
$embed_js_url = "https://gist.github.com/$gist_id.js";
$embed_raw_url = "https://gist.github.com/$gist_id/raw/";
$embed_json_url = "https://gist.github.com/$gist_id.json";
$out = "<span class='gist-embed-error'>Unknown error.</span>";
if ($raw) {
## If no filenames have been specified, we'll have to retrieve the file list from
## the server; otherwise, we'll have no idea what files to request, and just
## retrieving the 'raw' URL for a multi-file gist (with no file specified) gets
## the first file only...
if (empty($files)) {
$full_gist_data = json_decode(file_get_contents($embed_json_url),true);
$files = $full_gist_data['files'];
}
## The raw text of each file of a multi-file gist must be retrieved individually.
$out = array();
foreach ($files as $filename) {
$raw_text = file_get_contents($embed_raw_url.$filename);
if (!$raw_text) return Keep("<span class='gist-embed-error'>Could not retrieve gist!</span>");
$raw_lines = explode("\n", $raw_text);
## Convert HTML entities.
if (!$noPre) {
foreach ($raw_lines as $line)
$line = PVSE($line);
}
## Highlighting only works if no-pre is NOT enabled AND if we're displaying a
## single file only.
if (!empty($hl_line_numbers) && !$noPre && count($files) == 1) {
if ($hl_to_end_from >= 0)
$hl_line_numbers = array_merge($hl_line_numbers, range($hl_to_end_from, count($raw_lines) - 1));
foreach ($hl_line_numbers as $l) {
$raw_lines[$l] = "<span class='gist-embed-highlighted-line'>" . rtrim($raw_lines[$l]) . "</span>";
}
}
## Specifying line numbers only works if we're displaying a single file only.
if (!empty($line_numbers) && count($files) == 1) {
if ($to_end_from >= 0)
$line_numbers = array_merge($line_numbers, range($to_end_from, count($raw_lines) - 1));
$raw_lines = array_intersect_key($raw_lines, array_flip($line_numbers));
}
$raw_text = implode("\n", $raw_lines);
## The 'no-pre' option means we shouldn't wrap the text in a <pre> tag.
$out[] = $noPre ? $raw_text : Keep("<pre class='escaped gistRaw' id='gistEmbed_$id_$filename'>\n" . $raw_text . "\n</pre>\n");
}
$out = implode($noPre ? "\n\n" : "", $out);
} else if ($noJS) {
include_once('simple_html_dom.php');
$json_content = json_decode(file_get_contents($embed_json_url),true);
## The style sheet.
global $HTMLHeaderFmt;
$HTMLHeaderFmt[] = "<link rel='stylesheet' type='text/css' href='" . $json_content['stylesheet'] . "' />\n";
## The HTML.
$content_html = str_get_html(stripcslashes($json_content['div']));
$content = $content_html->find("div.gist", 0);
$content->id = "gistEmbed_$id";
## If specific files are specified, we simply delete the div.gist-file containers
## that contain files we don't want.
if (!empty($files)) {
$file_ids = preg_replace("/\./", "-", $files);
$gist_file_blocks = $content_html->find("div.gist-file");
foreach ($gist_file_blocks as $gist_file_block) {
if (!in_array(substr($gist_file_block->find("div.file", 0)->id, 5), $file_ids))
$gist_file_block->outertext = '';
}
}
## Specifying line numbers only works if we're displaying a single file only.
$displayed_gist_files = array_filter($content_html->find("div.gist-file"), function ($d) { return $d->outertext; });
if (!empty($line_numbers) && count($displayed_gist_files) == 1) {
$lines = reset($displayed_gist_files)->find(".js-file-line-container tr");
if ($to_end_from >= 0)
$line_numbers = array_merge($line_numbers, range($to_end_from, count($lines) - 1));
foreach ($lines as $l) {
$line_num =$l->childNodes(0)->getAttribute('data-line-number');
if (!in_array(--$line_num, $line_numbers))
$l->outertext = '';
}
}
## Highlighting specific line numbers only works if we're display a single file
## only.
if (!empty($hl_line_numbers) && count($displayed_gist_files) == 1) {
$lines = reset($displayed_gist_files)->find(".js-file-line-container tr");
if ($hl_to_end_from >= 0)
$hl_line_numbers = array_merge($hl_line_numbers, range($hl_to_end_from, count($lines) - 1));
foreach ($lines as $i => $line) {
if (in_array($i, $hl_line_numbers)) {
$line->children(1)->class .= " gist-embed-highlighted-line";
}
}
}
$out = Keep($content);
} else {
$out = Keep("<script id='gistEmbedScript_$id' src='$embed_js_url'></script>");
## If specific files are specified, we'll delete the div.gist-file containers
## that contain files we don't want (this script will run right after the script
## that adds the content in the first place).
if (!empty($files)) {
$files_js = preg_replace("/\./", "-", "[ '".implode("', '",$files)."' ]");
$out .= Keep("
<script>
var files = $files_js;
document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelectorAll('div.gist-file').forEach(function (gist_file_block) {
if (files.indexOf(gist_file_block.querySelector('div.file').id.substring(5)) == -1)
gist_file_block.parentElement.removeChild(gist_file_block);
});
</script>
");
}
## Specifying line numbers only works if we're displaying a single file only.
if (!empty($line_numbers) || !empty($hl_line_numbers)) {
$line_numbers_js = "[ ".implode(", ",$line_numbers)." ]";
$hl_line_numbers_js = "[ ".implode(", ",$hl_line_numbers)." ]";
$out .= Keep("
<script>
if (document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelectorAll('div.gist-file').length == 1) {
var num_lines = document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelector('div.gist-file').querySelectorAll('.js-file-line-container tr').length;
var line_numbers = $line_numbers_js;
var to_end_from = $to_end_from;
if (to_end_from >= 0)
line_numbers = [...line_numbers, ...[...Array(num_lines - to_end_from)].map((_, i) => to_end_from + i)];
var hl_line_numbers = $hl_line_numbers_js;
var hl_to_end_from = $hl_to_end_from;
if (hl_to_end_from >= 0)
hl_line_numbers = [...hl_line_numbers, ...[...Array(num_lines - hl_to_end_from)].map((_, i) => hl_to_end_from + i)];
document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelector('div.gist-file').querySelectorAll('.js-file-line-container tr').forEach(function (line, i) {
// Highlight specified line ranges (if any have been specified via the hl= parameter).
if (hl_line_numbers.indexOf(i) != -1)
line.children[1].className += ' gist-embed-highlighted-line';
// Filter specified line ranges (if any have been specified via the lines= parameter).
if (line_numbers.length > 0 && line_numbers.indexOf(i) == -1)
line.parentElement.removeChild(line);
});
}
</script>
");
}
GistEmbedAppendFooter();
}
global $HTMLStylesFmt;
if (!$raw && $noFooter) {
$HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id .gist-meta { display: none; }\n";
$HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id .gist-data { border-bottom: none; border-radius: 2px; }\n";
}
if (!$raw && $noLineNumbers) {
$HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id td.js-line-number { display: none; }\n";
}
GistEmbedInjectStyles();
$id++;
return $out;
}
function GistEmbedAppendFooter() {
static $ran_once = false;
if (!$ran_once) {
global $HTMLFooterFmt;
$HTMLFooterFmt[] =
"<script>
document.querySelectorAll('div.gist').forEach(function (embed) {
if (embed.previousSibling && embed.previousSibling.tagName == 'P') {
embed.id = 'gistEmbed_' + embed.previousSibling.firstChild.id.substring(16);
}
});
</script>\n";
}
$ran_once = true;
}
function GistEmbedInjectStyles() {
static $ran_once = false;
if (!$ran_once) {
global $HTMLStylesFmt, $GistEmbedHighlightStyle;
$styles = "
.gistRaw .gist-embed-highlighted-line { $GistEmbedHighlightStyle display: inline-block; width: calc(100% + 4px); padding-left: 4px; margin-left: -4px; }
.gist tr .gist-embed-highlighted-line { $GistEmbedHighlightStyle }
";
$HTMLStylesFmt['gist-embed'][] = $styles;
}
$ran_once = true;
}