2018-02-04 19:12:24 +02:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
2018-03-20 01:04:48 +02:00
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
<style>
|
|
|
|
body {
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
#content {
|
|
|
|
overflow-y: auto;
|
|
|
|
height: 100%;
|
|
|
|
padding-left: 10px;
|
|
|
|
padding-right: 10px;
|
|
|
|
}
|
|
|
|
|
2018-03-20 01:04:48 +02:00
|
|
|
mark {
|
|
|
|
background: #CF3F00;
|
|
|
|
color: white;
|
|
|
|
}
|
|
|
|
|
2018-05-19 20:09:27 +02:00
|
|
|
ul ul, ul ol, ol ul, ol ol {
|
|
|
|
margin-bottom: 0px;
|
|
|
|
}
|
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
.katex { font-size: 1.3em; } /* This controls the global Katex font size*/
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
|
2018-02-04 20:45:52 +02:00
|
|
|
<body id="body">
|
2018-02-04 19:12:24 +02:00
|
|
|
<div id="hlScriptContainer"></div>
|
2018-03-20 01:04:48 +02:00
|
|
|
<div id="markScriptContainer"></div>
|
2018-02-04 19:12:24 +02:00
|
|
|
<div id="content" ondragstart="return false;" ondrop="return false;"></div>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
const contentElement = document.getElementById('content');
|
|
|
|
|
2018-09-21 19:20:06 +02:00
|
|
|
const ipc = {};
|
|
|
|
|
|
|
|
window.addEventListener('message', (event) => {
|
|
|
|
// Here we only deal with messages that are sent from the main Electro process to the webview.
|
|
|
|
if (!event.data || event.data.target !== 'webview') return;
|
|
|
|
|
|
|
|
const callName = event.data.name;
|
|
|
|
const callData = event.data.data;
|
|
|
|
|
|
|
|
if (!ipc[callName]) {
|
|
|
|
console.warn('Missing IPC function:', event.data);
|
|
|
|
} else {
|
|
|
|
ipc[callName](callData);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
// Handle dynamically loading HLJS when a code element is present
|
|
|
|
// ----------------------------------------------------------------------
|
2017-11-10 20:43:54 +02:00
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
let hljsScriptAdded = false;
|
|
|
|
let hljsLoaded = false;
|
2017-11-10 20:43:54 +02:00
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
function loadHljs(callback) {
|
|
|
|
hljsScriptAdded = true;
|
|
|
|
|
|
|
|
const script = document.createElement('script');
|
|
|
|
script.onload = function () {
|
|
|
|
hljsLoaded = true;
|
2018-03-20 01:04:48 +02:00
|
|
|
applyHljs();
|
2018-02-04 19:12:24 +02:00
|
|
|
};
|
|
|
|
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);
|
2017-11-10 01:28:08 +02:00
|
|
|
}
|
2018-02-04 19:12:24 +02:00
|
|
|
|
|
|
|
function loadAndApplyHljs() {
|
|
|
|
var codeElements = document.getElementsByClassName('code');
|
|
|
|
if (!codeElements.length) return;
|
|
|
|
|
|
|
|
if (!hljsScriptAdded) {
|
|
|
|
this.loadHljs();
|
|
|
|
return;
|
2017-11-10 20:20:14 +02:00
|
|
|
}
|
2018-02-04 19:12:24 +02:00
|
|
|
|
|
|
|
// 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);
|
2017-11-10 20:20:14 +02:00
|
|
|
}
|
2017-11-28 22:58:07 +02:00
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
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);
|
2017-11-28 22:58:07 +02:00
|
|
|
}
|
2018-02-04 19:12:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
// / 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_;
|
2017-11-28 22:58:07 +02:00
|
|
|
}
|
2017-11-07 23:11:14 +02:00
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
function restorePercentScroll() {
|
|
|
|
setPercentScroll(percentScroll_);
|
2017-11-28 22:58:07 +02:00
|
|
|
}
|
|
|
|
|
2018-09-21 19:20:06 +02:00
|
|
|
ipc.setHtml = (event) => {
|
|
|
|
const html = event.html;
|
|
|
|
|
2018-02-04 20:45:52 +02:00
|
|
|
updateBodyHeight();
|
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
contentElement.innerHTML = html;
|
|
|
|
|
|
|
|
loadAndApplyHljs();
|
2017-11-07 23:11:14 +02:00
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
// Remove the bullet from "ul" for checkbox lists and extra padding
|
2018-05-01 17:45:17 +02:00
|
|
|
// 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;
|
|
|
|
// }
|
2017-11-07 23:11:14 +02:00
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
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);
|
|
|
|
}
|
2018-09-21 19:20:06 +02:00
|
|
|
}
|
2018-02-04 19:12:24 +02:00
|
|
|
|
|
|
|
let ignoreNextScrollEvent = false;
|
2018-09-21 19:20:06 +02:00
|
|
|
ipc.setPercentScroll = (event) => {
|
|
|
|
const percent = event.percent;
|
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
if (checkScrollIID_) {
|
|
|
|
clearInterval(checkScrollIID_);
|
|
|
|
checkScrollIID_ = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
ignoreNextScrollEvent = true;
|
|
|
|
setPercentScroll(percent);
|
2018-09-21 19:20:06 +02:00
|
|
|
}
|
2018-02-04 19:12:24 +02:00
|
|
|
|
2018-03-20 01:04:48 +02:00
|
|
|
let mark_ = null;
|
|
|
|
function setMarkers(keywords) {
|
|
|
|
if (!mark_) {
|
|
|
|
mark_ = new Mark(document.getElementById('content'), {
|
|
|
|
exclude: ['img'],
|
|
|
|
acrossElements: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
mark_.mark(keywords);
|
|
|
|
}
|
|
|
|
|
|
|
|
let markLoaded_ = false;
|
2018-09-21 19:20:06 +02:00
|
|
|
ipc.setMarkers = (event) => {
|
|
|
|
const keywords = event.keywords;
|
|
|
|
|
2018-03-20 01:04:48 +02:00
|
|
|
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);
|
2018-03-23 19:29:08 +02:00
|
|
|
markLoaded_ = true;
|
2018-03-20 01:04:48 +02:00
|
|
|
} else {
|
|
|
|
setMarkers(keywords);
|
|
|
|
}
|
2018-09-21 19:20:06 +02:00
|
|
|
}
|
2018-03-20 01:04:48 +02:00
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
function maxScrollTop() {
|
|
|
|
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
2017-11-07 23:11:14 +02:00
|
|
|
}
|
2018-02-04 19:12:24 +02:00
|
|
|
|
2018-02-04 20:45:52 +02:00
|
|
|
// 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';
|
|
|
|
}
|
|
|
|
|
2018-09-21 19:20:06 +02:00
|
|
|
const ipcProxySendToHost = (methodName, arg) => {
|
|
|
|
window.postMessage({ target: 'main', name: methodName, args: [ arg ] }, '*');
|
|
|
|
}
|
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
contentElement.addEventListener('scroll', function(e) {
|
|
|
|
if (ignoreNextScrollEvent) {
|
|
|
|
ignoreNextScrollEvent = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const m = maxScrollTop();
|
|
|
|
const percent = m ? contentElement.scrollTop / m : 0;
|
|
|
|
setPercentScroll(percent);
|
2018-09-21 19:20:06 +02:00
|
|
|
|
|
|
|
ipcProxySendToHost('percentScroll', percent);
|
2018-02-04 19:12:24 +02:00
|
|
|
});
|
|
|
|
|
2018-02-07 22:23:17 +02:00
|
|
|
document.addEventListener('contextmenu', function(event) {
|
2018-03-09 22:46:28 +02:00
|
|
|
let element = event.target;
|
|
|
|
|
|
|
|
// To handle right clicks on resource icons
|
|
|
|
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
|
|
|
|
|
2018-02-07 22:23:17 +02:00
|
|
|
if (element && element.getAttribute('data-resource-id')) {
|
2018-09-21 19:20:06 +02:00
|
|
|
ipcProxySendToHost('contextMenu', {
|
2018-06-26 01:52:46 +02:00
|
|
|
type: element.getAttribute('src') ? 'image' : 'resource',
|
2018-02-07 22:23:17 +02:00
|
|
|
resourceId: element.getAttribute('data-resource-id'),
|
|
|
|
});
|
2018-06-26 01:52:46 +02:00
|
|
|
} else {
|
|
|
|
const selectedText = window.getSelection().toString();
|
|
|
|
|
|
|
|
if (selectedText) {
|
2018-09-21 19:20:06 +02:00
|
|
|
ipcProxySendToHost('contextMenu', {
|
2018-06-26 01:52:46 +02:00
|
|
|
type: 'text',
|
|
|
|
textToCopy: selectedText,
|
|
|
|
});
|
|
|
|
} else if (event.target.getAttribute('href')) {
|
2018-09-21 19:20:06 +02:00
|
|
|
ipcProxySendToHost('contextMenu', {
|
2018-06-26 01:52:46 +02:00
|
|
|
type: 'link',
|
|
|
|
textToCopy: event.target.getAttribute('href'),
|
|
|
|
});
|
|
|
|
}
|
2018-02-07 22:23:17 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-10-05 20:21:23 +02:00
|
|
|
// Prevent URLs added via <a> tags from being opened within the application itself
|
|
|
|
document.addEventListener('click', function(event) {
|
|
|
|
const t = event.target;
|
|
|
|
if (t && t.nodeName === 'A' && !t.hasAttribute('data-from-md')) {
|
|
|
|
event.preventDefault();
|
|
|
|
ipcProxySendToHost(t.getAttribute('href'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
// 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();
|
|
|
|
});
|
2018-02-04 20:45:52 +02:00
|
|
|
|
|
|
|
window.addEventListener('resize', function() {
|
|
|
|
updateBodyHeight();
|
|
|
|
});
|
|
|
|
|
|
|
|
updateBodyHeight();
|
2018-02-04 19:12:24 +02:00
|
|
|
</script>
|
|
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|