-
Notifications
You must be signed in to change notification settings - Fork 8
Integrate AI Resources Page plugin #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
|
||
|
|
@@ -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' }); | ||
|
|
@@ -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); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||
| } | ||
|
|
||
| a.addEventListener('click', (e) => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||
| }); | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -115,6 +115,7 @@ plugins: | |||
| exclude: | ||||
| - ai/* | ||||
| - awesome-nav | ||||
| - ai_resources_page | ||||
|
||||
| - ai_resources_page |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.