diff --git a/docs/boss-guides/afk.md b/docs/boss-guides/afk.md
new file mode 100644
index 0000000000..cadce56ee7
--- /dev/null
+++ b/docs/boss-guides/afk.md
@@ -0,0 +1,5 @@
+---
+title: Redirecting...
+---
+
+
\ No newline at end of file
diff --git a/docs/boss-guides/basic-guides.md b/docs/boss-guides/basic-guides.md
new file mode 100644
index 0000000000..2a8233a3d8
--- /dev/null
+++ b/docs/boss-guides/basic-guides.md
@@ -0,0 +1,5 @@
+---
+title: Redirecting...
+---
+
+
\ No newline at end of file
diff --git a/docs/boss-guides/combat-achievements.md b/docs/boss-guides/combat-achievements.md
new file mode 100644
index 0000000000..3e7c4deb52
--- /dev/null
+++ b/docs/boss-guides/combat-achievements.md
@@ -0,0 +1,5 @@
+---
+title: Redirecting...
+---
+
+
\ No newline at end of file
diff --git a/docs/boss-guides/index.md b/docs/boss-guides/index.md
new file mode 100644
index 0000000000..0e37e54f4e
--- /dev/null
+++ b/docs/boss-guides/index.md
@@ -0,0 +1,54 @@
+---
+hide:
+ - toc
+
+hide_actions: true
+---
+# Boss & Slayer Guides - Directory
+
+
\ No newline at end of file
diff --git a/docs/boss-guides/one-tick-guides.md b/docs/boss-guides/one-tick-guides.md
new file mode 100644
index 0000000000..e396c90dac
--- /dev/null
+++ b/docs/boss-guides/one-tick-guides.md
@@ -0,0 +1,5 @@
+---
+title: Redirecting...
+---
+
+
\ No newline at end of file
diff --git a/docs/boss-guides/rs3-full-boss-guides.md b/docs/boss-guides/rs3-full-boss-guides.md
new file mode 100644
index 0000000000..831daf66c3
--- /dev/null
+++ b/docs/boss-guides/rs3-full-boss-guides.md
@@ -0,0 +1,5 @@
+---
+title: Redirecting...
+---
+
+
\ No newline at end of file
diff --git a/docs/boss-guides/slayer.md b/docs/boss-guides/slayer.md
new file mode 100644
index 0000000000..50ab7846f7
--- /dev/null
+++ b/docs/boss-guides/slayer.md
@@ -0,0 +1,5 @@
+---
+title: Redirecting...
+---
+
+
\ No newline at end of file
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
index b9928dee2a..59ea7bb099 100644
--- a/docs/stylesheets/extra.css
+++ b/docs/stylesheets/extra.css
@@ -65,4 +65,31 @@ p {
/* Hide navigation emojis when the category is opened */
.md-nav__link .nav-emoji {
display: none;
+}
+
+.md-typeset .grid {
+ grid-gap: 1rem;
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
+}
+
+.md-typeset .grid.cards>ul>li {
+ padding: 0;
+}
+
+.md-typeset .grid li > a {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ color: var(--md-primary-bg-color);
+ padding: 0.8rem;
+}
+
+.md-typeset .grid li:hover > a {
+ color: var(--md-primary-fg-color);
+}
+
+
+.md-typeset .grid h2 {
+ margin: 0;
}
\ No newline at end of file
diff --git a/gen_pages.py b/gen_pages.py
index e8351d6a59..9ead2f8aeb 100644
--- a/gen_pages.py
+++ b/gen_pages.py
@@ -31,7 +31,9 @@
# 'afk/**/*.txt'
# ])
-name_converter = NameConverter(settings.name_convert, settings.files.source_dir)
+source_dir = Path(settings.files.source_dir).resolve() if settings.files.source_dir else Path("docs").resolve()
+
+name_converter = NameConverter(settings.name_convert, source_dir)
+page_generator = PageGenerator(files, name_converter, source_dir)
-page_generator = PageGenerator(files, name_converter, settings.files.source_dir)
page_generator.generate_pages()
diff --git a/mkdocs.yml b/mkdocs.yml
index 5232726fa0..f1db05fae7 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -13,14 +13,11 @@ theme:
custom_dir: overrides
favicon: assets/logo.ico
features:
- - navigation.instant
- navigation.tabs
- - navigation.tabs.sticky
+ - navigation.instant
+ - navigation.indexes
- search.suggest
- search.highlight
- # - search.share
- - navigation.top
- # - navigation.indexes
- announce.dismiss
- content.action.edit
palette:
@@ -33,10 +30,8 @@ theme:
icon: material/weather-sunny
name: Switch to dark mode
-
plugins:
- search
-#- git-revision-date-localized
- macros
- gen-files:
scripts:
@@ -50,7 +45,8 @@ markdown_extensions:
- pymdownx.emoji
- admonition
- footnotes
-# - pymdownx.extra (markdown formatting in HTMl blocks)
+ - attr_list
+ - md_in_html
extra_css:
- stylesheets/extra.css
@@ -61,9 +57,23 @@ extra_javascript:
- javascripts/discordInvite.js
nav:
- - index.md
- - About:
- - Info: about/info.md
- - Keyboard Shortcuts: about/shortcuts.md
- - How The Site Works: about/workings.md
- - Changelog: about/changelog.md
\ No newline at end of file
+ - Home: index.md
+ - Getting Started:
+ - New to PvM: new-to-pvm
+ - New to Bossing: new-to-bossing
+ - Invention & Perks: invention-and-perks
+ - Boss & Slayer Guides:
+ - Guide Directory: boss-guides/index.md
+ - Basic Guides: boss-guides/basic-guides.md
+ - RS3 Full Boss Guides: boss-guides/rs3-full-boss-guides.md
+ - AFK Methods: boss-guides/afk.md
+ - Combat Achievements: boss-guides/combat-achievements.md
+ - Slayer Guides: boss-guides/slayer.md
+ - One Tick Guides: boss-guides/one-tick-guides.md
+ - Strategy:
+ - DPM Advice: dpm-advice
+ - Upgrade Paths: upgrading-info
+ - Miscellaneous:
+ - PVME Mastery Roles: get-involved
+ - About PVME: about
+ - Editor Resources: editor-resources
diff --git a/site_builder/navigation.py b/site_builder/navigation.py
index be11b20334..6b3292c16e 100644
--- a/site_builder/navigation.py
+++ b/site_builder/navigation.py
@@ -1,24 +1,93 @@
-from typing import List, Union, Tuple
+import yaml
+import logging
+from typing import List
+from pathlib import Path
+from collections import defaultdict
+logging.basicConfig(level=logging.DEBUG, format="%(levelname)s:%(name)s:%(message)s")
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
-class NavStructure(dict):
- def __setitem__(self, keys: List[str], value: Union[Tuple[str, str], str]):
- cur = self
- for key in keys:
- cur = cur.setdefault(key, {})
- cur[value[0]] = value[1]
+# Load NAV structure from mkdocs.yml
+def load_nav_from_mkdocs():
+ mkdocs_path = Path("mkdocs.yml")
+ with mkdocs_path.open("r", encoding="utf-8") as file:
+ config = yaml.safe_load(file)
+ return config.get("nav", [])
+HARDCODED_NAV = load_nav_from_mkdocs()
class NavInterface:
- def __init__(self, mkdocs_nav):
+ def __init__(self, mkdocs_nav, source_files, name_converter):
+ if mkdocs_nav is None:
+ raise ValueError("Error: MkDocs navigation is None. Ensure 'nav:' is properly initialized.")
+
self.__nav = mkdocs_nav
- self.__structure = NavStructure()
+ self.__source_files = source_files
+ self.__name_converter = name_converter
+ self.__structure = defaultdict(lambda: defaultdict(list))
+
+ def get_nav_structure(self):
+ """Returns the internal navigation structure."""
+ return self.__nav
- def add_item(self, category_name, forum_name, channel_name, output_file):
- keys = [category_name]
+ def add_item(self, category_name: str, forum_name: str, channel_name: str, output_file: str):
+ """Ensures Level 2 appears first, then nests Level 3 pages under it properly."""
if forum_name:
- keys.append(forum_name)
- self.__structure[keys] = channel_name, output_file
+ self.__structure[category_name][forum_name].append({channel_name: output_file})
+ else:
+ self.__structure[category_name][channel_name] = output_file
+
+ def rebuild_nav(self, mkdocs_files):
+ """Ensures that sub-pages are correctly collected and nested under their respective sections."""
+ logger.debug("🔍 Rebuilding MkDocs Navigation")
+
+ nav = []
+
+ # Force all paths to use forward slashes to avoid Windows path issues
+ all_md_files = [
+ Path(file.src_path).as_posix() for file in mkdocs_files if Path(file.src_path).suffix == ".md"
+ ]
+ logger.debug(f"📄 ALL MkDocs Markdown Files: {all_md_files}")
+
+ # Iterate through all sections in HARDCODED_NAV
+ for section in HARDCODED_NAV:
+ if isinstance(section, dict):
+ # Look for the "Boss & Slayer Guides" section and process it
+ if "Boss & Slayer Guides" in section:
+ category_entries = section["Boss & Slayer Guides"]
+ logger.debug(f"📂 Found Boss & Slayer Guides Categories: {category_entries}")
+
+ boss_guides_section = {"Boss & Slayer Guides": []}
+
+ for entry in category_entries:
+ if isinstance(entry, dict):
+ for category, folder in entry.items():
+ # Convert section name to lowercase and replace spaces with hyphens for folder path
+ category_folder = category.lower().replace(" ", "-")
+
+ # Find all markdown files for this category and subfolders (excluding 'index.md' for redirects)
+ category_guides = sorted(
+ [file for file in all_md_files if f"pvme-guides/{category_folder}/" in file and "index.md" not in file]
+ )
+
+ # Check if category guides are found, if so, add them
+ if category_guides:
+ boss_guides_section["Boss & Slayer Guides"].append({category: category_guides})
+ logger.debug(f"📌 Added {len(category_guides)} guides under '{category}'.")
+ else:
+ # Ensure empty categories still appear as placeholders
+ boss_guides_section["Boss & Slayer Guides"].append({category: folder})
+ logger.warning(f"⚠️ No guides found for '{category}', keeping as placeholder.")
+
+ # Add the Boss & Slayer Guides section to the nav
+ nav.append(boss_guides_section)
+
+ # For all other sections (excluding "Boss & Slayer Guides"), just add them to the nav
+ else:
+ nav.append(section)
- def update_mkdocs_nav(self):
- self.__nav.extend([{key: value} for key, value in self.__structure.items()])
+ # Rebuild the final nav structure, preserving the order defined in mkdocs.yml
+ self.__nav.clear()
+ self.__nav.extend(nav)
+ logger.debug(f"✅ Final Navigation Structure:\n{self.__nav}")
diff --git a/site_builder/page_generator.py b/site_builder/page_generator.py
index eaef899457..2d67a8edaf 100644
--- a/site_builder/page_generator.py
+++ b/site_builder/page_generator.py
@@ -1,19 +1,15 @@
import logging
from typing import List
from pathlib import Path
-
import mkdocs_gen_files
-
from site_builder.navigation import NavInterface
from site_builder.formatter.rules import DiscordChannelID
from site_builder.raw_message_parser import get_raw_messages
from site_builder.formatter.message_formatter import MessageFormatter
from site_builder.name_conversion import NameConverter
-
logger = logging.getLogger(__name__)
-
class PageGenerator:
def __init__(self, source_files: List[Path], name_converter: NameConverter, source_output_dir: Path):
self.__source_output_dir = source_output_dir
@@ -21,26 +17,37 @@ def __init__(self, source_files: List[Path], name_converter: NameConverter, sour
self.__name_converter = name_converter
mkdocs_process = mkdocs_gen_files.FilesEditor.current()
- self.__nav = NavInterface(mkdocs_process.config.nav)
+ mkdocs_nav = getattr(mkdocs_process.config, "nav", None)
+
+ if mkdocs_nav is None:
+ raise ValueError("Error: MkDocs navigation is not initialized. Ensure 'nav:' is defined in mkdocs.yml before running.")
+
+ self.__nav = NavInterface(mkdocs_nav, self.__source_files, self.__name_converter)
def generate_pages(self):
+ mkdocs_process = mkdocs_gen_files.FilesEditor.current()
+ mkdocs_files = mkdocs_process.files
+
for source_file in self.__source_files:
channel_name = self.__name_converter.channel(source_file)
output_file = source_file.with_suffix('.md')
self.generate_page(source_file, output_file, channel_name)
- self.__update_nav(source_file.relative_to(self.__source_output_dir).parent, output_file, channel_name)
+ self.__update_nav(source_file.parent, output_file, channel_name)
- self.__nav.update_mkdocs_nav()
+ # Rebuild navigation after processing all pages
+ self.__nav.rebuild_nav(mkdocs_files)
@staticmethod
def generate_page(source_file: Path, output_file: Path, channel_name):
- logger.debug(f"formatting: {output_file}")
+ logger.debug(f"📝 Generating page: {output_file}")
+
+ output_file.parent.mkdir(parents=True, exist_ok=True)
- # todo: work-around relative links, check if absolute links work
DiscordChannelID.CUR_FILE = output_file
- formatted_channel = f""
- for raw_message in get_raw_messages(source_file.read_text('utf-8')):
+
+ formatted_channel = f"# {channel_name}\n\n"
+ for raw_message in get_raw_messages(source_file.read_text('utf-8')):
message_formatter = MessageFormatter(raw_message)
message_formatter.format()
formatted_channel += str(message_formatter.formatted_message)
@@ -49,8 +56,91 @@ def generate_page(source_file: Path, output_file: Path, channel_name):
file.write(formatted_channel)
def __update_nav(self, category_forum_path: Path, output_file, channel_name):
- category, *forum = category_forum_path.parts
- category_name = self.__name_converter.category(category)
- forum_name = self.__name_converter.forum(forum[0]) if len(forum) > 0 else None
+ """Ensures correct Level 2 & 3 nesting in navigation."""
+ # Strip 'pvme-guides' from the path to get the actual category
+ path_parts = category_forum_path.parts[1:] # Skip the 'pvme-guides' part
+ category_name = path_parts[0].lower() # This should be the category like 'afk'
+ forum_name = path_parts[1].lower() if len(path_parts) > 1 else None # Get the forum if it exists
+
+ corrected_path = output_file.as_posix()
+
+ # Access the navigation structure
+ nav_structure = self.__nav.get_nav_structure()
+
+ # Keep a reference to the original structure
+ original_nav_structure = self.__store_original_nav_structure(nav_structure)
+
+ # Preprocess nav structure to trim paths before "/" and remove ".md"
+ self.__preprocess_nav_structure(nav_structure)
+
+ # logger.debug(f"🔍 Preprocessed nav structure: {nav_structure}")
+
+ # Iterate over the nav structure and look for the correct category and subcategory
+ for section in nav_structure:
+ if isinstance(section, dict):
+ for section_name, subcategories in section.items():
+ # Log each section and its subcategories for inspection
+ # logger.debug(f"📂 Inspecting section: {section_name} with subcategories: {subcategories}")
+
+ # Check if the category matches
+ if category_name in [item.lower() if isinstance(item, str) else item.lower() for sub_category in subcategories for item in (sub_category.values() if isinstance(sub_category, dict) else [sub_category])]:
+ logger.debug(f"✅ Found category: {category_name}")
+
+ # Iterate through subcategories in this section (level 2)
+ if isinstance(subcategories, list):
+ for sub_category in subcategories:
+ if isinstance(sub_category, dict):
+ for sub_category_name, sub_category_path in sub_category.items():
+ # Normalize subcategory name and path
+ sub_category_path_normalized = sub_category_path.lower().replace(" ", "-")
+
+ # Check if the path matches
+ if sub_category_path_normalized == category_name:
+ # Retrieve the original section name from the stored reference
+ original_section_name = original_nav_structure[section_name]
+
+ # Add the page to this subcategory using the original structure
+ original_section_name.append({channel_name: corrected_path})
+ logger.debug(f"📌 Added page under subcategory: '{sub_category_name}'")
+ return
+ else:
+ logger.warning(f"⚠️ Subcategory '{forum_name}' not found in '{category_name}'")
+ elif isinstance(sub_category, str): # Handle case when sub_category is just a string (without being inside a dictionary)
+ if sub_category.lower() == category_name:
+ # Directly add to subcategory
+ sub_category.append({channel_name: corrected_path})
+ logger.debug(f"📌 Added page under subcategory: '{sub_category}'")
+ return
+
+ else:
+ logger.warning(f"⚠️ Subcategories for '{category_name}' are not in the expected format.")
+
+ # If no matching category was found
+ logger.warning(f"⚠️ No matching category found for '{category_name}' in the nav structure.")
+
+ def __store_original_nav_structure(self, nav_structure):
+ """Store the original structure for later reference."""
+ original_structure = {}
+ for section in nav_structure:
+ if isinstance(section, dict):
+ for section_name, subcategories in section.items():
+ # Store original section names
+ original_structure[section_name] = subcategories
+ return original_structure
+
+ def __preprocess_nav_structure(self, nav_structure):
+ """Preprocess nav structure to trim paths and remove extensions"""
+ for section in nav_structure:
+ if isinstance(section, dict):
+ for section_name, subcategories in section.items():
+ # If section has subcategories (nested dictionaries)
+ if isinstance(subcategories, list):
+ for sub_category in subcategories:
+ for sub_category_name, sub_category_path in sub_category.items():
+ # If path contains "/", remove everything before it and also remove ".md"
+ sub_category_path = sub_category_path.split('/')[1] if '/' in sub_category_path else sub_category_path
+ sub_category_path = sub_category_path.replace(".md", "").lower()
+
+ # Update subcategory path
+ sub_category[sub_category_name] = sub_category_path
- self.__nav.add_item(category_name, forum_name, channel_name, output_file.as_posix())
\ No newline at end of file
diff --git a/structure_settings.yml b/structure_settings.yml
index 3466662713..df7671fd40 100644
--- a/structure_settings.yml
+++ b/structure_settings.yml
@@ -1,45 +1,29 @@
files:
includes:
- - getting-started/quick-start.txt
- - getting-started/how-to-use-pvme.txt
- - getting-started/interface-guide.txt
- - getting-started/early-moneymaking-ideas.txt
- - getting-started/noncombat-skilling-guides.txt
-
+ # Getting Started
+ - getting-started/**/*.txt
- new-to-pvm/**/*.txt
-
- new-to-bossing/**/*.txt
+ # Invention & Perks
- invention-and-perks/**/*.txt
-
- - miscellaneous-information/**/*.txt
-
- - upgrading-info/upgrade-order/*.txt
- - upgrading-info/**/*.txt
-
- - dpm-advice/**/*.txt
-
- - afk/afk-overview.txt
- - afk/**/*.txt
-
- - basic-guides/**/*.txt
-
- - rs3-full-boss-guides/**/*.txt
- - slayer/overview-of-slayer.txt
- - slayer/block-prefer-list.txt
- - slayer/ultimate-slayer.txt
- - slayer/miscellaneous-slayer.txt
- - slayer/**/*.txt
+ # Guides
+ - basic-guides/**/*.{txt,md}
+ - rs3-full-boss-guides/**/*.{txt,md}
+ - afk/**/*.{txt,md}
+ - combat-achievements/**/*.{txt,md}
+ - slayer/**/*.{txt,md}
+ - one-tick-guides/**/*.{txt,md}
- - get-involved/mastery-requirements.txt
- - get-involved/feats-requirements.txt
+ # Strategy
+ - dpm-advice/**/*.{txt,md}
+ - upgrading-info/**/*.{txt,md}
- - editor-resources/intro-to-editing.txt
- - editor-resources/intro-to-editing-continued.txt
- - editor-resources/editor-tools/*.txt
- - editor-resources/editor-references/*.txt
- - editor-resources/github-tutorials/*.txt
+ # Miscellaneous
+ - get-involved/**/*.{txt,md}
+ - editor-resources/**/*.{txt,md}
+ - miscellaneous-information/**/*.{txt,md}
excludes:
- navigation/**/*.txt
@@ -52,41 +36,35 @@ files:
- get-involved/editor-entry.txt
- .github/**/*
-# uncategorized:
-# - '**/*.txt'
-
name_convert:
category:
+ getting-started:
+ alias: Getting Started
+ new-to-pvm:
+ alias: New to PvM
+ new-to-bossing:
+ alias: New to Bossing
invention-and-perks:
- alias: perks
- emoji: "689509250946695292"
- miscellaneous-information:
- alias: miscellaneous
- upgrading-info:
- alias: upgrades
- slayer:
- emoji: "797896049548066857"
+ alias: Invention & Perks
+ basic-guides:
+ alias: Basic Guides
rs3-full-boss-guides:
- alias: full boss guides
+ alias: RS3 Full Boss Guides
+ afk:
+ alias: AFK Methods
+ combat-achievements:
+ alias: Combat Achievements
+ slayer:
+ alias: Slayer Guides
+ one-tick-guides:
+ alias: One Tick Guides
+ dpm-advice:
+ alias: Strategy
+ upgrading-info:
+ alias: Strategy
get-involved:
- alias: mastery
+ alias: PVME Mastery Roles
editor-resources:
- alias: editor info
-
- extra_channel:
- AFK Commander Zilyana Hard Mode: afk commander zilyana hm
- AFK General Graardor Hard Mode: afk general graardor hm
- AFK Kree-arra Hard Mode: afk kree'arra hm
- AFK K'ril Tsutsaroth Hard Mode: afk k'ril tsutsaroth hm
-
- word:
- osrs: OSRS
- gwd1: GWD1
- gwd2: GWD2
- pvm: PvM
- afk: AFK
- ed1: ED1
- ed2: ED2
- ed3: ED3
- dpm: DPM
- hm: HM
+ alias: Editor Resources
+ miscellaneous-information:
+ alias: Miscellaneous
\ No newline at end of file