move formatting from JS to PHP
authorMichael Wallner <mike@php.net>
Thu, 27 Jan 2022 15:01:02 +0000 (16:01 +0100)
committerMichael Wallner <mike@php.net>
Thu, 27 Jan 2022 15:01:02 +0000 (16:01 +0100)
15 files changed:
.github/workflows/publish.yml
composer.json
mdref/Action.php
mdref/File.php
mdref/Formatter.php
mdref/Formatter/Discount.php [deleted file]
mdref/Formatter/League.php [deleted file]
mdref/Formatter/Wrapper.php [new file with mode: 0644]
mdref/Generator/Cls.php
mdref/Generator/Func.php
mdref/Reference.php
public/index.css
public/index.js
views/index.phtml
views/mdref.phtml

index 40591da0276f8be902ffec69b2bd341f77526e3e..bb0413e97e808db93a25fd3296d1d637174361f4 100644 (file)
@@ -38,6 +38,9 @@ jobs:
           path: refs/raphf
       - name: Install dependencies
         run: |
+          v=8.0; for b in "" ize -config; do \
+            sudo update-alternatives --set php$b /usr/bin/php$b$v; \
+          done
           sudo apt-get update -y
           sudo apt-get install -y \
             php-cli \
@@ -75,7 +78,7 @@ jobs:
             ../bin/ref2stub ../refs/$ext
           done
       - uses: crazy-max/ghaction-github-pages@v2
-        if: success()
+        if: false
         env:
           GH_PAT: ${{ secrets.PUBLISH_SECRET }}
         with:
index a66e582af4cebbdfcd418f812252632d7c874dc3..e82c81f895b2faafdd6289f90b6706e2e8bd52c1 100644 (file)
         }
     },
     "require": {
-        "php": "^7.4 || ^8.0",
+        "php": "^8.0",
         "ext-ctype": "*",
+        "ext-dom": "*",
+        "ext-libxml": "*",
         "ext-filter": "*",
         "ext-pcre": "*",
-        "ext-http": "^3.2 || ^4.2",
-        "league/commonmark": "^2.0",
+        "ext-http": "^4.2",
+        "league/commonmark": "~2.1.0",
         "phpdocumentor/reflection-docblock": "^5.3"
-    },
-    "suggests": {
-        "ext-discount": "A Markdown renderer is needed, this is the preferred one."
     }
 }
index 3addfa0527fe4880259d57e3f4ab964ff9c04ae2..0c48dd6b403c448d5d6f9538b141e448aadc01d1 100644 (file)
@@ -77,6 +77,10 @@ class Action {
                $pld->ref = $this->baseUrl->pathinfo(
                        $this->baseUrl->mod($this->request->getRequestUrl()));
 
+               $pld->markup = function($page) use($pld) {
+                       return $this->reference->getFormatter()->markup($page, $pld);
+               };
+
                $pld->refs = $this->reference;
                $pld->baseUrl = $this->baseUrl;
 
index 3c1ca0a6d276e37314178606f336d595ad3137c2..0ca2bd848d4f761da288ee6900187ca649411e2e 100644 (file)
@@ -50,7 +50,7 @@ class File {
         * @return string
         * @throws Exception
         */
-       public function readDescription() : ?string {
+       public function readDescription() : string {
                if (!$this->rewind()) {
                        throw Exception::fromLastError();
                }
@@ -58,7 +58,7 @@ class File {
                && (false !== fgets($this->fd))) {
                        return fgets($this->fd);
                }
-               return null;
+               return "";
        }
 
        /**
@@ -67,7 +67,7 @@ class File {
         * @return string
         * @throws Exception
         */
-       public function readFullDescription() : ?string {
+       public function readFullDescription() : string {
                $desc = $this->readDescription();
                while (false !== ($line = fgets($this->fd))) {
                        if ($line[0] === "#") {
index ecde240b5ce588c81a94b8024e1e768167b3ae2f..95a276f0514737574a83a2893f2d1f3bfaf36058 100644 (file)
 
 namespace mdref;
 
-use function class_exists;
+use DOMDocument;
+use DOMElement;
+use DOMNode;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+use League\CommonMark\MarkdownConverter;
+use League\CommonMark\Normalizer;
+use League\CommonMark\Extension;
+use mdref\Formatter\Wrapper;
 
-abstract class Formatter {
-    abstract function formatString(string $string) : string;
-    abstract function formatFile(string $file) : string;
+class Formatter {
+       public function __construct(
+               protected ?MarkdownConverter $md = null,
+               protected ?Wrapper $wrapper = null,
+       ) {
+               if (!$this->md) {
+                       $this->md = new GithubFlavoredMarkdownConverter([
+                               "slug_normalizer" => [
+                                       "instance" => new class($this) implements Normalizer\TextNormalizerInterface {
+                                               protected $formatter;
+                                               function __construct(Formatter $fmt) {
+                                                       $this->formatter = $fmt;
+                                               }
+                                               function normalize(string $text, $context = null) : string {
+                                                       return $this->formatter->formatSlug($text);
+                                               }
+                                       }
+                               ],
+                       ]);
+                       $this->md->getEnvironment()->addExtension(
+                               new Extension\DescriptionList\DescriptionListExtension
+                       );
+                       $this->md->getEnvironment()->addExtension(
+                               new Extension\Attributes\AttributesExtension
+                       );
+               }
+               if (!$this->wrapper) {
+                       $this->wrapper = new Wrapper($this);
+               }
+       }
+
+       public function formatString(string $string) : string {
+               return $this->md->convertToHtml($string);
+       }
+
+       public function formatFile(string $file) : string {
+               $string = file_get_contents($file);
+               if ($string === false) {
+                       throw Exception::fromLastError();
+               }
+               return $this->md->convertToHtml($string);
+       }
+
+       /**
+        * Format a simplified url slug
+        * @param string $string input text, like a heading
+        * @return string the simplified slug
+        */
+       public function formatSlug(string $string) : string {
+               return preg_replace("/[^\$[:alnum:]:._-]+/", ".", $string);
+       }
+
+       /**
+        * @param string $page HTML content
+        * @param object $pld Action payload
+        * @return string marked up HTML content
+        */
+       public function markup(string $page, $pld) : string {
+               $dom = new DOMDocument("1.0", "utf-8");
+               $dom->formatOutput = true;
+               $dom->loadHTML("<!doctype html>\n <meta charset=utf-8>\n" . $page, LIBXML_HTML_NOIMPLIED);
+               foreach ($dom->childNodes as $node) {
+                       $this->walk($node, $pld);
+               }
+               $html = "";
+               foreach ($dom->childNodes as $child) {
+                       $html .= $dom->saveHTML($child);
+               }
+               return $html;
+       }
+
+       public function createPermaLink(DOMElement $node, string $slug, $pld) {
+               if (strlen($slug)) {
+                       $node->setAttribute("id", $slug);
+               }
+               $perm = $node->ownerDocument->createElement("a");
+               $perm->setAttribute("class", "permalink");
+               $perm->setAttribute("href", "$pld->ref#$slug");
+               $perm->textContent = "#";
+               return $perm;
+       }
+
+       protected function walk(DOMNode $node, $pld) {
+               switch ($node->nodeType) {
+                       case XML_ELEMENT_NODE:
+                               $this->walkElement($node, $pld);
+                               break;
+                       case XML_TEXT_NODE:
+                               $this->wrapper->wrap($node, $pld);
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       protected function highlightCode(DOMElement $node) {
+               foreach (["default", "comment", "html", "keyword", "string"] as $type) {
+                       ini_set("highlight.$type", "inherit\" class=\"$type");
+               }
+               $code = highlight_string($node->textContent, true);
+               $temp = new DOMDocument("1.0", "utf-8");
+               $temp->loadHTML($code, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
+               return $node->ownerDocument->importNode($temp->firstChild, true);
+       }
 
-       static function factory() : Formatter {
-               if (class_exists("League\\CommonMark\\GithubFlavoredMarkdownConverter", true)) {
-                       return new Formatter\League;
+       protected function walkElement(DOMElement $node, $pld) {
+               switch ($node->tagName) {
+                       case "h1":
+                               $perm = $this->createPermaLink($node, "", $pld);
+                               $node->insertBefore($perm, $node->firstChild);
+                               $pld->currentSection = null;
+                               break;
+                       case "h2":
+                               $pld->currentSection = $this->formatSlug($node->textContent);
+                       case "h3":
+                       case "h4":
+                       case "h5":
+                       case "h6":
+                               $slug = $this->formatSlug($node->textContent);
+                               $perm = $this->createPermaLink($node, $slug, $pld);
+                               $node->appendChild($perm);
+                               break;
+                       case "span":
+                               if (!empty($pld->currentSection) && $node->hasAttribute("class")) {
+                                       switch ($pld->currentSection) {
+                                               case "Properties:":
+                                               case "Constants:":
+                                                       switch ($node->getAttribute("class")) {
+                                                               case "constant":
+                                                               case "var":
+                                                                       $slug = $this->formatSlug($node->textContent);
+                                                                       $perm = $this->createPermaLink($node, $slug, $pld);
+                                                                       $node->insertBefore($perm);
+                                                                       break;
+                                                       }
+                                                       break;
+                                       }
+                               }
+                       case "a":
+                       case "br":
+                       case "hr":
+                       case "em":
+                               return; // !
+                       case "code":
+                               if ($node->parentNode && $node->parentNode->nodeName === "pre") {
+                                       $code = $this->highlightCode($node);
+                                       $this->walk($code, $pld);
+                                       $node->parentNode->replaceChild($code, $node);
+                               }
+                               return; // !
                }
-               if (extension_loaded("discount")) {
-                       return new Formatter\Discount;
+
+               // suck it out, because we're modifying the DOM
+               foreach (iterator_to_array($node->childNodes) as $child) {
+                       $this->walk($child, $pld);
                }
-               throw new \Exception("No Markdown implementation found");
        }
 }
diff --git a/mdref/Formatter/Discount.php b/mdref/Formatter/Discount.php
deleted file mode 100644 (file)
index c787c7a..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-namespace mdref\Formatter;
-
-use mdref\Exception;
-use mdref\Formatter;
-
-use MarkdownDocument;
-
-class Discount extends Formatter {
-
-       function formatString(string $string) : string {
-               $md = \MarkdownDocument::createFromString($string);
-               $md->compile(\MarkdownDocument::AUTOLINK);
-               return $md->getHtml();
-       }
-
-       function formatFile(string $file) : string {
-               $fd = fopen($file, "r");
-               if (!$fd) {
-                       throw Exception::fromLastError();
-               }
-
-               $md = \MarkdownDocument::createFromStream($fd);
-               $md->compile(\MarkdownDocument::AUTOLINK | \MarkdownDocument::TOC);
-               $html = $md->getHtml();
-
-               fclose($fd);
-
-               return $html;
-       }
-}
diff --git a/mdref/Formatter/League.php b/mdref/Formatter/League.php
deleted file mode 100644 (file)
index 60ff7d4..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-namespace mdref\Formatter;
-
-use mdref\Exception;
-use mdref\Formatter;
-
-use League\CommonMark\Extension;
-use League\CommonMark\GithubFlavoredMarkdownConverter;
-use League\CommonMark\Normalizer;
-
-use function file_get_contents;
-use function preg_replace;
-
-class League extends Formatter {
-       private $md;
-
-       function __construct() {
-               $this->md = new GithubFlavoredMarkdownConverter([
-                       "slug_normalizer" => [
-                               "instance" => new class implements Normalizer\TextNormalizerInterface {
-                                       function normalize(string $text, $context = null) : string {
-                                               return preg_replace("/[^[:alnum:]:._-]/", ".", $text);
-                                       }
-                               }
-                       ],
-                       "heading_permalink" => [
-                               "html_class" => "permalink",
-                               "id_prefix" => "",
-                               "fragment_prefix" => "",
-                               "title" => "",
-                               "symbol" => "#",
-                               "insert" => "after",
-                               "min_heading_level" => 2,
-                       ]
-               ]);
-               $this->md->getEnvironment()->addExtension(
-                       new Extension\DescriptionList\DescriptionListExtension
-               );
-               $this->md->getEnvironment()->addExtension(
-                       new Extension\HeadingPermalink\HeadingPermalinkExtension
-               );
-               $this->md->getEnvironment()->addExtension(
-                       new Extension\Attributes\AttributesExtension
-               );
-       }
-
-       function formatString(string $string) : string {
-               return $this->md->convertToHtml($string);
-       }
-
-       function formatFile(string $file) : string {
-               $string = file_get_contents($file);
-               if ($string === false) {
-                       throw Exception::fromLastError();
-               }
-               return $this->md->convertToHtml($string);
-       }
-}
diff --git a/mdref/Formatter/Wrapper.php b/mdref/Formatter/Wrapper.php
new file mode 100644 (file)
index 0000000..400de29
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+namespace mdref\Formatter;
+
+use DomNode;
+use DOMText;
+use ReflectionExtension;
+use mdref\Formatter;
+
+class Wrapper {
+       protected $docref = "https://php.net/manual/en/%s";
+       protected $types = [
+               "language.types.declarations#language.types.declarations.%s" => ["void", "mixed"],
+               "language.types.%s" => ["null", "boolean", "integer", "float", "string", "resource", "array", "callable", "iterable"],
+               "language.types.null" =>  ["NULL"],
+               "language.types.boolean" => ["true", "TRUE", "false", "FALSE", "bool",  "BOOL"],
+               "language.types.integer" => ["int", "long"],
+               "language.types.float" => ["double", "real"],
+               "language.types.object" => ["stdClass"],
+               "language.types.callable" => ["callback"],
+               "language.types.enumerations" => ["enum"],
+               "language.references" => ["reference"],
+       ];
+       protected $exts = ["standard", "core", "spl", "json", "date"];
+
+       function __construct(
+               protected Formatter $fmt
+       ) {}
+
+       public function wrap(DOMText $node, $pld) : void {
+               $nodes = [];
+
+               $split = "[&?\(\)\|\"'\s\][\.,-]+";
+               $items = preg_split("/($split)/", $node->textContent, 0, PREG_SPLIT_DELIM_CAPTURE);
+               foreach ($items as $item) {
+                       if (preg_match("/^($split|[[:punct:]+])*$/", $item)) {
+                               $nodes[] = $node->ownerDocument->createTextNode($item);
+                               continue;
+                       }
+
+                       $new = $this->wrapType($node, $item, $pld)
+                               ?: $this->wrapKeyWord($node, $item, $pld)
+                                       ?: $this->wrapSpecial($node, $item, $pld);
+                       if (is_array($new)) {
+                               foreach ($new as $n)
+                                       $nodes[] = $n;
+                       } elseif ($new) {
+                               $nodes[] = $new;
+                       } else {
+                               $nodes[] = $node->ownerDocument->createTextNode($item);
+                       }
+               }
+               if ($nodes) {
+                       $parent = $node->parentNode;
+                       $new_node = array_pop($nodes);
+                       $parent->replaceChild($new_node, $node);
+                       foreach ($nodes as $prev_node) {
+                               $parent->insertBefore($prev_node, $new_node);
+                       }
+               }
+       }
+
+       protected function getType(string $item) : ?string {
+               static $types;
+               if (!$types) {
+                       foreach ($this->types as $doc => $list) foreach ($list as $type) {
+                               $types[$type] = sprintf($this->docref, sprintf($doc, $type));
+                       }
+                       foreach ($this->exts as $ext) foreach ((new ReflectionExtension($ext))->getClassNames() as $class) {
+                               $types[$class] = sprintf($this->docref, "class." . strtolower($class));
+                       }
+               }
+
+               $item = trim($item, "\\");
+               if (!isset($types[$item])) {
+                       return null;
+               }
+               return $types[$item];
+       }
+       protected function wrapType(DOMText $node, string $item, $pld) : ?DOMNode {
+               if (!($type = $this->getType($item))) {
+                       return null;
+               }
+               $a = $node->ownerDocument->createElement("a");
+               $a->setAttribute("href", $type);
+               $a->textContent = $item;
+               $code = $node->ownerDocument->createElement("code");
+               $code->insertBefore($a);
+               return $code;
+       }
+
+       protected function wrapKeyword(DOMText $node, string $item, $pld) : DomNode|array|null {
+               switch ($item) {
+                       case "is":
+                               if ($node->parentNode->nodeName !== "h1") {
+                                       break;
+                               }
+                       case "extends":
+                       case "implements":
+                               if ($node->parentNode->nodeName === "h1") {
+                                       $nodes = [
+                                               $node->ownerDocument->createElement("br"),
+                                               $node->ownerDocument->createEntityReference("nbsp"),
+                                               $new = $node->ownerDocument->createElement("em")
+                                       ];
+                                       $new->textContent = $item;
+                                       return $nodes;
+                               }
+                       case "class":
+                       case "enum":
+                       case "interface":
+                       case "namespace":
+                       case "public":
+                       case "protected":
+                       case "private":
+                       case "static":
+                       case "final":
+                       case "abstract":
+                       case "self":
+                       case "parent":
+                               $new = $node->ownerDocument->createElement("em");
+                               $new->textContent = $item;
+                               return $new;
+               }
+               return null;
+       }
+
+       protected function isFirstDeclaration(DOMNode $node, string $item, bool $is_slug = false) : bool {
+               return $node->parentNode->nodeName === "li"
+                       && !$node->ownerDocument->getElementById($is_slug ? $item : $this->fmt->formatSlug($item));
+       }
+
+       protected function isVar(string $item) : bool {
+               return str_starts_with($item, "\$");
+       }
+
+       protected function wrapVar(DOMNode $node, string $item, $pld) : DOMNode {
+               $ele = $node->ownerDocument->createElement("span");
+               $ele->setAttribute("class", "var");
+               $ele->textContent = $item;
+
+               if (!empty($pld->currentSection)) {
+                       $slug = $this->fmt->formatSlug($item);
+                       if ($this->isFirstDeclaration($node, $slug, true)) {
+                               $perm = $this->fmt->createPermaLink($ele, $slug, $pld);
+                               $ele->insertBefore($perm);
+                       }
+               }
+               return $ele;
+       }
+
+       protected function isNamespaced(DOMNode $node, string $item, $pld) : bool {
+               return str_contains($item, "\\") || str_contains($item, "::");
+       }
+
+       protected function wrapNamespaced(DOMNode $node, string $item, $pld) : ?DOMNode {
+               $href = preg_replace("/\\\\|::/", "/", trim($item, "\\:"));
+               $canonical = null;
+               $repo = $pld->refs->getRepoForEntry($href, $canonical);
+
+               if ($repo) {
+                       if (!empty($canonical)) {
+                               $href = $canonical;
+                       }
+                       $link = $node->ownerDocument->createElement("a");
+                       $link->setAttribute("href", $href);
+                       $link->textContent = $item;
+                       return $link;
+               }
+
+               $hash = basename($href);
+               $href = dirname($href);
+               $repo = $pld->refs->getRepoForEntry($href, $canonical);
+               if ($repo) {
+                       if (!empty($canonical)) {
+                               $href = $canonical;
+                       }
+                       $link = $node->ownerDocument->createElement("a");
+                       $link->setAttribute("href", "$href#$hash");
+                       $link->textContent = $item;
+                       return $link;
+               }
+
+               return null;
+       }
+
+       protected function wrapConstant(DOMNode $node, string $item, $pld) : ?DOMNode {
+               $strict = "_";
+               if (!empty($pld->currentSection)) {
+                       switch ($pld->currentSection) {
+                               case "Properties:":
+                               case "Constants:":
+                                       $strict = "";
+                                       break;
+                       }
+               }
+               if (preg_match("/^[A-Z]({$strict}[A-Z0-9_v])+\$/", $item)) {
+                       // assume some constant
+                       $span = $node->ownerDocument->createElement("span");
+                       $span->setAttribute("class", "constant");
+                       $span->textContent = $item;
+                       if (!$strict && $pld->currentSection === "Constants:" && $node->parentNode->nodeName === "li" && $node->parentNode->firstChild === $node) {
+                               $perm = $this->createPermaLink($span, $this->formatSlug($item), $pld);
+                               $span->insertBefore($perm);
+                       }
+                       return $span;
+               }
+
+               return null;
+       }
+
+       protected function wrapSpecial(DOMNode $node, string $item, $pld) : ?DOMNode {
+               if ($this->isVar($item)) {
+                       if (($ele = $this->wrapVar($node, $item, $pld))) {
+                               return $ele;
+                       }
+               }
+               if ($this->isNamespaced($node, $item, $pld)) {
+                       if (($ele = $this->wrapNamespaced($node, $item, $pld))) {
+                               return $ele;
+                       }
+               }
+               return $this->wrapConstant($node, $item, $pld);
+       }
+}
index 5292c6d4a882cb344749c332b49e4381c4825f32..8c463e23edcbd0eccfcad5bef290674e9b8f9aba 100644 (file)
@@ -31,7 +31,15 @@ endif;
 if (($parent = $ref->getParentClass())) :
        ?> extends <?= $parent->getName() ?><?php
 endif;
-if (($implements = $ref->getInterfaceNames())) : sort($implements);
+if (($implements = $ref->getInterfaceNames())) :
+       foreach ($implements as $index => $iface) :
+               foreach ($implements as $implemented) :
+                       if ($iface !== $implemented && is_subclass_of($implemented, $iface)) :
+                               unset($implements[$index]);
+                       endif;
+               endforeach;
+       endforeach;
+       sort($implements);
        ?> implements <?= implode(", ", $implements); ?><?php
 endif;
 ?>
index f31941a200e5ce5256374d8817fca37d2d826545..dfee0e7fb77a1f0b8ec9c772bc1e0f8674d54cb5 100644 (file)
@@ -91,4 +91,25 @@ endif;
 ?>
 
 
+<?php
+if (($tags = $doc?->getTagsWithTypeByName("throws"))) :
+?>
+
+## Throws:
+
+<?php
+       foreach ($tags as $tag) :
+               ?>* <?= $tag->getType()
+               ?><?php
+               if ($tag->getDescription()?->getBodyTemplate()) :
+                       ?>, <?= $tag->getDescription()
+                       ?><?php
+               endif;
+               ?><?="\n"
+       ?><?php
+       endforeach;
+endif;
+?>
+
+
 <?php
index 6e3b27e29d53de3a31f3941c92db007beb5f3711..4197de1afa015c8aa6d14beefd81af125018ec69 100644 (file)
@@ -31,7 +31,15 @@ class Reference implements IteratorAggregate {
                        $repo = new Repo($path);
                        $this->repos[$repo->getName()] = $repo;
                }
-               $this->fmt = $fmt ?: Formatter::factory();
+               $this->fmt = $fmt ?: new Formatter;
+       }
+
+       /**
+        * Get the formatter.
+        * @return Formatter
+        */
+       public function getFormatter() : Formatter {
+               return $this->fmt;
        }
 
        /**
@@ -62,7 +70,7 @@ class Reference implements IteratorAggregate {
         * @param string $anchor
         * @return string
         */
-       public function formatAnchor(string $anchor) : string {
+       public function formatAnchor(string $anchor, string $location = null) : string {
                if (is_numeric($anchor)) {
                        return "L$anchor";
                }
@@ -74,8 +82,8 @@ class Reference implements IteratorAggregate {
         * @return string
         * @throws \Exception, Exception
         */
-       public function formatString(string $string) : string {
-               return $this->fmt->formatString($string);
+       public function formatString(string $string, string $location = null) : string {
+               return $this->fmt->formatString($string, $location);
        }
 
        /**
@@ -83,7 +91,7 @@ class Reference implements IteratorAggregate {
         * @return string
         * @throws \Exception, Exception
         */
-       public function formatFile(string $file) : string {
-               return $this->fmt->formatFile($file);
+       public function formatFile(string $file, string $location = null) : string {
+               return $this->fmt->formatFile($file, $location);
        }
 }
index c0204cdb3464ef6db258bceab1f50e84f737d12d..063b6a1b2bdcf6f37db25cbd553145f0f04bb457 100644 (file)
@@ -96,7 +96,16 @@ pre>code {
 
 pre>code, pre>code code {
        background: #333;
-       color: #eee;
+       color: ghostwhite;
+}
+pre>code .comment {
+       color: darkorange !important;
+}
+pre>code .string {
+       color: darkseagreen !important;
+}
+pre>code .keyword {
+       color: darkgray !important;
 }
 
 p, pre, table, dl {
@@ -152,6 +161,12 @@ a:hover {
 a[href^="http:"]:after, a[href^="https:"]:after {
        content: " ⬈";
 }
+code>a {
+       text-decoration: none;
+}
+code a[href^="http:"]:after, code a[href^="https:"]:after {
+       content: "";
+}
 
 a.permalink {
        position: relative;
index 5124ffee52e50acec7bec355821a6a5d4075716b..5c4d8f114ba3f1d42ecc20215dffc317e3d442c8 100644 (file)
@@ -5,190 +5,6 @@ $(function() {
                log: function log() {
                        console.log.apply(console, arguments);
                },
-               is_constant: function is_constant(s) {
-                       s = s.replace(/v\d+(_\d+)?$/, "");
-                       if (s.length < 2) {
-                               return false;
-                       }
-                       return s.toUpperCase(s) === s;
-               },
-               is_variable: function is_variable(s) {
-                       return s.substring(0,1) === "$";
-               },
-               type: function type(s, nn) {
-                       var i, j, t;
-                       // mdref.log("type", s);
-                       // nothing
-                       if (!s.match(/[a-zA-Z]/)) {
-                               return;
-                       }
-
-                       switch (s) {
-                       // types
-                       case "void":
-                       case "bool":
-                       case "int":
-                       case "float":
-                       case "string":
-                       case "resource":
-                       case "array":
-                       case "object":
-                       case "callable":
-                       case "mixed":
-                       // Zend/SPL
-                       case "stdClass":
-                       case "Exception":
-                       case "ErrorException":
-                       case "RuntimeException":
-                       case "UnexpectedValueException":
-                       case "DomainException":
-                       case "InvalidArgumentException":
-                       case "BadMethodCallException":
-                       case "Closure":
-                       case "Generator":
-                       case "Countable":
-                       case "Serializable":
-                       case "Traversable":
-                       case "Iterator":
-                       case "IteratorAggregate":
-                       case "RecursiveIterator":
-                       case "ArrayAccess":
-                       case "ArrayObject":
-                       case "ArrayIterator":
-                       case "RecursiveArrayIterator":
-                       case "SeekableIterator":
-                       case "SplObserver":
-                       case "SplSubject":
-                       case "SplObjectStorage":
-                       case "JsonSerializable":
-                               return "<code>";
-
-                       // keywords
-                       case "is":
-                               if (nn !== "H1") {
-                                       return;
-                               }
-                       case "extends":
-                       case "implements":
-                               if (nn === "H1") {
-                                       return "<br>&nbsp;<em>";
-                               }
-                       case "class":
-                       case "enum":
-                       case "interface":
-                       case "namespace":
-                       case "public":
-                       case "protected":
-                       case "private":
-                       case "static":
-                       case "final":
-                       case "abstract":
-                       case "self":
-                       case "parent":
-                       // phrases
-                       case "Optional":
-                       case "optional":
-                               return "<em>";
-                       }
-
-                       // class members
-                       if (-1 !== (i = s.indexOf("::"))) {
-                               t = s.substring(i+2);
-                               if (!mdref.is_constant(t) && !mdref.is_variable(t)) {
-                                       // methods
-                                       return "<a href=\"" + s.replace(/::|\\/g, "/") + "\">";
-                               }
-                       }
-                       if (-1 !== (j = s.lastIndexOf("\\")) && s.substr(j+1,1) !== "n") {
-                               t = s.substring(j+1);
-                               if (!mdref.is_constant(t) || s.match(/\\/g).length <= 1) {
-                                       return "<a href=\"" + s.replace(/\\/g, "/").replace(/::/, "#") + "\">";
-                               }
-                               return "<a href=\"" + s.substring(0,j).replace(/\\/g, "/") + "#" + t + "\">";
-                       }
-
-                       switch (s.toLowerCase()) {
-                       // variables
-                       default:
-                               if (!mdref.is_variable(s)) {
-                                       break;
-                               }
-                       // special constants
-                       case "null":
-                       case "true":
-                       case "false":
-                               return "<span class=\"var\">";
-                       }
-
-                       // constants
-                       if (mdref.is_constant(s)) {
-                               return "<span class=\"constant\">";
-                       }
-               },
-               wrap: function wrap(n, nn) {
-                       var $n = $(n)
-                       var a = [];
-
-                       $n.text().split(/([^a-zA-Z0-9_\\\$:]+)/).forEach(function(v) {
-                               var t;
-
-                               if ((t = mdref.type(v.replace(/:$/, ""), nn))) {
-                                       a.push($(t).text(v));
-                               } else if (a.length && a[a.length-1].nodeName === "#text") {
-                                       /* if we already have a text node and the next is also gonna be a text
-                                        * node, then join them, becuase chrome v30+ or something eats whitespace
-                                        * for breakfast, lunch and dinner!
-                                        */
-                                       a[a.length-1].textContent += v;
-                               } else {
-                                       a.push(document.createTextNode(v));
-                               }
-                       });
-                       $n.replaceWith(a);
-               },
-               walk: function walk(i, e) {
-                       // mdref.log("walk", i, e);
-
-                       switch (e.nodeName) {
-                       case "H1":
-                       case "H2":
-                       case "H3":
-                       case "H4":
-                       case "H5":
-                       case "H6":
-                               if (e.id.length) {
-                                       var href = document.location.pathname;
-                                       var perm = $("<a class=\"permalink\" href=\""+href+"#\">#</a>");
-                                       if (e.nodeName === "H1") {
-                                               perm.prependTo(e);
-                                       } else {
-                                               perm.attr("href", function(i, href) {
-                                                       return href + e.id;
-                                               });
-                                               perm.appendTo(e);
-                                       }
-                               }
-                               break;
-                       }
-
-                       $.each($.makeArray(e.childNodes), function(i, n) {
-                               switch (n.nodeName) {
-                               case "A":
-                               case "BR":
-                               case "HR":
-                               case "EM":
-                               case "CODE":
-                               case "SPAN":
-                                       break;
-                               case "#text":
-                                       mdref.wrap(n, e.nodeName);
-                                       break;
-                               default:
-                                       mdref.walk(-1, n);
-                                       break;
-                               }
-                       });
-               },
                blink: function blink(c) {
                        var $c = $(c);
 
@@ -202,15 +18,17 @@ $(function() {
                },
                hashchange: function hashchange() {
                        if (location.hash.length > 1) {
+                               var hash = decodeURIComponent(location.hash.substring(1));
                                var e;
                                if ((e = document.getElementById(location.hash.substring(1)))) {
                                        mdref.blink(e);
                                } else {
-                                       var hash = location.hash.substring(1);
-                                       var name = mdref.is_variable(hash) ? ".var" : ".constant";
                                        var scrolled = false;
 
-                                       $(name).each(hash.substring(hash.length-1) === "_" ? function(i, c) {
+                                       if (hash.substring(hash.length-1) === "*") {
+                                               hash = hash.substring(0, hash.length-1);
+                                       }
+                                       $((hash.substring(0,1) === "$") ? ".var" : ".constant").each(function(i, c) {
                                                if (c.textContent.substring(0, hash.length) === hash) {
                                                        if (!scrolled) {
                                                                $(window).scrollTop($(c).offset().top - 100);
@@ -218,19 +36,12 @@ $(function() {
                                                        }
                                                        mdref.blink(c);
                                                }
-                                       } : function(i, c) {
-                                               if (c.textContent === hash) {
-                                                       $(window).scrollTop($(c).offset().top - 100);
-                                                       mdref.blink(c);
-                                                       return false;
-                                               }
                                        });
                                }
                        }
                }
        };
 
-       $("h1,h2,h3,h4,h5,h6,p,li,code,td").each(mdref.walk);
        $(window).on("hashchange", mdref.hashchange);
        mdref.hashchange();
 
index db2accd3867eaf8ee18a9cac969e06e34bb1c55e..d2bd1fe741cb2966216b3da3dec823d167150420 100644 (file)
@@ -1,3 +1,4 @@
+<?php ob_start() ?>
 
 <h1>mdref</h1>
 
@@ -20,3 +21,5 @@
                <?php endforeach; ?>
        <?php endforeach; ?>
 <?php endif; ?>
+
+<?= $markup(ob_get_clean()); ?>
index c05ad78d815cb668d7f6255585b72aa6e36ad846..2739f919549fc2543574e1396dba608466308c46 100644 (file)
@@ -1,3 +1,4 @@
+<?php ob_start() ?>
 
 <?= $file($entry->getPath()) ?>
 
@@ -40,3 +41,5 @@
 
 </ul>
 <?php endif; ?>
+
+<?= $markup(ob_get_clean()); ?>