From e36f2e1c19b21f5140bbc6bedfc09401471f31fa Mon Sep 17 00:00:00 2001 From: Mara-Li Date: Tue, 24 Dec 2024 11:52:39 +0100 Subject: [PATCH] create files --- .github/find_unused_image.py | 65 ++++++ .github/workflows/deploy.yml | 55 +++++ .github/workflows/find_unused_images.yml | 51 +++++ .github/workflows/generate.yml | 134 ++++++++++++ .github/workflows/index.yml | 43 ++++ .github/workflows/optimize.yml | 23 ++ README.md | 6 +- docs/_assets/icons/file.svg | 1 + docs/_assets/icons/folder-open.svg | 1 + docs/_assets/icons/index.svg | 1 + docs/_assets/icons/owl.svg | 10 + docs/_assets/meta/error_404.svg | 1 + docs/_assets/meta/favicons.png | Bin 0 -> 14960 bytes docs/_assets/meta/logo.png | Bin 0 -> 3463 bytes docs/_static/css/admonition.css | 0 docs/_static/css/custom_attributes.css | 0 docs/_static/css/customization.css | 0 docs/_static/js/mathjax.js | 20 ++ docs/_static/js/snippets.js | 8 + docs/tags.md | 1 + generate_template.py | 84 ++++++++ mkdocs.yml | 151 +++++++++++++ overrides/404.html | 22 ++ overrides/article.html | 7 + overrides/hooks/category.py | 110 ++++++++++ overrides/hooks/on_env.py | 200 ++++++++++++++++++ overrides/hooks/on_page_markdown.py | 51 +++++ overrides/hooks/on_post_page.py | 15 ++ overrides/index.html | 25 +++ overrides/main.html | 40 ++++ overrides/partials/comments.html | 12 ++ overrides/partials/content.html | 54 +++++ overrides/partials/css_class.html | 17 ++ overrides/partials/meta.html | 62 ++++++ overrides/partials/nav-item.html | 162 ++++++++++++++ overrides/partials/post-list.html | 257 +++++++++++++++++++++++ overrides/partials/source-file.html | 38 ++++ overrides/partials/tabs-item.html | 43 ++++ pyproject.toml | 82 ++++++++ 39 files changed, 1850 insertions(+), 2 deletions(-) create mode 100644 .github/find_unused_image.py create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/find_unused_images.yml create mode 100644 .github/workflows/generate.yml create mode 100644 .github/workflows/index.yml create mode 100644 .github/workflows/optimize.yml create mode 100644 docs/_assets/icons/file.svg create mode 100644 docs/_assets/icons/folder-open.svg create mode 100644 docs/_assets/icons/index.svg create mode 100644 docs/_assets/icons/owl.svg create mode 100644 docs/_assets/meta/error_404.svg create mode 100644 docs/_assets/meta/favicons.png create mode 100644 docs/_assets/meta/logo.png create mode 100644 docs/_static/css/admonition.css create mode 100644 docs/_static/css/custom_attributes.css create mode 100644 docs/_static/css/customization.css create mode 100644 docs/_static/js/mathjax.js create mode 100644 docs/_static/js/snippets.js create mode 100644 docs/tags.md create mode 100644 generate_template.py create mode 100644 mkdocs.yml create mode 100644 overrides/404.html create mode 100644 overrides/article.html create mode 100644 overrides/hooks/category.py create mode 100644 overrides/hooks/on_env.py create mode 100644 overrides/hooks/on_page_markdown.py create mode 100644 overrides/hooks/on_post_page.py create mode 100644 overrides/index.html create mode 100644 overrides/main.html create mode 100644 overrides/partials/comments.html create mode 100644 overrides/partials/content.html create mode 100644 overrides/partials/css_class.html create mode 100644 overrides/partials/meta.html create mode 100644 overrides/partials/nav-item.html create mode 100644 overrides/partials/post-list.html create mode 100644 overrides/partials/source-file.html create mode 100644 overrides/partials/tabs-item.html create mode 100644 pyproject.toml diff --git a/.github/find_unused_image.py b/.github/find_unused_image.py new file mode 100644 index 0000000..e6cff43 --- /dev/null +++ b/.github/find_unused_image.py @@ -0,0 +1,65 @@ +from pathlib import Path +import os +import argparse +from typing import Optional +import yaml + + +def find_unused_media(img_path: Optional[Path] = None, dry_run: bool = False): + # load mkdocs.yml + with open("mkdocs.yml", "r", encoding="utf-8") as f: + # remove all !! pairs from the yaml file + data = f.read() + data = data.replace("!!", "") + config = yaml.safe_load(data) + + docs_dir = Path.cwd() / Path(config.get("docs_dir", "docs")) + assets_dir = Path(docs_dir, config["extra"]["attachments"]) + print(f"Looking for unused images in {assets_dir}...") + if img_path: + assets_dir = img_path + + images = [ + file + for file in assets_dir.rglob("*") + if file.is_file() and file.suffix in [".png", ".jpg", ".jpeg", ".gif", ".svg"] + ] + md_files = [file for file in docs_dir.rglob("*.md") if file.is_file()] + + # Search for images in markdown files + + used_images = [] + + for md_file in md_files: + for image in images: + with open(md_file, "r", encoding="utf-8") as f: + if image.name in f.read(): + used_images.append(image) + + # compare the two lists + unused_images = [image for image in images if image not in used_images] + + # delete unused images + + if unused_images: + print(f"Found {len(unused_images)} unused images in {assets_dir}. Deleting...") + for image in unused_images: + if not dry_run: + print(image) + os.remove(image) + else: + print(f"Would delete {image}") + else: + print(f"Found no unused images in {assets_dir}.") + + +if __name__ == "__main__": + # use argparse to get the path to the assets folder + parser = argparse.ArgumentParser() + parser.add_argument("--path", type=str, help="Path to the assets folder") + parser.add_argument( + "--dry-run", action="store_true", help="Do not delete unused images" + ) + args = parser.parse_args() + path = Path(args.path) if args.path else None + find_unused_media(img_path=path, dry_run=args.dry_run) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..be24c91 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,55 @@ +name: Publish + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'README.md' + - 'overrides/**' + - 'docs/**' + - 'mkdocs.yml' + - 'uv.lock' + repository_dispatch: + types: [build] + +permissions: + contents: write + pages: write + id-token: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_PAT }} + fetch-depth: 0 + submodules: 'recursive' + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + - name: "Submodule fetching" + continue-on-error: true + run: | + git submodule update --init --recursive --checkout -f --remote -- "docs" + git config --global user.name "GitHub Action" + git config --global user.email "noreply@github.com" + git commit -am "chore (update): fetch submodule" + git push + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + cache: "pip" + - name: Install dependencies + run: uv sync + - name: Build + run: | + uv run mkdocs gh-deploy --force + diff --git a/.github/workflows/find_unused_images.yml b/.github/workflows/find_unused_images.yml new file mode 100644 index 0000000..e8cfaf4 --- /dev/null +++ b/.github/workflows/find_unused_images.yml @@ -0,0 +1,51 @@ +name: Repository maintenance +on: + workflow_call: + inputs: + CLEAN: + description: "Clean unused images" + required: false + default: true + type: boolean + DRY_RUN: + description: "Dry run" + required: false + default: false + type: boolean + BRANCH: + type: string + description: 'Branch to push changes to' + required: false + default: 'main' + secrets: + GH_PAT: + required: true + author_email: + required: false + description: "The author email" + author_name: + required: false + description: "The author name" +jobs: + unused_images: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.13 + cache: pipenv + - name: Clean + if: inputs.CLEAN + run: | + echo "Cleaning" + pipenv install pyyaml + pipenv run python .github/find_unused_image.py + - name: Commit + if: inputs.CLEAN && inputs.DRY_RUN == false + uses: actions-js/push@master + with: + github_token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN}} + author_email: ${{ secrets.AUTHOR_EMAIL || 'github-actions[bot]@users.noreply.github.com' }} + author_name: ${{ secrets.AUTHOR_NAME || 'github-actions[bot]' }} + branch: ${{ inputs.BRANCH }} diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml new file mode 100644 index 0000000..3ff6b34 --- /dev/null +++ b/.github/workflows/generate.yml @@ -0,0 +1,134 @@ +name: Generate website + +on: + workflow_dispatch: + inputs: + site_name: + type: string + required: true + description: "The name of the site" + site_url: + type: string + required: true + description: "The url of the site" + site_description: + type: string + required: true + description: "The description of the site" + site_author: + type: string + required: true + description: "The author of the site" + language: + type: string + required: true + description: "The language of the site" + auto_h1: + type: boolean + required: false + description: "Automatically add h1 to pages" + default: false + comments: + type: boolean + required: false + description: "Enable comments" + default: false + auto_merge: + description: "Automatically merge the pull request" + required: false + default: false + type: boolean + +permissions: + contents: write + pull-requests: write + +jobs: + run: + runs-on: ubuntu-latest + steps: + - name: Print github.event.inputs + run: | + echo "📫 Commands arguments" >> $GITHUB_STEP_SUMMARY + echo "- site_name: ${{ github.event.inputs.site_name }}" >> $GITHUB_STEP_SUMMARY + echo "- site_url: ${{ github.event.inputs.site_url }}" >> $GITHUB_STEP_SUMMARY + echo "- site_description: ${{ github.event.inputs.site_description }}" >> $GITHUB_STEP_SUMMARY + echo "- site_author: ${{ github.event.inputs.site_author }}" >> $GITHUB_STEP_SUMMARY + echo "- language: ${{ github.event.inputs.language }}" >> $GITHUB_STEP_SUMMARY + echo "- auto_h1: ${{ github.event.inputs.auto_h1 }}" >> $GITHUB_STEP_SUMMARY + echo "- comments: ${{ github.event.inputs.comments }}" >> $GITHUB_STEP_SUMMARY + echo "- template_type: ${{ github.event.inputs.template_type }}" >> $GITHUB_STEP_SUMMARY + echo "ACT: ${{ env.ACT }}" + - name: checkout repo + uses: actions/checkout@v4 + - name: Install python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + pip install pydantic requests + - name: Format arguments + id: args + run: + | + auto_h1='' + comments='' + if [ "${{ github.event.inputs.auto_h1 }}" = "true" ]; then + auto_h1='--auto-h1' + fi + if [ "${{ github.event.inputs.comments }}" = "true" ]; then + comments='--comments' + fi + echo "auto_h1=$auto_h1" >> $GITHUB_OUTPUT + echo "comments=$comments" >> $GITHUB_OUTPUT + echo "- auto_h1: $auto_h1" >> $GITHUB_STEP_SUMMARY + echo "- comments: $comments" >> $GITHUB_STEP_SUMMARY + - name: Generate files + run: | + echo "📝 Generate files" + pwd + ls *.py + echo "📝 Generate files" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + python3 generate_template.py "${{github.event.inputs.site_name}}" "${{github.event.inputs.site_url}}" "${{github.event.inputs.site_description}}" "${{github.event.inputs.site_author}}" "${{github.event.inputs.language}}" ${{ steps.args.outputs.auto_h1 }} ${{ steps.args.outputs.comments }} >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + - name: Pull Request + id: cpr + uses: peter-evans/create-pull-request@v5 + with: + commit-message: Generating template + title: "First commit -- Generate template" + body: | + 📫 Commands arguments + - site_name: ${{ github.event.inputs.site_name }} + - site_url: ${{ github.event.inputs.site_url }} + - site_description: ${{ github.event.inputs.site_description }} + - site_author: ${{ github.event.inputs.site_author }} + - language: ${{ github.event.inputs.language }} + - auto_h1: ${{ github.event.inputs.auto_h1 }} + - comments: ${{ github.event.inputs.comments }} + - Generated by [Create Pull Request](https://github.com/peter-evans/create-pull-request) + labels: | + generate + branch: generate + base: "main" + delete-branch: true + token: ${{ secrets.GH_TOKEN }} + - name: AutoMerging + id: automerge + if: ${{ inputs.AUTO_MERGE }} + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + gh pr merge ${{ steps.cpr.outputs.pull-request-number }} --squash + echo "Pull Request merged" + echo "done=true" >> $GITHUB_OUTPUT + - name: Delete update branch if possible + if: ${{ inputs.AUTO_MERGE && steps.automerge.outputs.done == 'true' }} + run: | + # verify if the branch exists + if [[ $(git ls-remote --heads origin generate | wc -l) -eq 1 ]]; then + git push origin --delete generate + fi \ No newline at end of file diff --git a/.github/workflows/index.yml b/.github/workflows/index.yml new file mode 100644 index 0000000..44b6438 --- /dev/null +++ b/.github/workflows/index.yml @@ -0,0 +1,43 @@ +name: Index Creation + +on: + workflow_dispatch: + inputs: + category_name: + type: string + required: true + description: "The new folder name" + path: + type: string + required: false + description: "The path of the new folder. Ex: category/subcategory" + description: + type: string + required: false + description: "The description of the category." + toc_hide: + type: boolean + required: false + description: "Hide the toc in the index file." + nav_hide: + type: boolean + required: false + description: "Hide the navigation menu in the index file." + dry_run: + type: boolean + required: false + description: "Do not create new category index, just log." + +jobs: + run: + uses: Enveloppe/actions/.github/workflows/index.yml@main + with: + category_name: ${{ inputs.category_name}} + path: ${{ inputs.path}} + description: ${{ inputs.description}} + toc_hide: ${{ inputs.toc_hide}} + nav_hide: ${{ inputs.nav_hide}} + dry_run: ${{ inputs.dry_run}} + secrets: + GH_PAT: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/optimize.yml b/.github/workflows/optimize.yml new file mode 100644 index 0000000..65fbda2 --- /dev/null +++ b/.github/workflows/optimize.yml @@ -0,0 +1,23 @@ +name: Optimize images +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + optimize-images: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: optimize + uses: calibreapp/image-actions@main + with: + githubToken: ${{ secrets.GITHUB_TOKEN}} + ignorePaths: "_assets/img/avatar_index.gif,_assets/meta/**" + compressOnly: true + - name: "Commit & push" + uses: actions-js/push@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} + message: "[CI] Optimize images" diff --git a/README.md b/README.md index 2dc1591..8dbe64a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ -# mkdocs -Mkdocs V2 electric bungalow +# Requirements +- Python 3.12 or higher +- [uv](https://docs.astral.sh/uv/) + diff --git a/docs/_assets/icons/file.svg b/docs/_assets/icons/file.svg new file mode 100644 index 0000000..c0c3274 --- /dev/null +++ b/docs/_assets/icons/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/_assets/icons/folder-open.svg b/docs/_assets/icons/folder-open.svg new file mode 100644 index 0000000..6cb81f2 --- /dev/null +++ b/docs/_assets/icons/folder-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/_assets/icons/index.svg b/docs/_assets/icons/index.svg new file mode 100644 index 0000000..72af73e --- /dev/null +++ b/docs/_assets/icons/index.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/_assets/icons/owl.svg b/docs/_assets/icons/owl.svg new file mode 100644 index 0000000..4067c79 --- /dev/null +++ b/docs/_assets/icons/owl.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/docs/_assets/meta/error_404.svg b/docs/_assets/meta/error_404.svg new file mode 100644 index 0000000..4185a95 --- /dev/null +++ b/docs/_assets/meta/error_404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/_assets/meta/favicons.png b/docs/_assets/meta/favicons.png new file mode 100644 index 0000000000000000000000000000000000000000..3a49d34b581722921688fe4a317e7d4006d95602 GIT binary patch literal 14960 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 literal 0 HcmV?d00001 diff --git a/docs/_assets/meta/logo.png b/docs/_assets/meta/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..07696739d7ef0851d6f1592b5a713b358afbfed7 GIT binary patch literal 3463 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 { + MathJax.startup.output.clearCache() + MathJax.typesetClear() + MathJax.texReset() + MathJax.typesetPromise() +}) + diff --git a/docs/_static/js/snippets.js b/docs/_static/js/snippets.js new file mode 100644 index 0000000..51e7bfe --- /dev/null +++ b/docs/_static/js/snippets.js @@ -0,0 +1,8 @@ +//apply code font on numbers for wiki table admonition + +const admonition = document.querySelectorAll(".admonition.wiki table td"); +admonition.forEach((item) => { + if (item.textContent.match(/^\d+$/)) { + item.style.fontFamily = "var(--md-code-font)"; + } +}); diff --git a/docs/tags.md b/docs/tags.md new file mode 100644 index 0000000..c4a3cae --- /dev/null +++ b/docs/tags.md @@ -0,0 +1 @@ +[TAGS] \ No newline at end of file diff --git a/generate_template.py b/generate_template.py new file mode 100644 index 0000000..44b9c1e --- /dev/null +++ b/generate_template.py @@ -0,0 +1,84 @@ +import argparse +from pathlib import Path +from string import Template +from typing import Literal + +import requests +from pydantic import BaseModel + + +class TemplateModel(BaseModel): + site_name: str + site_url: str + site_description: str + site_author: str + language: str + auto_h1: bool + comments: bool + generate_graph: bool = False + + +class Environment(BaseModel): + env: str + deploy: str + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Generate a template for Obsidian Publisher" + ) + parser.add_argument("site_name", type=str, help="The name of the site") + parser.add_argument("site_url", type=str, help="The url of the site") + parser.add_argument( + "site_description", type=str, help="The description of the site" + ) + parser.add_argument("site_author", type=str, help="The author of the site") + parser.add_argument("language", type=str, help="The language of the site") + parser.add_argument( + "--auto-h1", action="store_true", help="Automatically add h1 to the title" + ) + parser.add_argument( + "--comments", action="store_true", help="Enable comments on the site" + ) + + args = parser.parse_args() + + template = TemplateModel( + template_type=args.template, + site_name=args.site_name, + site_url=args.site_url, + site_description=args.site_description, + site_author=args.site_author, + language=args.language, + auto_h1=args.auto_h1, + comments=args.comments, + generate_graph=True if args.template == "gh_pages" else False, + ) + # download the files + + mkdocs_yaml = Path("mkdocs.yml") + with mkdocs_yaml.open("r", encoding="UTF-8") as f: + mkdocs = f.read() + mkdocs = Template(mkdocs) + s = mkdocs.substitute( + site_name=template.site_name, + site_url=template.site_url, + site_description=template.site_description, + site_author=template.site_author, + language=template.language, + auto_h1=template.auto_h1, + comments=template.comments, + generate_graph=template.generate_graph, + ) + + + with mkdocs_yaml.open("w", encoding="UTF-8") as f: + f.write(s) + + print("Mkdocs template generated:") + print(s) + print("Done!") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..09e613c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,151 @@ +site_name: $site_name # Change this to your site name +site_description: $site_description +site_url: $site_url # Change this to your site URL +site_author: $site_author + +theme: + name: 'material' + logo: _assets/meta/logo.png + favicon: _assets/meta/favicons.png + custom_dir: overrides + font: + text: Karla + code: Ubuntu Mono + language: $language + palette: + # Light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: teal + accent: light blue + toggle: + icon: material/weather-night + name: Passer au mode sombre + + # Dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: blue grey + accent: light blue + toggle: + icon: material/weather-sunny + name: Passer en mode clair + features: + - navigation.indexes + - navigation.top + - navigation.tabs + - navigation.tabs.sticky + - search.suggest + - search.highlight +# Extensions +markdown_extensions: + - footnotes + - nl2br + - attr_list + - md_in_html + - sane_lists + - meta + - smarty + - tables + - mdx_breakless_lists + - def_list + - pymdownx.arithmatex: + generic: true + - pymdownx.details + - pymdownx.magiclink + - pymdownx.critic + - pymdownx.caret + - pymdownx.keys + - pymdownx.mark + - pymdownx.tilde + - pymdownx.highlight: + use_pygments: true + anchor_linenums: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + options: + custom_icons: + - overrides/icons + - admonition + - toc: + permalink: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences: + custom_fences: + - name: dataview + class: dataview + format: !!python/name:pymdownx.superfences.fence_div_format + - name: folderv + class: folderv + format: !!python/name:pymdownx.superfences.fence_div_format + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + preserve_tabs: true +plugins: + - search + - meta-descriptions + - awesome-pages + - blog: + blog_dir: blog + blog_toc: true + post_readtime_words_per_minute: 300 + - git-revision-date-localized: + type: date + fallback_to_build_date: true + locale: fr + custom_format: "%A %d %B %Y" + enable_creation_date: true + - ezlinks: + wikilinks: true + - embed_file: + callouts: true + custom-attributes: '_static/css/custom_attributes.css' + - custom-attributes: + file: '_static/css/custom_attributes.css' + - tags: + tags_file: tags.md + - callouts + - glightbox +hooks: + - overrides/hooks/on_page_markdown.py + - overrides/hooks/on_env.py + - overrides/hooks/on_post_page.py +extra_javascript: + - _static/js/mathjax.js + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js + - _static/js/snippets.js + - https://cdn.jsdelivr.net/gh/Enveloppe/_static@refs/heads/master/dist/index.js + +extra_css: + - https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css + - _static/css/admonition.css + - _static/css/custom_attributes.css + - _static/css/customization.css + - https://cdn.jsdelivr.net/gh/Enveloppe/_static@refs/heads/master/dist/styles.css +extra: + SEO: '_assets/meta/logo.png' + comments: $comments + attachments: '_assets/img/' + no-auto-h1: $auto_h1 + social: + - icon: fontawesome/brands/github + link: https://github.com/Enveloppe/ + blog_list: + pagination: true + pagination_message: true + pagination_translation: 'posts in' + no_page_found: 'No pages found!' + hooks: + strip_comments: true + fix_heading: true + icons: + folder: "icons/" #from root _assets ; see overrides/partials/nav-item.html and overrides/partials/tabs-item.html + default: + file: "file.svg" + folder: "folder-open.svg" + index: "index.svg" diff --git a/overrides/404.html b/overrides/404.html new file mode 100644 index 0000000..8f39318 --- /dev/null +++ b/overrides/404.html @@ -0,0 +1,22 @@ +{% extends "main.html" %} + + +{% block content %} + + +{% endblock %} \ No newline at end of file diff --git a/overrides/article.html b/overrides/article.html new file mode 100644 index 0000000..8eb4b3a --- /dev/null +++ b/overrides/article.html @@ -0,0 +1,7 @@ +{# template #} +{% extends "index.html" %} + +{# content #} +{% block content %} +{% include "partials/post-list.html" %} +{% endblock %} \ No newline at end of file diff --git a/overrides/hooks/category.py b/overrides/hooks/category.py new file mode 100644 index 0000000..0848026 --- /dev/null +++ b/overrides/hooks/category.py @@ -0,0 +1,110 @@ +""" +A simple script that help to create a category in the database. Create the folder and the `index.md` file. +Usage : +usage: category.py [-h] [--parent PARENT] [--description DESCRIPTION] [--toc] [--nav] name +positional arguments: + name Name of the category. + +options: + -h, --help show this help message and exit + --parent PARENT Parent category. + --description DESCRIPTION Description of the category. + --toc hide toc + --nav hide nav +""" + +import argparse +import yaml +from pathlib import Path +import re + + +def create_category(path, index_contents): + path.mkdir(parents=True, exist_ok=True) + index_file = Path(path, "index.md") + with open(index_file, "w", encoding="utf-8") as f: + f.write(index_contents.lstrip()) + + +def index_contents(name, description_yaml, hider, description): + index_contents = f""" + --- + index: true{description_yaml} + hidden: true + category: {name} + template: article.html + comments: false + title: {name}{hider} + --- + {description} + """ + index_contents = re.sub(" ", "", index_contents) + return index_contents + + +def resolving_args(args, docs_dir): + if args.parent: + path = Path(docs_dir, args.parent, args.name) + else: + path = Path(docs_dir, args.name) + if args.description: + description_yaml = "\ndescription: " + args.description + description_contents = args.description + else: + description_yaml = "" + description_contents = "" + hider = [] + if args.toc: + hider.append("toc") + if args.nav: + hider.append("navigation") + hider = "\nhide:\n- " + "\n- ".join(hider) if hider else "" + return path, description_yaml, hider, description_contents + + +def main(): + parser = argparse.ArgumentParser( + description="Create a new category your mkdocs documentations from your docs (or configured directory)." + ) + parser.add_argument("name", help="Name of the category.") + parser.add_argument( + "--parent", help='Parent category, in path. Example : "category/subcategory"' + ) + parser.add_argument("--description", help="Description of the category.") + parser.add_argument("--toc", help="hide toc", action="store_true") + parser.add_argument("--nav", help="hide nav", action="store_true") + parser.add_argument( + "--dry-run", + help="Dry run, do not create the category, just log", + action="store_true", + ) + args = parser.parse_args() + + mkdocs_config = Path("mkdocs.yml").resolve() + with open(mkdocs_config, "r", encoding="utf-8") as f: + config = yaml.load(f, Loader=yaml.BaseLoader) + docs_dir = config.get("docs_dir", "docs") + docs_dir = Path(docs_dir).resolve() + path, description_yaml, hider, description_contents = resolving_args(args, docs_dir) + index = index_contents(args.name, description_yaml, hider, description_contents) + if not args.dry_run: + print("📤 Creating category 📤...") + print( + f"\nArguments used :\n- Name: {args.name}\n- Parents: {args.parent}\n- Description: {args.description}\n- Toc: {args.toc}\n- Nav: {args.nav}\n" + ) + print(f"📌 Creating with path : {path}") + create_category(path, index) + print("Category created ! 🎉") + else: + print("🐍 Dry Run 🐍") + print( + f"\nArguments used :\n- Name: {args.name}\n- Parents: {args.parent}\n- Description: {args.description}\n- Toc: {args.toc}\n- Nav: {args.nav}\n" + ) + print(f"🚃 Path : {path}") + print(f"🗃️ Index : {index}") + + +if __name__ == "__main__": + main() + print("Done.") + exit(0) diff --git a/overrides/hooks/on_env.py b/overrides/hooks/on_env.py new file mode 100644 index 0000000..18c6e24 --- /dev/null +++ b/overrides/hooks/on_env.py @@ -0,0 +1,200 @@ +import os +import re +import urllib.parse +import datetime +from pathlib import Path + +import mkdocs.structure.pages +from babel.dates import format_date +from dateutil import parser + + +def get_last_part_URL(url): + """Get the last part of an URL. + + Args: + url (str): the URL + + Returns: + str: the last part of the URL + """ + if not url.endswith("/"): + url = url + "/" + head, tail = os.path.split(url) + return "/" + tail if tail != "" else "" + + +def regex_replace(s, find, replace): + """A non-optimal implementation of a regex filter""" + return re.sub(find, replace, s) + + +def log(text): + """Prints text to the console, in case you need to debug something. + + Using mainly in the template files. + Parameters: + text (str): The text to print. + Returns: + str: An empty string. + """ + print(text) + return "" + + +def time_time(time): + """Converts a time string to a human-readable format. + + Parameters: + time (any): The time string to convert. + Returns: + str|datetime: The converted time. + """ + time = time.replace("-", "/") + time = parser.parse(time).isoformat() + try: + time = datetime.datetime.fromisoformat(time) + return datetime.datetime.strftime(time, "%d %B %Y") + except AttributeError: + return datetime.datetime.strftime(str(time), "%d %B %Y") + except ValueError: + print("value error!") + return time + + +def to_local_time(time, locale): + """Convert to local time. + + Args: + time (any): the time to convert + locale (any): the locale to use + + Returns: + str: the converted time + """ + if isinstance(time, datetime.time) or isinstance(time, datetime.date): + time = time.isoformat() + date = time.replace("-", "/") + date = parser.parse(date) + return format_date(date, locale=locale) + + +def time_todatetime(time): + """convert time to datetime. + + Args: + time (any): time to convert + + Returns: + datetime: the converted time + """ + return parser.parse(time) + + +def time_to_iso(time): + """Convert time to ISO format. + + Args: + time (any): Time to convert + + Returns: + any|str: convert time or the original time if error + """ + if isinstance(time, datetime.time) or isinstance(time, datetime.date): + time = time.isoformat() + time = time.replace("-", "/") + try: + return parser.parse(time).isoformat() + except AttributeError: + return time + + +def page_exists(page): + """Check if a page exists. + + Args: + page (any): The page to check + + Returns: + bool: true if exists + """ + return Path(page).exists() + + +def url_decode(url): + """decode an url in a template. + + Args: + url (any): THE URL + + Returns: + str : the decoded url + """ + return urllib.parse.unquote(url) + + +def value_in_frontmatter(key, metadata): + """Check if a key exists in a dictionnary. + + Args: + key (any): the key to check + metadata (any): the dictionnary to check + + Returns: + bool: true if exists + """ + if key in metadata: + return metadata[key] + else: + return None + + +def icon_exists(path, config): + path = Path(config["docs_dir"]) / "_assets" / path + return Path(path).exists() + + +def replace_by_name(name: list[str] | str): + if isinstance(name, list): + return " ".join(name) + return name + + +def first(name: list[str] | str): + if isinstance(name, list): + return name[0] + return name + + +def links(name: list[str] | str): + if isinstance(name, list): + return "/".join(name) + return name + + +def is_section(path): + if isinstance(path, mkdocs.structure.pages.Page): + return False + return True + + +def on_env(env, config, files, **kwargs): + static_path = os.path.join(config["docs_dir"], "_assets") + if static_path not in env.loader.searchpath: + env.loader.searchpath.append(static_path) + env.filters["convert_time"] = time_time + env.filters["iso_time"] = time_to_iso + env.filters["time_todatetime"] = time_todatetime + env.filters["page_exists"] = page_exists + env.filters["url_decode"] = url_decode + env.filters["log"] = log + env.filters["to_local_time"] = to_local_time + env.filters["value_in_frontmatter"] = value_in_frontmatter + env.filters["regex_replace"] = regex_replace + env.filters["get_last_part_URL"] = get_last_part_URL + env.filters["icon_exists"] = lambda path: icon_exists(path, config) + env.filters["is_section"] = is_section + env.filters["replace_by_name"] = replace_by_name + env.filters["first"] = first + env.filters["links"] = links + return env diff --git a/overrides/hooks/on_page_markdown.py b/overrides/hooks/on_page_markdown.py new file mode 100644 index 0000000..9d498dc --- /dev/null +++ b/overrides/hooks/on_page_markdown.py @@ -0,0 +1,51 @@ +import re + + +def non_breaking_space(markdown): + return re.sub( + "[\u00a0\u1680\u180e\u2000-\u200b\u202f\u205f\u3000\ufeff]", " ", markdown + ) + + +def update_heading(markdown): + file_content = markdown.split("\n") + markdown = "" + code = False + for line in file_content: + if not code: + if line.startswith("```"): + code = True + elif line.startswith("#") and line.count("#") <= 5: + heading_number = line.count("#") + 1 + line = "#" * heading_number + " " + line.replace("#", "") + elif line.startswith("```") and code: + code = True + markdown += line + "\n" + return markdown + + +def strip_comments(markdown): + return re.sub(r"%%.*?%%", "", markdown, flags=re.DOTALL) + + +def fix_tags(metadata): + tags = metadata.get("tags", None) or metadata.get("tag", None) + if tags and isinstance(tags, str): + tags = tags.split("/") + tags = [tag.strip() for tag in tags] + metadata["tags"] = tags + return metadata + + +def on_page_markdown(markdown, files, page, config, **kwargs): + config_hooks = config.get( + "extra", {"hooks": {"strip_comments": True, "fix_heading": False}} + ).get("hooks", {"strip_comments": True, "fix_heading": False}) + if config_hooks.get("strip_comments", True): + markdown = strip_comments(markdown) + if config_hooks.get("fix_heading", False): + markdown = update_heading(markdown) + metadata = fix_tags(page.meta) + page.meta = metadata + markdown = non_breaking_space(markdown) + return markdown diff --git a/overrides/hooks/on_post_page.py b/overrides/hooks/on_post_page.py new file mode 100644 index 0000000..d2a5ce7 --- /dev/null +++ b/overrides/hooks/on_post_page.py @@ -0,0 +1,15 @@ +from bs4 import BeautifulSoup + + +def on_post_page(output, page, config) -> str: + soup = BeautifulSoup(output, "html.parser") + for a_tag in soup.find_all("a", {"class": "ezlinks_not_found"}): + a_tag["class"] = a_tag.get("class", []) + ["ezlinks_not_found"] + new_tag = soup.new_tag("span") + new_tag.string = a_tag.string or a_tag.get("href", "File not found") + for attr in a_tag.attrs: + if attr != "href": + new_tag[attr] = a_tag[attr] + new_tag["src"] = a_tag["href"] + a_tag.replaceWith(new_tag) + return str(soup) diff --git a/overrides/index.html b/overrides/index.html new file mode 100644 index 0000000..08f9424 --- /dev/null +++ b/overrides/index.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block extrahead %} + + + + + + {% include "partials/meta.html" %} +{% endblock %} +{% block content %} + {{ super() }} +{% endblock %} \ No newline at end of file diff --git a/overrides/main.html b/overrides/main.html new file mode 100644 index 0000000..a39ff1c --- /dev/null +++ b/overrides/main.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% block extrahead %} + + + + + + {% include "partials/meta.html" %} +{% endblock %} +{% block content %} + {{ super() }} + {% include "partials/css_class.html" %} + {% include "partials/comments.html" %} + {% set pg_title = 'title' | value_in_frontmatter(page.meta) %} + {% set pg_title = page.meta.title if pg_title is not none else page.title %} + {% set pg_title = page.parent.title if pg_title == 'Index' else pg_title %} + +{% endblock %} \ No newline at end of file diff --git a/overrides/partials/comments.html b/overrides/partials/comments.html new file mode 100644 index 0000000..9e94ea2 --- /dev/null +++ b/overrides/partials/comments.html @@ -0,0 +1,12 @@ +{% set local_comments = "comments" | value_in_frontmatter(page.meta) %} +{% set config_comments = "comments" | value_in_frontmatter(config.extra) %} +{% set config_comments = False if config_comments is none else config.extra["comments"] %} +{% set comments = config_comments if local_comments is none else page.meta['comments'] %} +{% if comments %} + +
+{% endif %} \ No newline at end of file diff --git a/overrides/partials/content.html b/overrides/partials/content.html new file mode 100644 index 0000000..d65296b --- /dev/null +++ b/overrides/partials/content.html @@ -0,0 +1,54 @@ + + + +{% if "material/tags" in config.plugins %} +{% include "partials/tags.html" %} +{% endif %} + + +{% include "partials/actions.html" %} + + +{% if "\x3ch1" not in page.content and not config.extra['no-auto-h1'] %} +

{{ page.title | d(config.site_name, true)}}

+{% endif %} + + +{{ page.content }} + + +{% if page.meta and ( +page.meta.git_revision_date_localized or +page.meta.revision_date +) %} +{% include "partials/source-file.html" %} +{% endif %} + + +{% include "partials/feedback.html" %} + + +{% include "partials/comments.html" %} \ No newline at end of file diff --git a/overrides/partials/css_class.html b/overrides/partials/css_class.html new file mode 100644 index 0000000..5641520 --- /dev/null +++ b/overrides/partials/css_class.html @@ -0,0 +1,17 @@ +{% if page.meta %} + {% if page.meta["cssclasses"] and page.meta["cssclasses"]|length > 0 %} + {% if page.meta["cssclasses"] is iterable %} + {% for cssclasses in page.meta["cssclasses"] %} + + {% endfor %} + {% else %} + + {% endif %} + {% endif %} +{% endif %} \ No newline at end of file diff --git a/overrides/partials/meta.html b/overrides/partials/meta.html new file mode 100644 index 0000000..c232b34 --- /dev/null +++ b/overrides/partials/meta.html @@ -0,0 +1,62 @@ +{% set image = config.site_url ~ config.extra['SEO'] %} +{% set title = config.site_name %} +{% set description = config.site_description %} +{% if page and page.meta %} + {% if not page.is_homepage %} + {% set title = page.title %} + {% elif page.meta.title %} + {% set title = page.meta.title %} + {% endif %} + {% if title == "Index" %} + {% if page.meta.name %} + {% set title = page.meta.name %} + {% else %} + {% set title = page.parent.title %} + {% endif %} + {% endif %} + {% if page.meta.description %} + {% set description = page.meta.description %} + {% endif %} + {% if page.meta.image %} + {% set attachmentsFolder = 'attachments' | value_in_frontmatter(config.extra) %} + {% set attachments = 'assets/img/' if attachmentsFolder is none else config.extra['attachments'] | regex_replace('/$', '') %} + {% set image = config.site_url ~ attachments ~ "/" ~ page.meta.image %} + {% elif page.meta.banner %} + {% set image = page.meta.banner %} + {% endif %} + +{% endif %} + + + + + + + + + + \ No newline at end of file diff --git a/overrides/partials/nav-item.html b/overrides/partials/nav-item.html new file mode 100644 index 0000000..734e463 --- /dev/null +++ b/overrides/partials/nav-item.html @@ -0,0 +1,162 @@ +{% macro render_status(nav_item, type) %} + {% set class = "md-status md-status--" ~ type %} + {% if config.extra.status and config.extra.status[type] %} + + + {% else %} + + {% endif %} +{% endmacro %} +{% macro render_content(nav_item, ref = nav_item) %} + {% if nav_item.is_page and nav_item.meta.icon %} + {% set icons_path = config.extra.icons.folder ~ nav_item.meta.icon ~ ".svg" %} + {% if not icons_path | icon_exists %} + {% set icons_path = config.extra.icons.folder ~ config.extra.icons.default.file %} + {% endif %} + {% include icons_path %} + {% elif nav_item.is_index %} + {% set icons_path = config.extra.icons.folder ~ config.extra.icons.default.index %} + {% include icons_path %} + {% elif nav_item.is_page %} + {% set icons_path = config.extra.icons.folder ~ config.extra.icons.default.file %} + {% include icons_path %} + {% else %} + {% set icons_path = config.extra.icons.folder ~ config.extra.icons.default.folder %} + {% include icons_path %} + {% endif %} + + {{ ref.title }} + + {% if nav_item.is_page and nav_item.meta.status %} + {{ render_status(nav_item, nav_item.meta.status) }} + {% endif %} +{% endmacro %} +{% macro render_pruned(nav_item, ref = nav_item) %} + {% set first = nav_item.children | first %} + {% if first and first.children %} + {{ render_pruned(first, ref) }} + {% else %} + + {{ render_content(ref) }} + {% if nav_item.children | length > 0 %} + + {% endif %} + + {% endif %} +{% endmacro %} +{% macro render(nav_item, path, level) %} + {% set class = "md-nav__item" %} + {% if nav_item.active %} + {% set class = class ~ " md-nav__item--active" %} + {% endif %} + {% if nav_item.pages %} + {% if page in nav_item.pages %} + {% set nav_item = page %} + {% endif %} + {% endif %} + {% if nav_item.children %} + {% set indexes = [] %} + {% if "navigation.indexes" in features %} + {% for nav_item in nav_item.children %} + {% if nav_item.is_index and not index is defined %} + {% set _ = indexes.append(nav_item) %} + {% endif %} + {% endfor %} + {% endif %} + {% if "navigation.tabs" in features %} + {% if level == 1 and nav_item.active %} + {% set class = class ~ " md-nav__item--section" %} + {% set is_section = true %} + {% endif %} + {% if "navigation.sections" in features %} + {% if level == 2 and nav_item.parent.active %} + {% set class = class ~ " md-nav__item--section" %} + {% set is_section = true %} + {% endif %} + {% endif %} + {% elif "navigation.sections" in features %} + {% if level == 1 %} + {% set class = class ~ " md-nav__item--section" %} + {% set is_section = true %} + {% endif %} + {% endif %} + {% if "navigation.prune" in features %} + {% if not is_section and not nav_item.active %} + {% set class = class ~ " md-nav__item--pruned" %} + {% set is_pruned = true %} + {% endif %} + {% endif %} +
  • + {% if not is_pruned %} + {% set checked = "checked" if nav_item.active %} + {% if "navigation.expand" in features and not checked %} + {% set indeterminate = "md-toggle--indeterminate" %} + {% endif %} + + {% if not indexes %} + {% set tabindex = "0" if not is_section %} + + {% else %} + {% set index = indexes | first %} + {% set class = "md-nav__link--active" if index == page %} + + {% endif %} + + {% else %} + {{ render_pruned(nav_item) }} + {% endif %} +
  • + {% elif nav_item == page %} +
  • + {% set toc = page.toc %} + + {% set first = toc | first %} + {% if first and first.level == 1 %} + {% set toc = first.children %} + {% endif %} + {% if toc %} + + {% endif %} + + {{ render_content(nav_item) }} + + {% if toc %} + {% include "partials/toc.html" %} + {% endif %} +
  • + {% else %} +
  • + + {{ render_content(nav_item) }} + +
  • + {% endif %} +{% endmacro %} diff --git a/overrides/partials/post-list.html b/overrides/partials/post-list.html new file mode 100644 index 0000000..2431258 --- /dev/null +++ b/overrides/partials/post-list.html @@ -0,0 +1,257 @@ +{# title #} +{% if not "\x3ch1" in page.content %} +

    {{ page.title | d(config.site_name, true) }}

    +{% endif %} + +{# description #} +

    + {{ page.content }} +

    + +{% if config.extra['blog_list'] %} + {% set config_pagination = config.extra['blog_list'] %} +{% else %} + {% set config_pagination = {'pagination': True, 'pagination_message': True, 'pagination_translation': 'posts in'} %} +{% endif %} + +{# page collection #} +{% set valid_pages=[] %} +{% for p in pages %} + {% set pg = p.page %} + {% if pg.meta and pg.meta.date %} + {% set date = pg.meta.date | iso_time %} + {% set _ = pg.__setattr__("date", date) %} + {% elif page.meta.git_creation_date_localized_raw_iso_date %} + {% set date = page.meta.git_creation_date_localized_raw_iso_date | iso_time %} + {% set _ = pg.__setattr__('date', page.meta.git_creation_date_localized_raw_iso_date) %} + {% else %} + {% set date = build_date_utc | iso_time %} + {% set _ = pg.__setattr__('date', date) %} + {% endif %} + {% set page_category = pg.url | url_decode | replace('/' ~ pg.title ~ '/', '') | lower %} + {% set main_category = page.meta.category | lower %} + {% if main_category in page_category %} + {{ valid_pages.append(pg) or "" }} + {% endif %} +{% endfor %} + +{% set blog_pages=[] %} +{% for pg in valid_pages | sort(attribute = 'date', reverse=True) %} + {% set hidden = true if (pg.meta and pg.meta.hidden) %} + {% if (not pg.is_homepage) and + (not pg.markdown == '') and + (not hidden) and not (pg.title == 'index') + %} + {% set datetime = pg.date |time_todatetime %} + {% set dateti=datetime.strftime('%d/%m/%Y') %} + {% set _ = pg.__setattr__('new_date', dateti) %} + {{ blog_pages.append(pg) or "" }} + {% endif %} +{% endfor %} + +{% if blog_pages|count > 0 %} + {# count the number of pages #} + {% set page_num = 1 %} + {% if config_pagination['pagination'] %} + {% set page_num = (blog_pages|count / 10)|round(method='ceil')|int %} + {% if config_pagination['pagination_translation'] %} + {% set pagination_translation = config_pagination["pagination_translation"] %} + {% endif %} +
    + +
    + +
    + {% endif %} + {# pagination #} +
    + {% for pg_group in blog_pages|slice(page_num) %} + {% if config_pagination['pagination'] %} +
    + {% endif %} + {% for pg in pg_group %} + {% set pg_image = "" %} + {% set pg_category = "" %} + {% set pg_description = "" %} + {% set category_link = "" %} + {% set pg_title = pg.title %} + {% if pg_title == "Index" %} + {% set pg_title = pg.parent.title %} + {% endif %} + {% set main_category = page.meta.category | first | lower %} + {% if pg.meta %} + {% if pg.meta.title %} + {% set pg_title = pg.meta.title %} + {% endif %} + {% if pg.meta.banner %} + {% set pg_image = pg.meta.banner %} + {% elif pg.meta.image %} + {% set attachmentsFolder = 'attachments' | value_in_frontmatter(config.extra) %} + {% set attachmentsFolder = 'assets/img/' if attachmentsFolder is none else config.extra['attachments'] | regex_replace('/$', '') %} + {% set pg_image = config.site_url ~ attachmentsFolder ~ '/' ~ pg.meta.image %} + {% endif %} + {% if pg.meta.category %} + {% set pg_category = pg.meta.category | replace_by_name | lower | replace(main_category, '') | replace('/', '') | title %} + {% if pg_category | length > 1 %} + {% set category_link = config.docs_dir ~ "/" ~ page.meta.category ~ '/' ~ pg_category ~ '/index.md' %} + {% set category_exists = category_link | page_exists %} + {% if category_exists %} + {% set link = pg.meta.category | links %} + {% set link = config.site_url ~ link ~ '/' %} + {% set pg_category = '' ~ pg_category ~ '' %} + {% endif %} + {% endif %} + {% endif %} + {% if pg.meta.description %} + {% set pg_description = pg.meta.description | truncate(200)%} + {% endif %} + {% endif %} +
    +

    + {{ pg_title }} +

    + +
    + {% endfor %} +
    + {% endfor %} +
    + + {% if config_pagination['pagination'] %} +
    + +
    + {% endif %} + + {% if config_pagination["pagination"] and config_pagination["pagination_message"] %} +

    Total : {{ blog_pages|count }} {{ pagination_translation }} {{ page_num }} pages.

    + {% endif %} + + + {% if config_pagination['pagination'] %} + + {% endif %} +{% else %} + +

    + {% set no_page_found = 'no_page_found' | value_in_frontmatter(config.extra['blog_list']) %} + {% set no_page_found = 'No pages found!' if no_page_found is none else config.extra['blog_list']['no_page_found'] %} + {{ no_page_found }} +

    +{% endif %} \ No newline at end of file diff --git a/overrides/partials/source-file.html b/overrides/partials/source-file.html new file mode 100644 index 0000000..1fe740e --- /dev/null +++ b/overrides/partials/source-file.html @@ -0,0 +1,38 @@ +{% import "partials/language.html" as lang with context %} + + + +
    +
    + + + + {% set locale = page.meta.locale or config.locale or config.theme.language or 'en' %} + {% if page.meta.git_revision_date_localized %} + {{ lang.t("source.file.date.updated") }} : + {{ page.meta.git_revision_date_localized }} + + {% elif page.meta.revision_date %} + {{ lang.t("source.file.date.updated") }} : + {{ page.meta.revision_date }} + {% endif %} + {% if page.meta.date %} +
    + {{ lang.t("source.file.date.created") }} : + {{ page.meta.date | to_local_time(locale) }} + {% elif page.meta.git_creation_date_localized %} +
    + {{ lang.t("source.file.date.created") }} : + {{ page.meta.git_creation_date_localized }} + {% endif %} + {% if page.meta.author %} +
    + Auteur : {{ page.meta.author }} + {% endif %} + {% if page.meta.illustration %} +
    + Illustration : {{ page.meta.illustration }} + {% endif %} +
    +
    \ No newline at end of file diff --git a/overrides/partials/tabs-item.html b/overrides/partials/tabs-item.html new file mode 100644 index 0000000..fb7b703 --- /dev/null +++ b/overrides/partials/tabs-item.html @@ -0,0 +1,43 @@ +{% macro render_content(nav_item, ref = nav_item) %} + {% if nav_item == ref or "navigation.indexes" in features %} + {% if nav_item.is_index and nav_item.meta.icon %} + {% set icons_path = config.extra.icons.folder ~ nav_item.meta.icon ~ ".svg" %} + {% if not icons_path | icon_exists %} + {% if nav_item | is_section %} + {% set icons_path = config.extra.icons.folder ~ config.extra.icons.default.folder %} + {% else %} + {% set icons_path = config.extra.icons.folder ~ config.extra.icons.default.file %} + {% endif %} + {% endif %} + {% include icons_path %} + {% else %} + {% set icons_path = config.extra.icons.folder ~ config.extra.icons.default.folder %} + {% include icons_path %} + {% endif %} + {% endif %} + {{ ref.title }} +{% endmacro %} +{% macro render(nav_item, ref = nav_item) %} + {% set class = "md-tabs__item" %} + {% if ref.active %} + {% set class = class ~ " md-tabs__item--active" %} + {% endif %} + {% if nav_item.children %} + {% set first = nav_item.children | first %} + {% if first.children %} + {{ render(first, ref) }} + {% else %} +
  • + + {{ render_content(first, ref) }} + +
  • + {% endif %} + {% else %} +
  • + + {{ render_content(nav_item) }} + +
  • + {% endif %} +{% endmacro %} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9a512d9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,82 @@ +[project] +name = "mkdocs" +version = "0.0.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "babel", + "beautifulsoup4", + "bracex", + "cairocffi", + "cairosvg", + "certifi", + "cffi", + "charset-normalizer", + "click", + "colorama", + "csscompressor", + "cssselect2", + "defusedxml", + "ghp-import", + "gitdb", + "gitpython", + "htmlmin2", + "idna", + "jinja2", + "jsmin", + "markdown", + "markupsafe", + "mdx-breakless-lists", + "mdx-wikilink-plus", + "mergedeep", + "mkdocs", + "mkdocs-awesome-pages-plugin", + "mkdocs-callouts", + "mkdocs-custom-fences", + "mkdocs-custom-tags-attributes", + "mkdocs-embed-file-plugin", + "mkdocs-encryptcontent-plugin", + "mkdocs-get-deps", + "mkdocs-git-revision-date-localized-plugin", + "mkdocs-glightbox", + "mkdocs-material", + "mkdocs-material-extensions", + "mkdocs-meta-descriptions-plugin", + "mkdocs-minify-plugin", + "mkdocs-obsidian-links", + "natsort", + "packaging", + "paginate", + "pathspec", + "pillow", + "platformdirs", + "pycparser", + "pycryptodome", + "pygments", + "pymdown-extensions", + "python-dateutil", + "python-frontmatter", + "pytz", + "pyyaml", + "pyyaml-env-tag", + "regex", + "requests", + "setuptools", + "six", + "smmap", + "soupsieve", + "tinycss2", + "urllib3", + "watchdog", + "wcmatch", + "webencodings", +] + +[tool.ruff.lint] +select = ["PTH", "ANN", "N", "Q", "PL", "E", "F", "I"] +ignore = ["E501"] +exclude = ["tests", "docs", "build", "dist", "venv", "venv3", "venv3.6", "venv3.7", "venv3.8", "venv3.9", "venv3.10", "__pycache__"] + +[tool.ruff.lint.flake8-quotes] +inline-quotes = "double"