Не рідко великі посади ми поділяємо логічними підзаголовками. Часом пост несе у собі якусь зібрану інформацію, розбиту на частини. Для таких постів я волію створювати “зміст” – список посилань з анкорами на підзаголовки в пості. Створювати такий зміст – заняття до того ж болюче, що простіше обійтися без нього (за рідкісним винятком, звичайно).
Написав невеликий клас, який дозволяє без шуму та пилу, а також красиво і головне швидко створювати зміст майже будь-якої складності. Для цього потрібно використовувати шоткод [contents] , там, де воно нам потрібно. На початку цього посту ви бачите той самий зміст.
Крім цього написаний мною клас дозволяє створювати зміст для будь-якого тексту, без використання шоткоду в ньому. А потім виводити цей зміст, наприклад, на початку посту або в бічній колонці (сайдбарі).
“Зміст” можна всіляко налаштувати:
не показувати заголовок: “Зміст:” – [contents embed] ;
не показувати посилання “До змісту” в тексті – параметр to_menu ;
налаштувати під себе CSS стилі – параметр css ;
змінити HTML теги за якими будуватиметься зміст. Можна вказати будь-які теги не тільки H1, H2але і strong, emі т.д. Або взагалі, вказати класи html тега, наприклад: .foo, .bar– [contents h1 em. Foo] ;
вказати мінімальну кількість заголовків для того, щоб зміст виводився – параметр min_found .
вказати мінімальну довжину тексту, щоб вміст виводився – параметр min_length .
вказати назву шоткода, який буде використовуватися в тексті для створення змісту – параметр shortcode .
<?php
/** @noinspection RegExpRedundantEscape */
namespace KamaWP;
interface Kama_Contents_Interface {
/** Creates an instance by specified parameters. */
public function __construct( array $args = [] );
/** Processes the text, turns the shortcode in it in a table of contents. */
public function apply_shortcode(string $content): string;
/** Відрізняються від kamaTOC shortcode з вмісту. */
public function strip_shortcode(string $content): string;
/** Відображає об'єкти в $content, creates and returns a table of contents. */
public function make_contents( string &$content, string $params = '' ): string;
}
/**
* Contents (table of contents) for large posts.
*
* @author Kama
* @ See http://wp-kama.ru/1513
*
* @version 4.3.10
*/
class Kama_Contents implements Kama_Contents_Interface {
use Kama_Contents__Html;
use Kama_Contents__Helpers;
use Kama_Contents__Legacy;
private static $default_opt = [
'margin' => '2em',
'selectors' => 'h2 h3 h4',
'to_menu' => 'до змісту ↑',
'title' => 'Зміст:',
'js' => '',
'min_found' => 1,
'min_length' => 2000,
'page_url' => '',
'shortcode' => 'contents',
'spec' => '',
'anchor_type' => 'id',
'anchor_attr_name' => 'id',
'markup' => false,
'anchor_link' => '',
'tomenu_simcount' => 800,
'leave_tags' => true,
// shortcode additional params
'as_table' => false,
'embed' => false,
];
/**
* @var object Instance options.
*/
private $opt;
/**
* Collects html (the contents).
*
* @var TOC_Elem[]
*/
protected $toc_elems;
/**
* @var array
*/
private $ temp;
/**
* Create instance.
*
* @param array $args {
* Parameters.
*
* @type string $margin Відступ зліва у підрозділів px|em|rem.
* @type string $selectors HTML теги по якому будуватиметься зміст: 'h2 h3 h4'.
* Порядок визначає рівень вкладеності.
* Можна вказати рядок/масив: 'h2 h3 h4' або [ 'h2', 'h3', 'h4' ].
* Можна вказати атрибут/class: 'h2 .class_name'.
* Якщо потрібно, щоб різні теги були на одному рівні,
* вказуємо їх через |: 'h2|dt h3' або ['h2|dt', 'h3'].
* @type string $to_menu Посилання на повернення до змісту. '' - прибрати посилання.
* @type string $title Заголовок. '' - прибрати заголовок.
* @type string $js JS код (додається після HTML коду)
* @type int $min_found Мінімальна кількість знайдених тегів, щоб вміст виводився.
* @type int $min_length Мінімальна довжина (символів) тексту, щоб вміст виводився.
* @type string $page_url Посилання на сторінку для якої збирається зміст.
* Якщо зміст виводитися на іншій сторінці...
* @type string $shortcode Назва шоткоду. Типово: 'contents'.
* @type string $spec Залишати символи в анкорах. Для прикладу: ``.+$*=`.
* @type string $anchor_type Який тип анкору використовувати: 'a' - `<a name="anchor"></a>` або 'id'.
* @type string $anchor_attr_name Назва атрибута тега зі значення якого буде братися
* анкор (якщо цей атрибут є в тега). Ставимо '', щоб вимкнути таку перевірку...
* @type bool $markup Включити мікророзмітку?
* @type string $anchor_link Додати 'знак' перед підзаголовком статті з посиланням
* На поточний анкор заголовка. Вкажіть '#', '&' або що вам подобається.
* @type int $tomenu_simcount Мінімальна кількість символів між заголовками змісту,
* для яких потрібно виводити посилання "до змісту".
* Не має сенсу, якщо параметр 'to_menu' вимкнено. З метою продуктивності,
* кирилиця вважається без урахування кодування. Тому 800 символів кирилиці -
* Це приблизно 1600 символів у цьому параметрі. 800 – розрахунок для сайтів на кирилиці.
* @type bool|string $leave_tags Чи потрібно залишати HTML теги в елементах змісту. З версії 4.3.4.
* Можна вказати лише які теги потрібно залишати. Пр: ``<b><strong><var><code>'`.
*
* }
*/
public function __construct( array $args = [] ) {
$this->set_opt( $args );
}
protected function set_opt( $args = [] ): void {
$this->opt = (object) array_merge( self::$default_opt, (array) $args );
}
/**
* Processes the text, turns the shortcode in it in a table of contents.
* Use shortcode [contents] or [[contents]] до show shortcode as it is.
*
* @param string $content The text with shortcode.
*
* @Return string Processed text with a table of contents, якщо це має shotcode.
*/
public function apply_shortcode( string $content ): string {
$shortcode = $this->opt->shortcode;
if( false === strpos( $content, "[$shortcode" ) ){
return $content;
}
// get contents data
// use `[[contents` to escape the shortcode
if( ! preg_match( "/^(.*)(?<![)[$shortcode([^]]*)](.*)$/su", $content, $m ) ){
return $content;
}
$toc = $this->make_contents( $m[3], $m[2] );
return $m[1] . $ toc . $ m [3];
}
/**
* Cuts out the kamaTOC shortcode з вмісту.
*
* @param string $content
*
* @return string
*/
public function strip_shortcode( string $content ): string {
return preg_replace( '~[' . $this->opt->shortcode . '[^]]*]~', '', $content );
}
/**
* Зберігати headings в тексті (за допомогою ref), твори і відновити table of contents.
*
* @param string $content The text from which you want to create a table of contents.
* @param string $params Array of HTML tags для перегляду в тексті.
* "h2 .foo" - Specify: tag names "h2 h3" або names of CSS classes ".foo .foo2".
* "embed" - Add "embed" mark here to get `<ul>` tag only (without header and wrapper block).
* "as_table="Title|Desc" - Швидка якість.
* Це може бути корисним для використання вмісту всередині тексту як листа.
*
* @return string Table of contents HTML.
*/
public function make_contents( string &$content, string $params = '' ): string {
// Text is too short
if( mb_strlen( strip_tags( $content ) ) < $this->opt->min_length ){
return '';
}
$this->temp = new stdClass();
$params_array = $this->parse_string_params( $params );
$tags = $this->split_params_and_tags( $params_array );
$tags = $this->get_actual_tags( $tags, $content );
if(! $tags) {
unset($this->temp);
return '';
}
$this->temp->toc_page_url = $this->opt->page_url ?: home_url( parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ) );
$this->collect_toc( $content, $tags );
if( count( $this->toc_elems ) < $this->opt->min_found ){
unset($this->temp);
return '';
}
$contents = $this->toc_html();
unset($this->temp);
return $contents;
}
/**
* @param string $params
*
* @return array
*/
protected function parse_string_params( string $params ): array {
$this->temp->original_string_params = $params;
$ extra_tags = [];
if( preg_match( '/(as_table)="([^"]+)"/', $params, $mm ) ){
$extra_tags[ $mm[1] ] = explode( '|', $mm[2] );
$params = str_replace(" $mm[0]", '', $params ); // cut
}
$params = array_map( 'trim', preg_split( '/[ ,|]+/', $params ) );
$params += $extra_tags;
return array_filter($params);
}
/**
* Split parameters and tags.
*
* @param array $params
* @param string $content
*
* @return array
*/
protected function split_params_and_tags( array $params ): array {
$tags = [];
foreach( $params as $key => $val ){
// extra tags
if( 'as_table' === $key ){
$this->opt->as_table = $val;
}
elseif( 'embed' === $val || 'embed' === $key ){
$this->opt->embed = true;
}
elseif( 'no_to_menu' === $val || 'no_to_menu' === $key ){
$this->opt->to_menu = false;
}
else {
$tags[$key] = $val;
}
}
if(! $tags) {
$tags = is_array( $this->opt->selectors )
? $this->opt->selectors
: explode( ' ', $this->opt->selectors );
}
return $tags;
}
/**
* Remove tag if it's no exists in content (for performance).
*/
protected function get_actual_tags( array $tags, string $content ): array {
foreach( $tags as $key => $tag ){
$patt = ( $tag[0] === '.' )
? 'class=['"][^'"]*' . substr ($ tag, 1)
: "<$tag";
if( ! preg_match( "/$patt/i", $content ) ){
unset ($ tags [$ key]);
}
}
return $tags;
}
/**
* Collect TOC (all titles) від визначеного вмісту.
* Replace HTML в спеціальному вмісті.
*
* @param string $content Changes by ref.
* @param array $tags HTML tags (selectors) to collect from content.
*
* @return void
*/
protected function collect_toc( string & $content, array $tags ): void {
$this->toc_elems = [];
$this->_set_tags_levels_and_regex_patt( $tags );
$ patt_in = [];
if( $this->temp->tag_regex_patt ){
$tags_in = implode( '|', $this->temp->tag_regex_patt );
$patt_in[] = "(?:<($tags_in)([^>]*)>(.*?)</1>)";
}
if( $this->temp->class_regex_patt ){
$class_in = implode( '|', $this->temp->class_regex_patt );
$patt_in[] = "(?:<([^ >]+) ([^>]*class=["'][^>]*($class_in)[^>]*["'][ ^>]*)>(.*?)</" . ( $patt_in ? '4' : '1' ) . '>)';
}
$patt_in = implode('|', $patt_in);
// collect and replace
$this->temp->orig_content = $content;
$new_content = preg_replace_callback( "/$patt_in/is", [ $this, 'collect_toc_replace_callback' ], $content, -1 );
if( count( $this->toc_elems ) >= $this->opt->min_found ){
$content = $new_content;
}
}
/**
* @param array $match
*
* @return array
*/
protected function _replace_parse_match( $match ){
$ full_match = $ match [0];
// it's class selector in pattern
if( count( $match ) === 5 ){
[$ tag, $ attrs, $ level_tag, $ tag_txt] = array_slice ($ match, 1);
}
// it's tag selector
elseif( count( $match ) === 4 ){
[$ tag, $ attrs, $ tag_txt] = array_slice ($ match, 1);
$level_tag = $tag; // class name
}
// it's class selector
else {
[$ tag, $ attrs, $ level_tag, $ tag_txt] = array_slice ($ match, 4);
}
return [$ full_match, $tag, $attrs, $level_tag, $tag_txt];
}
protected function _set_tags_levels_and_regex_patt( array $tags ): void {
// group HTML classes & tags for regex patterns
$tag_regex_patt = $class_regex_patt = $tags_levels = [];
foreach( $tags as $tag ){
// class
if( $tag[0] === '.' ){
$ tag = substr ($ tag, 1);
$_ln = &$class_regex_patt;
}
// html tag
else {
$_ln = &$tag_regex_patt;
}
$_ln[] = $tag;
$tags_levels[] = $tag;
}
$tags_levels = array_flip($tags_levels);
// fix levels if it's not start from zero
if( reset( $tags_levels ) !== 0 ){
while( reset( $tags_levels ) !== 0 ){
$tags_levels = array_map( static function( $val ) {
return $val - 1;
}, $tags_levels);
}
}
// Set equal level if tags specified with tag1|tag2
$_prev_tag = '';
foreach( $tags_levels as $tag => $lvl ){
if( $_prev_tag && false !== strpos( $this->temp->original_string_params, "$_prev_tag|$tag" ) ){
$tags_levels[ $tag ] = $_prev_lvl;
}
$_prev_tag = $tag;
$_prev_lvl = $lvl;
}
// Set levels one by one, if they have been broken after the last operation
$_prev_lvl = 0;
foreach( $tags_levels as & $lvl ){
// fix next lvl - it's wrong
if( ! in_array( $lvl, [ $_prev_lvl, $_prev_lvl + 1 ], true ) ){
$lvl = $_prev_lvl + 1;
}
$_prev_lvl = $lvl;
}
unset($lvl);
$this->temp->tags_levels = $tags_levels;
$this->temp->tag_regex_patt = $tag_regex_patt;
$this->temp->class_regex_patt = $class_regex_patt;
}
/**
* Callback function to replace and collect contents.
*
* @param array $match
*
* @return string
*/
protected function collect_toc_replace_callback( $match ): string {
[ $full_match, $tag, $attrs, $level_tag, $tag_text ] = $this->_replace_parse_match( $match );
$this->temp->counter = empty( $this->temp->counter ) ? 1 : $this->temp->counter + 1;
$anchor = $this->_toc_element_anchor( $tag_text, $attrs );
$this->toc_elems[] = new TOC_Elem( [
'full_match' => $full_match,
'tag' => $tag,
'anchor' => $anchor,
'text' => $this->_strip_tags_in_elem_txt( $tag_text ),
'level' => $this->temp->tags_levels[ $level_tag ] ?? 0,
]);
if( $this->opt->anchor_link ){
$tag_text = '<a rel="nofollow" class="kamatoc-anchlink" href="#' . $anchor . '">' . $this->opt->anchor_link . '</a> ' . $tag_text;
}
// anchor type: 'a' or 'id'
if( $this->opt->anchor_type === 'a' ){
$new_el = '<a class="kamatoc-anchor" name="' . $anchor . '"></a>' . "n<$tag $attrs>$tag_text</$tag>";
}
else {
$new_el = "n<$tag id="$anchor" $attrs>$tag_text</$tag>";
}
$to_menu = $this->_to_menu_link( $full_match );
return $to_menu . $new_el;
}
protected function _to_menu_link( $full_match ){
if( ! $this->opt->to_menu ){
return '';
}
// go to contents
$to_menu = '<a rel="nofollow" class="kamatoc-gotop" href="' . "{$this->opt->page_url}#tocmenu" . '">' . $this->opt->to_menu . '</a>';
// remove '$to_menu' if simbols beatween $to_menu too small (< 300)
// mb_strpos( $this->temp->orig_content, $full_match ) - у 150 разів повільніше!
$elpos = strpos( $this->temp->orig_content, $full_match );
if( empty( $this->temp->elpos ) ){
$prevpos = 0;
$this->temp->elpos = [ $elpos ];
}
else {
$prevpos = end( $this->temp->elpos );
$this->temp->elpos[] = $elpos;
}
$simbols_count = $elpos - $prevpos;
if( $simbols_count < $this->opt->tomenu_simcount ){
$to_menu = '';
}
return $to_menu;
}
}
trait Kama_Contents__Html {
/**
*
* @return string
*/
protected function _toc_html(): string {
$ toc = '';
foreach( $this->toc_elems as $elem ){
$elem_html = $this->toc_element_html( $elem );
$toc .= "t$elem_htmln";
}
return $toc;
}
protected function toc_html(): string {
// table
if( $this->opt->as_table ){
$contents = '
<table id="tocmenu" class="kamatoc kamatoc_js" {ItemList}>
{ItemName}
<thead>
<tr>
<th>'. esc_html( $this->opt->as_table[0] ) . '</th>
<th>'. esc_html( $this->opt->as_table[1] ) . '</th>
</tr>
</thead>
<tbody>
'. $this->_toc_html() . '
</tbody>
</table>';
}
// list
else {
$add_wrapper = $this->opt->title && ! $this->opt->embed;
$contents_wrap_patt = '%s';
if( $add_wrapper ){
$contents_wrap_patt = '
<div class="kamatoc-wrap">
<div class="kamatoc-wrap__title kamatoc_wrap_title_js">' . $this->opt->title . '</div>
'. $contents_wrap_patt .'
</div>
';
}
$contents = '
<ul id="tocmenu" class="kamatoc kamatoc_js" {ItemList}>
{ItemName}
'. $this->_toc_html() . '
</ul>';
$contents = sprintf($contents_wrap_patt, $contents);
}
$js_code = $this->opt->js
? '<script>' . preg_replace( '/[nt ]+/', ' , $this->opt->js ) . '</script>'
: '';
$contents = $this->replace_markup( $contents );
/**
* Allow to change result contents string.
*
* @param string $contents
* @param Kama_Contents $inst
*/
return apply_filters( 'kamatoc__contents', "$contentsn$js_code", $this );
}
protected function toc_element_html( TOC_Elem $elem ): string {
// table
if( $this->opt->as_table ){
// Take first sentence
$quoted_match = preg_quote ($ elem-> full_match, '/');
//preg_match( "/$quoted_matchs*<p>((?:.(?!</p>))+)/", $this->temp->orig_content, $mm )
preg_match( "/$quoted_matchs*<p>(.+?)</p>/", $this->temp->orig_content, $mm );
$tag_desc = $mm? $mm[1] : '';
$elem_html = '
<tr>
<td {ListElement}>
<a rel="nofollow" href="' . "{$this->opt->page_url}#$elem->anchor" . '">' . $elem->text . '</a>
{ListElement_item}
{ListElement_name}
{ListElement_pos}
</td>
<td>'. $tag_desc. '</td>
</tr>';
}
// list(li)
else {
if( $elem->level > 0 ){
$unit = preg_replace( '/d/', '', $this->opt->margin )?: 'px';
$elem_classes = "kamatoc__sub kamatoc__sub_{$elem->level}";
$elem_attr = $this->opt->margin ? ' style="margin-left:' . ( $elem->level * (int) $this->opt->margin ) . $unit . ';"' : '';
}
else {
$elem_classes = 'kamatoc__top';
$elem_attr = '';
}
$elem_html = '
<li class="'. $elem_classes .'" ' . $ elem_attr . '{ListElement}>
<a rel="nofollow" href="' . "{$this->opt->page_url}#$elem->anchor" . '">' . $elem->text . '</a>
{ListElement_item}
{ListElement_name}
{ListElement_pos}
</li>';
}
$elem_html = $this->replace_elem_markup( $elem_html, $elem );
/**
* Allow to change single TOC element HTML.
*
* @param string $elem_html
*/
return apply_filters( 'kamatoc__elem_html', $elem_html );
}
protected function replace_markup( $html ): string {
$is = $this->opt->markup;
$replace = [
'{ItemList}' => $is ? 'itemscope itemtype="https://schema.org/ItemList"' : '',
'{ItemName}' => $is ? '<meta itemprop="name" content="' . esc_attr( wp_strip_all_tags( $this->opt->title ) ) . '" />' : '',
];
return strtr($html, $replace);
}
protected function replace_elem_markup( $html, TOC_Elem $elem ): string {
$is = $this->opt->markup;
$replace = [
'{ListElement}' => $is ? ' itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"' : '',
'{ListElement_item}' => $is ? ' <meta itemprop="item" content="' . esc_attr( "{$this->temp->toc_page_url}#$elem->anchor" ) . '" />' : '',
'{ListElement_name}' => $is ? ' <meta itemprop="name" content="' . esc_attr( wp_strip_all_tags( $elem->text ) ) . '" />' : '',
'{ListElement_pos}' => $is ? ' <meta itemprop="position" content="' . $this->temp->counter . '" />' : '',
];
return strtr($html, $replace);
}
}
trait Kama_Contents__Helpers {
/**
* @param string $tag_txt
* @param string $attrs
*
* @return string
*/
protected function _toc_element_anchor( $tag_txt, $attrs ){
// if tag contains id|name|... attribute it becomes anchor.
if(
$this->opt->anchor_attr_name
&&
preg_match( '/ *(' . preg_quote( $this->opt->anchor_attr_name, '/' ) . ')=(['"])(.+?)2 */i', $attrs, $ match_anchor_attr )
) {
// delete 'id' або 'name' attr from attrs
if( in_array( $match_anchor_attr[1], [ 'id', 'name' ], true ) ){
$attrs = str_replace( $match_anchor_attr[0], '', $attrs );
}
$anchor = $this->_sanitaze_anchor( $match_anchor_attr[3] );
}
else {
$anchor = $this->_sanitaze_anchor( $tag_txt );
}
return $anchor;
}
/**
* @param string $tag_txt
*
* @return string
*/
protected function _strip_tags_in_elem_txt( $tag_txt ){
// strip all tags
if( ! $this->opt->leave_tags ){
$ tag_txt = strip_tags ($ tag_txt);
}
// strip all tags, except specified
elseif( is_string( $this->opt->leave_tags ) ){
$tag_txt = strip_tags( $tag_txt, $this->opt->leave_tags );
}
// leave tags
// $tag_txt не може містити A, IMG теги - видалимо якщо треба...
else {
if( false !== strpos( $tag_txt, '</a>' ) ){
$tag_txt = preg_replace( '~<a[^>]+>|</a>~', '', $tag_txt );
}
if( false !== strpos( $tag_txt, '<img' ) ){
$tag_txt = preg_replace( '~<img[^>]+>~', '', $tag_txt );
}
}
return $tag_txt;
}
/**
* anchor transliteration
*
* @param string $anch
*
* @return string
*/
protected function _sanitaze_anchor( $anch ) {
$ Anch = strip_tags ($ Anch);
$anch = apply_filters( 'kamatoc__sanitaze_anchor_before', $anch, $this );
$anch = html_entity_decode($anch);
// iso9
$ Anch = strtr ($ Anch, [
'А' => 'A',
'Б' => 'B',
'В' => 'V',
'Г' => 'G',
'Д' => 'D',
'Е' => 'E',
'Е' => 'YO',
'Ж' => 'ZH',
'З' => 'Z',
'І' => 'I',
'Й' => 'J',
'К' => 'K',
'Л' => 'L',
'М' => 'M',
'Н' => 'N',
'О' => 'O',
'П' => 'P',
'Р' => 'R',
'С' => 'S',
'Т' => 'T',
'У' => 'U',
'Ф' => 'F',
'Х' => 'H',
'Ц' => 'TS',
'Ч' => 'CH',
'Ш' => 'SH',
'Щ' => 'SHH',
'Ъ' => '',
'И' => 'Y',
'Ь' => '',
'Е' => 'E',
'Ю' => 'YU',
'Я' => 'YA',
// small
'а' => 'a',
'б' => 'b',
'в' => 'v',
'г' => 'g',
'д' => 'd',
'е' => 'e',
'є' => 'yo',
'ж' => 'zh',
'з' => 'z',
'і' => 'i',
'й' => 'j',
'к' => 'k',
'л' => 'l',
'м' => 'm',
'н' => 'n',
'про' => 'o',
'п' => 'p',
'р' => 'r',
'з' => 's',
'т' => 't',
'у' => 'u',
'ф' => 'f',
'х' => 'h',
'ц' => 'ts',
'год' => 'ch',
'ш' => 'sh',
'щ' => 'shh',
'ъ' => '',
'и' => 'y',
'ь' => '',
'е' => 'e',
'ю' => 'yu',
'я' => 'ya',
// other
'Б' => 'G',
'Ґ' => 'G',
'Є' => 'YE',
'Ѕ' => 'Z',
'Ј' => 'J',
'І' => 'I',
'Ї' => 'YI',
'Н' => 'K',
'К' => 'L',
'М' => 'N',
'Ў' => 'U',
'П' => 'DH',
'ѓ' => 'g',
'ґ' => 'g',
'є' => 'ye',
'ѕ' => 'z',
'ј' => 'j',
'і' => 'i',
'ї' => 'yi',
'ќ' => 'k',
'љ' => 'l',
'њ' => 'n',
'у' => 'u',
'џ' => 'dh',
]);
$spec = preg_quote( $this->opt->spec, '/');
$anch = preg_replace( "/[^a-zA-Z0-9_$spec-]+/", '-', $anch ); // все непотрібне на '-'
$anch = strtolower( trim( $anch, '-' ) );
$ Anch = substr ($ Anch, 0, 70); // shorten
$anch = apply_filters( 'kamatoc__sanitaze_anchor', $anch, $this );
return $this->_unique_anchor($anch);
}
/**
* Adds number at end if this anchor already exists.
*
* @param string $anch
*
* @return string
*/
protected function _unique_anchor( string $anch ): string {
if( ! isset( $this->temp->anchors ) ){
$this->temp->anchors = [];
}
// check and unique anchor
if( isset( $this->temp->anchors[ $anch ] ) ){
$lastnum = substr($anch, -1);
$lastnum = is_numeric( $lastnum )? $lastnum + 1: 2;
$anch = preg_replace( '/-d$/', '', $anch );
return $this->{ __FUNCTION__ }( "$anch-$lastnum" );
}
$this->temp->anchors[ $anch ] = 1;
return $anch;
}
}
trait Kama_Contents__Legacy {
/**
* Creates an instance з Kama_Contents for later use.
*
* @param array $args
*
* @return Kama_Contents
*/
public static function init( array $args = [] ) {
static $inst;
$args = array_intersect_key( $args, self::$default_opt ); // Leave allowed only
$inst_key = md5(serialize( $args ) );
if( empty( $inst[ $inst_key ] ) )){
$inst[$inst_key] = new self();
$inst[ $inst_key ]->set_opt( $args );
}
return $inst[$inst_key];
}
/**
* Alias of {@see apply_shortcode()}.
*/
public function shortcode( string $content ): string {
return $this->apply_shortcode( $content );
}
}
class TOC_Elem {
$full_match;
public $tag;
public $anchor;
public $text;
public $level;
public function __construct( array $data ){
foreach( $data as $key => $val ){
$this->$key = $val;
}
}
}
Як користуватися класом Kama_Contents
Насамперед потрібно підключити код:
Створіть файл, наприклад Kama_Contents.php, і скопіюйте код. Підключіть цей файл до файлу теми functions.php:
require_once __DIR__ .'/Kama_Contents.php';
Використовуйте композер:
composer require doiftrue/wp-kama-contents
Тепер можна використати клас. Для цього вибирайте відповідний код з прикладів нижче та додайте його до файлу теми functions.php або куди вам зручно.
#1 Зміст у тексті (шоткод [contents] )
Під час написання посту використовуйте шоткод [contents] або [contents h3] або [contents h3 h5] . На місці шоткода з’явиться Зміст тексту, який слідує після шоткоду:
Розмістіть цей код поряд з основним і на початку кожного посту у вас буде виводитись зміст, за вказаними тегами array(‘h2′,’h3’) , тобто. якщо в тексті будуть знайдені теги h2 або h3 , то з них буде зібрано зміст:
Цей код вставлятиме вміст на початку кожного запису. Але не на самому початку, а після першого параграфу. Номер параграфа (розділювача) і сам роздільник можна змінити змінних: $_sep_numі $_sepвідповідно.
// Виведення змісту зверху після зазначеного параграфа, автоматично всім записів
add_filter( 'the_content', 'contents_at_top_after_nsep', 20);
function contents_at_top_after_nsep( $text ){
if( ! is_singular() ){
return $text;
}
// Налаштування роздільника
$_sep = '</p>'; // роздільник у тексті
$_sep_num = 1; // після якого по порядку роздільника вставляти зміст?
// Налаштування змісту
$ args = array (
'min_length' => 4000,
'css' => false,
'markup' => true,
'selectors' => array('h2','h3'),
);
$toc = new KamaWPKama_Contents( $args );
// погнали...
$ex_text = explode($_sep, $text, $_sep_num + 1);
// якщо потрібний по порядку роздільник знайдений у тексті
if( isset( $ex_text[ $_sep_num ] ) ) ){
$contents = $toc->make_contents( $ex_text[ $_sep_num ] );
$ex_text[$_sep_num] = $contents. $ex_text[$_sep_num];
$text = implode($_sep, $ex_text);
}
// просто у вершині тексту
else {
$contents = $toc->make_contents( $text );
$text = $contents. $text;
}
return $text;
}
#4 Зміст у сайд-барі
Ці приклади схожі на другий – тут також використовується метод make_contents(), а не apply_shortcode()як у першому.
Варіант 1
Додайте цю функцію поруч із класом і використовуйте там де потрібно вивести зміст. У функцію потрібно передати об’єкт посту (за замовчуванням передається global $post ) для якого потрібно отримати зміст або можна передати сам текст для якого потрібно вивести зміст (текст потрібно передавати в змінній, яка потім буде використана для виведення тексту, саме ця змінна, тому що в ній за посиланням змінюється текст – до його заголовків додаються анкори).
// для виведення змісту
function get_kama_contents( & $post = false ){
if( ! $post ) $post = $GLOBALS['post'];
if( is_string( $post ) ){
$post_content = &$post;
}
else {
$post_content = & $post->post_content;
}
$toc = new KamaWPKama_Contents( [
'selectors' => [ 'h2', 'h3' ],
'min_found' => 1,
'margin' => 0,
'to_menu' => false,
'title' => false,
]);
$contents = $toc->make_contents( $post_content );
// щоб правильно працювала the_content() яка працює на основі get_the_content()
Global $pages;
if( $pages && count($pages) == 1 ){
$pages[0] = $post_content;
}
else {
// Тут потрібна окрема обробка...
}
return $contents;
}
Тепер виводимо зміст, наприклад у сайдбарі:
echo get_kama_contents();
Примітка: get_kama_contents() потрібно викликати раніше, ніж виводитися контент. Якщо HTML вміст потрібно вивести нижче ніж виводиться контент, то викличте функцію збережіть зміст і виведіть його нижче.
$contents = get_kama_contents();
// код код
the_content();
// виводимо зміст
echo $contents;
Варіант 2
Розмістивши цей код поряд з основним класом, зміст можна вивести в будь-якому місці шаблону, наприклад, у сайдбарі. Для цього використовуйте рядок:echo $GLOBALS['kc_contents'];
// Висновок змісту в сайдбарі
add_action( 'wp_head', 'sidebar_contents' );
function sidebar_contents(){
global $post;
if( ! is_singular() ){
return;
}
$args = array();
$args['selectors'] = ['h2', 'h3'];
//$args['margin'] = 50;
//$args['to_menu'] = false;
//$args['title'] = false;
$toc = new KamaWPKama_Contents( $args );
$GLOBALS['kc_contents'] = $toc->make_contents( $post->post_content );
}
// потім у сайдбарі виводимо: echo $GLOBALS['kc_contents'];
#5 Різні екземпляри
Якщо потрібно використовувати кілька класів із різними параметрами. Наприклад, щоб опрацювати різні тексти. То різні екземпляри класу можна створити так:
// 1
// перший текст із шорткодом [toc]
$text1 = 'текст [toc] текст';
$kamatoc = new KamaWPKama_Contents( [
'to_menu' = 'до змісту ↑',
'title' = 'Зміст:',
'shortcode' = 'toc',
//'page_url' = get_permalink(),
]);
echo $kamatoc->apply_shortcode( $text1 );
// 2
// другий текст із шорткодом [list]
$text2 = 'текст [list] текст';
$kamatoc2 = new KamaWPKama_Contents( [
'to_menu' = 'до списку ↑',
'title' = 'Навігація:',
'shortcode' = 'list',
]);
echo $kamatoc2->apply_shortcode( $text2 );
Налаштування Змісту
Ви помітили закоментовані рядки в прикладах? Це налаштування. До екземпляра класу можна передавати аргументи (налаштування):
margin (рядок) Відступ зліва у підрозділів px|em|rem.
selectors (рядок) HTML теги яким будуватиметься зміст: 'h2 h3 h4'. Порядок визначає рівень вкладеності. Можна вказати рядок або масив: [ 'h2', 'h3', 'h4' ]або 'h2 h3 h4'. Можна вказати атрибут class: 'h2 .class_name'. Якщо потрібно, щоб різні теги були на одному рівні, вказуємо їх через |: 'h2|dt h3'або [ 'h2|dt', 'h3' ].
to_menu (рядок) Посилання на повернення до змісту. ''– Забрати посилання.
title (рядок) Заголовок. ''– Забрати заголовок.
js (рядок) JS код (додається після HTML коду)
min_found (int) Мінімальна кількість знайдених тегів, щоб вміст виводився.
min_length (int) Мінімальна довжина (символів) тексту, щоб вміст виводився.
page_url (рядок) Посилання на сторінку для якої збирається зміст. Якщо вміст виводиться на іншій сторінці…
shortcode (рядок) Назва шоткоду.
spec (рядок) Залишати символи в анкорах. Для прикладу: ‘.+$*= .
anchor_type (рядок) Який тип анкору використовувати: ‘a’ – <a name=”anchor”></a> або ‘id’.
anchor_attr_name (рядок) Назва атрибута тега зі значення якого буде братися анкор (якщо цей атрибут є у тега). Ставимо ”, щоб вимкнути таку перевірку…
markup (true|false) Включити мікророзмітку?
anchor_link (рядок) Додати ‘знак’ перед підзаголовком статті з посиланням на поточний анкор заголовка. Вкажіть ‘#’, ‘&’ або що вам подобається.
tomenu_simcount (int) Мінімальна кількість символів між заголовками змісту, для яких потрібно виводити посилання “до змісту”. Немає сенсу, якщо параметр ‘to_menu’ вимкнено. З метою продуктивності кирилиця вважається без урахування кодування. Тому 800 символів кирилиці – це приблизно 1600 символів у цьому параметрі. 800 – розрахунок для сайтів на кирилиці.
leave_tags (true|false|рядок) Чи потрібно залишати HTML теги в елементах змісту. З версії 4.3.4. Можна вказати тільки теги, які потрібно залишати. Пр: ‘<b><strong><var><code>’ .
HTML та CSS
Весь зміст йде суцільними <li> , а рівнів вказуються CSS класи і лівий відступ – margin. За допомогою класів можна налаштувати відображення як завгодно.
Ось так виглядає HTML код, який генерує Kama_Contents:
style=”margin-left:40px;” додається автоматично на основі налаштування $args[‘margin’] = 40; . Немає зв’язку між числами в заголовках (h1, h2), рівні виставляються залежно від порядку указного в налаштуванні: $args[‘def_tags’] = array(‘h2′,’h3′,’h4’); . Тобто. якщо змінити на array(‘h3′,’h2′,’h4’);, то h3 буде у змісті верхнім рівнем – це потрібно, щоб вказувати відмінні від h * strong , em .
Деревоподібна нумерація списку
Щоб зробити деревоподібну нумерацію списку, як на картинці, встановіть для списку такі стилі CSS:
В цілому, можна ставити цей код на будь-який сайт де підключений jQuery і буде плавна прокрутка до якір, а зокрема, він добре поєднується з “змістом” (див. приклад, тисніть там на посилання). А це і js код:
// document.ready
jQuery(function($){
// Прокрутка на всі якорі (анкори) (#) і a[name]. v1.3
$(document).on( 'click.smoothscroll', 'a[href*="#"]', function( e ){
let hash = this.hash
let _hash = hash.replace( /#/, '' )
let theHref = $(this).attr('href').replace( /#.*/, '' )
// У кнопки є атрибут onclick означає у неї інше завдання
if( this.onclick )
return
// не поточна сторінка
if( theHref && location.href.replace( /#.*/, '' ) !== theHref )
return
let $target = (_hash === '') ? $(document.body) : $( hash + ', a[name="'+ _hash +'"]').first()
if(! $target.length)
return
e.preventDefault()
let scrollTo = $target.offset().top - 50
$('html:first, body:first')
.stop()
.animate( { scrollTop: scrollTo }, 200, 'swing', function(){
window.history.replaceState( null, document.title, hash )
} )
})
})
«Приховати/показати» зміст (jQuery код)
Варіант 1 (“як-в-вікіпедії”):
/**
* Показати/приховати Зміст. Кнопка додається після Тексту в заголовок - "Зміст: [приховати]"
* v 0.3
*/
// document.ready
jQuery(function($){
let $title = $('.kc__title')
let showtxt = '[показати]'
let hidetxt = '[приховати]'
let $but = $('<span class="kc-show-hide" style="cursor:pointer;margin-left:.5em;font-size:80%;">'+ hidetxt +'</span> ')
$but.on( 'click', function(){
let $the = $(this)
let $cont = $the.parent().next('.contents')
if( $the.text() === hidetxt ){
$the.text( showtxt )
$cont.slideUp()
}
else {
$the.text( hidetxt )
$cont.slideDown()
}
})
$title.append( $but )
});
Варіант 2 (заголовок-кнопка):
/**
* Показати/приховати Зміст. Заголовок є кнопкою і до нього приписується текст - "Зміст ▴"
* v 1.0
*/
jQuery(document).ready(function($){
let $title = $('.kc__title').css({ cursor:'pointer' })
let showico = '▾'
let hideico = '▴'
let collapsedKey = 'contents_collapse'
let setIco = function( $that, type ){
$that.text( type === 'hide' ? $that.text().replace( showico, hideico ) : $that.text().replace( hideico, showico ) )
}
$title.each(function(){
let $the = $(this);
$the.text( $the.text().replace(':','').trim() + hideico )
$the.on( 'click', function(){
let $cont = $the.next('.contents')
if( $cont.is(':visible') ){
$cont.slideUp(function(){
$the.addClass('collapsed')
setIco( $the, 'show' )
window.localStorage.setItem( collapsedKey, '1' )
})
}
else {
$cont.slideDown(function(){
$the.removeClass('collapsed')
setIco( $the, 'hide' )
window.localStorage.removeItem( collapsedKey )
})
}
})
// згорнемо/розгорнемо на основі куків
if( window.localStorage.getItem(collapsedKey) === '1' ){
setIco( $the, 'show' )
$the.next('.contents').hide()
}
})
});
Коди слід додати до наявних js скриптів. Код повинен спрацьовувати після підключення бібліотеки jQuery.
Плагіни для створення змісту
Є чимало причин використовувати готові плагіни, навіть для тих, хто може використовувати матеріал з цієї статті. Тому що це зручно! Ось плагіни для створення такого ж змісту:
Easy Table of Contents – зручний і функціональний плагін, який дозволяє вам вставляти зміст у ваші пости, сторінки та типи постів користувача.
Table of Contents Plus – дуже гнучко налаштовані плагін змісту в статтях. Також є кнопка у віз. редакторі.
LuckyWP Table of Contents – генерує зміст для записів, сторінок та довільних типів постів. Безліч налаштувань, Gutenbeg блок, кнопка в класичному редакторі. Підтримує як ручне, і автоматичне додавання змісту в пости.
Опитування: Що додати до скрипту “Зміст для великих постів”?
Що було зроблено завдяки опитуванню: – Додати Schema розмітку та оптимізувати код для пошукових систем (30 голосів)
Для улучшения SEO, Как добавить содержание после первого абзаца автоматически? * 47%, 142 голоса
142 голоса 47%
142 голоса – 47%
Ничего, все и так хорошо! 12%, 37 голосов
37 голосов 12%
37 голосов – 12%
Возможность исключить некоторые пункты из содержания * 10%, 31 голос
31 голос 10%
31 голос – 10%
Не работает с многостраничными статьями (nextpage) * 5%, 16 голосов
16 голосов 5%
16 голосов – 5%
Добавить в оглавление прокрутку до любого пункта на странице (похожие записи, начало комментариев) * 5%, 14 голосов
14 голосов 5%
14 голосов – 5%
При скрытии содержания, сохранить эти данные в куках, чтобы при переходе на следующие страницы, содержимое оставалось скрытым или открытым * 4%, 12 голосов
12 голосов 4%
12 голосов – 4%
Оглавление для текстов, добавляемых в категорию. В т.ч. и при помощи произвольных полей * 4%, 12 голосов
12 голосов 4%
12 голосов – 4%
[готово] Возможность указывать свой анкор… 3%, 9 голосов
9 голосов 3%
9 голосов – 3%
Возможность внедрить на страницы /amp/ * 2%, 7 голосов
7 голосов 2%
7 голосов – 2%
Запаковать в плагин с настройками и добавить в статью для скачивания * 2%, 7 голосов
7 голосов 2%
7 голосов – 2%
Возможность автоматически проставлять якори «к содержанию» перед каждым под-заголовком * 2%, 5 голосов
5 голосов 2%
5 голосов – 2%
Возможность сделать содержание “Fixed”, как например в этом плагине https://codecanyon.net/item/fixed-toc-wordpress-plugin/7264676 * 2%, 5 голосов
5 голосов 2%
5 голосов – 2%
Возможность показа содержания на турбо-страницах Яндекса * 1%, 4 голоса
Замовити за дуже недорогою ціною коментарі в Інстаграмі Ви можете на сайті Avi1.ru. При цьому Вам не доведеться витрачати свій час на пошук справді надійного сервісу. Тут Ви знайдете все, що потрібно: якісні послуги, приємні ціни, гарантії та ввічливе обслуговування.