forked from modelcontextprotocol/modelcontextprotocol
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathversion-warning.js
More file actions
197 lines (171 loc) · 7.29 KB
/
Copy pathversion-warning.js
File metadata and controls
197 lines (171 loc) · 7.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Version Warning Banner
// Displays a warning banner on older spec and documentation versions and
// on draft spec pages.
//
// Mintlify automatically loads all .js files in the docs directory, and
// the site maintains /specification/latest/* and /docs/latest/* redirects
// that always point to the current latest version of each section. We use
// those redirects as the source of truth, so this script requires no
// updates when a new version is published. Styling re-uses Mintlify's
// native callout classes.
const DRAFT_VERSION = "draft";
const SECTIONS = [
{
pathRegex: /\/specification\/(\d{4}-\d{2}-\d{2}|draft|latest)(\/.*)?$/,
latestAliasPath: "/specification/latest",
storageKey: "mcp-latest-spec-version",
noun: "specification",
draftMessage: "You are viewing a draft of a not-yet-finalized specification.",
},
{
pathRegex: /\/docs\/(\d{4}-\d{2}-\d{2}|draft|latest)(\/.*)?$/,
latestAliasPath: "/docs/latest",
storageKey: "mcp-latest-docs-version",
noun: "documentation",
draftMessage:
"You are viewing draft documentation for a not-yet-finalized specification.",
},
];
const BANNER_ATTR = "data-spec-version-banner";
const CALLOUT_CLASS_WARNING =
"callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 " +
"border border-yellow-200 bg-yellow-50 " +
"dark:border-yellow-900 dark:bg-yellow-600/20 " +
"[&_[data-component-part='callout-icon']]:mt-px";
const CALLOUT_CLASS_INFO =
"callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 " +
"border border-neutral-200 bg-neutral-50 " +
"dark:border-neutral-700 dark:bg-white/10";
const CONTENT_CLASS_BASE =
"text-sm prose dark:prose-invert min-w-0 w-full " +
"[&_a]:!text-current [&_a]:border-current [&_strong]:!text-current ";
const CONTENT_CLASS_WARNING =
CONTENT_CLASS_BASE + "text-yellow-800 dark:text-yellow-300";
const CONTENT_CLASS_INFO =
CONTENT_CLASS_BASE + "text-neutral-800 dark:text-neutral-300";
const ICON_WRAPPER_CLASS = "mt-0.5 w-4";
const ICON_SVG_WARNING =
'<svg class="flex-none size-5 text-yellow-800 dark:text-yellow-300" ' +
'fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" ' +
'aria-label="Warning">' +
'<path stroke-linecap="round" stroke-linejoin="round" ' +
'd="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 ' +
"4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" +
'"></path></svg>';
const ICON_SVG_INFO =
'<svg class="flex-none size-5 text-neutral-800 dark:text-neutral-300" ' +
'viewBox="0 0 20 20" fill="currentColor" aria-label="Info">' +
'<path d="M8 0C3.58125 0 0 3.58125 0 8C0 12.4187 3.58125 16 8 ' +
"16C12.4187 16 16 12.4187 16 8C16 3.58125 12.4187 0 8 0ZM8 14.5C4.41563 " +
"14.5 1.5 11.5841 1.5 8C1.5 4.41594 4.41563 1.5 8 1.5C11.5844 1.5 14.5 " +
"4.41594 14.5 8C14.5 11.5841 11.5844 14.5 8 14.5ZM9.25 10.5H8.75V7.75C8.75 " +
"7.3375 8.41563 7 8 7H7C6.5875 7 6.25 7.3375 6.25 7.75C6.25 8.1625 6.5875 " +
"8.5 7 8.5H7.25V10.5H6.75C6.3375 10.5 6 10.8375 6 11.25C6 11.6625 6.3375 " +
"12 6.75 12H9.25C9.66406 12 10 11.6641 10 11.25C10 10.8359 9.66563 10.5 " +
"9.25 10.5ZM8 6C8.55219 6 9 5.55219 9 5C9 4.44781 8.55219 4 8 4C7.44781 " +
'4 7 4.44687 7 5C7 5.55313 7.44687 6 8 6Z"></path></svg>';
function parseVersionedPath(pathname) {
for (const section of SECTIONS) {
const match = pathname.match(section.pathRegex);
if (match) return { section, version: match[1], subPath: match[2] || "" };
}
return null;
}
async function resolveLatestVersion(section) {
const cached = sessionStorage.getItem(section.storageKey);
if (cached) return cached;
const response = await fetch(section.latestAliasPath, { method: "HEAD" });
const resolvedPath = new URL(response.url).pathname;
const match = resolvedPath.match(section.pathRegex);
const version = match ? match[1] : null;
if (version && version !== "latest") {
sessionStorage.setItem(section.storageKey, version);
}
return version;
}
function createBanner(message, linkHref, linkText, isDraft) {
const banner = document.createElement("div");
banner.className = isDraft ? CALLOUT_CLASS_INFO : CALLOUT_CLASS_WARNING;
banner.setAttribute("role", "alert");
banner.setAttribute(BANNER_ATTR, "");
banner.setAttribute("data-callout-type", isDraft ? "info" : "warning");
const iconWrapper = document.createElement("div");
iconWrapper.className = ICON_WRAPPER_CLASS;
iconWrapper.setAttribute("data-component-part", "callout-icon");
iconWrapper.innerHTML = isDraft ? ICON_SVG_INFO : ICON_SVG_WARNING;
const content = document.createElement("div");
content.className = isDraft ? CONTENT_CLASS_INFO : CONTENT_CLASS_WARNING;
content.setAttribute("data-component-part", "callout-content");
const text = document.createElement("span");
text.textContent = message + " ";
const link = document.createElement("a");
link.href = linkHref;
link.textContent = linkText;
content.appendChild(text);
content.appendChild(link);
banner.appendChild(iconWrapper);
banner.appendChild(content);
return banner;
}
let inserting = false;
async function insertWarningBanner() {
if (inserting) return;
if (document.querySelector(`[${BANNER_ATTR}]`)) return;
const current = parseVersionedPath(window.location.pathname);
if (!current) return;
inserting = true;
try {
const latest = await resolveLatestVersion(current.section).catch(
() => null,
);
if (!latest || current.version === latest) return;
// Re-check after the await — a concurrent call may have already
// inserted a banner, or SPA navigation may have changed the page.
if (document.querySelector(`[${BANNER_ATTR}]`)) return;
if (
parseVersionedPath(window.location.pathname)?.version !== current.version
)
return;
const contentArea = document.querySelector(
"#content-area, main, article, .content",
);
if (!contentArea) return;
// Mintlify redirects <section>/latest/<sub-path> to the actual
// version, so users land on the equivalent page in the latest version.
const latestHref = current.section.latestAliasPath + current.subPath;
const linkText = `View the latest version (${latest})`;
const isDraft = current.version === DRAFT_VERSION;
const message = isDraft
? current.section.draftMessage
: `You are viewing an older version (${current.version}) of the ${current.section.noun}.`;
const banner = createBanner(message, latestHref, linkText, isDraft);
// Place the banner just below the page title block. Mintlify wraps
// the H1 in <header id="header">; fall back to a raw <h1> or the
// top of the content area.
const header =
contentArea.querySelector("header#header") ||
contentArea.querySelector("h1");
if (header) {
header.insertAdjacentElement("afterend", banner);
} else {
contentArea.insertBefore(banner, contentArea.firstChild);
}
} finally {
inserting = false;
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", insertWarningBanner);
} else {
insertWarningBanner();
}
// Re-insert after SPA navigation.
const observer = new MutationObserver(() => {
if (
!document.querySelector(`[${BANNER_ATTR}]`) &&
parseVersionedPath(window.location.pathname)
) {
insertWarningBanner();
}
});
observer.observe(document.body, { childList: true, subtree: true });