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