| 1 |
<?php |
| 2 |
/* |
| 3 |
Name: 文章目录 |
| 4 |
URI: https://mp.weixin.qq.com/s/vgNtvc1RcWyVCmnQdxAV0A |
| 5 |
Description: 自动根据文章� |
| 6 |
容里的子标题提取出文章目录,并显示在� |
| 7 |
容前。 |
| 8 |
Version: 3.0 |
| 9 |
*/ |
| 10 |
class WPJAM_Toc{ |
| 11 |
public static function __callStatic($method, $args){ |
| 12 |
return wpjam_call_option(static::class, $method, ...$args); |
| 13 |
} |
| 14 |
|
| 15 |
public static function get_fields(){ |
| 16 |
return [ |
| 17 |
'individual'=> ['title'=>'单独设置', 'type'=>'checkbox', 'value'=>1, 'label'=>'文章列表和编辑页面可以单独设置是否显示文章目录以及显示到第几级。'], |
| 18 |
'depth' => ['title'=>'显示层级', 'type'=>'select', 'value'=>6, 'options'=> wpjam_fill(range(2, 6), fn($v)=> 'H'.$v)], |
| 19 |
'position' => ['title'=>'显示位置', 'type'=>'select', 'value'=>'content', 'options'=>['content'=>'显示在文章� |
| 20 |
容前面', 'shortcode'=>'使用[toc]插� |
| 21 |
�� |
| 22 |
容中', 'function'=>'显示在侧边栏/调用函数<code>wpjam_get_toc()</code>显示']], |
| 23 |
]+(current_theme_supports('style', 'toc') ? [] : [ |
| 24 |
'css' => ['title'=>'显示样式', 'type'=>'textarea', 'description'=>'也可以将相� |
| 25 |
�的样式代码复制主题的对应文件中,点击这里获取<a href="https://blog.wpjam.com/m/toc-js-css-code/" target="_blank">文章目录的默认的 CSS</a>'] |
| 26 |
]); |
| 27 |
} |
| 28 |
|
| 29 |
public static function output($wrap=true){ |
| 30 |
$toc = is_singular() ? (string)self::get_arg('toc') : ''; |
| 31 |
|
| 32 |
return $toc && $wrap ? '<details id="toc" open>'."\n".'<summary>文章目录</summary>'."\n".$toc.'</details>'."\n" : $toc; |
| 33 |
} |
| 34 |
|
| 35 |
public static function filter_content($content){ |
| 36 |
$depth = self::get_setting('individual', 1) ? (int)get_post_meta(get_the_ID(), 'toc_depth', true) : 0; |
| 37 |
$depth = $depth ?: self::get_setting('depth', 6); |
| 38 |
|
| 39 |
if($depth && $depth != -1){ |
| 40 |
$i = 0; |
| 41 |
$stack = []; |
| 42 |
$output = $toc = ''; |
| 43 |
$index = str_contains($content, '[toc]'); |
| 44 |
$tag = $index ? 'ol' : 'ul'; |
| 45 |
$parts = preg_split('#(<h([1-'.$depth.'])\b([^>]*)>(.*?)</h\2>)#is', $content, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); |
| 46 |
|
| 47 |
while($i < count($parts)){ |
| 48 |
$part = $parts[$i]; |
| 49 |
|
| 50 |
if(str_starts_with($part, '<h') && $part[2] >= 1 && $part[2] <= $depth){ |
| 51 |
$level = (int)$parts[$i+1]; |
| 52 |
$attr = $parts[$i+2] ? shortcode_parse_atts($parts[$i+2]) : []; |
| 53 |
|
| 54 |
if(!str_contains($attr['class'] ?? '', 'toc-noindex')){ |
| 55 |
if(empty($attr['id'])){ |
| 56 |
$attr['id'] = 'toc_'.($i+1); |
| 57 |
} |
| 58 |
|
| 59 |
if($stack){ |
| 60 |
if($level > array_last($stack)){ |
| 61 |
$toc .= "\n<".$tag.">\n"; |
| 62 |
$output .= $index ? '<section class="section">'."\n" : ''; |
| 63 |
}else{ |
| 64 |
while($level <= array_last($stack)){ |
| 65 |
$toc .= "</li>\n"; |
| 66 |
|
| 67 |
if($level < array_pop($stack)){ |
| 68 |
$toc .= "</".$tag.">\n"; |
| 69 |
$output .= $index ? "</section>\n" : ''; |
| 70 |
} |
| 71 |
} |
| 72 |
} |
| 73 |
} |
| 74 |
|
| 75 |
$stack[] = $level; |
| 76 |
$toc .= '<li><a href="#'.$attr['id'].'">'.trim(strip_tags($parts[$i+3])).'</a>'; |
| 77 |
} |
| 78 |
|
| 79 |
$output .= wpjam_tag('h'.$level, $attr, $parts[$i+3]); |
| 80 |
$i += 4; |
| 81 |
}else{ |
| 82 |
$output .= $part; |
| 83 |
$i += 1; |
| 84 |
} |
| 85 |
} |
| 86 |
|
| 87 |
if($stack){ |
| 88 |
$content = implode([ |
| 89 |
!$index && self::get_setting('position', 'content') == 'content' ? '[toc]' : '', |
| 90 |
$output, |
| 91 |
$index ? str_repeat("</section>\n", count($stack)-1) : '' |
| 92 |
]); |
| 93 |
|
| 94 |
self::update_arg('toc', "<".$tag.">\n".$toc.str_repeat("</li>\n</".$tag.">\n", count($stack))); |
| 95 |
} |
| 96 |
} |
| 97 |
|
| 98 |
return str_replace('[toc]', self::output(), $content); |
| 99 |
} |
| 100 |
|
| 101 |
public static function add_hooks(){ |
| 102 |
wpjam_register_widget('wpjam-toc', 'WPJAM - 文章目录', [ |
| 103 |
'classname' => 'widget_toc', |
| 104 |
'fields' => ['title' => ['type'=>'text', 'before'=>'列表标题:', 'class'=>'medium-text']], |
| 105 |
'widget' => fn()=> self::output(false) |
| 106 |
]); |
| 107 |
|
| 108 |
wpjam_once('maybe', 'the_content', fn($content)=> !doing_filter('get_the_excerpt') && wpjam_is('single', get_the_ID()) ? self::filter_content($content) : null, 11); |
| 109 |
|
| 110 |
if($css = self::get_setting('css')){ |
| 111 |
wpjam_hook('wp_head', fn()=> is_singular() && !current_theme_supports('style', 'toc') && wpjam_echo('<style type="text/css">'."\n".$css."\n".'</style>')); |
| 112 |
} |
| 113 |
|
| 114 |
self::get_setting('individual', 1) && wpjam_register_post_option('wpjam-toc', [ |
| 115 |
'title' => '文章目录', |
| 116 |
'context' => 'side', |
| 117 |
'list_table' => true, |
| 118 |
'post_type' => fn($type)=> is_post_type_viewable($type) && post_type_supports($type, 'editor'), |
| 119 |
'fields' => ['toc_depth' => [ |
| 120 |
'title' => '层级:', |
| 121 |
'type' => 'select', |
| 122 |
'show_in_rest' => ['type'=>'integer'], |
| 123 |
'default' => 0, |
| 124 |
'options' => [0=>'默认']+wpjam_fill(range(2, 6), fn($v)=> 'H'.$v)+[-1=>'不显示'] |
| 125 |
]] |
| 126 |
]); |
| 127 |
} |
| 128 |
} |
| 129 |
|
| 130 |
wpjam_register_option('wpjam-toc', [ |
| 131 |
'model' => 'WPJAM_Toc', |
| 132 |
'title' => '文章目录', |
| 133 |
'menu_page' => ['tab_slug'=>'toc', 'plugin_page'=>'wpjam-posts', 'summary'=> __FILE__] |
| 134 |
]); |
| 135 |
|
| 136 |
function wpjam_get_toc(){ |
| 137 |
return WPJAM_Toc::output(); |
| 138 |
} |