Knowledge base: /docs/<title> permalinks, search, categories, count, better PDF

Articles get their own address (shareable, back/forward work); the list is
searchable by title and text, shows the article count, and is grouped into
Applications and Troubleshooting & Guides via a hidden category line set
from the editor. PDF export lays out at page width with page breaks, a title
block and page numbers; Print uses the browser's vector print. Inline code
is a pill that glows blue on hover, blockquotes no longer squeeze, and
GitHub-style [!NOTE] alerts render. [[Badge]] syntax inside code spans is
left alone.
This commit is contained in:
Johan Sandoval committed 2026-10-06 19:31:19 +00:00
1 parent 514bc31566
commit a5c0a5210e
24 files changed
+525 -11

No files matched your search

+24 -10
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>USMT Dash</title>
<base href="/">
<link rel="icon" href="/favicon.png" type="image/png">
<script src="/vendor/marked/marked.min.js"></script>
@@ -353,9 +354,11 @@
</style>
<link rel="stylesheet" href="/layout.css">
<link rel="stylesheet" href="/overview.css">
<link rel="stylesheet" href="/docs.css">
<script src="/layout.js"></script>
<script src="/device-info.js"></script>
<script src="/overview.js"></script>
<script src="/docs.js"></script>
<script src="/excel-filter.js" defer></script>
</head>
<body>
@@ -1751,7 +1754,7 @@
const preview = document.getElementById('docContent');
if (preview && preview.style.display !== 'none') {
// Encode spaces ONLY in UNC/Local paths so marked.js doesn't fail to parse them
let safeText = content.replace(/\[([^\]]+)\]\(((\\\\|[A-Za-z]:\\)[^\)]+)\)/g, (match, title, url) => {
let safeText = shieldCode(content).replace(/\[([^\]]+)\]\(((\\\\|[A-Za-z]:\\)[^\)]+)\)/g, (match, title, url) => {
return `[${title}](${url.replace(/ /g, '%20')})`;
});
@@ -1772,7 +1775,7 @@
return `<a href="javascript:void(0);" class="copy-trigger" data-copy="${safeVal}" title="Click to copy">${safeDisplay}</a>`;
});
preview.innerHTML = typeof marked !== 'undefined' ? marked.parse(safeText) : `<pre>${safeText}</pre>`;
preview.innerHTML = typeof marked !== 'undefined' ? marked.parse(unshieldCode(safeText)) : `<pre>${unshieldCode(safeText)}</pre>`;
formatCodeBlocks();
}
}
@@ -1826,6 +1829,17 @@
startDashboard();
};
// Code spans and fenced blocks are set aside while the [[Badge:..]] / [[Copy:..]] / UNC
// path rewrites run, so an article can show that syntax literally inside `code`
const CODE_SHIELD = [];
function shieldCode(text) {
CODE_SHIELD.length = 0;
return String(text).replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]+`)/g, m => `\u0000CODE${CODE_SHIELD.push(m) - 1}\u0000`);
}
function unshieldCode(text) {
return String(text).replace(/\u0000CODE(\d+)\u0000/g, (m, i) => CODE_SHIELD[+i]);
}
async function loadDocsList() {
try {
const res = await fetch('/get-docs-list?t=' + Date.now());
@@ -1890,7 +1904,7 @@
`;
// Encode spaces ONLY in UNC/Local paths so marked.js doesn't fail to parse them
let safeText = text.replace(/\[([^\]]+)\]\(((\\\\|[A-Za-z]:\\)[^\)]+)\)/g, (match, title, url) => {
let safeText = shieldCode(text).replace(/\[([^\]]+)\]\(((\\\\|[A-Za-z]:\\)[^\)]+)\)/g, (match, title, url) => {
return `[${title}](${url.replace(/ /g, '%20')})`;
});
@@ -1911,7 +1925,7 @@
return `<a href="javascript:void(0);" class="copy-trigger" data-copy="${safeVal}" title="Click to copy">${safeDisplay}</a>`;
});
const parsedContent = typeof marked !== 'undefined' ? marked.parse(safeText) : `<pre style="white-space: pre-wrap; font-family: inherit;">${safeText}</pre>`;
const parsedContent = typeof marked !== 'undefined' ? marked.parse(unshieldCode(safeText)) : `<pre style="white-space: pre-wrap; font-family: inherit;">${unshieldCode(safeText)}</pre>`;
document.getElementById('docContent').innerHTML = parsedContent;
// Keep the actual spaces in the editor so the user doesn't see ugly %20 encoding!
@@ -1975,7 +1989,7 @@
const rawText = editor.value;
if (rawText) {
// Encode spaces ONLY in UNC/Local paths so marked.js doesn't fail to parse them
let safeText = rawText.replace(/\[([^\]]+)\]\(((\\\\|[A-Za-z]:\\)[^\)]+)\)/g, (match, title, url) => {
let safeText = shieldCode(rawText).replace(/\[([^\]]+)\]\(((\\\\|[A-Za-z]:\\)[^\)]+)\)/g, (match, title, url) => {
return `[${title}](${url.replace(/ /g, '%20')})`;
});
@@ -1996,7 +2010,7 @@
return `<a href="javascript:void(0);" class="copy-trigger" data-copy="${safeVal}" title="Click to copy">${safeDisplay}</a>`;
});
preview.innerHTML = typeof marked !== 'undefined' ? marked.parse(safeText) : `<pre>${safeText}</pre>`;
preview.innerHTML = typeof marked !== 'undefined' ? marked.parse(unshieldCode(safeText)) : `<pre>${unshieldCode(safeText)}</pre>`;
formatCodeBlocks();
}
}
@@ -2688,7 +2702,7 @@
const nameText = s.Name || '--';
const linkMatch = availableDocs.find(d => d.toLowerCase() === nameText.toLowerCase());
const nameDisplay = linkMatch
? `<a href="?doc=${encodeURIComponent(linkMatch)}" target="_blank" onclick="event.stopPropagation();" style="color: #3498db;"><b>${nameText}</b></a>`
? `<a href="/docs/${encodeURIComponent(linkMatch)}" target="_blank" onclick="event.stopPropagation();" style="color: #3498db;"><b>${nameText}</b></a>`
: `<b>${nameText}</b>`;
return `
<tr>
@@ -2833,7 +2847,7 @@
let hyperlinkedApps = sApps.map(app => {
const linkMatch = availableDocs.find(d => d.toLowerCase() === app.toLowerCase());
return linkMatch
? `<a href="?doc=${encodeURIComponent(linkMatch)}" target="_blank" onclick="event.stopPropagation();" style="color: #3498db; font-weight: bold;">${app}</a>`
? `<a href="/docs/${encodeURIComponent(linkMatch)}" target="_blank" onclick="event.stopPropagation();" style="color: #3498db; font-weight: bold;">${app}</a>`
: app;
});
specialtySoftware = hyperlinkedApps.length > 0 ? hyperlinkedApps.join("<br>") : "None";
@@ -3058,7 +3072,7 @@
let hyperlinkedMissing = missing.map(app => {
const linkMatch = availableDocs.find(d => d.toLowerCase() === app.toLowerCase());
return linkMatch
? `<a href="?doc=${encodeURIComponent(linkMatch)}" target="_blank" onclick="event.stopPropagation();" style="color: #3498db; font-weight: bold;">${app}</a>`
? `<a href="/docs/${encodeURIComponent(linkMatch)}" target="_blank" onclick="event.stopPropagation();" style="color: #3498db; font-weight: bold;">${app}</a>`
: app;
});
missingSoftwareDisplay = hyperlinkedMissing.length > 0 ? hyperlinkedMissing.join(", ") : "None";
@@ -3171,7 +3185,7 @@
let hyperlinkedMissing = missing.map(app => {
const linkMatch = availableDocs.find(d => d.toLowerCase() === app.toLowerCase());
return linkMatch
? `<a href="?doc=${encodeURIComponent(linkMatch)}" target="_blank" onclick="event.stopPropagation();" style="color: #3498db; font-weight: bold;">${app}</a>`
? `<a href="/docs/${encodeURIComponent(linkMatch)}" target="_blank" onclick="event.stopPropagation();" style="color: #3498db; font-weight: bold;">${app}</a>`
: app;
});
enriched["MissingSoftwareDisplay"] = hyperlinkedMissing.length > 0 ? hyperlinkedMissing.join(", ") : "None";