Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions llms_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,35 @@
"Tooling",
"Reference"
],
"categories_info": {
"Basics": {
"description": "Polkadot general knowledge base to provide context around overview and beginner-level content."
},
"Reference": {
"description": "Reference material including key functions and glossary."
},
"Smart Contracts": {
"description": "How to develop and deploy Solidity smart contracts on Polkadot Hub."
},
"Parachains": {
"description": "How-to guides related to building, customizing, deploying, and maintaining a parachain."
},
"dApps": {
"description": "Information and tutorials for application developers."
},
"Networks": {
"description": "Information about Polkadot networks."
},
"Polkadot Protocol": {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure about using the display-like labels as keys. I think this could potentially cause issues down the road depending on how things evolve. It might make more sense to have like a "label" key inside each category with the display name and use lower case/underscores when necessary.

Not sure how it's used so as I dig into this a bit more, I might have more comments on this.

"description": "In-depth details about the Polkadot Protocol."
},
"Infrastructure": {
"description": "Guide to node infrastructure."
},
"Tooling": {
"description": "Developer tooling resources."
}
},
"exclusions": {
"skip_basenames": [
"README.md",
Expand Down
78 changes: 34 additions & 44 deletions material-overrides/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -341,33 +341,21 @@
});
</script>
<script>
/**
* AI Resources Page Interactivity
*
* Purpose: Handle client-side interactivity (Download, Clipboard) that cannot be performed
* by the static HTML generator plugin (ai_resources_page).
*
* The plugin generates the HTML structure with `data-path` attributes to ensure
* correct paths. This script consumes those paths to invoke browser APIs.
*/
document.addEventListener('DOMContentLoaded', () => {
const SITE_BASE = location.origin.replace(/\/+$/, ''); // current host
const AI_BASE = '/ai'; // relative path to AI content on same host

const stripLeading = (value) => value.replace(/^\/+/, '');

const toAbsolute = (rawPath) => {
if (!rawPath) return null;
const dataPath = rawPath.trim();
if (/^https?:\/\//i.test(dataPath)) return dataPath; // already absolute

const normalized = stripLeading(dataPath);

// All AI content is served from the AI_BASE on the current host
if (normalized.startsWith('ai/')) {
return `${SITE_BASE}${AI_BASE}/${stripLeading(normalized.replace(/^ai\//, ''))}`;
}

// NOTE: same-origin assets: default for everything we ship in docs/ (llms, ai bundles, etc.)
// llms.txt is served from the site root; other AI artifacts live under /ai
if (normalized === 'llms.txt') return `${SITE_BASE}/${normalized}`;
if (dataPath.startsWith('/')) return `${SITE_BASE}/${stripLeading(dataPath)}`;

// Fallback → site-relative
return `${SITE_BASE}/${normalized}`;
};

/**
* Justification: Standard HTML <a download> tags often open text/markdown files
* in the browser instead of saving them. Fetching as a Blob forces a true "Save As" behavior.
*/
const downloadViaFetch = async (url, filename) => {
try {
const res = await fetch(url, { credentials: 'omit' });
Comment on lines 359 to 361
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In downloadViaFetch, the object URL is revoked immediately after triggering link.click(). In some browsers the download may not have started yet, which can lead to flaky/failed downloads. Revoke the object URL after a short timeout (or other deferred cleanup) instead of immediately.

Copilot uses AI. Check for mistakes.
Expand All @@ -388,6 +376,10 @@
}
};

/**
* Justification: Static HTML cannot access the user's system clipboard.
* We must fetch the content client-side and use the navigator API to copy it.
*/
const copyTextFromUrl = async (url) => {
try {
const res = await fetch(url, { credentials: 'omit' });
Expand All @@ -413,46 +405,44 @@
document.querySelectorAll('.llms-copy').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.preventDefault();
const url = toAbsolute(btn.getAttribute('data-path'));
// Plugin generates the correct absolute path in data-path
const url = btn.getAttribute('data-path');
if (url) copyTextFromUrl(url);
});
});

// DOWNLOAD buttons: force a real download via Blob
document.querySelectorAll('.llms-dl').forEach((a) => {
// Set href for right-click "Copy link address" convenience
const abs = toAbsolute(a.getAttribute('data-path'));
if (abs) a.setAttribute('href', abs);
// Plugin generates the correct absolute path in data-path
const url = a.getAttribute('data-path');
if (url) a.setAttribute('href', url);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't be setting an href on this side. That should be done plugin side if needed.


a.addEventListener('click', (e) => {
e.preventDefault();
const path = a.getAttribute('data-path') || '';
const url = toAbsolute(path);
if (!url) return;
// Prefer explicit filename; else derive from last path segment
const filename =
a.getAttribute('data-filename') ||
path.split('/').pop() ||
'download.txt';
downloadViaFetch(url, filename);
const path = a.getAttribute('data-path');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're already getting this value on line 417

if (!path) return;

// Plugin provides the clean filename for saving
const filename = a.getAttribute('data-filename') || 'download.txt';
downloadViaFetch(path, filename);
});
});

// VIEW buttons: open raw files in a new tab
document.querySelectorAll('.llms-view').forEach((a) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just be a link. We shouldn't require any logic at all on this side of things for this to work.

const abs = toAbsolute(a.getAttribute('data-path'));
if (abs) {
a.setAttribute('href', abs);
const url = a.getAttribute('data-path');
if (url) {
a.setAttribute('href', url);
a.setAttribute('target', '_blank');
a.setAttribute('rel', 'noopener');
Comment on lines +435 to 437
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New tab links elsewhere in this template use rel="noopener noreferrer", but this code sets only noopener. For consistency and to avoid leaking the referrer to the raw file URL, consider using noopener noreferrer here as well (and include noreferrer in the window.open feature string if keeping the JS fallback).

Copilot uses AI. Check for mistakes.
}

a.addEventListener('click', (e) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how this previously made it's way in. I feel like I made a bunch of comments on those PRs about adding an event listener to an a tag. Duplicating the browsers default functionality.

Not that it really matters, because there shouldn't be any extra logic needed for the View functionality. None of this is necessary anymore.

const url = toAbsolute(a.getAttribute('data-path') || '');
if (!url) return;
const path = a.getAttribute('data-path');
if (!path) return;
if (a.getAttribute('target') !== '_blank') {
e.preventDefault();
window.open(url, '_blank', 'noopener');
window.open(path, '_blank', 'noopener');
}
Comment on lines 440 to 446
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .llms-view click handler is effectively dead code because the script always sets target="_blank" when data-path is present, so the if (a.getAttribute('target') !== '_blank') branch will never run. Consider removing this listener or adjusting the logic so it covers the intended scenario.

Copilot uses AI. Check for mistakes.
});
});
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ plugins:
exclude:
- ai/*
- awesome-nav
- ai_resources_page
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabling the ai_resources_page MkDocs plugin will break builds unless the plugin package is installed in the build environment. Please ensure the dependency is added/pinned in the Python requirements used by CI/local builds (or otherwise vendored) so mkdocs can import it.

Suggested change
- ai_resources_page

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we talked about this, but have you looked into accepting a file path in the config here for where the AI resources page should live, and then on the plugin side, building it from scratch so that we don't need an empty placeholder file in the docs dir? There could be lots of side effects from this approach. But just curious

- resolve_md:
llms_config: llms_config.json
- page_toggle
Expand Down