Building a Lightweight Knowledge Index for Your Blog (Part 8): Site-wide Content Navigation Based on Series Data
Article Summary
针对博客内容中系列文章导航在全局认知层缺失的问题,本文设计了首页右侧系列菜单作为站点入口层补充,通过双JSON(series-meta.json与series-map.json)实现系列配置与内容结构的解耦,构建统一数据源。系统采用离线生成系列结构、PHP动态加载合并数据、前端轻量交互的三层架构,在WordPress PJAX环境下实现首页系列入口的稳定展示,使用户无需进入具体文章即可感知博客知识体系,提升系列内容的发现效率与阅读路径完整性。
Qwen3-14B · 2026-07-31

1. Linking from within the article to the site entry: The first upgrade to the series reading structure

In previous articles (see:Building a Lightweight Knowledge Index for Your Blog (Part 4): Navigation and Reading Path Design for a Series of ArticlesTo address the issue of discontinuous horizontal reading paths between articles in the same series within blog content, I introduced a "Series Article Card" feature at the bottom of the article text.

The main function of this component is to explicitly present the structural position of an article within its series, including its current sequence number, the total number of articles in the series, and the navigation between articles, thereby helping readers establish "local continuity" during the reading process. The actual effect is as follows:

image.png

From a user experience perspective, this solution is effective in "single-article reading scenarios," addressing a very specific problem:How can we ensure that readers do not lose the context of a series of articles while reading them?.

However, as the content volume increased, I gradually discovered a more fundamental problem: for some visitors, their access path was not "articles → series", but rather:

  • Accessing a single article via a search engine

  • Accessing a specific article via an external link

  • Or perhaps you just happened to browse the homepage.

In these scenarios, the series of cards is actually a "post-implementation capability"—it can only be perceived after the user has entered a particular article. This leads to a structural gap: the series of articles system is visible at the "local reading layer," but it is missing at the "global cognitive layer."

In other words, users who haven't yet developed a sense of series are unaware that blogs can be organized as "series," and they cannot actively choose to enter a particular knowledge path. Therefore, simply having "series cards within articles" is insufficient.

Based on this issue, I believe the system is missing a key entry point: a series of navigation layers facing the "site entry layer," which would provide overall visibility and quick access to the series structure when users enter the website's initial page (mainly the homepage).

In terms of implementation, this entry point is better suited for placement in the blank area in the middle right of the homepage. This area has low information density in the current layout, providing good space for "supporting auxiliary navigation components" without interfering with the reading path of the main content flow.

image.png

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

  • The homepage provides an aggregated entry point for all series.
  • Allow users to perceive the entire knowledge structure before they even delve into a specific article.
  • It also supports quick access to any series of reading paths.

From a system design perspective, this is actually completing a "higher-level index layer of the knowledge structure".

2 Design

2.1 Design concept of the right-hand menu for the homepage series of articles

After deciding to add a homepage series entry to the homepage, the real question to consider is:In what form should this entrance be presented?

The simplest way is to place a "Series Articles" button on the right, which redirects to a dedicated series page. This solution has the lowest implementation cost and meets the functional requirements. However, it's more like a regular navigation link; besides telling users "there's a series page here," it doesn't allow users to directly see what series the blog currently has on the homepage, nor does it help them quickly determine which content is worth reading further.

Another approach is to display a complete list of articles from all series directly on the homepage. This method provides the most comprehensive information, but as the number of series increases, and each series contains multiple articles, the entire component can easily become too large, taking up a lot of homepage space and disrupting the original browsing rhythm that focuses on the latest articles.

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

This approach has several advantages. First, it allows visitors to quickly browse the blog's existing knowledge base within seconds, without having to read numerous article titles. Second, since the homepage's role is to "discover content" rather than "read content," the homepage menu is better suited as a lightweight index, while the actual article organization and reading path are handled by subsequent series of pages.

In other words, the homepage menu addresses "what to discover," while the series pages address "how to read." From a system design perspective, they correspond to different responsibilities: the former is the entry layer of the knowledge structure, while the latter is the presentation layer of the knowledge structure.

Therefore, the homepage series menu ultimately adopted a design approach of "overview first, in-depth exploration as needed"—first helping users discover series of interest, and then entering the corresponding page to complete the subsequent reading, rather than trying to carry the entire series of content on the homepage.

2.2 Data Structures and System Inputs: A Series of Organizational Basis Based on Dual JSON

After finalizing the overall design of the homepage menu series, the next issue to address is:How to organize these series of data.

A more straightforward approach is to have WordPress dynamically query all series and their contained posts when the page loads, and then have PHP generate the menu content based on the query results. However, this approach means that the series structure needs to be reorganized every time the homepage is visited, and the runtime calculations become increasingly complex as the number of series increases.

Since the entire "Lightweight Knowledge Index" blog series has consistently followed the design philosophy of "completing calculations during the construction phase and only handling display during the runtime phase," the same strategy is employed here:The series structure is pre-built as static JSON, which is then directly consumed by the front-end and PHP at runtime.

Overall, this system mainly relies on two core JSON files, each with different responsibilities, which together form the entire organizational structure.

1. 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 Tutorial", "order": 1, "description": "Cloudflare Series Articles", "hide": false } }

As you can see, this layer doesn't care which articles are in the series, but only describes the series' own attributes, such as: series name; display order; series introduction; and whether it is displayed on the front end.

Therefore, from a systems perspective,series-meta.json It's more like a "configuration layer" for the entire series system. It defines how a series should be presented, rather than what a series contains.

The advantage of doing this is that the display configuration of the series is completely decoupled from the article organization, so even if the sorting is adjusted, the title is modified, or a series is temporarily hidden in the future, there is no need to reorganize the article structure.

2. series-map.json

The second layer is a series content mapping layer, corresponding to... series-map.jsonThis layer describes which articles are included in each series, with a typical structure as follows:

{ "cloudflare": { "count": 10, "posts": [ { "index": 1, "title": "Introduction to Cloudflare Tunnel", "url": "/technology/xxxx/" } ] }

Compared to series-meta.jsonThis layer truly begins to focus on content organization. Each series will save the total number of articles in the series;
The order of each article in the series; title; URL.

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

Therefore, whether it's the homepage series menu, series pages (or even the previous "series article cards"), they can all directly consume this structured data without needing to maintain their own independent logic.

in other words,series-map.json In effect, it became the sole source of truth for the entire series of systems.


Note: Why split it into two JSON files instead of one?

From a data scale perspective, it's certainly possible to put all the information into a single JSON object. For example, each series could store all fields such as title, description, and posts. However, from an engineering design perspective, this approach would couple "configuration" and "content" together.

The current use of dual JSON is essentially a separation of responsibilities. Specifically:series-meta.json Responsible for describing the series,series-map.json Responsible for describing the article.

Although both will eventually be merged and used by PHP, they can be maintained separately during the build phase. For example, operations such as modifying the series title, adjusting the sorting, or temporarily hiding the series will only affect the main content. series-meta.jsonAdding new articles or adjusting their order will only affect... series-map.json.

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


2.3 System Overall Architecture and Engineering Value

From an overall structural perspective, these two JSON documents are not independent of each other, but rather built upon the existing data foundation of the entire "lightweight knowledge index" system, together forming the data organization structure of the series of articles system:

WordPress Posts │ ▼ article-index.json │ ├───────────────┐ ▼ ▼ series-meta.json series-map.json │ │ └──────┬────────┘ ▼ Series Post System │ ┌────────┴────────┐ ▼ ▼ Home Series Menu/Series Page │ ▼ Series Post Cards

From a data flow perspective, the entire series of articles is not built directly on top of the original WordPress content, but rather continues the layered organization method consistently used in the "Lightweight Knowledge Index" series.

First, by article-index.json Provides unified basic article data for the entire blog. Based on this, a series of related data structures are further developed: Among them,series-meta.json This section is responsible for describing the metadata of the series itself, such as series name, display order, description, and whether it is displayed; while series-map.json This is responsible for describing which articles are included in each series and how these articles are arranged within the series.

In this way, a clear separation of responsibilities is established between the configuration of the series itself and the content of the series: the former defines "what the series is," and the latter defines "what the series contains." Although they both ultimately serve the series article system, their respective responsibilities are completely different, and they can be maintained and expanded separately.

Looking further, these two JSON files don't just serve a single specific function; rather, they collectively constitute the unified data source for the entire article series system. Whether it's the homepage series menu, series pages, or the previously implemented series article cards, everything is built upon this unified data structure. Therefore, when the series structure changes, only these two JSON files need to be updated, and all related functions can automatically obtain consistent data without needing to maintain their own separate data logic.

From an engineering perspective, this design continues a core principle that has been consistently upheld throughout the "Lightweight Knowledge Index" series:Completely separate content organization from page display.

In this architecture, each data structure layer performs only a single responsibility:

  • article-index.jsonProvides unified basic data for articles;
  • series-meta.jsonMeta-information describing the series;
  • series-map.json: The content structure of the organization series.

Ultimately, different pages only need to consume this pre-organized data according to their own needs, without having to recalculate series relationships or reorganize the article structure.

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

3. Implementation

3.1 Overall Process of Project Implementation

Based on the design concept in Chapter 2, this homepage series of articles system can also be broken down into three distinct steps in actual engineering implementation, each with its corresponding deliverables and execution location.

The first step is to generate a series of organizational data files. series-meta.json and series-map.jsonThis part is written in script. build_series_map.py Responsible for completing, the core role is based on existing article-index.json Based on this, the organizational relationships of all series in the blog are organized, and series metadata and series content mappings are generated separately. The former is responsible for describing the attributes of the series itself, and the latter is responsible for describing the articles contained in the series and their order. These two JSON documents together constitute the data foundation of the entire series system.

The second step is to establish a unified data retrieval entry point in the WordPress backend. The system will first read... series-meta.json and series-map.jsonThe system performs basic data processing such as merging, sorting, and filtering, ultimately generating a unified series of data structures. Subsequent homepage menus and series pages will directly use this unified data source, eliminating the need to maintain separate data logic for each.

The third step involves implementing the corresponding display logic on different pages. During system runtime, this is first achieved through a unified data entry point. series_load_data() load series-meta.json and series-map.jsonThe system performs basic data processing such as merging, sorting, and filtering, generating a unified series data structure. Then, different pages consume this data according to their specific needs: the homepage displays the aggregated entry point for all series, guiding visitors to discover the blog's overall knowledge structure; series pages display a complete list of articles within a particular series; and the previously implemented series article cards provide navigation within the series during article reading. Although these three functions are presented differently, they are all built upon the same series data structure, thus maintaining a consistent organizational relationship.

series-meta.json series-map.json │ │ └────────┬──────────┘ ▼ series_load_data() │ ┌───────────┼────────────┐ ▼ ▼ ▼ Homepage Series Menu / Series Page Series Article Cards

The biggest advantage of this splitting method is that it will...Data organization, data consumption, and page displayThe three phases are completely decoupled. The series relationships only need to be organized once during the build phase, and the runtime phase always revolves around a unified data structure. Different pages are only responsible for displaying data in their own way, without having to repeatedly organize series content or recalculate the relationships between articles.

3.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, this script mainly accomplishes three things:Read article index data, identify the series to which the articles belong, generate the series article structure, and output a JSON file..

The most crucial part is not the parsing logic itself, but the entire "series structure extraction process." In this stage, the system builds upon the structure generated in the previous stage. article-index.jsonBy analyzing the serial number information in the article titles, the originally scattered articles are organized into a structured series of data.

Final output series-map.jsonEach series will contain the following core fields:

  • series_keyUnique identifier for the series
  • countNumber of articles in the series
  • postsList of articles in a series (including index / title / URL)

From a systems perspective, the significance of this step is:Transform the implicit "series relationships" that were originally scattered throughout the article layers into an explicit structured data model.Subsequent homepage menus, series pages, and article cards are all displayed directly based on this structure.

3.3 Implementation of WordPress Homepage Menu Series: Front-end Rendering and Structure Consumption Layer

3.3.1 Front-end architecture breakdown: division of responsibilities for PHP / JS / CSS

This section is the final presentation layer of the "Series Post 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 chapter, this section shifts the focus from "how to build the series structure" to "how to consume series data and complete the display in WordPress".

In terms of implementation structure, this module is also divided into three layers: PHP (data loading and structure organization layer), JavaScript (interactive control layer), and CSS (visual presentation layer).

This split is not essentially a front-end architecture design, but rather a natural consequence of WordPress's existing rendering model: PHP is responsible for deterministic data output, JS is responsible for lightweight interactive states, and CSS is responsible for visual expression.


1. PHP section

In this architecture, PHP is the core of the entire system: PHP is responsible for making calls. series_load_data() The method reads pre-generated data from the cache.series-meta.jsonandseries-map.jsonThen, it merges the two into a unified data structure. Based on this, PHP will perform three key tasks: filtering invisible series (hide field); sorting by the order field; and generating lightweight structured data required for the homepage menu.

The final output to the front end is a structured array, not the query results or dynamic calculation logic. This can be understood as:
PHP here serves as a "consumption and organization layer for the series structure," rather than a computation layer.

2. JavaScript section

The JavaScript in this article is only responsible for the lightest level of interactive control. Its core responsibilities are: expanding menus by hovering/clicking; controlling the display state of submenus; and handling simple DOM transitions.

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

3. CSS section

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

The design goal of this layer is to maintain a "low profile," that is, to provide quick access without interfering with the main content of the homepage.


Through this three-tiered breakdown, the entire homepage menu system forms a very clear structure:

  • PHP: Structured data output (series_load_data)

  • JS: Interactive State Control

  • CSS: Visual Expression

From a system perspective, the characteristics of this module are: It relies entirely on offline data construction and does not introduce any complex runtime calculations.

3.3.2 WordPress Side Data Loading and Menu Generation (PHP)

This section describes the implementation of the post series system in WordPress. Unlike traditional WordPress pages that directly save content, this feature uses a blank page as the entry point, with PHP dynamically generating the content.

First, you need to create a blank page in WordPress and set a fixed URL (/series/This page serves as the entry point for displaying a series of articles. It does not contain any actual content; PHP, through the page lifecycle provided by WordPress, takes over the content generation logic when a request matches this URL.

Specifically, the system through the_content The filter determines whether the current page is... /series/If a match is found, the series data loading function is called. series_load_data()Read the data generated in the previous build phase from the cache directory. series-map.json and series-meta.json.

This layer's responsibility is not to generate series relationships, but to organize and transform existing series data: the system will merge the information from the two JSON files, filter hidden series, and generate a unified series list structure according to the sorting rules in the configuration. Then, it will decide whether to display all series entries or a list of articles under a specific series based on the current URL parameters.

In other words, this layer is responsible for the transformation of "structured data into WordPress page display".series-map.json Provide the actual relationship between articles and series.series-meta.json This provides display information such as series titles, descriptions, and sorting; the combination of the two forms... /series/ The data foundation required for the page and the homepage menu series.

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

During the data preparation phase, the system reads from the cache directory. series-map.json and series-meta.jsonThe former provides a list of articles in a series and their quantity, while the latter provides the series title, sort weight, and hiding settings. (Followed by...) series_key Merge the indexes and filter out... hide A series of markings.

Based on this, each series will be standardized into a single structure, including fields such as title, description, sorting weight, and article list, and then... order The fields are sorted as a whole to ensure that the front-end display order is consistent with the configuration.

exist /series On the page, the system is based on series_load_data() The output structure is rendered, and the URL parameters determine whether it is in "full series mode" or "single series mode". In full series mode, all series are displayed, along with their article counts and descriptions; in single series mode, only the articles of the current series are displayed, maintaining a strict sequential structure.

On the homepage, the system uses... render_series_menu() Generate a lightweight series entry menu that displays only the series title and the number of articles, and provides a jump to... /series/?series=xxx The entry link is used to quickly access the corresponding series of content.

At the WordPress integration level, the system uses... the_content filter injection /series Page content, and through wp_footer A series of menus are mounted at the bottom of the homepage, thus achieving a dual display structure at both the page level and the entry point level.

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

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.3 Implementation of Homepage Menu Interaction Logic (Extended Logic in sidebar.js)

This part describes the control logic for the "Series Articles Menu" on the homepage during front-end runtime. It is not a separate module but is integrated into... sidebar.js The extended capabilities within the framework are used to control the state of the homepage entry component under a unified front-end initialization system.

From an overall implementation perspective, the responsibility of this part of the logic is very singular:Control the display and hiding of the series menus on the homepage based on the current page routing status, and ensure that the status remains consistent during PJAX and browser history switching.

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

1) Homepage visibility control logic

The system passes syncSeriesMenu() The function that determines the homepage relies on the current URL path.

  • When the path is the site root path (homepage), display .series-menu-wrapper
  • This component is hidden when the path is to another page.

This logic ensures that the series menus exist only as the "homepage entry layer" and do not enter the article reading scenario, thus avoiding information hierarchy pollution.

2) Consistency with sidebar.js lifecycle

Because this logic is integrated in sidebar.js Therefore, it must be consistent with the existing initialization system, rather than running independently.

The system will syncSeriesMenu() Mount it to the same lifecycle chain as the right-hand menu:

  • Initial load: Initialization is executed after DOM is ready.
  • PJAX switching: via window.pjaxLoaded Resynchronization status
  • Browser forward/back: via pageshow and popstate Correction status

The essence of this design is to incorporate "page-level UI state" into a unified lifecycle management system, thereby avoiding inconsistencies in DOM state caused by partial replacements in PJAX.

3) Relationship with the right-side menu system

Although this part shares the same JS file as the semantic menu on the right, the two are completely separate in terms of their responsibilities:

  • Right-hand menu: Content recommendation system based on semantic data
  • Homepage Menu Series: Entry-Level Control System Based on Routing Status

Their only common ground is that they both depend on sidebar.js Unified initialization lifecycle (pjaxLoaded + init

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

series-menu control codes (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 existing systems)

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 fills a key entry point in the entire series structure: making the "series of articles" no longer just exist within the articles or category structure, but forming a stable entry point at the homepage level.

At the same time, 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 maintains a unified interactive behavior model in the PJAX environment.


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

It's worth noting that in the Argon theme, due to the enabled PJAX (PushState + AJAX) no-refresh loading mechanism, page transitions do not trigger a complete browser reload process. Therefore, traditional... DOMContentLoaded This only applies upon first entering the site.

Under this mechanism, the official provided window.pjaxLoaded It serves as a unified page transition completion hook, used to re-execute the front-end initialization logic after each article transition.

Therefore, if you need to add JavaScript functionality to the Argon theme yourself and want it to remain effective when switching between different posts, you must mount the initialization logic to... window.pjaxLoaded In addition, it cannot rely solely on the initial load event.

Otherwise, typical problems will occur:

  • The page functions normally on first visit.
  • After switching articles via PJAX, the JS logic no longer executes.
  • Event bindings fail or state is lost after a DOM update.

In this article syncSeriesMenu() Following the same mechanism, by mounting to window.pjaxLoadedThis ensures that state synchronization is re-executed after each page switch, thereby guaranteeing that the homepage menus maintain consistent behavior across different access paths.

From a practical perspective, in PJAX themes like Argon,pjaxLoaded In fact, it is equivalent to the "page lifecycle entry point of the front-end application". All custom scripts that depend on the DOM state should use this hook as the reconstruction trigger point by default.


3.3.4 Homepage Menu Styles and Visual Presentation (CSS)

This section is the visual presentation layer of the homepage's "Series of Articles Menu" on the WordPress page, implemented using CSS stylesheets. Its role is not to handle any interactive logic or data processing, but rather to transform the structured series of data output by PHP into a hierarchical visual list, thus completing the final presentation of the homepage entry layer.

From an overall structural perspective, this section of the design mainly revolves around two levels:Homepage series menu wrapper and series list cards.The former controls the component's position and hovering behavior on the page, while the latter carries the specific series of information and article list.


1) Overall layout and suspension 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, through... position: fixed and transform: translateY(-50%) Achieve vertical centering. This layout ensures that the entry point remains visible regardless of where the page is scrolled, without interfering with the main text reading area.

The container itself is designed with a narrow width and a high height. z-indexThis allows it to maintain its visual "edge entry" positioning rather than being part of the main content, thus reinforcing its role as a "navigation entry layer".

2) Main Container and Unfolding Mechanism

#series-menu This layer carries the actual content and is collapsed by default, displaying only the title area. It is triggered when the user hovers over the element, via a CSS selector. .series-menu-list and .series-menu-footer The display achieves a lightweight expansion effect.

The core features of this implementation are: extremely compact default state (only the title); the full list is only exposed on hover; and no JavaScript is needed to control expansion. From an interaction model perspective, this is a typical "pure CSS-driven lightweight interactive menu" used to reduce the implementation complexity of the homepage entry layer.

3) Series Card Structure

Each series through .series-item It is used to carry articles, and its internal structure adopts a three-part structure of "title + description + article list".

in:

  • .series-title Used to display the series name and number of articles.
  • .series-desc Used to provide a series of semantic descriptions (weak information layer)
  • .series-posts Used to display a list of articles in this series.

The article list uses a left border for visual grouping, which clearly distinguishes it from the title area in terms of spatial structure, thereby enhancing the hierarchical relationship between "series → sub-articles".

4) Article list and information density control

.series-post-item This section is responsible for displaying specific article entries. Its links use a simple, single-line text structure and provide basic feedback through hover states. The overall design goal is to control information density, allowing the homepage entry layer to accommodate multiple series within a limited space without appearing cluttered.

In handling long titles, through... word-break and overflow-wrap Ensure text readability in narrow layouts and avoid horizontal overflow affecting overall layout stability.

5) Visual consistency and dark mode

The style system provides additional features html.darkmode The following adaptation rules are used to ensure that the layer clarity is maintained in night mode.

The main adjustments include: changing the background color from white to a darker color scheme; reducing the contrast of borders and dividing lines; improving the readability of link colors; and using a semi-transparent background in 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 part of the CSS is not complex visual effects, but a clear structural constraint: to stably express the hierarchical relationship of "series collection → series cards → article list" within a limited space, while maintaining the lightweight and non-intrusive nature of the homepage entry.

Unlike the semantic menu on the right side of the previous article, this component is more of a "static information entry point". Therefore, it adopts a lighter interaction model (hover expansion + stateless switching) to reduce system complexity.

The CSS code is as follows (the "Extra CSS" section for 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. Container ===================================================== */ .series-menu-wrapper { position: fixed; right: 20px; top: 50%; transform: translateY(-50%); width: 200px; max-height: 60vh; overflow: hidden; z-index: 9999; } /* ======================================================= 1. Main frame (default state = collapsed) ======================================================== */ #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. Title ===================================================== */ .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. List (Default: Completely Hidden) ======================================================== */ .series-menu-list { display: none; margin-top: 8px; max-height: 300px; overflow-y: auto; } /* only displayed when hovering*/ #series-menu:hover .series-menu-list { display: block; } /* ====================================================== 4. Single series ======================================================== */ .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. Title Row ====================================================== */ .series-menu-header { display: flex; align-items: flex-start; gap: 6px; } /* ======================================================= 6. Links (Controlling the two core lines) ======================================================== */ .series-menu-link { flex: 1; min-width: 0; display: block; line-height: 1.25; /* Two-line limit*/ 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 (hidden by default) ============================================================ */ .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. Night mode (correct 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.3.5 Homepage Article Series Menu Operation Effect and Interactive Performance

After the system finishes loading, a series article entry component will appear on the right side of the homepage. This component initially remains collapsed, displaying only a simple title area and minimal visual cues, allowing users to perceive the existence of the "series structure entry" without disrupting the homepage's reading structure.

image.png

When a user hovers their mouse over this area, the component expands, displaying a list of all available series. Each series is presented as a card, including the series name and the number of articles, allowing users to gain an overall understanding of the blog's content structure directly from the homepage.

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

image.png

When a user clicks on a specific series, the system enters the series' details display mode. All articles in that series are displayed in a predetermined order and presented as a list. This order is derived from the article numbering logic within the series, thus the reading path itself is linear and predictable.

At this level, each article provides a direct jump entry, allowing users to complete continuous reading along the series structure without relying on tags or search to reorganize their path.

image.png

In terms of interactive behavior, this component adopts a very lightweight state model: it expands when the mouse enters and collapses when the mouse leaves. Internal clicks only handle page navigation and do not introduce additional state management. This design avoids the uncertainty brought about by complex interaction logic, making the entire component's behavior stable and predictable.

Meanwhile, since this component runs at the homepage level, its responsibility is not to carry content consumption, but to provide a structural entry point. Therefore, it always maintains a low information density in its visual design, and will not interfere with the main content area of the homepage even when it is expanded.

From an overall user experience perspective, the significance of this menu series lies not in "displaying articles," but in establishing a clear entry point for understanding the content:

Upon entering the homepage, users will immediately realize that the blog's content is not an isolated collection of articles, but rather consists of multiple interconnected "series structures," allowing them to directly access any knowledge path from the structural layer.

4. The relationship between series menus and series cards

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

They all seem to be doing the same thing—presenting a series of articles—but they are actually solving different problems.

These article cards are used during the article reading process. They appear on specific article pages and their function is very direct: to tell you...What other articles are in this series, and in what order?Essentially, it addresses the question of "how to continue looking within a series".

The series article menu, however, is completely independent of the current article; it is placed on the side of the homepage and displays...A complete entry point for all seriesIt addresses the questions of "which series are available to watch and how to access them".

Therefore, the difference between the two is actually quite clear: series cards – solve "how to continue reading within a series" (horizontal); series menus – solve "which series are available to choose from" (vertical).

In terms of usage scenarios, they complement each other perfectly: after entering an article, users can use cards to continue reading within the series;
Before selecting any content, users can explore the entire series through the menu.

Therefore, this part is more like a complementary relationship: the cards are responsible for "how to read in sequence while reading"; the menu is responsible for "how to select before starting".

Only by combining the two can we complete the entire loop of "series content" from discovery to reading.

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

📌 Content Structure Hints:
This content belongs to "Blog Knowledge MapThis is part of the document; you can view the full content path here: Blog Knowledge Map .
View related categories · 3 matches
📎 Related Articles
Share this article
All blog content is original; please indicate the source when reprinting! The blog's RSS address is:https://blog.tangwudi.com/feed, welcome to subscribe; if necessary, you can joinTelegram GroupDiscuss the problem together.
No Comments

Send Comment Edit Comment


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

👋 Welcome to "Invincible Personal Blog"“

This section will focus on long-term exploration in the following areas:

🧱 Building Personal Digital Infrastructure and Blog Systems
☁️ Cloudflare and Network Architecture Practices
🧠 Exploring AI and Knowledge Systems
🛡️ Network security and access optimization
🎵 Music and Sound Cognition
👁️ Cognitive Perspective and Worldview