mirror of
https://github.com/hongweipeng/MenuTree.git
synced 2024-12-21 20:30:26 +08:00
init
This commit is contained in:
commit
0a58e3c1bb
220
MenuTree/Plugin.php
Normal file
220
MenuTree/Plugin.php
Normal file
@ -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();
|
||||
|
||||
}
|
||||
}
|
165
MenuTree/dropdown.js
Normal file
165
MenuTree/dropdown.js
Normal file
@ -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);
|
108
MenuTree/menutree.css
Normal file
108
MenuTree/menutree.css
Normal file
@ -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;
|
||||
}
|
26
README.md
Normal file
26
README.md
Normal file
@ -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/
|
Loading…
Reference in New Issue
Block a user