PmWiki recipe that lets you embed GitHub Gists in a wikipage.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

gist-embed.php 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <?php if (!defined('PmWiki')) exit();
  2. /** \pastebin-embed.php
  3. * \Copyright 2017-2021 Said Achmiz
  4. * \Licensed under the MIT License
  5. * \brief Embed Gists in a wikipage.
  6. */
  7. $RecipeInfo['GistEmbed']['Version'] = '2018-10-19';
  8. ## (:gist-embed:)
  9. Markup('gist-embed', '<fulltext', '/\\(:gist-embed\\s+(.+?)\\s*:\\)/', 'GistEmbed');
  10. SDV($GistEmbedHighlightStyle, "background-color: yellow;");
  11. function GistEmbed($m) {
  12. static $id = 1;
  13. ## Parse arguments to the markup.
  14. $parsed = ParseArgs($m[1]);
  15. ## These are the "bare" arguments (ones which don't require a key, just value(s)).
  16. $args = $parsed[''];
  17. $gist_id = $args[0];
  18. $noJS = in_array('no-js', $args);
  19. $noFooter = in_array('nofooter', $args);
  20. $noLineNumbers = in_array('nolinenums', $args);
  21. $raw = in_array('raw', $args);
  22. $noPre = in_array('no-pre', $args);
  23. ## Check whether specific files are being specified.
  24. $files = array();
  25. if ($args[1] && !in_array($args[1], array('no-js', 'nofooter', 'nolinenums', 'raw', 'no-pre')))
  26. $files = explode(',',$args[1]);
  27. ## Convert the comma-delimited line ranges to an array containing each line to be
  28. ## included as values.
  29. ## Note that the line numbers will be zero-indexed (for use with raw text, etc.).
  30. $line_ranges = $parsed['lines'] ? explode(',', $parsed['lines']) : array();
  31. $line_numbers = array();
  32. $to_end_from = -1;
  33. foreach ($line_ranges as $key => $line_range) {
  34. if (preg_match("/([0-9]+)[-–]([0-9]+)/", $line_range, $m)) {
  35. $line_numbers = array_merge($line_numbers, range(--$m[1],--$m[2]));
  36. } else if (preg_match("/([0-9]+)[-–]$/", $line_range, $m)) {
  37. $line_numbers[] = $to_end_from = --$m[1];
  38. } else {
  39. $line_numbers[] = --$line_range;
  40. }
  41. }
  42. ## Same thing, but for highlighted line ranges.
  43. $hl_line_ranges = $parsed['hl'] ? explode(',', $parsed['hl']) : array();
  44. $hl_line_numbers = array();
  45. $hl_to_end_from = -1;
  46. foreach ($hl_line_ranges as $key => $hl_line_range) {
  47. if (preg_match("/([0-9]+)[-–]([0-9]+)/", $hl_line_range, $m)) {
  48. $hl_line_numbers = array_merge($hl_line_numbers, range(--$m[1],--$m[2]));
  49. } else if (preg_match("/([0-9]+)[-–]$/", $hl_line_range, $m)) {
  50. $hl_line_numbers[] = $hl_to_end_from = --$m[1];
  51. } else {
  52. $hl_line_numbers[] = --$hl_line_range;
  53. }
  54. }
  55. $embed_js_url = "https://gist.github.com/$gist_id.js";
  56. $embed_raw_url = "https://gist.github.com/$gist_id/raw/";
  57. $embed_json_url = "https://gist.github.com/$gist_id.json";
  58. $out = "<span class='gist-embed-error'>Unknown error.</span>";
  59. if ($raw) {
  60. ## If no filenames have been specified, we'll have to retrieve the file list from
  61. ## the server; otherwise, we'll have no idea what files to request, and just
  62. ## retrieving the 'raw' URL for a multi-file gist (with no file specified) gets
  63. ## the first file only...
  64. if (empty($files)) {
  65. $full_gist_data = json_decode(file_get_contents($embed_json_url),true);
  66. $files = $full_gist_data['files'];
  67. }
  68. ## The raw text of each file of a multi-file gist must be retrieved individually.
  69. $out = array();
  70. foreach ($files as $filename) {
  71. $raw_text = file_get_contents($embed_raw_url.$filename);
  72. if (!$raw_text) return Keep("<span class='gist-embed-error'>Could not retrieve gist!</span>");
  73. $raw_lines = explode("\n", $raw_text);
  74. ## Convert HTML entities.
  75. if (!$noPre) {
  76. foreach ($raw_lines as $line)
  77. $line = PVSE($line);
  78. }
  79. ## Highlighting only works if no-pre is NOT enabled AND if we're displaying a
  80. ## single file only.
  81. if (!empty($hl_line_numbers) && !$noPre && count($files) == 1) {
  82. if ($hl_to_end_from >= 0)
  83. $hl_line_numbers = array_merge($hl_line_numbers, range($hl_to_end_from, count($raw_lines) - 1));
  84. foreach ($hl_line_numbers as $l) {
  85. $raw_lines[$l] = "<span class='gist-embed-highlighted-line'>" . rtrim($raw_lines[$l]) . "</span>";
  86. }
  87. }
  88. ## Specifying line numbers only works if we're displaying a single file only.
  89. if (!empty($line_numbers) && count($files) == 1) {
  90. if ($to_end_from >= 0)
  91. $line_numbers = array_merge($line_numbers, range($to_end_from, count($raw_lines) - 1));
  92. $raw_lines = array_intersect_key($raw_lines, array_flip($line_numbers));
  93. }
  94. $raw_text = implode("\n", $raw_lines);
  95. ## The 'no-pre' option means we shouldn't wrap the text in a <pre> tag.
  96. $out[] = $noPre ? $raw_text : Keep("<pre class='escaped gistRaw' id='gistEmbed_$id_$filename'>\n" . $raw_text . "\n</pre>\n");
  97. }
  98. $out = implode($noPre ? "\n\n" : "", $out);
  99. } else if ($noJS) {
  100. include_once('simple_html_dom.php');
  101. $json_content = json_decode(file_get_contents($embed_json_url),true);
  102. ## The style sheet.
  103. global $HTMLHeaderFmt;
  104. $HTMLHeaderFmt[] = "<link rel='stylesheet' type='text/css' href='" . $json_content['stylesheet'] . "' />\n";
  105. ## The HTML.
  106. $content_html = str_get_html(stripcslashes($json_content['div']));
  107. $content = $content_html->find("div.gist", 0);
  108. $content->id = "gistEmbed_$id";
  109. ## If specific files are specified, we simply delete the div.gist-file containers
  110. ## that contain files we don't want.
  111. if (!empty($files)) {
  112. $file_ids = preg_replace("/\./", "-", $files);
  113. $gist_file_blocks = $content_html->find("div.gist-file");
  114. foreach ($gist_file_blocks as $gist_file_block) {
  115. if (!in_array(substr($gist_file_block->find("div.file", 0)->id, 5), $file_ids))
  116. $gist_file_block->outertext = '';
  117. }
  118. }
  119. ## Specifying line numbers only works if we're displaying a single file only.
  120. $displayed_gist_files = array_filter($content_html->find("div.gist-file"), function ($d) { return $d->outertext; });
  121. if (!empty($line_numbers) && count($displayed_gist_files) == 1) {
  122. $lines = reset($displayed_gist_files)->find(".js-file-line-container tr");
  123. if ($to_end_from >= 0)
  124. $line_numbers = array_merge($line_numbers, range($to_end_from, count($lines) - 1));
  125. foreach ($lines as $l) {
  126. $line_num =$l->childNodes(0)->getAttribute('data-line-number');
  127. if (!in_array(--$line_num, $line_numbers))
  128. $l->outertext = '';
  129. }
  130. }
  131. ## Highlighting specific line numbers only works if we're display a single file
  132. ## only.
  133. if (!empty($hl_line_numbers) && count($displayed_gist_files) == 1) {
  134. $lines = reset($displayed_gist_files)->find(".js-file-line-container tr");
  135. if ($hl_to_end_from >= 0)
  136. $hl_line_numbers = array_merge($hl_line_numbers, range($hl_to_end_from, count($lines) - 1));
  137. foreach ($lines as $i => $line) {
  138. if (in_array($i, $hl_line_numbers)) {
  139. $line->children(1)->class .= " gist-embed-highlighted-line";
  140. }
  141. }
  142. }
  143. $out = Keep($content);
  144. } else {
  145. $out = Keep("<script id='gistEmbedScript_$id' src='$embed_js_url'></script>");
  146. ## If specific files are specified, we'll delete the div.gist-file containers
  147. ## that contain files we don't want (this script will run right after the script
  148. ## that adds the content in the first place).
  149. if (!empty($files)) {
  150. $files_js = preg_replace("/\./", "-", "[ '".implode("', '",$files)."' ]");
  151. $out .= Keep("
  152. <script>
  153. var files = $files_js;
  154. document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelectorAll('div.gist-file').forEach(function (gist_file_block) {
  155. if (files.indexOf(gist_file_block.querySelector('div.file').id.substring(5)) == -1)
  156. gist_file_block.parentElement.removeChild(gist_file_block);
  157. });
  158. </script>
  159. ");
  160. }
  161. ## Specifying line numbers only works if we're displaying a single file only.
  162. if (!empty($line_numbers) || !empty($hl_line_numbers)) {
  163. $line_numbers_js = "[ ".implode(", ",$line_numbers)." ]";
  164. $hl_line_numbers_js = "[ ".implode(", ",$hl_line_numbers)." ]";
  165. $out .= Keep("
  166. <script>
  167. if (document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelectorAll('div.gist-file').length == 1) {
  168. var num_lines = document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelector('div.gist-file').querySelectorAll('.js-file-line-container tr').length;
  169. var line_numbers = $line_numbers_js;
  170. var to_end_from = $to_end_from;
  171. if (to_end_from >= 0)
  172. line_numbers = [...line_numbers, ...[...Array(num_lines - to_end_from)].map((_, i) => to_end_from + i)];
  173. var hl_line_numbers = $hl_line_numbers_js;
  174. var hl_to_end_from = $hl_to_end_from;
  175. if (hl_to_end_from >= 0)
  176. hl_line_numbers = [...hl_line_numbers, ...[...Array(num_lines - hl_to_end_from)].map((_, i) => hl_to_end_from + i)];
  177. document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelector('div.gist-file').querySelectorAll('.js-file-line-container tr').forEach(function (line, i) {
  178. // Highlight specified line ranges (if any have been specified via the hl= parameter).
  179. if (hl_line_numbers.indexOf(i) != -1)
  180. line.children[1].className += ' gist-embed-highlighted-line';
  181. // Filter specified line ranges (if any have been specified via the lines= parameter).
  182. if (line_numbers.length > 0 && line_numbers.indexOf(i) == -1)
  183. line.parentElement.removeChild(line);
  184. });
  185. }
  186. </script>
  187. ");
  188. }
  189. GistEmbedAppendFooter();
  190. }
  191. global $HTMLStylesFmt;
  192. if (!$raw && $noFooter) {
  193. $HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id .gist-meta { display: none; }\n";
  194. $HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id .gist-data { border-bottom: none; border-radius: 2px; }\n";
  195. }
  196. if (!$raw && $noLineNumbers) {
  197. $HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id td.js-line-number { display: none; }\n";
  198. }
  199. GistEmbedInjectStyles();
  200. $id++;
  201. return $out;
  202. }
  203. function GistEmbedAppendFooter() {
  204. static $ran_once = false;
  205. if (!$ran_once) {
  206. global $HTMLFooterFmt;
  207. $HTMLFooterFmt[] =
  208. "<script>
  209. document.querySelectorAll('div.gist').forEach(function (embed) {
  210. if (embed.previousSibling && embed.previousSibling.tagName == 'P') {
  211. embed.id = 'gistEmbed_' + embed.previousSibling.firstChild.id.substring(16);
  212. }
  213. });
  214. </script>\n";
  215. }
  216. $ran_once = true;
  217. }
  218. function GistEmbedInjectStyles() {
  219. static $ran_once = false;
  220. if (!$ran_once) {
  221. global $HTMLStylesFmt, $GistEmbedHighlightStyle;
  222. $styles = "
  223. .gistRaw .gist-embed-highlighted-line { $GistEmbedHighlightStyle display: inline-block; width: calc(100% + 4px); padding-left: 4px; margin-left: -4px; }
  224. .gist tr .gist-embed-highlighted-line { $GistEmbedHighlightStyle }
  225. ";
  226. $HTMLStylesFmt['gist-embed'][] = $styles;
  227. }
  228. $ran_once = true;
  229. }