mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-02 02:44:41 +00:00
520 lines
15 KiB
HTML
Vendored
520 lines
15 KiB
HTML
Vendored
<!DOCTYPE html>
|
|
<html dir="{{rtl}}">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
|
|
<script>
|
|
// Load the saved width immediately
|
|
const savedWidth = localStorage.getItem("tocWidth") || "178px";
|
|
document.documentElement.style.setProperty("--toc-width", savedWidth);
|
|
</script>
|
|
|
|
<link rel="stylesheet" href="highlight.js/styles/github.min.css" />
|
|
<link
|
|
rel="stylesheet"
|
|
href="highlight.js/styles/github-dark.min.css"
|
|
media="(prefers-color-scheme: dark)"
|
|
/>
|
|
|
|
<script src="highlight.js/highlight.min.js"></script>
|
|
|
|
<script src="js/markdownItAnchor.umd.js"></script>
|
|
</head>
|
|
<body class="markdown-body">
|
|
<link rel="stylesheet" href="css/github-markdown.css" />
|
|
|
|
<script src="js/markdown-it.min.js"></script>
|
|
|
|
<style>
|
|
body {
|
|
background-color: var(--bgColor-default);
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
/* Add a wrapper div for the flex layout */
|
|
.container {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
#preview {
|
|
flex: 1;
|
|
padding: 1rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* From github.com */
|
|
.container-lg {
|
|
max-width: 1012px;
|
|
margin-right: auto;
|
|
margin-left: auto;
|
|
}
|
|
|
|
#toc {
|
|
font-family: "Segoe UI", Helvetica, Arial, sans-serif, "Segoe UI Emoji",
|
|
"Segoe UI Symbol";
|
|
position: sticky;
|
|
top: 0;
|
|
left: 0;
|
|
|
|
width: var(--toc-width);
|
|
|
|
min-width: 4px;
|
|
max-width: 80%;
|
|
|
|
height: 100vh;
|
|
background: var(--bgColor-muted);
|
|
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
|
|
max-height: 100vh;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#toc a {
|
|
display: block;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.table-of-contents {
|
|
font-size: 0.8em;
|
|
padding: 1em 2em;
|
|
}
|
|
|
|
.table-of-contents ul {
|
|
list-style-type: none;
|
|
padding-left: 1em;
|
|
}
|
|
|
|
.table-of-contents > ul {
|
|
padding: 0;
|
|
}
|
|
|
|
.table-of-contents li {
|
|
margin: 0 0 0.75em 0;
|
|
}
|
|
|
|
.table-of-contents a {
|
|
color: var(--fgColor-default);
|
|
text-decoration: none;
|
|
border-left: 2px solid transparent;
|
|
margin-left: -1em;
|
|
padding-left: calc(1em - 2px);
|
|
transition: border-left-color 0.2s ease-out;
|
|
}
|
|
|
|
.table-of-contents a:hover {
|
|
color: var(--fgColor-accent);
|
|
}
|
|
|
|
.table-of-contents .active {
|
|
color: var(--fgColor-accent);
|
|
border-left: 2px solid var(--fgColor-accent);
|
|
margin-left: -1em;
|
|
padding-left: calc(1em - 2px);
|
|
transition: all 0.2s ease-out;
|
|
}
|
|
|
|
h1:target,
|
|
h2:target,
|
|
h3:target,
|
|
h4:target {
|
|
animation: highlight 1s ease;
|
|
}
|
|
|
|
@keyframes highlight {
|
|
from {
|
|
background: var(--bgColor-attention-muted);
|
|
}
|
|
to {
|
|
background: var(--bgColor-default);
|
|
}
|
|
}
|
|
|
|
.resize-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 9999;
|
|
cursor: ew-resize;
|
|
user-select: none;
|
|
}
|
|
|
|
/* RTL support */
|
|
html[dir="rtl"] .container {
|
|
flex-direction: row-reverse;
|
|
}
|
|
|
|
html[dir="rtl"] #toc {
|
|
right: 0;
|
|
left: auto;
|
|
}
|
|
|
|
html[dir="rtl"] .table-of-contents a {
|
|
border-right: 2px solid transparent;
|
|
border-left: none;
|
|
margin-right: -1em;
|
|
margin-left: 0;
|
|
padding-right: calc(1em - 2px);
|
|
padding-left: 0;
|
|
}
|
|
|
|
html[dir="rtl"] .table-of-contents .active {
|
|
border-right: 2px solid var(--fgColor-accent);
|
|
border-left: none;
|
|
margin-right: -1em;
|
|
margin-left: 0;
|
|
padding-right: calc(1em - 2px);
|
|
padding-left: 0;
|
|
}
|
|
</style>
|
|
|
|
<textarea id="text-input" style="display: none">{{content}}</textarea>
|
|
|
|
<div class="container">
|
|
<div id="toc" class="toc"></div>
|
|
<div id="preview" class="container-lg"></div>
|
|
</div>
|
|
|
|
<script>
|
|
var md = window
|
|
.markdownit({
|
|
html: true,
|
|
typographer: false,
|
|
quotes: "“”‘’",
|
|
highlight: function (str, lang) {
|
|
if (lang && hljs.getLanguage(lang)) {
|
|
try {
|
|
return (
|
|
'<pre class="hljs"><code>' +
|
|
hljs.highlight(str, { language: lang, ignoreIllegals: true })
|
|
.value +
|
|
"</code></pre>"
|
|
);
|
|
} catch (__) {}
|
|
}
|
|
return (
|
|
'<pre class="hljs"><code>' +
|
|
md.utils.escapeHtml(str) +
|
|
"</code></pre>"
|
|
);
|
|
},
|
|
})
|
|
.use(markdownItAnchor, {
|
|
permalink: false,
|
|
slugify: (s) =>
|
|
s
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9 -]/g, "")
|
|
.replace(/\s+/g, "-")
|
|
.replace(/-+/g, "-"),
|
|
});
|
|
document.getElementById("preview").innerHTML = md.render(
|
|
document.getElementById("text-input").value
|
|
);
|
|
|
|
/* codes below are adopted from https://codepen.io/jtojnar/full/Juiop */
|
|
var ToC = `<nav role='navigation' class='table-of-contents'>
|
|
<h2>Contents</h2>
|
|
<ul>`;
|
|
|
|
var newLine,
|
|
el,
|
|
title,
|
|
link,
|
|
level = 0,
|
|
baseLevel = 0,
|
|
count = 0;
|
|
|
|
document
|
|
.querySelectorAll("#preview h1, #preview h2, #preview h3, #preview h4")
|
|
.forEach(function (heading) {
|
|
count++;
|
|
el = heading;
|
|
title = el.textContent;
|
|
|
|
if (!el.id) el.id = "h-" + count;
|
|
link = "#" + el.id;
|
|
|
|
var prevLevel = level || 0;
|
|
level = heading.nodeName.substr(1);
|
|
if (!baseLevel) {
|
|
// make sure you start with highest level of heading or it won't work
|
|
baseLevel = level;
|
|
}
|
|
if (prevLevel == 0) {
|
|
newLine = "<li>";
|
|
} else if (level == prevLevel) {
|
|
newLine = "</li><li>";
|
|
} else if (level > prevLevel) {
|
|
newLine = "</li><ul><li>";
|
|
} else if (level < prevLevel) {
|
|
newLine = "</li></ul>".repeat(prevLevel - level) + "</li><li>";
|
|
}
|
|
|
|
newLine += `<a href="${link}"${
|
|
count === 1 ? ` class="active"` : ``
|
|
}>${title}</a>`;
|
|
|
|
ToC += newLine;
|
|
});
|
|
|
|
ToC +=
|
|
"</li></ul>".repeat(level - baseLevel) + "</li>" + "</ul>" + "</nav>";
|
|
|
|
document.getElementById("toc").innerHTML = ToC;
|
|
|
|
if (count < 2) {
|
|
document.getElementById("toc").style.display = "none";
|
|
document.querySelector(".markdown-body").style.width = "100%";
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
// == Resizable TOC width ==
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const toc = document.getElementById("toc");
|
|
let isResizing = false;
|
|
let lastDownX = 0;
|
|
let overlay = null;
|
|
const RESIZE_HANDLE_WIDTH = 8; // Width of the resize handle area in pixels
|
|
|
|
// Create overlay function
|
|
function createOverlay() {
|
|
overlay = document.createElement("div");
|
|
overlay.className = "resize-overlay";
|
|
document.body.appendChild(overlay);
|
|
document.body.style.userSelect = "none"; // Disable text selection
|
|
}
|
|
|
|
// Remove overlay function
|
|
function removeOverlay() {
|
|
if (overlay && overlay.parentNode) {
|
|
overlay.parentNode.removeChild(overlay);
|
|
overlay = null;
|
|
document.body.style.userSelect = ""; // Re-enable text selection
|
|
}
|
|
}
|
|
|
|
// Check if cursor is in resize area
|
|
function isInResizeArea(clientX, rect) {
|
|
const toc = document.getElementById("toc");
|
|
const hasScrollbar = toc.scrollHeight > toc.clientHeight;
|
|
const scrollbarWidth = hasScrollbar ? 17 : 0; // Standard scrollbar width in most browsers
|
|
return (
|
|
clientX > rect.right - scrollbarWidth - RESIZE_HANDLE_WIDTH &&
|
|
clientX < rect.right - scrollbarWidth + RESIZE_HANDLE_WIDTH
|
|
);
|
|
}
|
|
|
|
// Handle cursor style when moving over the resize area
|
|
toc.addEventListener("mousemove", function (e) {
|
|
const rect = toc.getBoundingClientRect();
|
|
if (isInResizeArea(e.clientX, rect)) {
|
|
toc.style.cursor = "ew-resize";
|
|
} else {
|
|
toc.style.cursor = "auto";
|
|
}
|
|
});
|
|
|
|
toc.addEventListener("mousedown", function (e) {
|
|
const rect = toc.getBoundingClientRect();
|
|
if (isInResizeArea(e.clientX, rect)) {
|
|
isResizing = true;
|
|
lastDownX = e.clientX;
|
|
createOverlay();
|
|
}
|
|
});
|
|
|
|
document.addEventListener("mousemove", function (e) {
|
|
if (!isResizing) return;
|
|
const width = toc.getBoundingClientRect().width;
|
|
const newWidth = width + (e.clientX - lastDownX);
|
|
|
|
toc.style.width = newWidth + "px";
|
|
|
|
lastDownX = e.clientX;
|
|
e.preventDefault();
|
|
});
|
|
|
|
document.addEventListener("mouseup", function () {
|
|
if (isResizing) {
|
|
isResizing = false;
|
|
removeOverlay();
|
|
// Save the new width
|
|
const newWidth = toc.style.width;
|
|
localStorage.setItem("tocWidth", newWidth);
|
|
document.documentElement.style.setProperty("--toc-width", newWidth);
|
|
}
|
|
});
|
|
|
|
// Reset cursor when leaving the TOC
|
|
toc.addEventListener("mouseleave", function () {
|
|
if (!isResizing) {
|
|
toc.style.cursor = "auto";
|
|
}
|
|
});
|
|
|
|
// Prevent default behavior of spacebar scrolling the page, while we use spacebar to dismiss the preview
|
|
document.addEventListener("keydown", function (e) {
|
|
if (e.code === "Space") {
|
|
e.preventDefault();
|
|
}
|
|
|
|
// Support keyboard shortcuts for RTL and LTR text direction
|
|
// RTL: Ctrl + RShift
|
|
// LTR: Ctrl + LShift
|
|
if ((e.ctrlKey || e.metaKey)) {
|
|
if (e.shiftKey && e.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
|
|
// Right Shift + Ctrl: RTL
|
|
document.documentElement.setAttribute('dir', 'rtl');
|
|
e.preventDefault();
|
|
} else if (e.shiftKey && e.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) {
|
|
// Left Shift + Ctrl: LTR
|
|
document.documentElement.setAttribute('dir', 'ltr');
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<script>
|
|
// == Help track your position in the file with highlighted TOC link ==
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
let isManualScroll = false;
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
if (isManualScroll) return;
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
updateActiveHeading(entry.target);
|
|
}
|
|
});
|
|
},
|
|
{
|
|
rootMargin: "-10% 0px -85% 0px", // Adjust these values to change when the active state triggers
|
|
}
|
|
);
|
|
|
|
// Function to update active heading
|
|
function updateActiveHeading(heading) {
|
|
document.querySelectorAll(".table-of-contents a").forEach((link) => {
|
|
link.classList.remove("active");
|
|
});
|
|
|
|
const id = heading.getAttribute("id");
|
|
const tocLink = document.querySelector(
|
|
`.table-of-contents a[href="#${id}"]`
|
|
);
|
|
if (tocLink) {
|
|
tocLink.classList.add("active");
|
|
|
|
//#region Add auto-scroll functionality
|
|
const toc = document.getElementById("toc");
|
|
const tocRect = toc.getBoundingClientRect();
|
|
const linkRect = tocLink.getBoundingClientRect();
|
|
|
|
// Check if the active link is outside the visible area
|
|
if (
|
|
linkRect.top < tocRect.top ||
|
|
linkRect.bottom > tocRect.bottom
|
|
) {
|
|
// Simply scroll the element into view with a small offset
|
|
tocLink.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "nearest",
|
|
});
|
|
}
|
|
//#endregion
|
|
}
|
|
}
|
|
|
|
// Function to find the current heading based on scroll position
|
|
function findCurrentHeading() {
|
|
const headings = Array.from(
|
|
document.querySelectorAll(
|
|
"#preview h1, #preview h2, #preview h3, #preview h4"
|
|
)
|
|
);
|
|
|
|
for (const heading of headings) {
|
|
const rect = heading.getBoundingClientRect();
|
|
// Check if the heading is in the viewport
|
|
if (rect.top >= 0 && rect.top <= window.innerHeight * 0.3) {
|
|
return heading;
|
|
}
|
|
}
|
|
|
|
// If no heading is found in the viewport, return the last heading before viewport
|
|
for (let i = headings.length - 1; i >= 0; i--) {
|
|
const rect = headings[i].getBoundingClientRect();
|
|
if (rect.top < 0) {
|
|
return headings[i];
|
|
}
|
|
}
|
|
|
|
// If no heading is found, return the first heading
|
|
return headings[0];
|
|
}
|
|
|
|
// Modify scroll event handler
|
|
document.addEventListener("scroll", () => {
|
|
if (isManualScroll) return;
|
|
const currentHeading = findCurrentHeading();
|
|
if (currentHeading) {
|
|
updateActiveHeading(currentHeading);
|
|
}
|
|
});
|
|
|
|
// Modify anchor link click handler
|
|
document.querySelectorAll(".table-of-contents a").forEach((link) => {
|
|
link.addEventListener("click", () => {
|
|
isManualScroll = true;
|
|
const targetId = link.getAttribute("href").substring(1);
|
|
const targetHeading = document.getElementById(targetId);
|
|
if (targetHeading) {
|
|
updateActiveHeading(targetHeading);
|
|
}
|
|
// Reset the flag after a delay
|
|
// We do this to ensure the clicked heading is highlighted, especially when at the bottom of the page
|
|
setTimeout(() => {
|
|
isManualScroll = false;
|
|
}, 10);
|
|
});
|
|
});
|
|
|
|
// Observe all headings
|
|
document
|
|
.querySelectorAll(
|
|
"#preview h1, #preview h2, #preview h3, #preview h4"
|
|
)
|
|
.forEach((heading) => {
|
|
observer.observe(heading);
|
|
});
|
|
|
|
// Initial active heading
|
|
const currentHeading = findCurrentHeading();
|
|
if (currentHeading) {
|
|
updateActiveHeading(currentHeading);
|
|
}
|
|
});
|
|
|
|
// Support MathJax inline math
|
|
window.MathJax = {
|
|
tex: {
|
|
inlineMath: [['$', '$'], ['\\(', '\\)']]
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<script id="MathJax-script" async src="mathjax/tex-mml-svg.js"></script>
|
|
</body>
|
|
</html>
|