include README.md
include requirements.txt
-include nhentai/doujinshi.html
+include nhentai/viewer/index.html
+include nhentai/viewer/styles.css
+include nhentai/viewer/scripts.js
+++ /dev/null
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <title>{TITLE}</title>
- <style>
- html, body {{
- background-color: #e8e6e6;
- height: 100%;
- padding: 0;
- margin: 0;
- overflow: hidden;
- }}
-
- .container img {{
- display: block;
- width: 100%;
- margin: 30px 0;
- padding: 10px;
- cursor: pointer;
- }}
-
- .container {{
- height: 100%;
- overflow: scroll;
- background: #e8e6e6;
- width: 200px;
- padding: 30px;
- float: left;
- }}
-
- .image {{
- margin-left: 260px;
- height: 100%;
- background: #222;
- text-align: center;
- }}
-
- .image img {{
- height: 100%;
- }}
-
- .i a {{
- display: block;
- position: absolute;
- top: 0;
- width: 50%;
- height: 100%;
- }}
-
- .i {{
- position: relative;
- height: 100%;
- }}
-
- .current {{
- background: #BBB;
- border-radius: 10px;
- }}
- </style>
-
- <script>
- function cursorfocus(elem) {{
- var container = document.getElementsByClassName('container')[0];
- container.scrollTop = elem.offsetTop - 500;
- }}
-
- function getImage(type) {{
- var current = document.getElementsByClassName("current")[0];
- current.className = "image-item";
- var img_src = type == 1 ? current.getAttribute('attr-next') : current.getAttribute('attr-prev');
- if (img_src === "") {{
- img_src = current.src;
- }}
-
- var img_list = document.getElementsByClassName("image-item");
- for (i=0; i<img_list.length; i++) {{
- if (img_list[i].src.endsWith(img_src)) {{
- img_list[i].className = "image-item current";
- cursorfocus(img_list[i]);
- break;
- }}
- }}
- var display = document.getElementById("dest");
- display.src = img_src;
- display.focus();
- }}
- </script>
-</head>
-<body>
-
-<div class="container">
-{IMAGES}</div>
-<div class="image">
- <div class="i">
- <img src="" id="dest">
- <a href="javascript:getImage(-1)" style="left: 0;"></a>
- <a href="javascript:getImage(1)" style="left: 50%;"></a>
- </div>
-</div>
-</body>
-
-<script>
- var img_list = document.getElementsByClassName("image-item");
-
- var display = document.getElementById("dest");
- display.src = img_list[0].src;
-
- for (var i = 0; i < img_list.length; i++) {{
- img_list[i].addEventListener('click', function() {{
- var current = document.getElementsByClassName("current")[0];
- current.className = "image-item";
- this.className = "image-item current";
- var display = document.getElementById("dest");
- display.src = this.src;
- display.focus();
- }}, false);
- }}
-
- document.onkeypress = function(e) {{
- if (e.keyCode == 32) {{
- getImage(1);
- }}
- }}
-</script>
-</html>
\ No newline at end of file
return urlparse(url)
+def readfile(path):
+ loc = os.path.dirname(__file__)
+
+ with open(os.path.join(loc, path), 'r') as file:
+ return file.read()
def generate_html(output_dir='.', doujinshi_obj=None):
image_html = ''
- previous = ''
if doujinshi_obj is not None:
doujinshi_dir = os.path.join(output_dir, format_filename('%s-%s' % (doujinshi_obj.id,
file_list = os.listdir(doujinshi_dir)
file_list.sort()
- for index, image in enumerate(file_list):
+ for image in file_list:
if not os.path.splitext(image)[1] in ('.jpg', '.png'):
continue
- try:
- next_ = file_list[file_list.index(image) + 1]
- except IndexError:
- next_ = ''
-
- image_html += '<img src="{0}" class="image-item {1}" attr-prev="{2}" attr-next="{3}">\n'\
- .format(image, 'current' if index == 0 else '', previous, next_)
- previous = image
+ image_html += '<img src="{0}" class="image-item"/>\n'\
+ .format(image)
- with open(os.path.join(os.path.dirname(__file__), 'doujinshi.html'), 'r') as template:
- html = template.read()
+ html = readfile('viewer/index.html')
+ css = readfile('viewer/styles.css')
+ js = readfile('viewer/scripts.js')
if doujinshi_obj is not None:
title = doujinshi_obj.name
else:
title = 'nHentai HTML Viewer'
- data = html.format(TITLE=title, IMAGES=image_html)
+ data = html.format(TITLE=title, IMAGES=image_html, SCRIPTS=js, STYLES=css)
with open(os.path.join(doujinshi_dir, 'index.html'), 'w') as f:
f.write(data)
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>{TITLE}</title>
+ <style>
+{STYLES}
+ </style>
+</head>
+<body>
+
+<nav id="list">
+{IMAGES}</nav>
+
+<div id="image-container">
+ <span id="page-num"></span>
+ <div id="dest"></div>
+</div>
+
+<script>
+{SCRIPTS}
+</script>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+const pages = Array.from(document.querySelectorAll('img.image-item'));
+let currentPage = 0;
+
+function changePage(pageNum) {
+ const previous = pages[currentPage];
+ const current = pages[pageNum];
+
+ if (current == null) {
+ return;
+ }
+
+ previous.classList.remove('current');
+ current.classList.add('current');
+
+ currentPage = pageNum;
+
+ const display = document.getElementById('dest');
+ display.style.backgroundImage = `url("${current.src}")`;
+
+ document.getElementById('page-num')
+ .innerText = [
+ (pageNum + 1).toLocaleString(),
+ pages.length.toLocaleString()
+ ].join('\u200a/\u200a');
+}
+
+changePage(0);
+
+document.getElementById('list').onclick = event => {
+ if (pages.includes(event.target)) {
+ changePage(pages.indexOf(event.target));
+ }
+};
+
+document.getElementById('image-container').onclick = event => {
+ const width = document.getElementById('image-container').clientWidth;
+ const clickPos = event.clientX / width;
+
+ if (clickPos < 0.5) {
+ changePage(currentPage - 1);
+ } else {
+ changePage(currentPage + 1);
+ }
+};
+
+document.onkeypress = event => {
+ switch (event.key.toLowerCase()) {
+ // Previous Image
+ case 'arrowleft':
+ case 'a':
+ changePage(currentPage - 1);
+ break;
+
+ // Next Image
+ case ' ':
+ case 'enter':
+ case 'arrowright':
+ case 'd':
+ changePage(currentPage + 1);
+ break;
+ }
+};
\ No newline at end of file
--- /dev/null
+*, *::after, *::before {
+ box-sizing: border-box;
+}
+
+img {
+ vertical-align: middle;
+}
+
+html, body {
+ display: flex;
+ background-color: #e8e6e6;
+ height: 100%;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ font-family: sans-serif;
+}
+
+#list {
+ height: 100%;
+ overflow: auto;
+ width: 260px;
+ text-align: center;
+}
+
+#list img {
+ width: 200px;
+ padding: 10px;
+ border-radius: 10px;
+ margin: 15px 0;
+ cursor: pointer;
+}
+
+#list img.current {
+ background: #0003;
+}
+
+#image-container {
+ flex: auto;
+ height: 100vh;
+ background: #222;
+ color: #fff;
+ text-align: center;
+ cursor: pointer;
+ -webkit-user-select: none;
+ user-select: none;
+ position: relative;
+}
+
+#image-container #dest {
+ height: 100%;
+ width: 100%;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+#image-container #page-num {
+ position: absolute;
+ font-size: 18pt;
+ left: 10px;
+ bottom: 5px;
+ font-weight: bold;
+ opacity: 0.75;
+ text-shadow: /* Duplicate the same shadow to make it very strong */
+ 0 0 2px #222,
+ 0 0 2px #222,
+ 0 0 2px #222;
+}
\ No newline at end of file