Building a “Lightweight Knowledge Index” for the Blog (Part 9): Site-Wide Content Navigation Based on Series Data
Article Abstract
Addressing the issue of the lack of series article navigation in the global cognitive layer of blog content, this article designs a homepage right-side series menu as a supplement to the site entry point layer. Through dual JSON (series-meta.json and series-map.json), it decouples series configuration from content structure to build a unified data source. The system adopts a three-tier architecture consisting of offline generation of series structure, PHP dynamic loading and merging of data, and lightweight frontend interaction. This implements stable display of the homepage series entry point under the WordPress PJAX environment, allowing users to perceive the blog's knowledge system without entering specific articles, thereby improving the discovery efficiency of series content and the integrity of the reading path.
Qwen3-14B · 2026-07-31

1 From In-Article Association to Site Entry Point: The First Upgrade of the Series Reading Structure

In previous articles (see:Building a “Lightweight Knowledge Index” for My Blog (Part 4): Series Article Navigation and Reading Path Design), to solve the problem of discontinuous horizontal reading paths between articles of the same series in the blog content, I introduced the “Series Article Card” feature at the bottom of the article body.

The main role of this component is to explicitly present the structural position of the article within its series in the context of a single article, including the current index, the total number of series articles, and the jump relationships between preceding and succeeding articles, thereby helping readers establish “local continuity” during the reading process. The actual effect is as follows:

image.png

From the user experience perspective, this solution is effective in the “single article reading scenario,” solving a very clear problem:How to prevent readers from losing the series context structure when reading a specific article。

But as the scale of content grows, I have gradually discovered a more fundamental problem: for some visitors, their access path is not “article → series,” but rather:

  • Entering a single article from search engines

  • Entering a specific piece of content from external links

  • Or just browsing the homepage by chance

In these scenarios, the series card is actually a “post-capability”—it relies on the user already entering a specific article to be perceived. This leads to a structural gap: the series article system is visible at the “local reading layer” but missing at the “global cognitive layer.”

In other words, for users who have not yet formed a cognitive awareness of the series, they do not know that the blog has a “series” organization method, nor can they actively choose to enter a certain knowledge path. Therefore, having only the “in-article series card” is not enough.

Based on this issue, I believe the system still lacks a key entry point: a series navigation layer facing the “site entry point layer,” used to provide overall visibility and rapid accessibility of the series structure when users enter the initial page of the website (mainly the homepage).

In terms of concrete implementation, this entry point is more suitable to be placed in the blank area in the middle of the right side of the homepage. This area belongs to a low information density zone in the current layout, possessing good spatial conditions for “bearing auxiliary navigation components” without interfering with the reading path of the main content stream:

image.png

The goal of this entry point is not to replace the series cards within articles, but to serve as a higher-level structural supplement:

  • Providing an aggregated entry point for all series on the homepage
  • Allowing users to perceive the entire knowledge structure before entering a specific article
  • And supporting quick entry into the reading path of any series

From the perspective of system design, this is actually completing an “upper-level index layer of the knowledge structure.”

2 Design

1 Design Ideas for the Homepage Right-Side Series Article Menu

After determining the need to add a homepage series entry point on the homepage, what really needs to be considered is:In what form this entry point should be presented.

One of the simplest ways is to directly place a ”Series Articles” button on the right side, which jumps to a dedicated series page when clicked. This solution has the lowest implementation cost and can meet functional requirements. However, it is more like a regular navigation link; besides telling users ”there is a series page here,” it does not allow users to directly learn what series the blog currently has on the homepage, nor does it help users quickly judge which content is worth further reading.

Another solution is to directly display the complete article list of all series on the homepage. This approach provides the most complete information, but as the number of series continues to increase and each series contains multiple articles, the entire component can easily become too bulky, occupying a large amount of homepage space and disrupting the original browsing rhythm focused on the latest articles.

Therefore, I ultimately chose a compromise solution between the two:The homepage only displays the series itself, without directly expanding the series content. For each series, only a few of the most core pieces of information are retained: the series name; the number of articles contained in the series; and the entry point to enter that series.

This has several benefits. First, it allows visitors to quickly browse the entire knowledge system currently established by the blog in a few seconds, without needing to read a large number of article titles. Second, since the homepage bears the responsibility of ”discovering content” rather than ”reading content,” the homepage menu is more suitable as a lightweight index, while the actual article organization and reading paths are left to the subsequent series pages.

In other words, the homepage menu solves ”what to discover,” while the series page solves ”how to read.” From the perspective of system design, the two correspond to different responsibilities: the former belongs to the entry point layer of the knowledge structure, and the latter belongs to the display layer of the knowledge structure.

Therefore, the homepage series menu ultimately adopts an ”overview first, deep dive on demand” design concept—first helping users discover series of interest, and then entering the corresponding page to complete subsequent reading, rather than trying to carry the entire series content on the homepage.

2 Data Structure and System Input: The Foundation of Series Organization Composed of Dual JSON

After determining the overall design of the homepage series menu, the next problem to be solved is:How to organize these series data.

A relatively direct implementation method is to let WordPress dynamically query all series and their contained articles when the page loads, and then have PHP generate the menu content based on the query results. However, this solution means that every visit to the homepage requires reorganizing the series structure, and as the number of series continues to increase, runtime computation will also become increasingly complex.

Since the entire blog's ”lightweight knowledge index” series has always followed the design philosophy of ”completing computation during the build phase, and only being responsible for display during the run phase,” the same strategy is still adopted here:Pre-building the series structure into static JSON, to be directly consumed by the frontend and PHP at runtime.

Overall, this system mainly relies on two core JSON files, which assume different responsibilities and together constitute the entire series organization system.

series-meta.json

First is the series metadata layer, corresponding to series-meta.jsonThis layer is responsible for describing ”what the series itself is”, for example:

{
  "cloudflare": {
    "title": "Cloudflare 教程",
    "order": 1,
    "description": "Cloudflare 系列文章",
    "hide": false
  }
}

As you can see, this layer does not care about which posts are in the series, but only describes the attributes of the series itself, such as: series name; display order; series description; whether to display on the front end.

Therefore, from a system perspective,series-meta.json it is more like the ”configuration layer” of the entire series system. It defines: how a series should be presented, rather than: what content a series contains.

The benefit of this is that the display configuration of the series is completely decoupled from the post organization. Even if you adjust the sorting, modify the title, or temporarily hide a series in the future, you do not need to reorganize the post structure.

series-map.json

The second layer is the series content mapping layer, corresponding to series-map.jsonThis layer is responsible for describing: which posts each series contains, with a typical structure as follows:

{
  "cloudflare": {
    "count": 10,
    "posts": [
        {
            "index": 1,
            "title": "Cloudflare Tunnel 基础介绍",
            "url": "/technology/xxxx/"
        }
    ]
}

Compared to series-meta.json, this layer begins to truly enter content organization. Each series will store: the total number of series posts;
the order of each post in the series; title; URL.

As you can see, what is stored here is entirely pre-organized static structure, rather than querying the WordPress database at runtime.

Therefore, whether it is the homepage series menu, the series page (or even the previous ”series post card”), they can directly consume this structured data without needing to maintain their own independent logic.

In other words,series-map.json It actually becomes the single source of truth (Single Source of Truth) for the entire series system.


Note: Why split into two JSONs instead of one?

From the perspective of data scale, putting all information into a single JSON is of course feasible. For example, storing all fields like title, description, and posts under each series. However, from an engineering design perspective, this approach would couple ”configuration” and ”content” together.

The reason for adopting dual JSONs currently is essentially the separation of concerns. Among them:series-meta.json is responsible for describing the series,series-map.json is responsible for describing the posts.

Although both will eventually be merged and used by PHP, they can be maintained separately during the generation phase. For example: modifying the series title, adjusting the sorting, or temporarily hiding a series will only affect series-meta.jsonWhile adding new posts or adjusting the order of posts will only affect series-map.json。

This split not only reduces data maintenance costs but also makes the entire series system easier to extend.


3 Overall System Architecture and Engineering Value

From the overall structure, these two JSONs do not exist independently of each other, but are built on the existing data foundation of the entire ”lightweight knowledge index” system, together forming the data organization structure of the series post system:

WordPress 文章
        │
        ▼
article-index.json
        │
        ├───────────────┐
        ▼               ▼
series-meta.json   series-map.json
        │               │
        └──────┬────────┘
               ▼
        系列文章系统
               │
      ┌────────┴────────┐
      ▼                 ▼
  首页系列菜单       /series 页面
                           │
                           ▼
                     系列文章卡片

From the perspective of data flow, the entire series post system is not built directly on top of the original WordPress content, but continues the layered organization method consistently adopted by the entire ”lightweight knowledge index” series.

First, article-index.json provides the unified basic post data for the entire blog. On this basis, the series-related data structures are further organized: among them,series-meta.json is responsible for describing the meta-information of the series itself, such as configurations like series name, display order, description, and whether to display; while series-map.json is responsible for describing which posts each series contains, as well as the arrangement relationship of these posts within the series.

In this way, a clear separation of concerns is formed between the configuration of the series itself and the series content: the former defines ”what the series is”, and the latter defines ”what the series contains”. Although both will ultimately serve the series post system together, their respective responsibilities are completely different, and they can be maintained and extended separately.

Furthermore, these two JSONs do not just serve a specific function, but together constitute the unified data source for the entire series post system. Whether it is the homepage series menu, the series page, or the series post cards implemented in the previous article, they are all built on top of this unified data structure. Therefore, when the series structure changes, you only need to update these two JSONs, and all related functions can automatically obtain consistent data without needing to maintain their respective data logic separately.

From an engineering perspective, this design continues a core idea that the entire ”lightweight knowledge index” series has always adhered to:completely separating content organization from page display.

In this architecture, each layer of the data structure only assumes a single responsibility:

  • article-index.json: provides unified basic post data;
  • series-meta.json: describes the meta-information of the series;
  • series-map.json: organizes the content structure of the series.

Ultimately, different pages only need to consume this pre-organized data according to their own needs, without needing to recalculate series relationships or reorganize post structures.

Therefore, the value of this architecture lies not only in adding a new homepage series menu, but in establishing a reusable series organization foundation throughout the entire blog. As series-related functions continue to increase, this data structure can still continue to serve as a unified data source, providing consistent and stable series information for different pages.

3 Implementation

1 Overall Process of Engineering Implementation

According to the design ideas in Chapter 2, corresponding to the actual engineering implementation, this homepage series post system can also be split into three clear steps, each with its corresponding output and execution location.

The first step is to generate the series organization data files series-meta.json 与 series-map.jsonThis part is handled by the script build_series_map.py is responsible for completion, and its core role is to, on the basis of the existing article-index.json , organize the organizational relationships of all series in the blog, and generate series metadata and series content mapping respectively. The former is responsible for describing the attributes of the series itself, while the latter is responsible for describing the articles contained in the series and their order. These two JSON files together constitute the data foundation of the entire series system.

The second step is to establish a unified data reading entry point in the WordPress backend. When the system runs, it first reads series-meta.json 与 series-map.json, and completes basic processing such as data merging, sorting, and filtering, ultimately generating a unified series data structure. Subsequently, whether it is the homepage series menu or the series page, they all directly use this unified data source, rather than separately maintaining their own data logic.

The third step is to complete the corresponding display logic on different pages. When the system runs, it first uses the unified data reading entry point series_load_data() to load series-meta.json 与 series-map.json, completing basic processing such as data merging, sorting, and filtering, and generating a unified series data structure. Subsequently, different pages consume this data according to their respective needs: the homepage is responsible for displaying the aggregated entry point for all series, guiding visitors to discover the series knowledge structure of the entire blog; the series page is responsible for displaying the complete list of articles under a specific series; the series article card implemented in the previous article is responsible for providing internal reading navigation during the article reading process. Although the display formats of these three functions are different, they are all built on the same series data structure, so they can always maintain a consistent organizational relationship.

series-meta.json      series-map.json
        │                   │
        └────────┬──────────┘
                 ▼
         series_load_data()
                 │
     ┌───────────┼────────────┐
     ▼           ▼            ▼
  首页系列菜单   /series 页面   系列文章卡片

The biggest advantage of this split method is that it completely decouplesdata organization, data consumption, and page displayinto three stages. The series relationships only need to be organized once during the build phase, while the runtime phase always revolves around the unified data structure. Different pages are only responsible for displaying data in their own ways, without needing to repeatedly organize series content or recalculate relationships between articles.

2 Series Structure Generation: build_series_map.py

The code for the build_series_map.py script is as follows:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import json
import os
import re
import sys

# ======================================================
# Config
# ======================================================

CACHE_DIR = "/docker/wordpress/html/wp-content/themes/argon-theme-master/cache"
ARTICLE_INDEX = os.path.join(CACHE_DIR, "article-index.json")
SERIES_MAP = os.path.join(CACHE_DIR, "series-map.json")


# ======================================================
# Load article index
# ======================================================

def load_article_index():
    if not os.path.exists(ARTICLE_INDEX):
        raise FileNotFoundError(f"Input file not found:\n{ARTICLE_INDEX}")
    with open(ARTICLE_INDEX, "r", encoding="utf-8") as f:
        return json.load(f)


# ======================================================
# Extract series info(强约束规则)
# ======================================================

def extract_series_info(title):
    title = title.replace(" ", " ").strip()

    m = re.search(r"[((]([一二三四五六七八九十])[))]", title)
    if not m:
        return None

    cn_map = {
        "一": 1, "二": 2, "三": 3, "四": 4, "五": 5,
        "六": 6, "七": 7, "八": 8, "九": 9, "十": 10
    }

    index = cn_map.get(m.group(1), 0)
    if index <= 0:
        return None

    series_key = re.split(r"[((]", title)[0].strip()

    return {
        "series_key": series_key,
        "series_index": index
    }


# ======================================================
# Build series map
# ======================================================

def build_series_map(article_index):
    series = {}

    for post_id, article in article_index.items():
        title = article.get("title")
        url = article.get("url")
        if not title or not url:
            continue

        info = extract_series_info(title)
        if not info:
            continue

        key = info["series_key"]

        if key not in series:
            series[key] = {
                "series_key": key,
                "count": 0,
                "posts": []
            }

        series[key]["posts"].append({
            "id": int(post_id),
            "index": info["series_index"],
            "title": title,
            "url": url
        })

    for item in series.values():
        item["posts"].sort(key=lambda x: x["index"])
        item["count"] = len(item["posts"])

    return dict(sorted(series.items(), key=lambda x: x[0]))


# ======================================================
# Save
# ======================================================

def save_series_map(series_map):
    with open(SERIES_MAP, "w", encoding="utf-8") as f:
        json.dump(series_map, f, ensure_ascii=False, indent=2)


# ======================================================
# Main
# ======================================================

def main():
    print("=" * 50)
    print("Build Series Map")
    print("=" * 50)

    try:
        articles = load_article_index()
        series_map = build_series_map(articles)
        save_series_map(series_map)

        total_series = len(series_map)
        total_articles = sum(item["count"] for item in series_map.values())

        print()
        print(f"Input  : {ARTICLE_INDEX}")
        print(f"Output : {SERIES_MAP}")
        print()
        print(f"Series count    : {total_series}")
        print(f"Series articles : {total_articles}")
        print()
        print("Status : Success")

    except Exception as e:
        print()
        print("Status : Failed")
        print(e)
        sys.exit(1)


if __name__ == "__main__":
    main()

Functionally speaking, this script mainly accomplishes three things:Read article index data, identify the series to which the article belongs, generate the series article structure, and output JSON files。

The most critical part is not the parsing logic itself, but the entire “series structure extraction process.” At this stage, based on the data generated in the previous stage, the system article-index.json, by parsing the sequence number information in the article titles, organizes the originally scattered articles into structured series data.

The final output series-map.json, and each series will contain the following core fields:

  • series_key: Unique identifier of the series
  • count: Number of articles in the series
  • posts: List of articles in the series (including index / title / url)

From a system perspective, the significance of this step is:To convert the implicit “series relationships” originally scattered at the article level into an explicit structured data modelSubsequently, the homepage series menu, series pages, and series article cards are all displayed directly based on this structure.

3 WordPress Homepage Series Menu Implementation: Frontend Rendering and Structure Consumption Layer

3.1 Frontend Architecture Split: Division of Responsibilities for PHP / JS / CSS

This part is the final presentation layer of the “series article system” in WordPress, which is the series entry menu in the middle right of the homepage.

Unlike the “data generation layer (build phase)” in the previous chapters, the focus of this part has shifted from “how to build the series structure” to “how to consume series data in WordPress and complete the display.”

From the perspective of implementation structure, this module is also split into three layers: PHP (data loading and structure organization layer), JavaScript (interactive control layer), and CSS (visual presentation layer).

The essence of this split is not frontend architectural design, but rather the natural constraint result of WordPress under the existing rendering model: PHP is responsible for deterministic data output, JS is responsible for lightweight interaction states, and CSS is responsible for visual expression.


PHP Part

In this structure, PHP is the core of the entire system: PHP is responsible for calling the series_load_data() method to read the pre-generatedseries-meta.json和series-map.jsonfrom the cache, and merging the two into a unified data structure. On this basis, PHP will complete three key tasks: filtering invisible series (hide field); sorting by the order field; and generating the lightweight structured data required for the homepage menu.

What is ultimately output to the frontend is a structured array, rather than query results or dynamic calculation logic. It can be understood as:
PHP here acts as the “consumption and organization layer of the series structure,” rather than the calculation layer.

JavaScript Part

The JavaScript in this article is only responsible for the most lightweight interaction control, and its core responsibilities are: hover / click to expand menus; control submenu display states; and handle simple DOM switching.

All series data has already been injected into the page by PHP, so JS no longer participates in any data construction or sorting logic, and is only responsible for UI state changes.

CSS Part

The CSS part is entirely responsible for visual expression, including: the layout of the floating area in the middle right of the homepage; hover expansion animations; category hierarchy indentation; and font size and spacing control.

The design goal of this layer is to maintain a “low presence,” providing a quick entry point without interfering with the main content of the homepage.


Through this three-layer split, the entire homepage series menu system forms a very clear structure:

  • PHP: Structured data output (series_load_data)

  • JS: Interaction state control

  • CSS: Visual expression

From a system perspective, the characteristics of this module are: Completely relies on offline build data, introducing no complex runtime calculations.

3.2 WordPress-Side Data Loading and Menu Generation (PHP)

This part is the presentation implementation layer of the series article system in WordPress. Unlike traditional WordPress pages that directly save content, this feature is implemented using a “blank page as an entry point, with PHP dynamically generating content” approach.

First, you need to create a blank page in WordPress and set a fixed URL (/series/) as the entry point for displaying series articles. The page itself contains no actual content; PHP will take over the content generation logic via the page lifecycle provided by WordPress when a request matches this URL.

Specifically, the system uses the_content filters to determine whether the current page is /series/, and if it matches, calls the series data loading function series_load_data()to read the data generated during the previous build phase from the cache directory series-map.json 和 series-meta.json。

The responsibility of this layer is not to generate series relationships, but to organize and transform existing series data: the system merges information from two JSON files, filters out hidden series, and generates a unified series list structure according to the sorting rules in the configuration. It then decides whether to display all series entry points or the article list under a specific series based on the current URL parameters.

In other words, this layer handles the conversion process from “structured data to WordPress page presentation.”series-map.json provides the actual association between articles and series,series-meta.json provides presentation information such as series titles, descriptions, and sorting. The combination of both forms /series/ the data foundation required for the page and the homepage series menu.

Overall, this PHP code mainly accomplishes three things: data loading and merging, series structure normalization, and output adaptation for the homepage and series pages.

During the data preparation phase, the system reads from the cache directory series-map.json 与 series-meta.json. The former provides the series article list and count information, while the latter provides the series titles, sorting weights, and hidden configurations. It then uses series_key as the index to merge them, and filters out hide marked series.

On this basis, each series is standardized into a uniform structure, including fields such as title, description, sorting weight, and article list, and is sorted overall via the order field, thereby ensuring that the front-end display order is consistent with the configuration.

在 /series page, the system renders based on the series_load_data() output structure, and decides whether the current mode is “all-series mode” or “single-series mode” based on the URL parameters. In all-series mode, it displays all series lists along with their article counts and descriptions; in single-series mode, it only displays the article list of the current series, maintaining a strict sequential structure.

In the homepage section, the system uses render_series_menu() to generate a lightweight series entry menu, displaying only the series titles and article counts, and providing an entry link to jump to /series/?series=xxx to quickly access the corresponding series content.

At the WordPress integration level, the system uses the_content filters to inject /series page content, and uses wp_footer to mount the series menu at the bottom of the homepage, thereby achieving a dual presentation structure at both the page and entry levels.

From the perspective of implementation positioning, this PHP layer does not participate in any series generation logic, but is merely a standard structure consumption layer: it is responsible for converting the series data generated during the build phase into a presentation structure that can be directly used by WordPress pages and homepage entries.

The complete PHP code is as follows:

// ======================================================
// ① Load & merge data
// ======================================================
function series_load_data(){
    cache_dir=get_template_directory().'/cache';series_map_file=cache_dir.'/series-map.json';series_meta_file=cache_dir.'/series-meta.json';
    if(!file_exists(series_map_file)||!file_exists(series_meta_file)){return[];}series_map=json_decode(file_get_contents(series_map_file),true);series_meta=json_decode(file_get_contents(series_meta_file),true);
    if(!series_map||!series_meta){return[];}series_list=[];
    foreach(series_map askey=>data){
        if(!isset(series_meta[key]))continue;meta=series_meta[key];
        if(!empty(meta['hide']))continue;series_list[]=[
            'key'=>key,
            'title'=>meta['title']??key,
            'order'=>meta['order']??999,
            'description'=>meta['description']??'',
            'posts'=>data['posts']??[],
            'count'=>data['count']??0
        ];
    }
    usort(series_list,function(a,b){return a['order']<=>b['order'];});
    return series_list;
}

// ======================================================
// ② Render page (/series)
// ======================================================
function render_series_page(){series_list=series_load_data();
    if(empty(series_list)){echo'<p>Series data not found.</p>';return;}active_series=isset(_GET['series'])?sanitize_text_field(_GET['series']):null;
    ob_start();
    echo'<div class="series-page">';

    // =========================
    // HEADER
    // =========================
    if(active_series){current=null;
        foreach(series_list asseries){
            if(series['key']===active_series){
                current=series;
                break;
            }
        }

        if(current){
            echo'<div class="series-header">';
            echo'<h1>📚 '.esc_html(current['title']).'</h1>';

            if(!empty(current['description'])){
                echo'<div class="series-desc">';
                echo esc_html(current['description']);
                echo'</div>';
            }

            echo'<p>共 '.intval(current['count']).' 篇文章</p>';
            echo'<p><a href="/series/">← 返回所有系列</a></p>';
            echo'</div>';
        }

    }else{
        echo'<div class="series-header">';
        echo'<h1>📚 系列文章</h1>';
        echo'<p>共 '.count(series_list).' 个系列</p>';
        echo'</div>';
    }

    // =========================
    // LIST
    // =========================
    echo'<div class="series-list">';

    foreach(series_list asseries){

        if(active_series &&series['key']!==active_series){
            continue;
        }

        // ❗关键修改:单系列模式不再重复展示标题/描述
        if(!active_series){
            echo'<div class="series-item">';

            echo'<div class="series-title">';
            echo'<strong>'.esc_html(series['title']).'</strong>';
            echo'<span class="series-count">('.intval(series['count']).')</span>';
            echo'</div>';

            if(!empty(series['description'])){
                echo'<div class="series-desc">'.esc_html(series['description']).'</div>';
            }

        }else{
            // 单系列模式只保留容器,不重复标题信息
            echo'<div class="series-item single-series">';
        }

        if(!empty(series['posts'])){

            echo'<div class="series-posts">';

            foreach(series['posts'] as post){
                echo'<div class="series-post-item">';
                echo'<a href="'.esc_url(post['url']).'">';
                echo'第 '.intval(post['index']).' 篇:'.esc_html(post['title']);
                echo'</a>';
                echo'</div>';
            }

            echo'</div>';
        }

        echo'</div>';
    }

    echo'</div>';
    echo'</div>';

    echo ob_get_clean();
}

// ======================================================
// ③ Render menu
// ======================================================
function render_series_menu(){
    series_list=series_load_data();
    if(empty(series_list))return;
    echo'<div id="series-menu"><div class="series-menu-title">📚 浏览文章系列</div><div class="series-menu-list">';
    foreach(series_list asseries){
        echo'<div class="series-menu-item"><div class="series-menu-header"><a class="series-menu-link" href="/series/?series='.esc_attr(series['key']).'"><span class="series-name">'.esc_html(series['title']).'</span></a><span class="series-count">('.intval(series['count']).')</span></div></div>';
    }
    echo'</div><div class="series-menu-footer"><a href="/series/">浏览所有系列 →</a></div></div>';
}

// ======================================================
// ④ /series page hook
// ======================================================
add_filter('the_content',function(content){
    if(!is_page('series'))return content;
    ob_start();
    render_series_page();
    returncontent.ob_get_clean();
},20);

// ======================================================
// ⑤ HOME ONLY menu + PJAX safe guard
// ======================================================
add_action('wp_footer', function () {
    if (!is_front_page()) return;
    echo '<div class="series-menu-wrapper">';
    render_series_menu();
    echo '</div>';
});

3.3 Implementation of Homepage Series Menu Interaction Logic (sidebar.js Extension Logic)

This part is the front-end runtime control logic for the homepage “series article menu.” It is not an independent module, but is integrated as an extended capability in sidebar.js to control the state of the homepage entry components under a unified front-end initialization system.

From the overall implementation perspective, the responsibility of this logic is very straightforward:Control the display and hiding of the series menu on the homepage based on the current page routing state, and ensure state consistency during PJAX and browser history transitions.

Unlike the semantic menu on the right, this part does not involve any data rendering or structure generation; it is essentially a “page-level visibility controller.”

Homepage visibility control logic

The system uses the syncSeriesMenu() function to determine if it is the homepage, with the core basis being the current URL path:

  • When the path is the site root path (homepage), it displays .series-menu-wrapper
  • When the path is another page, hide this component

This logic ensures that the series menu only exists as the “homepage entry layer” and does not enter the article reading scenario, thereby avoiding information hierarchy pollution.

Unification with the sidebar.js lifecycle

Since this logic is integrated into sidebar.js , it must remain consistent with the existing initialization system rather than running independently.

The system mounts syncSeriesMenu() to the same lifecycle chain as the right menu:

  • First load: Initialized and executed after DOM ready
  • PJAX switch: via window.pjaxLoaded resynchronizing state
  • Browser forward/back: via pageshow 与 popstate correcting state

The essence of this design is to incorporate “page-level UI state” into unified lifecycle management, avoiding DOM state inconsistency caused by PJAX partial replacement.

Relationship with the right menu system

Although this part shares the same JS file with the right semantic menu, the two are completely isolated in terms of responsibilities:

  • Right menu: Content recommendation system based on semantic data
  • Homepage series menu: Entry layer control system based on routing state

Their only intersection is that both rely on sidebar.js 's unified initialization lifecycle (pjaxLoaded + init)

Therefore, it can be understood as:sidebar.js simultaneously undertakes two different levels of control logic, “semantic recommendation UI” and “site structure entry UI”, but the two are completely decoupled in data and responsibilities.

series-menu control code (excerpt)

function syncSeriesMenu() {
    const menu = document.querySelector(".series-menu-wrapper");
    if (!menu) return;

    const path = location.pathname.replace(/\/+$/, "");
    const isHome = (path === "");

    menu.style.display = isHome ? "" : "none";
}

Lifecycle mounting (consistent with the existing system)

window.pjaxLoaded = function () {
    initialized = false;
    init(); // sidebar 主系统初始化
    syncSeriesMenu(); // 新增:首页系列菜单状态同步
};

window.addEventListener("pageshow", syncSeriesMenu);
window.addEventListener("popstate", syncSeriesMenu);

The core value of this part lies not in its logical complexity, but in that it completes a key entry layer in the entire series structure: making the “article series system” no longer exist only within articles or category structures, but forming a stable entry node at the homepage level.

Meanwhile, by reusing the lifecycle system of sidebar.js, it achieves a state synchronization mechanism consistent with the semantic menu, thereby ensuring that the entire site still maintains a unified interaction behavior model under the PJAX environment.


Explanation of execution constraints under the PJAX (Argon theme) environment

It needs to be additionally explained that in the Argon theme, since the PJAX (PushState + AJAX) no-refresh loading mechanism is enabled, page switching does not trigger a full browser reload process, so the traditional DOMContentLoaded only takes effect when entering the site for the first time.

Under this mechanism, the official provides window.pjaxLoaded as a unified page switch completion hook, used to re-execute frontend initialization logic after each article switch is completed.

Therefore, if you need to add custom JavaScript functionality in the Argon theme and want it to “remain continuously effective when switching between different articles”, you must mount the initialization logic to window.pjaxLoaded , rather than relying solely on the first-load event.

Otherwise, typical problems will occur:

  • The function works normally when entering the page for the first time
  • After switching articles via PJAX, the JS logic is no longer executed
  • DOM updates after event binding fails or state is lost

The syncSeriesMenu() in this article also follows this mechanism, by mounting to window.pjaxLoaded, ensuring that state synchronization can be re-executed after each page switch is completed, thereby guaranteeing that the homepage series menu always maintains consistent behavior under different access paths.

From a practical perspective, in PJAX themes like Argon,pjaxLoaded is actually equivalent to the “page lifecycle entry of the frontend application.” All custom scripts that rely on DOM state should default to using this hook as the reconstruction trigger point.


3.4 Homepage Series Menu Styling and Visual Presentation (CSS)

This part is the visual presentation layer of the homepage “article series menu” in WordPress pages, and the corresponding implementation is the CSS style file. Its responsibility does not involve any interaction logic or data processing, but rather converts the structured series data output by PHP into a visual list with hierarchical relationships, thereby completing the final presentation of the homepage entry layer.

From the overall structure, this part of the style mainly revolves around two levels:Homepage Series Menu Wrapper and Series Item Card. The former is responsible for controlling the component's position and floating behavior on the page, while the latter carries the specific series information and article list.


Overall Layout and Floating Structure

.series-menu-wrapper As the outer container of the entire component, it is fixed in the middle of the right side of the page, and through position: fixed 与 transform: translateY(-50%) achieves vertical centering. This layout ensures that no matter where the page is scrolled, the entry remains visible at all times without interfering with the main reading area.

The container itself is set with a narrow width and a high z-index, keeping its visual positioning as an “edge entry” rather than part of the main content, thereby reinforcing its role as a “navigation entry layer.”

Main Container and Expansion Mechanism

#series-menu serves as the actual content carrier layer. Its default state is a collapsed structure, showing only the title area. When a user hovers, it triggers the display of .series-menu-list 与 .series-menu-footer via CSS selectors, achieving a lightweight expansion effect.

The core characteristics of this implementation are: an extremely collapsed default state (title only); the full list is exposed only on hover; and no JS is required to control the expansion. From an interaction model perspective, this is a typical “pure CSS-driven light interaction menu” designed to reduce the implementation complexity of the homepage entry layer.

Series Card Structure

Each series is carried by .series-item , which internally adopts a three-stage structure of “Title + Description + Article List”.

Where:

  • .series-title is used to display the series name and the number of articles
  • .series-desc is used to provide semantic descriptions of the series (weak information layer)
  • .series-posts is used to display the list of articles under that series

The article list uses a left border line for visual grouping, creating a clear hierarchical distinction from the title area in terms of spatial structure, thereby enhancing the “series → sub-article” belonging relationship.

Article List and Information Density Control

.series-post-item is responsible for displaying specific article items. Its links use a concise single-line text structure and provide basic feedback via hover states. The overall design goal is to control information density, allowing the homepage entry layer to accommodate multiple series in a limited space without appearing crowded.

For long titles, readability in narrow layouts is ensured through word-break 与 overflow-wrap to prevent horizontal overflow from affecting the stability of the overall layout.

Visual Consistency and Dark Mode

The style system additionally provides adaptation rules under html.darkmode to ensure that hierarchical clarity is maintained even in dark mode.

The main adjustments include: transitioning the background color from white to a dark palette; reducing the contrast of borders and dividers; improving link color readability; and using a semi-transparent background for the description area to maintain a sense of hierarchy.

The design goal of this layer is not to “redraw the UI,” but to ensure that the readability of the information structure remains consistent across different theme modes.


Overall, the core of this CSS section is not complex visual effects, but a clear structural constraint: stably expressing the hierarchical relationship of “series collection → series card → article list” within a limited space, while keeping the homepage entry lightweight and non-intrusive.

Unlike the semantic menu on the right side of previous articles, this component leans more toward a “static information entry.” Therefore, it adopts a lighter interaction model overall (hover expansion + stateless switching) to reduce system complexity.

The CSS code is as follows (for the ”Additional CSS” section of the current WordPress theme):

/* =========================
   Series Page Layout
========================= */

.series-page {
    max-width: 1000px;
    margin: 40px auto;
    padding: 0 20px;
}

/* header */
.series-header {
    margin-bottom: 30px;
}

.series-header h1 {
    font-size: 26px;
    margin: 0;
}

.series-header p {
    color: #666;
    margin-top: 6px;
}

/* =========================
   Series Item Card
========================= */

.series-item {
    padding: 18px 20px;
    margin-bottom: 24px;
    border: 1px solid #eaeaea;
    border-radius: 10px;
    background: #fff;
}

/* series title row */
.series-title {
    font-size: 18px;
    font-weight: 600;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 8px;
}

/* series count */
.series-count {
    font-size: 14px;
    color: #888;
    font-weight: normal;
}

/* =========================
   Description
========================= */

.series-desc {
    margin: 14px 0 18px;
    padding: 10px 14px;
    border-left: 4px solid #4f8ef7;
    background: #f8f9fa;
    border-radius: 0 8px 8px 0;
    font-size: 14px;
    color: #666;
    line-height: 1.75;
}

/* =========================
   Posts List
========================= */

.series-posts {
    padding-left: 8px;
    border-left: 2px solid #f0f0f0;
}

.series-post-item {
    margin: 6px 0;
    font-size: 14px;
}

.series-post-item a {
    text-decoration: none;
    color: #333;
}

.series-post-item a:hover {
    color: #0073aa;
}

/* =========================
   Visual separation boost
========================= */

.series-item + .series-item {
    margin-top: 18px;
}

/* =========================
   Series menu Layout
========================= */

/* ======================================================
0. 容器
====================================================== */
.series-menu-wrapper {
    position: fixed;
    right: 20px;
    top: 50%;
    transform: translateY(-50%);
    width: 200px;
    max-height: 60vh;
    overflow: hidden;
    z-index: 9999;
}

/* ======================================================
1. 主框体(默认状态 = 收起)
====================================================== */
#series-menu {
    font-size: 13px;
    background: #fff;
    border: 1px solid #eaeaea;
    border-radius: 8px;
    padding: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.08);
    position: relative;
}

/* ======================================================
2. 标题
====================================================== */
.series-menu-title {
    font-weight: 600;
    font-size: 14px;
    padding: 8px 6px;
    text-align: center;
    border-bottom: 1px solid #eee;
    background: #fafafa;
    border-radius: 6px;
    cursor: pointer;
}

/* ======================================================
3. 列表(默认:彻底隐藏)
====================================================== */
.series-menu-list {
    display: none;
    margin-top: 8px;
    max-height: 300px;
    overflow-y: auto;
}

/* hover 才显示 */
#series-menu:hover .series-menu-list {
    display: block;
}

/* ======================================================
4. 单个系列
====================================================== */
.series-menu-item {
    padding: 2px 4px;
    margin-bottom: 2px;
    border-radius: 6px;
    border: 1px solid #f0f0f0;
    background: #fafafa;
    line-height: 1.1;
}

.series-menu-item:hover {
    background: #f5f5f5;
}

/* ======================================================
5. 标题行
====================================================== */
.series-menu-header {
    display: flex;
    align-items: flex-start;
    gap: 6px;
}

/* ======================================================
6. 链接(控制两行核心)
====================================================== */
.series-menu-link {
    flex: 1;
    min-width: 0;
    display: block;

    line-height: 1.25;

    /* 两行限制 */
    max-height: 2.5em;
    overflow: hidden;

    white-space: normal;
    word-break: break-word;
    overflow-wrap: anywhere;

    color: #333;
    text-decoration: none;
}

.series-menu-link:hover {
    color: #000;
}

/* ======================================================
7. footer(默认隐藏)
====================================================== */
.series-menu-footer {
    display: none;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid #eee;
    text-align: center;
}

#series-menu:hover .series-menu-footer {
    display: block;
}

/* ======================================================
8. 夜间模式(正确 selector:html.darkmode)
====================================================== */

html.darkmode #series-menu {
    background: #1e1e1e !important;
    border-color: #333 !important;
}

html.darkmode .series-menu-title {
    background: #2a2a2a !important;
    color: #eaeaea !important;
    border-color: #333 !important;
}

html.darkmode .series-menu-item {
    background: #2a2a2a !important;
    border-color: #333 !important;

}

html.darkmode .series-menu-link,
html.darkmode .series-name {
    color: #eee !important;
}

html.darkmode .series-menu-footer a {
    color: #aaa !important;
}

html.darkmode .series-menu-footer a:hover {
    color: #fff !important;
}

html.darkmode .series-desc {
    background: rgba(255,255,255,.04);
      border-left-color: #6aa9ff;
    color: #ddd;
}

3.5 Running Effect and Interaction Performance of the Homepage Article Series Menu

After the system finishes loading, a series article entry component will appear on the right side of the homepage. In its initial state, this component remains collapsed, retaining only a concise title area and light visual cues, allowing users to perceive the existence of the “series structure entry” without interfering with the reading structure of the homepage:

image.png

When the user hovers the mouse over this area, the component enters an expanded state, and the system displays all available series in a list format. Each series is presented as a card, including the series name and the corresponding number of articles, enabling users to directly gain an overall understanding of the entire blog's content structure from the homepage.

In this state, the homepage is no longer just a content entry point, but becomes a “structural entry layer.” Users can dive into the corresponding knowledge path from any series without having to enter a specific article first and then trace back the structural relationships:

image.png

When the user further clicks on a series, the system enters the detail display mode for that series, where all articles under it are fully expanded and presented in a list format according to the established order. This order originates from the numbering logic of the articles within the series, making the reading path itself linear and deterministic.

Within this level, each article provides a direct jump link, allowing users to complete continuous reading along the series structure without relying on tags or search to reorganize the path:

image.png

In terms of interaction behavior, this component adopts a very lightweight state model: it expands when the mouse enters and collapses when the mouse leaves, with internal clicks only responsible for page navigation without introducing extra state management. This design avoids the uncertainty brought by complex interaction logic, keeping the entire component stable and predictable in its behavior.

Meanwhile, because this component operates at the homepage level, its responsibility is not to host content consumption but to provide a structural entry point. Therefore, its visual design consistently maintains low information density, ensuring that even in the expanded state, it does not interfere with the main content area of the homepage.

From the perspective of the overall experience, the significance of this series menu lies not in “displaying articles,” but in establishing a clear cognitive entry point:

The moment users enter the homepage, they can immediately realize that the entire blog's content is not an isolated collection of articles, but is composed of multiple “series structures” that can be read continuously, and they can directly enter any knowledge path from the structural level.

4 Relationship Between Series Menu and Series Card

If we break down the “series structure” in the current blog, there are actually already two different presentation methods: one is the series article cards made previously, and the other is the series article menu implemented in this article.

They seem to be doing the same thing—displaying series articles—but the problems they actually solve are different.

The series article card is used during the article reading process. It appears on specific article pages, and its role is very direct, which is to tell you:what other articles are in this series, and what their order is. Essentially, it solves the problem of “how to continue reading within a series.”

On the other hand, the series article menu does not rely on the current article at all; it is placed on the side of the homepage, displayingthe overall entry point for all series. It solves the problem of “which series can be read and where to enter.”

So the difference between the two is actually very clear: series card—solves “how to continue reading within a series” (horizontal); series menu—solves “which series can be selected” (vertical).

From the perspective of usage scenarios, they also happen to complement each other: after users enter an article, they will use the card to continue reading within the series;
And when they haven't selected content yet, they will discover the entire series system through the menu.

So this part is more like a complementary relationship: the card is responsible for “how to read along while reading”; the menu is responsible for “how to choose and read before starting.”

Only by putting the two together can the closed loop of “series content” from discovery to reading be completed.

📚 系列文章:为博客构建“轻量级知识索引”(9 / 10)

View Related Categories · 3 Matches
📌 Content Structure Prompt:
This content is part of the following knowledge map, where you can view the complete content path: WordPress Learning Roadmap。
📎 Related Articles
Share this article
The blog content is original, please indicate the source when reposting! The RSS address of the blog is:https://blog.tangwudi.com/feed, welcome to subscribe; if needed, you can join theTelegram Groupto discuss questions together.
No Comments

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗ
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
Kaomoji
Emoji
Little Dinosaur
Flower!
Previous
Next
       

👋 Welcome to “Wudi's Personal Blog”

Here, long-term exploration is mainly carried out around the following directions:

🧱 Personal Digital Infrastructure and Blog System Construction
☁️ Cloudflare and Network Architecture Practice
🧠 AI and Knowledge System Exploration
🛡️ Network Security and Access Optimization
🎵 Music and Sound Cognition
👁️ Cognitive Perspectives and Worldviews