Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ back_to_top_text: "Back to top"

# Footer content
footer_content: "Copyright &copy; 2025 <a href='https://patrickvice.com'>Patrick Vice</a>. Distributed under an <a href=\"https://github.com/patvice/ruby_llm-mcp/tree/main/LICENSE\">MIT license.</a>"
markdown_source_base_url: "https://raw.githubusercontent.com/patvice/ruby_llm-mcp/main/docs"

# navigation
nav_enabled: true
Expand Down
7 changes: 7 additions & 0 deletions docs/_includes/head_custom.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,10 @@
}
})();
</script>
<script>
window.__rubyllmMcpMarkdownSourceBaseUrl = {{ site.markdown_source_base_url | default: "" | jsonify }};
window.__rubyllmMcpMarkdownPath = {{ page.path | default: "" | jsonify }};
window.__rubyllmDocsRepoNwo = {{ site.github.repository_nwo | default: "patvice/ruby_llm-mcp" | jsonify }};
window.__rubyllmDocsSourceBranch = {{ site.github.source.branch | default: "main" | jsonify }};
</script>
<script src="{{ '/assets/js/copy-markdown.js' | relative_url }}" defer></script>
83 changes: 83 additions & 0 deletions docs/_sass/custom/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,86 @@ html[data-rubyllm-theme="dark"] .logo-container .home-logo-dark {
font-size: 1.05rem;
}
}

.main-content {
position: relative;
}

.page-actions {
position: absolute;
top: 0.2rem;
right: 0;
z-index: 2;
}

.page-copy-button {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.35rem 0.7rem;
border: 1px solid rgba(20, 20, 20, 0.15);
border-radius: 999px;
background: transparent;
color: rgba(20, 20, 20, 0.85);
font-size: 0.75rem;
font-weight: 600;
line-height: 1.1;
letter-spacing: 0.01em;
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease, transform 120ms ease;
}

.page-copy-button:hover {
background: rgba(20, 20, 20, 0.06);
border-color: rgba(20, 20, 20, 0.25);
}

.page-copy-button:active {
transform: scale(0.97);
}

.page-copy-button:focus-visible {
outline: 2px solid rgba(20, 20, 20, 0.35);
outline-offset: 2px;
}

html[data-rubyllm-theme="dark"] .page-copy-button {
border-color: rgba(255, 255, 255, 0.25);
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.03);
}

html[data-rubyllm-theme="dark"] .page-copy-button:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.35);
}

html[data-rubyllm-theme="dark"] .page-copy-button:focus-visible {
outline-color: rgba(255, 255, 255, 0.5);
}

@media (prefers-color-scheme: dark) {
html:not([data-rubyllm-theme="light"]) .page-copy-button {
border-color: rgba(255, 255, 255, 0.25);
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.03);
}

html:not([data-rubyllm-theme="light"]) .page-copy-button:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.35);
}

html:not([data-rubyllm-theme="light"]) .page-copy-button:focus-visible {
outline-color: rgba(255, 255, 255, 0.5);
}
}

@media (max-width: 50rem) {
.page-actions {
position: static;
margin-bottom: 0.75rem;
display: flex;
justify-content: flex-end;
}
}
266 changes: 266 additions & 0 deletions docs/assets/js/copy-markdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
(function () {
function normalizeUrl(base, path) {
if (!base || !path) {
return null;
}
var trimmedBase = base.replace(/\/+$/, "");
var trimmedPath = path.replace(/^\/+/, "");
return trimmedBase + "/" + trimmedPath;
}

function defaultMarkdownBase() {
var repo = window.__rubyllmDocsRepoNwo || "";
var branch = window.__rubyllmDocsSourceBranch || "main";

if (!repo) {
return "";
}

return "https://raw.githubusercontent.com/" + repo + "/" + branch + "/docs";
}

function resolveMarkdownBase(button) {
var configuredBase = button.dataset.markdownBase || "";
if (configuredBase) {
return configuredBase;
}

var inferredBase = defaultMarkdownBase();
if (inferredBase) {
button.dataset.markdownBase = inferredBase;
}
return inferredBase;
}

function setButtonLabel(button, label) {
button.textContent = label;
}

function restoreLabelAfterDelay(button, label, delay) {
window.setTimeout(function () {
if (!button.dataset || button.dataset.isBusy === "true") {
return;
}
setButtonLabel(button, label);
}, delay);
}

function copyText(text) {
if (
window.navigator &&
window.navigator.clipboard &&
window.navigator.clipboard.writeText
) {
return window.navigator.clipboard.writeText(text);
}

return new Promise(function (resolve, reject) {
var textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();

var ok = false;
try {
ok = document.execCommand("copy");
} catch (error) {
ok = false;
} finally {
document.body.removeChild(textarea);
}

if (ok) {
resolve();
} else {
reject(new Error("Unable to copy."));
}
});
}

function getVisiblePageText() {
var main = document.querySelector("#main-content > main");
if (!main) {
return "";
}

var clone = main.cloneNode(true);
var selectorsToRemove = [
".page-actions",
".anchor-heading",
"#markdown-toc",
"#table-of-contents",
"script",
"style"
];

selectorsToRemove.forEach(function (selector) {
clone.querySelectorAll(selector).forEach(function (node) {
node.remove();
});
});

return (clone.textContent || "")
.replace(/\u00a0/g, " ")
.replace(/[ \t]+\n/g, "\n")
.replace(/\n{3,}/g, "\n\n")
.trim();
}

function fetchMarkdown(sourceUrl) {
if (!sourceUrl) {
return Promise.reject(new Error("Missing markdown source URL."));
}

return window.fetch(sourceUrl, { cache: "no-store" })
.then(function (response) {
if (!response.ok) {
throw new Error("Failed to fetch markdown source.");
}
return response.text();
})
.then(function (markdown) {
return stripFrontMatter(markdown);
});
}

function stripFrontMatter(markdown) {
var lines = markdown.split("\n");
if (lines.length < 3 || lines[0].trim() !== "---") {
return markdown;
}

var endIndex = -1;
for (var i = 1; i < lines.length; i += 1) {
if (lines[i].trim() === "---") {
endIndex = i;
break;
}
}

if (endIndex === -1) {
return markdown;
}

return lines.slice(endIndex + 1).join("\n").replace(/^\n+/, "");
}

function setupButton(button) {
var base = resolveMarkdownBase(button);
var path = button.dataset.markdownPath;
var defaultLabel = button.dataset.labelDefault || "Copy page";
var successLabel = button.dataset.labelSuccess || "Copied";
var errorLabel = button.dataset.labelError || "Copy failed";

setButtonLabel(button, defaultLabel);

var sourceUrl = normalizeUrl(base, path);
if (sourceUrl) {
window.fetch(sourceUrl, { cache: "no-store" })
.then(function (response) {
if (!response.ok) {
throw new Error("Failed to fetch markdown source.");
}
return response.text();
})
.then(function (markdown) {
button.dataset.markdownSource = stripFrontMatter(markdown);
})
.catch(function () {
button.dataset.markdownSource = "";
});
} else {
button.dataset.markdownSource = "";
button.title = "Missing markdown source configuration.";
}

button.addEventListener("click", function () {
if (button.dataset.isBusy === "true") {
return;
}

button.dataset.isBusy = "true";
button.disabled = true;
setButtonLabel(button, "Copying...");

var cachedMarkdown = button.dataset.markdownSource || "";
var copyPromise = cachedMarkdown
? copyText(cachedMarkdown)
: fetchMarkdown(sourceUrl)
.then(function (strippedMarkdown) {
button.dataset.markdownSource = strippedMarkdown;
return copyText(strippedMarkdown);
})
.catch(function () {
var visibleText = getVisiblePageText();
if (!visibleText) {
throw new Error("Unable to load copy content.");
}
return copyText(visibleText);
});

copyPromise
.then(function () {
setButtonLabel(button, successLabel);
button.title = "Copied to clipboard.";
button.dataset.isBusy = "false";
button.disabled = false;
restoreLabelAfterDelay(button, defaultLabel, 2000);
})
.catch(function () {
setButtonLabel(button, errorLabel);
button.title = "Unable to copy markdown.";
button.dataset.isBusy = "false";
button.disabled = false;
restoreLabelAfterDelay(button, defaultLabel, 2500);
});
});
}

function createButtonIfMissing() {
if (document.querySelector(".js-copy-page-markdown")) {
return;
}

var markdownPath = window.__rubyllmMcpMarkdownPath || "";
if (!markdownPath || markdownPath.indexOf(".md") === -1) {
return;
}

var main = document.querySelector("#main-content > main");
if (!main) {
return;
}

var actions = document.createElement("div");
actions.className = "page-actions";

var button = document.createElement("button");
button.type = "button";
button.className = "page-copy-button js-copy-page-markdown";
button.dataset.markdownBase = window.__rubyllmMcpMarkdownSourceBaseUrl || "";
button.dataset.markdownPath = markdownPath;
button.dataset.labelDefault = "📋 Copy page";
button.dataset.labelSuccess = "✅ Copied!";
button.dataset.labelError = "⚠ Copy failed";
button.innerHTML = '<span class="page-copy-button__text">Copy page</span>';

actions.appendChild(button);
main.insertBefore(actions, main.firstElementChild);
}

document.addEventListener("DOMContentLoaded", function () {
createButtonIfMissing();

var buttons = document.querySelectorAll(".js-copy-page-markdown");
if (!buttons.length) {
return;
}

buttons.forEach(function (button) {
setupButton(button);
});
});
})();
3 changes: 3 additions & 0 deletions docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ This section contains advanced implementation guidance.
## OAuth
- **[OAuth]({% link guides/oauth.md %})** {: .label .label-green } 1.0 - OAuth 2.1 support with PKCE and browser authentication

## Agent mode
- **[Agents]({% link guides/agents.md %})** - MCP toolset DSL and `RubyLLM::MCP::Agents` integration

## Upgrading

- **[Upgrading]({% link guides/upgrading.md %})** - Unified migration guide with sections for updates to 1.0, 0.8, and 0.7
Loading