1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-15 09:04:04 +02:00
joplin/ElectronClient/app/gui/note-viewer/index.html

260 lines
7.2 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
overflow: hidden;
}
#content {
overflow-y: auto;
height: 100%;
padding-left: 10px;
padding-right: 10px;
}
mark {
background: #CF3F00;
color: white;
}
ul ul, ul ol, ol ul, ol ol {
margin-bottom: 0px;
}
.katex { font-size: 1.3em; } /* This controls the global Katex font size*/
</style>
</head>
<body id="body">
<div id="hlScriptContainer"></div>
<div id="markScriptContainer"></div>
<div id="content" ondragstart="return false;" ondrop="return false;"></div>
<script>
const contentElement = document.getElementById('content');
// ----------------------------------------------------------------------
// Handle dynamically loading HLJS when a code element is present
// ----------------------------------------------------------------------
let hljsScriptAdded = false;
let hljsLoaded = false;
function loadHljs(callback) {
hljsScriptAdded = true;
const script = document.createElement('script');
script.onload = function () {
hljsLoaded = true;
applyHljs();
};
script.src = 'highlight/highlight.pack.js';
document.getElementById('hlScriptContainer').appendChild(script);
const link = document.createElement('link');
link.rel = 'stylesheet';
// https://ace.c9.io/build/kitchen-sink.html
// https://highlightjs.org/static/demo/
link.href = 'highlight/styles/atom-one-light.css';
document.getElementById('hlScriptContainer').appendChild(link);
}
function loadAndApplyHljs() {
var codeElements = document.getElementsByClassName('code');
if (!codeElements.length) return;
if (!hljsScriptAdded) {
this.loadHljs();
return;
}
// If HLJS is not loaded yet, no need to do anything. When it loads
// it will automatically apply the style to all the code elements.
if (hljsLoaded) applyHljs(codeElements);
}
function applyHljs(codeElements) {
if (typeof codeElements === 'undefined') codeElements = document.getElementsByClassName('code');
for (var i = 0; i < codeElements.length; i++) {
try {
hljs.highlightBlock(codeElements[i]);
} catch (error) {
console.warn('Cannot highlight code', error);
}
}
}
// ----------------------------------------------------------------------
// / Handle dynamically loading HLJS when a code element is present
// ----------------------------------------------------------------------
// Note: the scroll position source of truth is "percentScroll_". This is easier to manage than scrollTop because
// the scrollTop value depends on the images being loaded or not. For example, if the scrollTop is saved while
// images are being displayed then restored while images are being reloaded, the new scrollTop might be changed
// so that it is not greater than contentHeight. On the other hand, with percentScroll it is possible to restore
// it at any time knowing that it's not going to be changed because the content height has changed.
// To restore percentScroll the "checkScrollIID" interval is used. It constantly resets the scroll position during
// one second after the content has been updated.
//
// ignoreNextScroll is used to differentiate between scroll event from the users and those that are the result
// of programmatically changing scrollTop. We only want to respond to events initiated by the user.
let percentScroll_ = 0;
let checkScrollIID_ = null;
function setPercentScroll(percent) {
percentScroll_ = percent;
contentElement.scrollTop = percentScroll_ * maxScrollTop();
}
function percentScroll() {
return percentScroll_;
}
function restorePercentScroll() {
setPercentScroll(percentScroll_);
}
ipcRenderer.on('setHtml', (event, html) => {
updateBodyHeight();
contentElement.innerHTML = html;
loadAndApplyHljs();
// Remove the bullet from "ul" for checkbox lists and extra padding
// const checkboxes = document.getElementsByClassName('checkbox');
// for (let i = 0; i < checkboxes.length; i++) {
// const cb = checkboxes[i];
// const ul = cb.parentElement.parentElement;
// if (!ul) {
// console.warn('Unexpected layout for checkbox');
// continue;
// }
// ul.style.listStyleType = 'none';
// ul.style.paddingLeft = 0;
// }
let previousContentHeight = contentElement.scrollHeight;
let startTime = Date.now();
ignoreNextScrollEvent = true;
restorePercentScroll();
if (!checkScrollIID_) {
checkScrollIID_ = setInterval(() => {
const h = contentElement.scrollHeight;
if (h !== previousContentHeight) {
previousContentHeight = h;
ignoreNextScrollEvent = true;
restorePercentScroll();
}
if (Date.now() - startTime >= 1000) {
clearInterval(checkScrollIID_);
checkScrollIID_ = null;
}
}, 1);
}
});
let ignoreNextScrollEvent = false;
ipcRenderer.on('setPercentScroll', (event, percent) => {
if (checkScrollIID_) {
clearInterval(checkScrollIID_);
checkScrollIID_ = null;
}
ignoreNextScrollEvent = true;
setPercentScroll(percent);
});
let mark_ = null;
function setMarkers(keywords) {
if (!mark_) {
mark_ = new Mark(document.getElementById('content'), {
exclude: ['img'],
acrossElements: true,
});
}
mark_.mark(keywords);
}
let markLoaded_ = false;
ipcRenderer.on('setMarkers', (event, keywords) => {
if (!keywords.length && !markLoaded_) return;
if (!markLoaded_) {
const script = document.createElement('script');
script.onload = function() {
setMarkers(keywords);
};
script.src = '../../node_modules/mark.js/dist/mark.min.js';
document.getElementById('markScriptContainer').appendChild(script);
markLoaded_ = true;
} else {
setMarkers(keywords);
}
});
function maxScrollTop() {
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
}
// The body element needs to have a fixed height for the content to be scrollable
function updateBodyHeight() {
document.getElementById('body').style.height = window.innerHeight + 'px';
}
contentElement.addEventListener('scroll', function(e) {
if (ignoreNextScrollEvent) {
ignoreNextScrollEvent = false;
return;
}
const m = maxScrollTop();
const percent = m ? contentElement.scrollTop / m : 0;
setPercentScroll(percent);
ipcRenderer.sendToHost('percentScroll', percent);
});
document.addEventListener('contextmenu', function(event) {
let element = event.target;
// To handle right clicks on resource icons
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
if (element && element.getAttribute('data-resource-id')) {
ipcRenderer.sendToHost('contextMenu', {
type: element.getAttribute('src') ? 'image' : 'link',
resourceId: element.getAttribute('data-resource-id'),
});
}
});
// Disable drag and drop otherwise it's possible to drop a URL
// on it and it will open in the view as a website.
document.addEventListener('drop', function(e) {
e.preventDefault();
e.stopPropagation();
});
document.addEventListener('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
});
document.addEventListener('dragover', function(e) {
e.preventDefault();
});
window.addEventListener('resize', function() {
updateBodyHeight();
});
updateBodyHeight();
</script>
</body>
</html>