Create two files in your vault root: `publish.css` and `publish.js`. These files won't be editable inside Obsidian, unless you install a dedicated plugin for that. Use an external text editor to edit the files. You can also download these files from [here](https://gist.github.com/zsviczian/0bb31aa2d08ba689c14158e82cbbda5a)
# publish.css
```css
@font-face {font-family: "Virgil";src: url("https://excalidraw.com/Virgil.woff2");}
@font-face {font-family: "Cascadia";src: url("https://excalidraw.com/Cascadia.woff2");}
@font-face {font-family: "Assistant";src: url("https://excalidraw.com/Assistant-Regular.woff2");}
div.markdown-embed-title {
display: none;
}
div.markdown-embed {
border: none;
padding: 0px;
background-color: inherit;
}
div.excalidraw-svg {
/*width: fit-content;*/
height: 100%;
}
svg.excalidraw-svg {
max-width:100%;
max-height: 90vh;
width: var(--page-width);
}
svg.excalidraw-svg.ex-pageheight {
width: initial;
height: 100%;
}
svg.excalidraw-svg.ex-pagewidth {
width: 90vw;
height: initial;
}
.excalidraw-svg .text {
width: 100%;
text-align: center;
}
div.excalidraw-svg.enlarged {
position: fixed;
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
body {
--background-primary: #FFF6F0;
}
a.site-body-left-column-site-name {
display: none;
}
div.site-footer {
display: none;
}
```
# Publish.js
```js
const clickToEnlarge = "Click and hold to enlarge. SHIFT + wheel to zoom. ESC to reset.";
const clickToCollapse = "ESC to reset. Click and hold to collapse. SHIFT + wheel to zoom";
//check if in iFrame - if yes the page is assumed to be an embedded frame
if(window.self !== window.top) {
const elements = [
"div.site-body-right-column",
"div.site-body-left-column",
"div.site-header",
"div.site-footer"
];
elements.forEach(x=>{
document.querySelectorAll(x).forEach(div=>{
div.style.display = "none";
});
});
}
const baseUrl = `${window.location.origin}/`;
const [isDesktop, isMobile, isTablet] = (()=>{
const userAgent = navigator.userAgent;
const mobileKeywords = ['Mobile', 'Android', 'iPhone', 'iPad', 'Windows Phone'];
const isMobile = mobileKeywords.some(keyword => userAgent.includes(keyword));
const isTablet = /iPad/i.test(userAgent) || (isMobile && !/Mobile/i.test(userAgent));
const isDesktop = !isMobile && !isTablet;
return [isDesktop, isMobile, isTablet];
})();
const addNavigationToDiv = (container) => {
const svgElement = container?.querySelector('.excalidraw-svg');
if(!svgElement) return;
container.addClass("excalidraw-svg");
svgElement.removeAttribute("width");
svgElement.removeAttribute("height");
if(!isDesktop) return;
const textDiv = document.createElement('div');
textDiv.className = 'text';
textDiv.textContent = clickToEnlarge;
container.appendChild(textDiv);
let isEnlarged = false;
let timeout = null;
let isReadyToPan = false;
let isPanning = false;
let zoomLevel = 1;
let panX = 0;
let panY = 0;
let pinchStartDistance = 0;
let panStartX = 0;
let panStartY = 0;
const clearEnlargeTimeout = () => {
if(timeout) clearTimeout(timeout);
timeout = null;
}
const enablePointerEvents = (val) => {
svgElement.querySelectorAll("a").forEach(el=>{
el.style.pointerEvents = val ? "all" : "none";
});
}
const applyTransform = () => {
svgElement.style.transform = `scale(${zoomLevel}) translate(${panX}px, ${panY}px)`;
clearEnlargeTimeout();
};
//Wheel zoom
svgElement.addEventListener('wheel', (event) => {
if(!event.shiftKey ) return;
if (event.deltaY > 0) {
zoomLevel -= zoomLevel > 4
? (zoomLevel > 6
? (zoomLevel > 10 ? 0.4 : 0.3)
: 0.2)
: 0.1;
} else {
zoomLevel += zoomLevel > 4
? (zoomLevel > 6
? (zoomLevel > 10 ? 0.4 : 0.3)
: 0.2)
: 0.1;
}
applyTransform();
});
// Panning
svgElement.addEventListener('mousedown', (event) => {
isReadyToPan = true;
panStartX = event.clientX;
panStartY = event.clientY;
});
svgElement.addEventListener('mousemove', (event) => {
const deltaX = event.clientX - panStartX;
const deltaY = event.clientY - panStartY;
const distance = Math.sqrt(deltaX**2+deltaY**2);
if (isReadyToPan && (distance > 20)) {
if(!isPanning) {
enablePointerEvents(false);
isPanning = true;
}
panX += deltaX/zoomLevel;
panY += deltaY/zoomLevel;
panStartX = event.clientX;
panStartY = event.clientY;
applyTransform();
}
});
svgElement.addEventListener('mouseup', () => {
enablePointerEvents(true);
isPanning = false;
isReadyToPan = false;
});
svgElement.addEventListener('mouseleave', () => {
enablePointerEvents(true);
isPanning = false;
isReadyToPan = false;
});
//abort on Escape
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
enablePointerEvents(true);
isEnlarged = false;
isPanning = false;
isReadyToPan = false;
container.classList.remove("enlarged");
textDiv.textContent = clickToEnlarge;
zoomLevel = 1;
panX = 0;
panY = 0;
applyTransform();
}
});
//Enlarge on long click
svgElement.addEventListener('mouseup', () => clearEnlargeTimeout());
svgElement.addEventListener('mousedown', () => {
timeout = setTimeout(()=> {
timeout = null;
if(isPanning) return;
isReadyToPan = false;
if (isEnlarged) {
// Collapse the image
container.classList.remove("enlarged");
textDiv.textContent = clickToEnlarge;
} else {
// Enlarge the image
container.addClass("enlarged");
textDiv.textContent = clickToCollapse;
}
isEnlarged = !isEnlarged;
},1000);
});
applyTransform();
}
const processIMG = (img) => {
const svgURL = img.src;
const container = img.parentElement;
fetch(svgURL)
.then((response) => {
if (response.ok) {
return response.text();
}
throw new Error('Failed to fetch SVG');
})
.then((svgContent) => {
svgContainer = document.createElement('div');
svgContainer.innerHTML = svgContent;
svgContainer.querySelectorAll(`a[href^="obsidian://open?vault="`).forEach(el=>{
el.setAttribute("href",unescape(el.getAttribute("href").replace(/.*&file=/,baseUrl).replaceAll(" ","+")));
});
svgContainer.querySelectorAll(`iframe[src^="obsidian://open?vault="`).forEach(el=>{
el.setAttribute("src",unescape(el.getAttribute("src").replace(/.*&file=/,baseUrl).replaceAll(" ","+")));
});
container.removeChild(img);
container.appendChild(svgContainer);
addNavigationToDiv(svgContainer);
})
.catch((error) => {
console.error('Error: ' + error);
});
}
const addImgMutationObserver = () => {
const targetElement = document.body;
const handleImgAddition = (mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node instanceof Element && node.querySelector(`img[alt$=".svg"]`)) {
processIMG(node.querySelector(`img[alt$=".svg"]`));
};
});
}
}
}
const observer = new MutationObserver(handleImgAddition);
const config = { childList: true, subtree: true };
observer.observe(targetElement, config);
}
//process images after loading
document.body.querySelectorAll(`img[alt$=".svg"`).forEach(img => {
processIMG(img);
});
addImgMutationObserver();
```