permalinks
authorMichael Wallner <mike@php.net>
Wed, 15 Jun 2016 11:00:11 +0000 (13:00 +0200)
committerMichael Wallner <mike@php.net>
Wed, 15 Jun 2016 11:00:11 +0000 (13:00 +0200)
mdref/Action.php
mdref/Entry.php
mdref/File.php
mdref/Reference.php
public/index.css
public/index.js
views/index.phtml
views/mdref.phtml

index 19cf9106f363c9edb6e89e1b83a9cd6fa6aa5ac0..df974b9f1f1595d9fb48f0ad2822475874ffbb1d 100644 (file)
@@ -29,7 +29,7 @@ class Action {
         * @var \http\Url
         */
        private $baseUrl;
-       
+
        /**
         * Initialize the reference
         */
@@ -44,7 +44,7 @@ class Action {
        function esc($txt) {
                return htmlspecialchars($txt);
        }
-       
+
        /**
         * Create the view payload
         * @param \http\Controller $ctl
@@ -52,8 +52,9 @@ class Action {
         */
        private function createPayload() {
                $pld = new \stdClass;
-               
+
                $pld->esc = "htmlspecialchars";
+               $pld->anchor = [$this->reference, "formatAnchor"];
                $pld->quick = [$this->reference, "formatString"];
                $pld->file = [$this->reference, "formatFile"];
 
@@ -62,10 +63,10 @@ class Action {
 
                $pld->refs = $this->reference;
                $pld->baseUrl = $this->baseUrl;
-                       
+
                return $pld;
        }
-       
+
        /**
         * Redirect to canononical url
         * @param string $cnn
@@ -75,7 +76,7 @@ class Action {
                $this->response->setResponseCode(301);
                $this->response->send();
        }
-       
+
        /**
         * Serve index.css
         */
@@ -84,7 +85,7 @@ class Action {
                $this->response->setBody(new \http\Message\Body(fopen(ROOT."/public/index.css", "r")));
                $this->response->send();
        }
-       
+
        /**
         * Serve index.js
         */
@@ -93,7 +94,7 @@ class Action {
                $this->response->setBody(new \http\Message\Body(fopen(ROOT."/public/index.js", "r")));
                $this->response->send();
        }
-       
+
        /**
         * Serve a preset
         * @param \stdClass $pld
@@ -124,7 +125,7 @@ class Action {
                include ROOT."/views/layout.phtml";
                $this->response->send();
        }
-       
+
        public function handle() {
                try {
 
@@ -144,11 +145,11 @@ class Action {
                                        return;
                                }
                        }
-               
+
                } catch (\Exception $e) {
                        $pld->exception = $e;
                }
 
                $this->serve($pld);
        }
-}
\ No newline at end of file
+}
index db858a9c2d2a207ba694ed8b4a2518c427ff405d..4febd764604b678ba9e1f658b3ff3d895b479e2c 100644 (file)
@@ -11,31 +11,31 @@ class Entry implements \IteratorAggregate {
         * @var string
         */
        private $name;
-       
+
        /**
         * Split name
         * @var array
         */
        private $list;
-       
+
        /**
         * The containing repository
         * @var \mdref\Repo
         */
        private $repo;
-       
+
        /**
         * The file path, if the refentry exists
-        * @var type 
+        * @var type
         */
        private $path;
-       
+
        /**
         * The file instance of this entry
         * @var \mdref\File
         */
        private $file;
-       
+
        /**
         * @param string $name the compound name of the ref entry, e.g. "pq/Connection/exec"
         * @param \mdref\Repo $repo the containing repository
@@ -46,7 +46,7 @@ class Entry implements \IteratorAggregate {
                $this->list = explode("/", $name);
                $this->path = $repo->hasEntry($name);
        }
-       
+
        /**
         * Get the compound name, e.g. "pq/Connection/exec"
         * @return string
@@ -54,7 +54,7 @@ class Entry implements \IteratorAggregate {
        public function getName() {
                return $this->name;
        }
-       
+
        /**
         * Get the containing repository
         * @return \mdref\Repo
@@ -62,7 +62,7 @@ class Entry implements \IteratorAggregate {
        public function getRepo() {
                return $this->repo;
        }
-       
+
        /**
         * Get the file path, if any
         * @return string
@@ -70,7 +70,7 @@ class Entry implements \IteratorAggregate {
        public function getPath() {
                return $this->path;
        }
-       
+
        /**
         * Get the file instance of this entry
         * @return \mdref\File
@@ -81,57 +81,57 @@ class Entry implements \IteratorAggregate {
                }
                return $this->file;
        }
-       
+
        /**
-        * Get edit URL 
+        * Get edit URL
         * @return string
         */
        public function getEditUrl() {
                return $this->repo->getEditUrl($this->name);
        }
-       
+
        /**
         * Read the title of the ref entry file
         * @return string
         */
        public function getTitle() {
                if ($this->isFile()) {
-                       return $this->getFile()->readTitle();
+                       return trim($this->getFile()->readTitle());
                }
                if ($this->isRoot()) {
-                       return $this->repo->getRootEntry()->getTitle();
+                       return trim($this->repo->getRootEntry()->getTitle());
                }
                return $this->name;
        }
-       
+
        /**
         * Read the description of the ref entry file
         * @return string
         */
        public function getDescription() {
                if ($this->isFile()) {
-                       return $this->getFile()->readDescription();
+                       return trim($this->getFile()->readDescription());
                }
                if ($this->isRoot()) {
-                       return $this->repo->getRootEntry()->getDescription();
+                       return trim($this->repo->getRootEntry()->getDescription());
                }
                return $this;
        }
-       
+
        /**
         * Read the intriductory section of the refentry file
         * @return string
         */
        public function getIntro() {
                if ($this->isFile()) {
-                       return $this->getFile()->readIntro();
+                       return trim($this->getFile()->readIntro());
                }
                if ($this->isRoot()) {
-                       return $this->repo->getRootEntry()->getIntro();
+                       return trim($this->repo->getRootEntry()->getIntro());
                }
                return "";
        }
-       
+
        /**
         * Check if the refentry exists
         * @return bool
@@ -139,7 +139,7 @@ class Entry implements \IteratorAggregate {
        public function isFile() {
                return strlen($this->path) > 0;
        }
-       
+
        /**
         * Check if this is the first entry of the reference tree
         * @return bool
@@ -147,7 +147,7 @@ class Entry implements \IteratorAggregate {
        public function isRoot() {
                return count($this->list) === 1;
        }
-       
+
        /**
         * Get the parent ref entry
         * @return \mdref\Entry
@@ -161,7 +161,7 @@ class Entry implements \IteratorAggregate {
                        return $this->repo->getEntry($dirn);
                }
        }
-       
+
        /**
         * Get the list of parents up-down
         * @return array
@@ -173,7 +173,7 @@ class Entry implements \IteratorAggregate {
                }
                return $parents;
        }
-       
+
        /**
         * Guess whether this ref entry is about a function or method
         * @return bool
@@ -182,7 +182,7 @@ class Entry implements \IteratorAggregate {
                $base = end($this->list);
                return $base{0} === "_" || ctype_lower($base{0});
        }
-       
+
        /**
         * Guess whether this ref entry is about a namespace, interface or class
         * @return bool
@@ -191,7 +191,7 @@ class Entry implements \IteratorAggregate {
                $base = end($this->list);
                return ctype_upper($base{0});
        }
-       
+
        /**
         * Display name
         * @return string
@@ -203,11 +203,11 @@ class Entry implements \IteratorAggregate {
                        return $myself;
                }
                $parent = end($parts);
-               
+
                switch ($myself{0}) {
                case ":":
                        return "★" . substr($myself, 1);
-                       
+
                default:
                        if (!ctype_lower($myself{0}) || ctype_lower($parent{0})) {
                                return $myself;
@@ -216,7 +216,7 @@ class Entry implements \IteratorAggregate {
                        return $parent . "::" . $myself;
                }
        }
-       
+
        /**
         * Get the base name of this ref entry
         * @return string
@@ -224,7 +224,7 @@ class Entry implements \IteratorAggregate {
        public function getBasename() {
                return dirname($this->path) . "/" . basename($this->path, ".md");
        }
-       
+
        /**
         * Guess whether there are any child nodes
         * @param string $glob
@@ -240,7 +240,7 @@ class Entry implements \IteratorAggregate {
                        return is_dir($this->getBasename());
                }
        }
-       
+
        /**
         * Guess whether there are namespace/interface/class child nodes
         * @return bool
@@ -248,7 +248,7 @@ class Entry implements \IteratorAggregate {
        function hasNsClasses() {
                return $this->hasIterator("/[A-Z]*.md", true);
        }
-       
+
        /**
         * Guess whether there are function/method child nodes
         * @return bool
@@ -256,7 +256,7 @@ class Entry implements \IteratorAggregate {
        function hasFunctions() {
                return $this->hasIterator("/[a-z_]*.md");
        }
-       
+
        /**
         * Implements \IteratorAggregate
         * @return \mdref\Tree child nodes
index 0ac3da26f1ac3f43d50a00c3aeb2e200ed60ec79..4afd3b430559a16b804945f1e14aa0eb6b4dadf2 100644 (file)
@@ -10,7 +10,7 @@ class File {
         * @var resource
         */
        private $fd;
-       
+
        /**
         * Open the file
         * @param string $path
@@ -18,7 +18,7 @@ class File {
        public function __construct($path) {
                $this->fd = fopen($path, "rb");
        }
-       
+
        /**
         * Read the title of the refentry
         * @return string
@@ -28,7 +28,7 @@ class File {
                        return fgets($this->fd);
                }
        }
-       
+
        /**
         * Read the description of the refentry
         * @return string
@@ -40,7 +40,7 @@ class File {
                        return fgets($this->fd);
                }
        }
-       
+
        /**
         * Read the first subsection of a global refentry
         * @return string
@@ -49,7 +49,7 @@ class File {
                $intro = "";
                if (0 === fseek($this->fd, 0, SEEK_SET)) {
                        $header = false;
-                       
+
                        while (!feof($this->fd)) {
                                if (false === ($line = fgets($this->fd))) {
                                        break;
index 7c22dba37fee16e9b9b4cfd7b90746b892ced4fb..892be7e096ab8da0f66e140bbd39dc4f2a53d8e4 100644 (file)
@@ -11,7 +11,7 @@ class Reference implements \IteratorAggregate {
         * @var array
         */
        private $repos = array();
-       
+
        /**
         * @param array $refs list of mdref repository paths
         */
@@ -21,7 +21,7 @@ class Reference implements \IteratorAggregate {
                        $this->repos[$repo->getName()] = $repo;
                }
        }
-       
+
        /**
         * Lookup the repo containing a ref entry
         * @param string $entry requested reference entry, e.g. "pq/Connection/exec"
@@ -35,7 +35,7 @@ class Reference implements \IteratorAggregate {
                        }
                }
        }
-       
+
        /**
         * Implements \IteratorAggregate
         * @return \ArrayIterator repository list
@@ -44,12 +44,19 @@ class Reference implements \IteratorAggregate {
                return new \ArrayIterator($this->repos);
        }
 
+       public function formatAnchor($anchor) {
+               if (is_numeric($anchor)) {
+                       return "L$anchor";
+               }
+               return preg_replace("/[^[:alnum:]\.:_]/", ".", $anchor);
+       }
+
        public function formatString($string) {
                $md = \MarkdownDocument::createFromString($string);
                $md->compile(\MarkdownDocument::AUTOLINK);
                return $md->getHtml();
        }
-       
+
        public function formatFile($file) {
                $fd = fopen($file, "r");
                $md = \MarkdownDocument::createFromStream($fd);
index 22417db30804ddc7c0267a4535c45c2ba10a871a..4fd8949f03ad57f3bf829e5b29515fc2e58e3da7 100644 (file)
@@ -7,7 +7,7 @@ html, body{
        min-height: 100%;
 }
 body, code {
-       font-family: Inconsolata, Monospace, 'Courier New', Courier, monospace;
+       font-family: Inconsolata, 'Courier New', Courier, monospace;
 }
 body {
        line-height: 1.5;
@@ -131,6 +131,14 @@ a[href^="http:"]:after, a[href^="https:"]:after {
        content: " ⬈";
 }
 
+a.permalink {
+       position: relative;
+       top: 0;
+       right: 0;
+       color: #999999;
+       opacity: 0.25;
+}
+
 .var {
        color: #800000;
 }
index 4878936a383649981948a0b3645b81534c10b3f3..8d6c506bd533a71ff70ed92b0e33f0c32f07b2bc 100644 (file)
@@ -129,7 +129,7 @@ $(function() {
 
                        $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") {
@@ -147,6 +147,28 @@ $(function() {
                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":
@@ -178,29 +200,34 @@ $(function() {
                },
                hashchange: function hashchange() {
                        if (location.hash.length > 1) {
-                               var hash = location.hash.substring(1);
-                               var name = mdref.is_variable(hash) ? ".var" : ".constant";
-                               var scrolled = false;
+                               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 (c.textContent.substring(0, hash.length) === hash) {
-                                               if (!scrolled) {
+                                       $(name).each(hash.substring(hash.length-1) === "_" ? function(i, c) {
+                                               if (c.textContent.substring(0, hash.length) === hash) {
+                                                       if (!scrolled) {
+                                                               $(window).scrollTop($(c).offset().top - 100);
+                                                               scrolled = true;
+                                                       }
+                                                       mdref.blink(c);
+                                               }
+                                       } : function(i, c) {
+                                               if (c.textContent === hash) {
                                                        $(window).scrollTop($(c).offset().top - 100);
-                                                       scrolled = true;
+                                                       mdref.blink(c);
+                                                       return false;
                                                }
-                                               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 acab9e27e026f215aa44922877d3da0819f7a57f..0baafb841237b52860385c196c26d413a8e974b1 100644 (file)
@@ -7,10 +7,10 @@
 <?php elseif (isset($refs)) : ?>
        <?php foreach ($refs as $repo) : /* @var \mdref\Repo $repo */ ?>
                <?php foreach ($repo as $entry) : /* @var \mdref\Entry $entry */ ?>
-               <h2>
+               <h2 id="<?= $anchor($entry->getTitle()) ?>">
                        <a href="<?= $esc($entry->getName()) ?>"
                        ><?= $esc($entry->getTitle()) ?></a></h2>
                <div><?= $quick($entry->getIntro()) ?></div>
                <?php endforeach; ?>
        <?php endforeach; ?>
-<?php endif; ?>
\ No newline at end of file
+<?php endif; ?>
index a9e34ef3a0918c417bc90be91f34604a76d8f637..370d8d85b5b7dd03bb1ddf18951be33ddf53ae89 100644 (file)
@@ -1,7 +1,7 @@
 <?= $file($entry->getPath()) ?>
 
 <?php if ($entry->hasFunctions()) : ?>
-<h2>Functions:</h2>
+<h2 id="Functions:">Functions:</h2>
 <ul>
        <?php foreach($entry as $sub) : if (!$sub->isFunction()) continue; ?>
        <li>
@@ -14,7 +14,7 @@
 <?php endif; ?>
 
 <?php if ($entry->hasNsClasses()) : ?>
-<h2>Namespaces, Interfaces and Classes:</h2>
+<h2 id="Namespaces,.Interfaces.and.Classes:">Namespaces, Interfaces and Classes:</h2>
 <ul>
        <?php foreach ($entry as $sub) : if (!$sub->isNsClass()) continue; ?>
        <li>