From 0a58e3c1bb451c5f5e0d49ed8906fbd2f0214107 Mon Sep 17 00:00:00 2001
From: hongweipeng <961365124@qq.com>
Date: Fri, 1 Apr 2016 11:39:04 +0800
Subject: [PATCH] init

---
 MenuTree/Plugin.php   | 220 ++++++++++++++++++++++++++++++++++++++++++
 MenuTree/dropdown.js  | 165 +++++++++++++++++++++++++++++++
 MenuTree/menutree.css | 108 +++++++++++++++++++++
 README.md             |  26 +++++
 4 files changed, 519 insertions(+)
 create mode 100644 MenuTree/Plugin.php
 create mode 100644 MenuTree/dropdown.js
 create mode 100644 MenuTree/menutree.css
 create mode 100644 README.md

diff --git a/MenuTree/Plugin.php b/MenuTree/Plugin.php
new file mode 100644
index 0000000..55441f6
--- /dev/null
+++ b/MenuTree/Plugin.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ * 文章目录树
+ * 
+ * @package MenuTree 
+ * @author hongweipeng
+ * @version 0.6.1
+ * @link https://www.hongweipeng.com
+ */
+class MenuTree_Plugin implements Typecho_Plugin_Interface {
+    /**
+     * 索引ID
+     */
+    public static $id = 1;
+    
+    /**
+     * 目录树
+     */
+    public static $tree = array();
+
+     /**
+     * 激活插件方法,如果激活失败,直接抛出异常
+     * 
+     * @access public
+     * @return void
+     * @throws Typecho_Plugin_Exception
+     */
+    public static function activate() {
+        Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array(__CLASS__, 'parse');
+        Typecho_Plugin::factory('Widget_Archive')->header = array(__CLASS__, 'header');
+        Typecho_Plugin::factory('Widget_Archive')->footer = array(__CLASS__, 'footer');
+    }
+
+    /**
+     * 禁用插件方法,如果禁用失败,直接抛出异常
+     * 
+     * @static
+     * @access public
+     * @return void
+     * @throws Typecho_Plugin_Exception
+     */
+    public static function deactivate(){}
+
+    /**
+     * 获取插件配置面板
+     * 
+     * @access public
+     * @param Typecho_Widget_Helper_Form $form 配置面板
+     * @return void
+     */
+    public static function config(Typecho_Widget_Helper_Form $form){
+
+        $jq_import = new Typecho_Widget_Helper_Form_Element_Radio('jq_import', array(
+            0   =>  _t('不引入'),
+            1   =>  _t('引入')
+        ), 1, _t('是否引入jQuery'), _t('此插件需要jQuery,如已有选择不引入避免引入多余jQuery'));
+        $form->addInput($jq_import->addRule('enum', _t('必须选择一个模式'), array(0, 1)));
+
+
+    }
+
+    /**
+     * 个人用户的配置面板
+     * 
+     * @access public
+     * @param Typecho_Widget_Helper_Form $form
+     * @return void
+     */
+    public static function personalConfig(Typecho_Widget_Helper_Form $form){}
+
+    /**
+     * 插件实现方法
+     * 
+     * @access public
+     * @return void
+     */
+    public static function render() {
+        
+    }
+
+    /**
+     * 解析
+     * 
+     * @access public
+     * @param array $matches 解析值
+     * @return string
+     */
+    public static function parseCallback( $match ) {
+        $parent = &self::$tree;
+
+        $html = $match[0];
+        $n = $match[1];
+        $menu = array(
+            'num' => $n,
+            'title' => trim( strip_tags( $html ) ),
+            'id' => 'menu_index_' . self::$id,
+            'sub' => array()
+        );
+        $current = array();
+        if( $parent ) {
+            $current = &$parent[ count( $parent ) - 1 ];
+        }
+        // 根
+        if( ! $parent || ( isset( $current['num'] ) && $n <= $current['num'] ) ) {
+            $parent[] = $menu;
+        } else {
+            while( is_array( $current[ 'sub' ] ) ) {
+                // 父子关系
+                if( $current['num'] == $n - 1 ) {
+                    $current[ 'sub' ][] = $menu;
+                    break;
+                }
+                // 后代关系,并存在子菜单
+                elseif( $current['num'] < $n && $current[ 'sub' ] ) {
+                    $current = &$current['sub'][ count( $current['sub'] ) - 1 ];
+                }
+                // 后代关系,不存在子菜单
+                else {
+                    for( $i = 0; $i < $n - $current['num']; $i++ ) {
+                        $current['sub'][] = array(
+                            'num' => $current['num'] + 1,
+                            'sub' => array()
+                        );
+                        $current = &$current['sub'][0];
+                    }
+                    $current['sub'][] = $menu;
+                    break;
+                }
+            }
+        }
+        self::$id++;
+        return "<span id=\"{$menu['id']}\" name=\"{$menu['id']}\"></span>" . $html;
+    }
+    
+    /**
+     * 构建目录树,生成索引
+     * 
+     * @access public
+     * @return string
+     */
+    public static function buildMenuHtml( $tree, $include = true ) {
+        $menuHtml = '';
+        foreach( $tree as $menu ) {
+            if( ! isset( $menu['id'] ) && $menu['sub'] ) {
+                $menuHtml .= self::buildMenuHtml( $menu['sub'], false );
+            } elseif( $menu['sub'] ) {
+                $menuHtml .= "<li><a data-scroll href=\"#{$menu['id']}\" title=\"{$menu['title']}\">{$menu['title']}</a>" . self::buildMenuHtml( $menu['sub'] ) . "</li>";
+            } else {
+                $menuHtml .= "<li><a data-scroll href=\"#{$menu['id']}\" title=\"{$menu['title']}\">{$menu['title']}</a></li>";
+            }
+        }
+        if( $include ) {
+            $menuHtml = '<ul>' . $menuHtml . '</ul>';
+        }
+        return $menuHtml;
+    }
+
+    /**
+     * 判断是否是内容页,避免主页加载插件
+     */
+    public static function is_content() {
+        static $is_content = null;
+        if($is_content === null) {
+            $widget = Typecho_Widget::widget('Widget_Archive');
+            $is_content = !($widget->is('index') || $widget->is('search') || $widget->is('date') || $widget->is('category'));
+        }
+        return $is_content;
+    }
+    /**
+     * 插件实现方法
+     * 
+     * @access public
+     * @return string
+     */
+    public static function parse( $html, $widget, $lastResult ) {
+        $html = empty( $lastResult ) ? $html : $lastResult;
+        if (!self::is_content()) {
+            return $html;
+        }
+        $html = preg_replace_callback( '/<h([1-6])[^>]*>.*?<\/h\1>/s', array( 'MenuTree_Plugin', 'parseCallback' ), $html );
+        return $html;
+    }
+
+    /**
+     *为header添加css文件
+     *@return void
+     */
+    public static function header() {
+        if (!self::is_content()) {
+            return;
+        }
+        $cssUrl = Helper::options()->pluginUrl . '/MenuTree/menutree.css';
+        echo '<link rel="stylesheet" type="text/css" href="' . $cssUrl . '" />';
+    }
+
+    /**
+     *为footer添加js文件
+     *@return void
+     */
+    public static function footer() {
+        if (!self::is_content()) {
+            return;
+        }
+        if (Helper::options()->plugin('MenuTree')->jq_import) {
+            echo '<script src="//cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>';
+        }
+
+        $html = '<div class="in-page-preview-buttons in-page-preview-buttons-full-reader"><ul><li title="内容目录 Ctrl+Alt+O"id="preview-toc-button"class="in-page-button dropdown"><svg data-toggle="dropdown"class="dropdown-toggle icon-list" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 22 22" style="enable-background:new 0 0 22 22;" xml:space="preserve"><path style="fill-rule:evenodd;clip-rule:evenodd;" d="M22,0L0,7.5l11,3.459L14.5,22L22,0z M2.512,7.534L20.5,1.5l-6,17.5 l-2.75-8.75L2.512,7.534z"/></svg><div class="dropdown-menu theme pull-right theme-white"id="toc-list"><h3>内容目录</h3><hr><div class="table-of-contents"><div class="toc"><ul><li>'. self::buildMenuHtml( self::$tree ) .'</li></ul></div></div></div></li></ul></div>';
+        $js = Helper::options()->pluginUrl . '/MenuTree/dropdown.js';
+        echo <<<HTML
+            <script src="{$js}"></script>
+            <script type="text/javascript">
+            jQuery('body').append('$html');
+            </script>
+HTML;
+        self::$id = 1;
+        self::$tree = array();
+
+    }
+}
diff --git a/MenuTree/dropdown.js b/MenuTree/dropdown.js
new file mode 100644
index 0000000..0fa87c7
--- /dev/null
+++ b/MenuTree/dropdown.js
@@ -0,0 +1,165 @@
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.3.6
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle="dropdown"]'
+  var Dropdown = function (element) {
+    $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.VERSION = '3.3.6'
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+  function clearMenus(e) {
+    if (e && e.which === 3) return
+    $(backdrop).remove()
+    $(toggle).each(function () {
+      var $this         = $(this)
+      var $parent       = getParent($this)
+      var relatedTarget = { relatedTarget: this }
+
+      if (!$parent.hasClass('open')) return
+
+      if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
+
+      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this.attr('aria-expanded', 'false')
+      $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
+    })
+  }
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+        // if mobile we use a backdrop because click events don't delegate
+        $(document.createElement('div'))
+          .addClass('dropdown-backdrop')
+          .insertAfter($(this))
+          .on('click', clearMenus)
+      }
+
+      var relatedTarget = { relatedTarget: this }
+      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this
+        .trigger('focus')
+        .attr('aria-expanded', 'true')
+
+      $parent
+        .toggleClass('open')
+        .trigger($.Event('shown.bs.dropdown', relatedTarget))
+    }
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive && e.which != 27 || isActive && e.which == 27) {
+      if (e.which == 27) $parent.find(toggle).trigger('focus')
+      return $this.trigger('click')
+    }
+
+    var desc = ' li:not(.disabled):visible a'
+    var $items = $parent.find('.dropdown-menu' + desc)
+
+    if (!$items.length) return
+
+    var index = $items.index(e.target)
+
+    if (e.which == 38 && index > 0)                 index--         // up
+    if (e.which == 40 && index < $items.length - 1) index++         // down
+    if (!~index)                                    index = 0
+
+    $items.eq(index).trigger('focus')
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.dropdown')
+
+      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown             = Plugin
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+    .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
+
+}(jQuery);
diff --git a/MenuTree/menutree.css b/MenuTree/menutree.css
new file mode 100644
index 0000000..f1803d7
--- /dev/null
+++ b/MenuTree/menutree.css
@@ -0,0 +1,108 @@
+.in-page-preview-buttons-full-reader {
+    right:50px;
+    top:-15px;
+    border-radius: 50%;
+    width: 48px;
+    height: 48px;
+    background: rgba(255, 255, 255, 0.2);
+    box-shadow: 0 1px 8px 1.5px rgba(0, 0, 0, 0.35), 0 20px 70px 8px rgba(0, 0, 0, 0.25);
+}
+
+.in-page-editor-buttons,.in-page-preview-buttons {
+    margin-top:100px;
+    position:fixed;
+    z-index:10;
+}
+
+.in-page-button {
+    /*margin-right:15px;*/
+    width:20px;
+    height:20px;
+    display:inline-block;
+    list-style:none;
+    cursor:pointer;
+    font-size:17px;
+}
+.in-page-button>svg {
+    width:25px;
+    height:25px;
+    display:inline-block;
+    padding-left: 12px;
+    padding-top:  12px;
+}
+.icon-list:before{
+    display: inline-block;
+    text-decoration: inherit;
+}
+.icon-list {
+}
+.in-page-preview-buttons ul {
+    color: #2c3e50;
+    padding-left: 0;
+}
+#toc-list {
+    background-clip:padding-box;
+    border-radius:4px;
+    float:left;
+    font-size:14px;
+    list-style:none outside none;
+    margin:2px 0 0;
+    min-width:160px;
+    position:absolute;
+    padding:5px 0 20px;
+    top:100%;
+    z-index:1000;
+    box-shadow: 0 1px 8px 1.5px rgba(0, 0, 0, 0.35), 0 20px 70px 8px rgba(0, 0, 0, 0.25);
+}
+#toc-list .toc ul {
+    margin-left:20px;
+    padding-left: 0;
+}
+#toc-list .toc>ul {
+    margin:0
+}
+#toc-list a:hover {
+    background:0 0;
+    color:#005580;
+    text-decoration:underline;
+}
+#toc-list a {
+    color:#08c;
+    text-decoration:none;
+    display:inline;
+}
+#toc-list h3 {
+    margin:10px 0;
+    padding-left:15px;
+}
+#toc-list hr {
+    margin:10px 0;
+}
+.dropdown-menu {
+    display: none;
+}
+.dropdown-menu.pull-right {
+    left: auto;
+    right: 0;
+}
+.theme-white {
+    background-color: #f9f9f5;
+    /*background-color: rgba(0, 0, 0, 0.5);*/
+    color: #2c3e50;
+}
+.pull-right {
+    float: right;
+}
+.table-of-contents {
+    overflow-x:hidden;
+    overflow-y:auto;
+    width:330px;
+    max-height:400px;
+    padding:5px 0;
+}
+.toc ul {
+    list-style-type:none;
+}
+.open > .dropdown-menu {
+    display: block;
+}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1c49a7f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+## 起步
+
+悬浮式文章目录树,定在右侧。
+
+## 使用方法
+
+第一步:下载本插件,放在 `usr/plugins/` 目录中;
+第二步:激活插件;
+
+## 预览
+
+![20160401112707.png][1]
+
+github开源地址:[https://github.com/hongweipeng/MenuTree_for_typecho][2]
+
+## 与我联系:
+
+作者:hongweipeng
+主页:[https://www.hongweipeng.com/][3]
+或者通过 Emai: hongweichen8888@sina.com
+有任何问题也可评论留言
+
+
+  [1]: https://www.hongweipeng.com/usr/uploads/2016/04/1824863673.png
+  [2]: https://github.com/hongweipeng/MenuTree_for_typecho
+  [3]: https://www.hongweipeng.com/
\ No newline at end of file