Skip to content

Instantly share code, notes, and snippets.

@MaxWolf-01
Created December 5, 2024 01:09
Show Gist options
  • Select an option

  • Save MaxWolf-01/354de940ad7ed80a9f2fe9884f5c99bc to your computer and use it in GitHub Desktop.

Select an option

Save MaxWolf-01/354de940ad7ed80a9f2fe9884f5c99bc to your computer and use it in GitHub Desktop.
Quartz copy raw markdown component

Preview:

image

Setup:

  • copy RawMarkdown.tsx to quartz/components
  • copy rawmarkdown.scss to quartz/components/styles
  • copy rawmarkdown.inline.ts to quartz/components/scripts
  • put it somewhere in quartz.layout.ts I put it as the last component before the body (view it in action):
export const defaultContentPageLayout: PageLayout = {
  beforeBody: [
    ...
    Component.RawMarkdown(),
  ],
  ...
  • execute the three lines below in your deploy script after the build step to make the raw md files accessible at mypage.com/raw-content:
      - name: Copy Raw Markdown Content
        run: |
          mkdir -p public/raw-content
          cp -r content/* public/raw-content/
          find public/raw-content -type f ! -name "*.md" -delete
async function setupButtons() {
const buttons = document.querySelector(".raw-markdown-buttons")
if (!buttons) return
const rawUrl = buttons.getAttribute("data-raw-url")
const filename = buttons.getAttribute("data-filename")
if (!rawUrl || !filename) return
async function getContent() {
const response = await fetch(rawUrl as string)
if (!response.ok) {
console.error("Failed to fetch:", rawUrl)
return null
}
return await response.text()
}
const copyBtn = buttons.querySelector(".raw-btn-copy")
if (copyBtn) {
copyBtn.addEventListener("click", async () => {
const content = await getContent()
if (!content) return
await navigator.clipboard.writeText(content)
const originalIcon = copyBtn.innerHTML
copyBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>`
setTimeout(() => (copyBtn.innerHTML = originalIcon), 1000)
})
}
const downloadBtn = buttons.querySelector(".raw-btn-download")
if (downloadBtn) {
downloadBtn.addEventListener("click", async () => {
const content = await getContent()
if (!content) return
const a = document.createElement("a")
const blob = new Blob([content], { type: "text/markdown" })
a.href = URL.createObjectURL(blob)
a.download = filename
a.click()
URL.revokeObjectURL(a.href)
})
}
const viewBtn = buttons.querySelector(".raw-btn-view")
if (viewBtn) {
viewBtn.addEventListener("click", async () => {
const content = await getContent()
if (!content) return
const modal = document.createElement("div")
modal.className = "raw-modal"
modal.innerHTML = `
<div class="raw-modal-content">
<div class="raw-modal-header">
<span>${filename}</span>
<button class="raw-modal-close">×</button>
</div>
<pre class="raw-modal-pre">${content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</pre>
</div>
`
document.body.appendChild(modal)
const closeModal = () => document.body.removeChild(modal)
modal.querySelector(".raw-modal-close")?.addEventListener("click", closeModal)
modal.addEventListener("click", (e) => {
if (e.target === modal) closeModal()
})
})
}
}
document.addEventListener("nav", setupButtons)
.raw-markdown {
display: inline-flex;
gap: 4px;
}
.raw-markdown-buttons {
display: flex;
gap: 4px;
}
.raw-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.3rem;
border: 1px solid var(--lightgray);
border-radius: 4px;
color: var(--darkgray);
cursor: pointer;
transition: all 150ms ease;
background: transparent;
&:hover {
color: var(--secondary);
border-color: var(--secondary);
}
}
.dark .raw-btn {
color: var(--lightgray);
border-color: var(--darkgray);
&:hover {
color: var(--secondary);
border-color: var(--secondary);
}
}
.raw-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 200ms ease;
}
.raw-modal-content {
background: var(--light);
border-radius: 8px;
width: 90%;
max-width: 800px;
max-height: 90vh;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
animation: slideIn 200ms ease;
}
.dark .raw-modal-content {
background: var(--dark);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}
.raw-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--lightgray);
font-size: 14px;
}
.dark .raw-modal-header {
border-color: var(--darkgray);
}
.raw-modal-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: var(--darkgray);
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 150ms ease;
&:hover {
background: var(--lightgray);
}
}
.dark .raw-modal-close {
color: var(--lightgray);
&:hover {
background: var(--darkgray);
}
}
.raw-modal-pre {
overflow: auto;
padding: 16px;
margin: 0;
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, monospace;
font-size: 13px;
line-height: 1.5;
background: var(--light);
color: var(--darkgray);
}
.dark .raw-modal-pre {
background: var(--dark);
color: var(--lightgray);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideIn {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { classNames } from "../util/lang"
// @ts-ignore
import script from "./scripts/rawmarkdown.inline"
import style from "./styles/rawmarkdown.scss"
export default (() => {
const RawMarkdown: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
if (!fileData?.filePath) return null
const rawUrl = `/raw-content/${fileData.filePath.replace(/^content\//, '')}`
const filename = fileData.filePath.split('/').pop() ?? 'document.md'
return (
<div class={classNames(displayClass, "raw-markdown")}>
<div
class="raw-markdown-buttons"
data-raw-url={rawUrl}
data-filename={filename}
>
<button
class="raw-btn raw-btn-copy"
aria-label="Copy raw content"
title="Copy raw content"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>
<button
class="raw-btn raw-btn-download"
aria-label="Download raw file"
title="Download raw file"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
</button>
<button
class="raw-btn raw-btn-view"
aria-label="View raw file"
title="View raw file"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
</button>
</div>
</div>
)
}
RawMarkdown.css = style
RawMarkdown.afterDOMLoaded = script
return RawMarkdown
}) satisfies QuartzComponentConstructor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment