diff --git a/.github/workflows/link_check.yaml b/.github/workflows/link_check.yaml new file mode 100644 index 00000000..6a778986 --- /dev/null +++ b/.github/workflows/link_check.yaml @@ -0,0 +1,111 @@ +name: "Check documentation links" + +on: + push: + branches: + - master + - "[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + force_recheck: + description: "Clear lychee cache and recheck all links from scratch" + type: boolean + default: false + devdoc_50_branch: + description: "ibexa/documentation-developer branch for 5.0" + type: string + default: "5.0" + devdoc_46_branch: + description: "ibexa/documentation-developer branch for 4.6" + type: string + default: "4.6" + userdoc_50_branch: + description: "ibexa/documentation-user branch for 5.0" + type: string + default: "5.0" + userdoc_46_branch: + description: "ibexa/documentation-user branch for 4.6" + type: string + default: "4.6" + connect_branch: + description: "ibexa/documentation-connect branch" + type: string + default: "main" + pull_request: ~ + schedule: + - cron: "0 6 * * *" + +jobs: + link-check: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.13] + + steps: + - uses: actions/checkout@v4 + + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.AUTOMATION_CLIENT_ID }} + private-key: ${{ secrets.AUTOMATION_CLIENT_SECRET }} + owner: ${{ github.repository_owner }} + + - name: Configure git credentials for private repositories + run: | + git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/ibexa/documentation-connect".insteadOf \ + "https://github.com/ibexa/documentation-connect" + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install MkDocs dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Build documentation + run: mkdocs build --strict + + - name: Clone and build versioned repositories for link remap + run: >- + bash tools/clone-repositories.sh + "${{ inputs.devdoc_50_branch || '5.0' }}" + "${{ inputs.devdoc_46_branch || '4.6' }}" + "${{ inputs.userdoc_50_branch || '5.0' }}" + "${{ inputs.userdoc_46_branch || '4.6' }}" + "${{ inputs.connect_branch || 'main' }}" + + - name: Restore lychee cache + if: ${{ !inputs.force_recheck }} + uses: actions/cache@v4 + with: + path: .lycheecache + key: lychee-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + lychee-${{ github.ref_name }}- + lychee- + + - name: Check links + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 + with: + args: >- + --config lychee.toml + --cache + --cache-exclude-status "400.." + site + output: lychee-report.md + jobSummary: true + fail: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Comment broken links + if: always() && github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 + with: + path: lychee-report.md diff --git a/.gitignore b/.gitignore index a31beee4..2f81f484 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,13 @@ /vendor/ **/.DS_Store __pycache__/* +hooks/__pycache__/* /site/ **/.idea/ /node_modules/ yarn.lock **.css.map +.lycheecache +lychee.toml +/repositories/ +lychee-report.md diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 00000000..b678cf00 --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,3 @@ +# Hosts that block automated requests (403) +^https?://help\.openai\.com +^https?://support\.qualifio\.com diff --git a/README.md b/README.md index 12784c9d..7658bdd8 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,49 @@ mkdocs serve After a short while your documentation should be reachable at http://localhost:8000. If it's not, check the output of the command. +## Checking links + +External links in the built documentation are checked using [lychee](https://lychee.cli.rs). + +### Running the link checker + +```bash +# 1. Build the main docs site +mkdocs build --strict + +# 2. Clone and build versioned repositories, and generate lychee.toml +./tools/clone-repositories.sh + +# 3. Check links +lychee --config lychee.toml --cache --cache-exclude-status "400.." site +``` + +After fixing any reported links, run `mkdocs build --strict` before rerunning `lychee`. + +#### Using non-standard branches + +The script accepts optional branch names before cloning repositories: + +```bash +./tools/clone-repositories.sh [DEVDOC_50] [DEVDOC_46] [USERDOC_50] [USERDOC_46] [CONNECT] +``` + +| Argument | Repository | Default | +|--------------|---------------------------------|---------| +| `DEVDOC_50` | `ibexa/documentation-developer` | `5.0` | +| `DEVDOC_46` | `ibexa/documentation-developer` | `4.6` | +| `USERDOC_50` | `ibexa/documentation-user` | `5.0` | +| `USERDOC_46` | `ibexa/documentation-user` | `4.6` | +| `CONNECT` | `ibexa/documentation-connect` | `main` | + +Example — checking link for release PRs: + +```bash +./tools/clone-repositories.sh release-5.0.10 release-4.6.70 4.6 4.6 main +``` + +The same parameters are available as inputs when triggering the GitHub Actions workflow manually. + ## Where to View https://doc.ibexa.co diff --git a/docs/ai_actions/ai_actions.md b/docs/ai_actions/ai_actions.md index b461eebe..70767649 100644 --- a/docs/ai_actions/ai_actions.md +++ b/docs/ai_actions/ai_actions.md @@ -5,7 +5,7 @@ description: Learn how AI Actions can help editors by automating repetitive task # AI Actions AI Actions is a set of capabilities that enhance the efficiency and creativity of content editors. -The feature is included in [[= product_name =]] by default but may require some [configuration]([[= developer_doc =]]/ai_actions/configure_ai_actions/) before you can use it. +The feature is included in [[= product_name =]] by default but may require some [configuration]([[= developer_doc =]]/ai/ai_actions/configure_ai_actions/) before you can use it. Once AI Actions are configured, content editors with `Action configuration/View` and `Action configuration/Execute` permissions have access to AI-driven tools. For example, they can [enhance or improve the text](create_edit_content_items.md#ai-assistant) that they have written, or [generate alternative text](upload_images.md#ai) for images. diff --git a/docs/ai_actions/work_with_ai_actions.md b/docs/ai_actions/work_with_ai_actions.md index 4a154b4e..0c00a3fb 100644 --- a/docs/ai_actions/work_with_ai_actions.md +++ b/docs/ai_actions/work_with_ai_actions.md @@ -26,12 +26,12 @@ Out of the box, there are two categories of AI actions present in the system: - **Refine text** - used by default in [online editor](create_edit_content_items.md#ai-assistant) for refining text, for example: "Rewrite text in formal tone" - **Generate alternative text** - used by default in the [image asset editing screen](upload_images.md#ai) to generate alternative text, for example: "Generate short alternative description of an image" -It may happen that a set of sample AI actions has been [installed with the AI actions package]([[= developer_doc =]]/ai_actions/install_ai_actions/#install-sample-ai-action-configurations-optional), and there is already a number of existing AI actions that you can modify and clone. +It may happen that a set of sample AI actions has been installed with the AI actions package, and there is already a number of existing AI actions that you can modify and clone. !!! note "Custom action types" In your specific case, the types available can be different, and your organization's development team can create custom AI action types. - For more information, see [developer documentation]([[= developer_doc =]]/ai_actions/ai_actions/). + For more information, see [developer documentation]([[= developer_doc =]]/ai/ai_actions/ai_actions/). ### View AI action details @@ -68,7 +68,7 @@ You can modify the existing AI actions. - **Temperature** - controls the randomness of the response. Takes a value between 0 and 2, but the usual range is between 0 and 1. The output is more random at higher temperatures. -For more information, see the parameter's description in [OpenAI API reference](https://platform.openai.com/docs/api-reference/chat/create#chat-create-temperature) or [Anthropic's glossary](https://docs.claude.com/en/docs/about-claude/glossary#temperature) +For more information, see the parameter's description in [Anthropic's glossary](https://docs.claude.com/en/docs/about-claude/glossary#temperature) ![AI action options](img/ai_action_options.png) @@ -82,7 +82,7 @@ You can create AI actions that perform actions of different types, using differe Before you can work with AI actions, models must be configured and enabled by your organization's development team. If there are more AI service connectors available, you might be able to create AI actions that perform the same type of actions but use different models. - For more information, see [developer documentation]([[= developer_doc =]]/ai_actions/ai_actions_guide/#model). + For more information, see [developer documentation]([[= developer_doc =]]/ai/ai_actions/ai_actions_guide/#model). 1. Navigate to the Admin Panel and select **AI actions**. @@ -145,7 +145,7 @@ One such example could be sending out a text for translation by one service, and !!! note "[[= product_name_connect =]] configuration required" - To use AI actions that interface with [[= product_name_connect =]], you must first [configure and initiate the connection]([[= developer_doc =]]/ai_actions/install_ai_actions/#configure-access-to-ibexa-connect), and [define templates](https://doc.ibexa.co/projects/connect/en/latest/scenarios/scenario_templates/#creating-templates) and/or [scenarios](https://doc.ibexa.co/projects/connect/en/latest/scenarios/creating_a_scenario/) in [[= product_name_connect =]]. + To use AI actions that interface with [[= product_name_connect =]], you must first [configure and initiate the connection]([[= developer_doc =]]/ai/ai_actions/configure_ai_actions/#configure-access-to-ibexa-connect), and [define templates](https://doc.ibexa.co/projects/connect/en/latest/scenarios/scenario_templates/#creating-templates) and/or [scenarios](https://doc.ibexa.co/projects/connect/en/latest/scenarios/creating_a_scenario/) in [[= product_name_connect =]]. 1\. Navigate to the Admin Panel and select **AI actions**. diff --git a/docs/content_management/create_edit_pages.md b/docs/content_management/create_edit_pages.md index 902c4b2b..55878367 100644 --- a/docs/content_management/create_edit_pages.md +++ b/docs/content_management/create_edit_pages.md @@ -98,7 +98,7 @@ Page Builder has two main views that you can use while creating a page: The page layouts that an editor has access to are up to you to choose. The default, built-in Page layout has only one zone, but developers can create other layouts in configuration. -For more information, see [Configure layout](https://doc.ibexa.co/en/latest/templating/render_content/render_page/#configure-layout). +For more information, see [Configure layout]([[= developer_doc =]]/templating/render_content/render_page/#configure-layout). While editing, you can switch layout for your page. To do it, click **Switch layouts** button. diff --git a/docs/getting_started/dashboard/dashboard_block_reference.md b/docs/getting_started/dashboard/dashboard_block_reference.md index 4de1a5d4..ceb67d57 100644 --- a/docs/getting_started/dashboard/dashboard_block_reference.md +++ b/docs/getting_started/dashboard/dashboard_block_reference.md @@ -65,7 +65,7 @@ Displays a chart presenting orders split by status, with their number and percen On the **Properties** tab, set values in the following fields: - **Name** - Enter a name for the block. -- **Statuses** - Set the statuses of orders that should be included in the list. Default value = All. The list of statuses depends on configured [order workflow](https://doc.ibexa.co/en/latest/commerce/order_management/configure_order_management/) and can be customized. +- **Statuses** - Set the statuses of orders that should be included in the list. Default value = All. The list of statuses depends on configured [order workflow]([[= developer_doc =]]/commerce/order_management/configure_order_management/) and can be customized. - **Time period** - Set the time period: All time, Last day, Last week, Last month. Default value = Last month. On the **Design** tab, in the **View** field, select the layout to be used to present a list of orders and submit your changes. diff --git a/docs/ibexa_engage/ibexa_engage.md b/docs/ibexa_engage/ibexa_engage.md index f7627360..b377a81f 100644 --- a/docs/ibexa_engage/ibexa_engage.md +++ b/docs/ibexa_engage/ibexa_engage.md @@ -73,7 +73,7 @@ You can create, configure, and manage campaigns with [Qualifio Engage](https://d To access the Qualifio Manager page and create or manage campaigns, click the **Manage campaigns in Qualifio** button under the **Ibexa Engage** menu. To display your campaign on the website, use one of the available tools - Campaign block or Campaign custom tag. -For more information about creating and managing campaigns, see [Qualifio Engage documentation](https://support.qualifio.com/hc/en-us/categories/202280638-Qualifio-Engage). +For more information about creating and managing campaigns, see [Qualifio Campaigns documentation](https://support.qualifio.com/hc/en-us/categories/202280638-Campaigns). ### Campaign block @@ -95,4 +95,4 @@ This list includes all campaigns available on user's Qualifio account which are You can set the dimensions of the field in which the campaign is displayed. To do it, select units, and provide width and height values in the proper fields. If size fields are blank, the system sets default template values. It's recommended to adjust them for better results. -![Campaign custom tag](img/campaign_custom_tag.png "Campaign custom tag") \ No newline at end of file +![Campaign custom tag](img/campaign_custom_tag.png "Campaign custom tag") diff --git a/docs/persona_paths/administrator.md b/docs/persona_paths/administrator.md index 2ecd8192..e2ffb648 100644 --- a/docs/persona_paths/administrator.md +++ b/docs/persona_paths/administrator.md @@ -7,7 +7,7 @@ description: Discover administrative tasks that you can perform in the back offi When you perform administrative duties for your organization, you often are responsible for the configuration and maintenance of [[= product_name =]]-based application, and solving architectural challenges and performance problems. In [[= product_name =]], most of such tasks are done outside of the UI. -It's described in detail in [Developer Documentation]([[= developer_doc =]]). +It's described in detail in [Developer Documentation]([[= developer_doc =]]/). However, certain things can be done in the back office. ## What would you like to do today? diff --git a/docs/recent_activity/recent_activity.md b/docs/recent_activity/recent_activity.md index c1f26c87..768cd66d 100644 --- a/docs/recent_activity/recent_activity.md +++ b/docs/recent_activity/recent_activity.md @@ -25,7 +25,7 @@ By default, actions on the following items are displayed: By default, log entries are kept for 30 days. This time can be modified through configuration. - For more information, see [Developer Documentation]([[= developer_doc =]]/administration/recent_activity/recent_activity/#configuration-and-cronjob). + For more information, see [Developer Documentation]([[= developer_doc =]]/administration/recent_activity/recent_activity/#configuration). Log entries are grouped by date, then by logical bond (like web request, or migration file). diff --git a/hooks/edit_uri.py b/hooks/edit_uri.py new file mode 100644 index 00000000..dbd25be7 --- /dev/null +++ b/hooks/edit_uri.py @@ -0,0 +1,12 @@ +import os + +def on_config(config, **kwargs): + if os.environ.get("READTHEDOCS_VERSION_TYPE") == "external": + ref = os.environ.get("READTHEDOCS_GIT_COMMIT_HASH") + else: + ref = os.environ.get("GITHUB_HEAD_REF") + + if ref: + config["edit_uri"] = f"blob/{ref}/docs/" + + return config diff --git a/lychee.toml.dist b/lychee.toml.dist new file mode 100644 index 00000000..a4f45cfa --- /dev/null +++ b/lychee.toml.dist @@ -0,0 +1,191 @@ +############################# Display ############################# + +# Verbose program output +verbose = "warn" + +# Output format +format = "markdown" + +# Path to report output file +output = "lychee-report.md" + +# Don't show interactive progress bar while checking links. +no_progress = false + +############################# Cache ############################### + +# Enable link caching to avoid re-checking identical links across runs. +cache = true + +# Discard cached results older than this duration. +max_cache_age = "2d" + +############################# Runtime ############################# + +# Maximum number of allowed redirects. Set to 0 to fail on any redirect — +# a redirect usually signals moved or reorganised content that should be +# updated at the source. +max_redirects = 1 + +# Maximum number of allowed retries before a link is declared dead. +max_retries = 3 + +# Minimum wait time in seconds between retries of failed requests. +retry_wait_time = 2 + +# Maximum number of concurrent link checks across all hosts. +max_concurrency = 8 + +############################# Requests ############################ + +# Website timeout from connect to response finished (seconds). +timeout = 20 + +# Comma-separated list of accepted status codes for valid links. +accept = ["200", "202"] + +# Proceed for server connections considered insecure (invalid TLS). +insecure = false + +# Check https/http and file:// (used by remap rules below). +# Internal file:// links (resolved via root_dir) are excluded separately — see the +# exclude list below. Only cross-doc file:// links produced by remap rules are checked. +scheme = ["https", "http", "file"] + +# GET is required for fragment checking (HEAD responses have no body). +method = "GET" + +user_agent = "Mozilla/5.0 (compatible; lychee link checker)" + +# Check anchor fragments in cross-doc links (remapped to explicit index.html paths). +# Internal fragments are already validated by `mkdocs build --strict` and are excluded +# via the file:// exclude pattern below. +include_fragments = "full" + +# Do NOT check links inside and
 blocks.
+include_verbatim = false
+
+#############################  Exclusions  ##########################
+
+# Exclude URLs from checking (treated as regular expressions).
+# External-host exclusions (bot-blocks, redirects, false positives) live in .lycheeignore.
+# Only structural patterns that depend on the build path are kept here.
+exclude = [
+    # Internal file:// links are resolved by lychee from root-relative hrefs via root_dir.
+    # They point to the local MkDocs output and are already validated by `mkdocs build --strict`.
+    # Cross-doc links produced by the remap rules below go through the `repositories/`
+    # subdirectory and are therefore NOT matched by this pattern.
+    "^file://__BASE_DIR__/site/",
+    # Versionless project root links (e.g. /projects/connect, /projects/userguide) appear in
+    # the MkDocs theme sidebar as cross-project navigation and are not real content links.
+    # The https form appears as absolute links; the file:// form appears after root_dir resolution
+    # of root-relative hrefs like /projects/connect in the built HTML.
+    "^https?://doc\\.ibexa\\.co/projects/[^/]+/?$",
+    "^file://.*?/site/projects/[^/]+/?$",
+]
+
+# Exclude these input paths from being scanned.
+exclude_path = [
+    # Search index, assets and sitemap contain no meaningful external links
+    "site/search/search_index.json",
+    "site/assets",
+    "site/sitemap.xml",
+    # PHP API reference HTML is generated by phpDocumentor and uses  to resolve
+    # relative links — lychee does not honour  tags and would flag them as broken.
+    # Links pointing *to* these files from other pages are still checked.
+    "site/api/php_api/php_api_reference/",
+]
+
+# Check the specified file extensions
+extensions = ["html"]
+
+# Exclude all private IPs from checking.
+exclude_all_private = true
+
+#############################  Local files  #########################
+
+# Required to resolve root-relative links (e.g. href="/…") found in every page into
+# file:// URLs. Those file:// URLs are then excluded from checking by the pattern in
+# the exclude list — internal links are validated by `mkdocs build --strict`.
+# `site` is the default `mkdocs build` output directory.
+root_dir = "site"
+
+#############################  Remap  ###############################
+
+# Rewrite doc.ibexa.co links to locally-built MkDocs sites, avoiding HTTP
+# requests to Cloudflare-protected hosts.
+#
+# - en/latest/           → current build's site/ directory (self-referential links from cards() macro)
+# - en/4.6/              → repositories/devdoc-4.6/site/
+# - en/5.0/              → repositories/devdoc-5.0/site/
+# - userguide/en/latest/ → repositories/userdoc-5.0/site/ (newest available)
+# - userguide/en/4.6/    → repositories/userdoc-4.6/site/
+# - userguide/en/5.0/    → repositories/userdoc-5.0/site/
+# - connect/en/latest/   → repositories/connect/site/
+#
+# Three patterns per version handle all URL shapes.
+# NOTE: lychee applies remap BEFORE stripping the fragment, so each pattern uses
+# an optional (#.*)? group to capture and forward fragments to the local file path.
+#
+#   Pattern 1 — direct .html files (e.g. REST/PHP API reference):
+#               ([^#]+\.html)(#.*)?$
+#
+#   Pattern 2 — directory paths (with or without trailing slash, with path segments):
+#               ([^#/]+(?:/[^#/]+)*)/?(#.*)?$
+#               $1 captures the path without a trailing slash; replacement always adds /index.html.
+#
+#   Pattern 3 — bare version root (e.g. /en/5.0/ with nothing after):
+#               $  (exact match of the version prefix)
+remap = [
+    # GitHub line-number fragments (#L7, #L12-L15) are rendered via JavaScript —
+    # lychee cannot find them in the raw HTML. Strip the fragment so the file
+    # existence is still verified.
+    "^(https://github\\.com/[^#]+)#L[0-9].* $1",
+    # MDN URLs without a locale redirect to the browser's preferred language.
+    # Remap to en-US so lychee can verify the link without following the redirect.
+    "^https://developer\\.mozilla\\.org/docs/(.+) https://developer.mozilla.org/en-US/docs/$1",
+    # doc.ibexa.co/en/latest/ links are self-referential: the cards() macro generates
+    # them when READTHEDOCS_VERSION_NAME is not set (e.g. in CI). Remap to the locally
+    # built site/ directory. These file:// URLs are then matched by the exclude pattern
+    # above and skipped — internal links are validated by `mkdocs build --strict`.
+    "https://doc\\.ibexa\\.co/projects/userguide/en/latest/([^#]+\\.html)(#.*)?$ file://__BASE_DIR__/site/$1$2",
+    "https://doc\\.ibexa\\.co/projects/userguide/en/latest/([^#/]+(?:/[^#/]+)*)/?(#.*)?$ file://__BASE_DIR__/site/$1/index.html$2",
+    "https://doc\\.ibexa\\.co/projects/userguide/en/latest/$ file://__BASE_DIR__/site/index.html",
+    # dev doc en/latest/ — map to the newest available clone (devdoc-5.0)
+    "https://doc\\.ibexa\\.co/en/latest/([^#]+\\.html)(#.*)?$ file://__BASE_DIR__/repositories/devdoc-5.0/site/$1$2",
+    "https://doc\\.ibexa\\.co/en/latest/([^#/]+(?:/[^#/]+)*)/?(#.*)?$ file://__BASE_DIR__/repositories/devdoc-5.0/site/$1/index.html$2",
+    "https://doc\\.ibexa\\.co/en/latest/$ file://__BASE_DIR__/repositories/devdoc-5.0/site/index.html",
+    # devdoc 4.6
+    "https://doc\\.ibexa\\.co/en/4\\.6/([^#]+\\.html)(#.*)?$ file://__BASE_DIR__/repositories/devdoc-4.6/site/$1$2",
+    "https://doc\\.ibexa\\.co/en/4\\.6/([^#/]+(?:/[^#/]+)*)/?(#.*)?$ file://__BASE_DIR__/repositories/devdoc-4.6/site/$1/index.html$2",
+    "https://doc\\.ibexa\\.co/en/4\\.6/$ file://__BASE_DIR__/repositories/devdoc-4.6/site/index.html",
+    # devdoc 5.0
+    "https://doc\\.ibexa\\.co/en/5\\.0/([^#]+\\.html)(#.*)?$ file://__BASE_DIR__/repositories/devdoc-5.0/site/$1$2",
+    "https://doc\\.ibexa\\.co/en/5\\.0/([^#/]+(?:/[^#/]+)*)/?(#.*)?$ file://__BASE_DIR__/repositories/devdoc-5.0/site/$1/index.html$2",
+    "https://doc\\.ibexa\\.co/en/5\\.0/$ file://__BASE_DIR__/repositories/devdoc-5.0/site/index.html",
+    # userdoc 4.6
+    "https://doc\\.ibexa\\.co/projects/userguide/en/4\\.6/([^#]+\\.html)(#.*)?$ file://__BASE_DIR__/repositories/userdoc-4.6/site/$1$2",
+    "https://doc\\.ibexa\\.co/projects/userguide/en/4\\.6/([^#/]+(?:/[^#/]+)*)/?(#.*)?$ file://__BASE_DIR__/repositories/userdoc-4.6/site/$1/index.html$2",
+    "https://doc\\.ibexa\\.co/projects/userguide/en/4\\.6/$ file://__BASE_DIR__/repositories/userdoc-4.6/site/index.html",
+    # userdoc 5.0
+    "https://doc\\.ibexa\\.co/projects/userguide/en/5\\.0/([^#]+\\.html)(#.*)?$ file://__BASE_DIR__/repositories/userdoc-5.0/site/$1$2",
+    "https://doc\\.ibexa\\.co/projects/userguide/en/5\\.0/([^#/]+(?:/[^#/]+)*)/?(#.*)?$ file://__BASE_DIR__/repositories/userdoc-5.0/site/$1/index.html$2",
+    "https://doc\\.ibexa\\.co/projects/userguide/en/5\\.0/$ file://__BASE_DIR__/repositories/userdoc-5.0/site/index.html",
+    # userdoc master — treated as the newest available (userdoc-5.0)
+    "https://doc\\.ibexa\\.co/projects/userguide/en/master/([^#]+\\.html)(#.*)?$ file://__BASE_DIR__/repositories/userdoc-5.0/site/$1$2",
+    "https://doc\\.ibexa\\.co/projects/userguide/en/master/([^#/]+(?:/[^#/]+)*)/?(#.*)?$ file://__BASE_DIR__/repositories/userdoc-5.0/site/$1/index.html$2",
+    "https://doc\\.ibexa\\.co/projects/userguide/en/master/$ file://__BASE_DIR__/repositories/userdoc-5.0/site/index.html",
+    # connect — single branch, mapped from both en/latest/
+    "https://doc\\.ibexa\\.co/projects/connect/en/latest/([^#]+\\.html)(#.*)?$ file://__BASE_DIR__/repositories/connect/site/$1$2",
+    "https://doc\\.ibexa\\.co/projects/connect/en/latest/([^#/]+(?:/[^#/]+)*)/?(#.*)?$ file://__BASE_DIR__/repositories/connect/site/$1/index.html$2",
+    "https://doc\\.ibexa\\.co/projects/connect/en/latest/$ file://__BASE_DIR__/repositories/connect/site/index.html",
+]
+
+#############################  Hosts  ###############################
+
+# Global limit: at most 2 simultaneous requests to any single host.
+host_concurrency = 2
+
+# Global minimum interval between requests to the same host.
+host_request_interval = "500ms"
+
diff --git a/main.py b/main.py
index a504b52b..e5a2a360 100644
--- a/main.py
+++ b/main.py
@@ -92,7 +92,7 @@ def cards(pages, columns=1, style="cards", force_version=False):
             elif re.search(".html$", path):
                 html = True
                 content = open("docs/%s" % path, "r").read()
-                page = '/'.join((
+                page = 'https:/' + '/'.join((
                     '/',
                     site,
                     project,
@@ -105,7 +105,7 @@ def cards(pages, columns=1, style="cards", force_version=False):
                 html = False
                 path = path.rstrip('/')
                 content = open("docs/%s.md" % path, "r").read()
-                page = '/'.join((
+                page = 'https:/' + '/'.join((
                     '/',
                     site,
                     project,
diff --git a/mkdocs.yml b/mkdocs.yml
index b7050d56..7ee804b7 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,8 +1,12 @@
 INHERIT: plugins.yml
 
 site_name: User Documentation
-repo_url: https://github.com/ezsystems/user-documentation
+repo_url: https://github.com/ibexa/documentation-user
 site_url: https://doc.ibexa.co/projects/userguide/en/latest/
+edit_uri: blob/5.0/docs
+hooks:
+    - hooks/edit_uri.py
+edit_uri: edit/5.0/docs
 copyright: "Copyright 1999-2026 Ibexa AS and others"
 validation:
     omitted_files: warn
diff --git a/theme/partials/source.html b/theme/partials/source.html
index 76e885dc..0948754c 100644
--- a/theme/partials/source.html
+++ b/theme/partials/source.html
@@ -1,5 +1,5 @@
 {% import "partials/language.html" as lang with context %}
-
+
     
{% include ".icons/fontawesome/brands/github-alt.svg" %}
diff --git a/tools/clone-repositories.sh b/tools/clone-repositories.sh new file mode 100755 index 00000000..3fbed486 --- /dev/null +++ b/tools/clone-repositories.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Clones and builds versioned documentation repositories used by lychee's remap rules, +# then generates lychee.toml from lychee.toml.dist with absolute paths substituted in. +# +# Usage: ./tools/clone-repositories.sh [DEVDOC_50_BRANCH] [DEVDOC_46_BRANCH] [USERDOC_50_BRANCH] [USERDOC_46_BRANCH] [CONNECT_BRANCH] +# +# DEVDOC_50_BRANCH Branch of ibexa/documentation-developer to use for 5.0 (default: 5.0) +# DEVDOC_46_BRANCH Branch of ibexa/documentation-developer to use for 4.6 (default: 4.6) +# USERDOC_50_BRANCH Branch of ibexa/documentation-user to use for 5.0 (default: 5.0) +# USERDOC_46_BRANCH Branch of ibexa/documentation-user to use for 4.6 (default: 4.6) +# CONNECT_BRANCH Branch of ibexa/documentation-connect (default: main) +# +# Run this once before running lychee. Re-run to refresh clones or after moving +# the repository to a new path (the path in lychee.toml will be updated automatically). + +set -euo pipefail + +DEVDOC_50_BRANCH="${1:-5.0}" +DEVDOC_46_BRANCH="${2:-4.6}" +USERDOC_50_BRANCH="${3:-5.0}" +USERDOC_46_BRANCH="${4:-4.6}" +CONNECT_BRANCH="${5:-main}" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# The script lives in tools/; all paths (repositories/, lychee.toml.dist, lychee.toml) +# are relative to the repository root one level up. +REPO_DIR="$(dirname "$SCRIPT_DIR")" + +# Prepend the local Python installation so pip/mkdocs resolve correctly. +# Adjust this if your Python is installed elsewhere. +export PATH="$HOME/python/bin:$PATH" + +cd "$REPO_DIR" + +echo "==> Cloning versioned repositories..." +echo " devdoc 5.0 → branch '$DEVDOC_50_BRANCH'" +echo " devdoc 4.6 → branch '$DEVDOC_46_BRANCH'" +echo " userdoc 5.0 → branch '$USERDOC_50_BRANCH'" +echo " userdoc 4.6 → branch '$USERDOC_46_BRANCH'" +echo " connect → branch '$CONNECT_BRANCH'" +mkdir -p repositories +git clone --depth=1 --branch "$DEVDOC_46_BRANCH" https://github.com/ibexa/documentation-developer.git repositories/devdoc-4.6 & +git clone --depth=1 --branch "$DEVDOC_50_BRANCH" https://github.com/ibexa/documentation-developer.git repositories/devdoc-5.0 & +git clone --depth=1 --branch "$USERDOC_46_BRANCH" https://github.com/ibexa/documentation-user.git repositories/userdoc-4.6 & +git clone --depth=1 --branch "$USERDOC_50_BRANCH" https://github.com/ibexa/documentation-user.git repositories/userdoc-5.0 & +git clone --depth=1 --branch "$CONNECT_BRANCH" https://github.com/ibexa/documentation-connect.git repositories/connect & +wait + +echo "==> Installing dependencies for versioned repositories..." +for dir in repositories/devdoc-4.6 repositories/devdoc-5.0 repositories/userdoc-4.6 repositories/userdoc-5.0 repositories/connect; do + (cd "$dir" && pip install -q -r requirements.txt) +done + +echo "==> Building versioned repositories..." +for dir in repositories/devdoc-4.6 repositories/devdoc-5.0 repositories/userdoc-4.6 repositories/userdoc-5.0 repositories/connect; do + (cd "$dir" && mkdocs build --quiet) & +done +wait + +echo "==> Generating lychee.toml from lychee.toml.dist..." +sed "s|__BASE_DIR__|$REPO_DIR|g" lychee.toml.dist > lychee.toml +echo " __BASE_DIR__ → '$REPO_DIR'" + +echo "Done."