diff --git a/cbreader/settings/base.py b/cbreader/settings/base.py index 64783f2..f7b23f1 100644 --- a/cbreader/settings/base.py +++ b/cbreader/settings/base.py @@ -22,7 +22,6 @@ SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", None) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.getenv('DJANGO_DEBUG', False) == 'True' -# DEBUG = False ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",") @@ -39,7 +38,6 @@ INSTALLED_APPS = ( 'webpack_loader', 'bootstrap4', "comic", - "comic_auth", 'django_extensions', 'imagekit', 'django_boost', @@ -102,8 +100,6 @@ STATIC_URL = "/static/" STATICFILES_DIRS = [ Path(BASE_DIR, "static"), - # Path(BASE_DIR, "node_modules"), - Path(BASE_DIR, "frontend", "node_modules"), Path(BASE_DIR, "frontend", "dist") ] @@ -169,14 +165,6 @@ PERMISSIONS_POLICY = { "usb": [], } -# SESSION_COOKIE_HTTPONLY = True -# SESSION_COOKIE_SECURE = True -# SESSION_COOKIE_SAMESITE = 'Strict' -# CSRF_COOKIE_HTTPONLY = True -# CSRF_COOKIE_SECURE = True -# CSRF_COOKIE_SAMESITE = 'Strict' -# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') - REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, diff --git a/cbreader/urls.py b/cbreader/urls.py index 1b067df..c669c38 100644 --- a/cbreader/urls.py +++ b/cbreader/urls.py @@ -18,14 +18,14 @@ from django.conf.urls import include from django.conf.urls.static import static from django.contrib import admin from django.urls import path, re_path -from rest_framework import routers, permissions -from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView -from rest_framework_extensions.routers import ExtendedDefaultRouter - from django.views.generic import TemplateView - -from drf_yasg.views import get_schema_view from drf_yasg import openapi +from drf_yasg.views import get_schema_view +from rest_framework import permissions +from rest_framework_extensions.routers import ExtendedDefaultRouter +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView + +from comic import rest, feeds schema_view = get_schema_view( openapi.Info( @@ -39,15 +39,8 @@ schema_view = get_schema_view( permission_classes=[permissions.AllowAny] ) - -import comic.views -import comic_auth.views -from comic import rest, feeds - router = ExtendedDefaultRouter() router.register(r'users', rest.UserViewSet) -# router.register(r'usermisc', rest.UserMiscViewSet) -# router.register(r'groups', rest.GroupViewSet) router.register(r'browse', rest.BrowseViewSet, basename='browse') router.register(r'generate_thumbnail', rest.GenerateThumbnailViewSet, basename='generate_thumbnail') router.register(r'read', rest.ReadViewSet, basename='read')\ @@ -60,11 +53,6 @@ router.register(r'initial_setup', rest.InitialSetup, basename='initial_setup') urlpatterns = [ - # url(r"^$", comic.views.comic_redirect), - # url(r"^login/", comic_auth.views.comic_login), - # url(r"^logout/", comic_auth.views.comic_logout), - # url(r"^setup/", comic.views.initial_setup), - # url(r"^comic/", include("comic.urls")), path('admin/', admin.site.urls), path("feed//", feeds.RecentComicsAPI()), re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), diff --git a/comic/feeds.py b/comic/feeds.py index c2bab55..6c2be96 100644 --- a/comic/feeds.py +++ b/comic/feeds.py @@ -1,49 +1,10 @@ -import uuid - from django.contrib.auth.models import User from django.contrib.syndication.views import Feed from django.db.models import Case, When, PositiveSmallIntegerField, F from django.http import HttpRequest from django.shortcuts import get_object_or_404 -from django.urls import reverse -from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode -from .models import ComicBook, UserMisc, Directory - - -class RecentComics(Feed): - title = "CBWebReader Recent Comics" - link = "/comics/" - description = "Recently added Comics" - user: User - - def get_object(self, request: HttpRequest, user_selector: str, *args, **kwargs) -> UserMisc: - user_selector = uuid.UUID(bytes=urlsafe_base64_decode(user_selector)) - user_misc = get_object_or_404(UserMisc, feed_id=user_selector) - self.user = user_misc.user - return user_misc.user - - def items(self) -> ComicBook: - comics = ComicBook.objects.order_by("-date_added") - comics = comics.annotate( - classification=Case( - When(directory__isnull=True, then=Directory.Classification.C_18), - default=F('directory__classification'), - output_field=PositiveSmallIntegerField(choices=Directory.Classification.choices) - ) - ) - comics = comics.filter(classification__lte=self.user.usermisc.allowed_to_read) - return comics[:10] - - def item_title(self, item: ComicBook) -> str: - return item.file_name - - def item_description(self, item: ComicBook) -> str: - return item.date_added.strftime("%a, %e %b %Y %H:%M") - - # item_link is only needed if NewsItem has no get_absolute_url method. - def item_link(self, item: ComicBook) -> str: - return reverse('read_comic', args=(urlsafe_base64_encode(item.selector.bytes),)) +from comic.models import ComicBook, UserMisc, Directory class RecentComicsAPI(Feed): diff --git a/comic/forms.py b/comic/forms.py index ea5a57e..e69de29 100644 --- a/comic/forms.py +++ b/comic/forms.py @@ -1,111 +0,0 @@ -from os import path - -from django import forms -from django.contrib.auth.models import User - -from comic.models import Directory - - -class InitialSetupForm(forms.Form): - username = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"})) - email = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"})) - password = forms.CharField(widget=forms.PasswordInput(attrs={"class": "form-control"})) - password_confirm = forms.CharField( - widget=forms.PasswordInput(attrs={"class": "form-control"}) - ) - - def clean(self): - form_data = self.cleaned_data - if form_data["password"] != form_data["password_confirm"]: - raise forms.ValidationError("Passwords do not match.") - if len(form_data["password"]) < 8: - raise forms.ValidationError("Password is too short") - return form_data - - -class AccountForm(forms.Form): - username = forms.CharField( - required=False, - widget=forms.TextInput(attrs={"class": "form-control disabled", "readonly": True}), - ) - email = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"})) - password = forms.CharField( - required=False, widget=forms.PasswordInput(attrs={"class": "form-control"}) - ) - password_confirm = forms.CharField( - required=False, - widget=forms.PasswordInput(attrs={"class": "form-control"}), - ) - - def clean_email(self): - data = self.cleaned_data["email"] - user = User.objects.get(username=self.cleaned_data["username"]) - if data == user.email: - return data - if User.objects.filter(email=data).exists(): - raise forms.ValidationError("Email Address is in use") - return data - - def clean(self): - form_data = self.cleaned_data - if form_data["password"] != form_data["password_confirm"]: - raise forms.ValidationError("Passwords do not match.") - if len(form_data["password"]) < 8 & len(form_data["password"]) != 0: - raise forms.ValidationError("Password is too short") - return form_data - - -class AddUserForm(forms.Form): - username = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"})) - email = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"})) - password = forms.CharField(widget=forms.PasswordInput(attrs={"class": "form-control"})) - password_confirm = forms.CharField( - widget=forms.PasswordInput(attrs={"class": "form-control"}) - ) - - def clean_username(self): - data = self.cleaned_data["username"] - if User.objects.filter(username=data).exists(): - raise forms.ValidationError("This username Exists.") - return data - - def clean_email(self): - data = self.cleaned_data["email"] - if User.objects.filter(email=data).exists(): - raise forms.ValidationError("Email Address is in use") - return data - - def clean(self): - form_data = self.cleaned_data - if form_data["password"] != form_data["password_confirm"]: - raise forms.ValidationError("Passwords do not match.") - if len(form_data["password"]) < 8: - raise forms.ValidationError("Password is too short") - return form_data - - -class EditUserForm(forms.Form): - username = forms.CharField( - required=False, - widget=forms.TextInput(attrs={"class": "form-control disabled", "readonly": True}), - ) - email = forms.EmailField(widget=forms.TextInput(attrs={"class": "form-control"})) - password = forms.CharField( - required=False, widget=forms.PasswordInput(attrs={"class": "form-control"}) - ) - allowed_to_read = forms.ChoiceField(choices=Directory.Classification.choices) - - @staticmethod - def get_initial_values(user): - out = {"username": user.username, "email": user.email, "allowed_to_read": user.usermisc.allowed_to_read} - return out - - def clean_password(self): - data = self.cleaned_data["password"] - if len(data) < 8 & len(data) != 0: - raise forms.ValidationError("Password is too short") - return data - - -class DirectoryEditForm(forms.Form): - classification = forms.ChoiceField(choices=Directory.Classification.choices) diff --git a/comic/models.py b/comic/models.py index 6207194..30388d7 100644 --- a/comic/models.py +++ b/comic/models.py @@ -3,21 +3,21 @@ import mimetypes import uuid import zipfile from functools import reduce -from itertools import zip_longest, chain +from itertools import zip_longest from pathlib import Path -from typing import Optional, List, Union, Tuple +from typing import Optional, List, Union, Tuple, Final +# noinspection PyPackageRequirements import fitz import rarfile from PIL import Image, UnidentifiedImageError +from PIL.Image import Image as Image_type from django.conf import settings from django.contrib.auth.models import User, AbstractUser from django.core.files.uploadedfile import InMemoryUploadedFile from django.db import models from django.db.models import UniqueConstraint from django.db.transaction import atomic -from django.templatetags.static import static -from django.utils.http import urlsafe_base64_encode from django_boost.models.fields import AutoOneToOneField from imagekit.models import ProcessedImageField from imagekit.processors import ResizeToFill @@ -57,34 +57,14 @@ class Directory(models.Model): return "Directory: {0}; {1}".format(self.name, self.parent) @property - def title(self): + def title(self) -> str: return self.name @property - def type(self): + def type(self) -> str: return 'Directory' - def mark_read(self, user): - books = ComicBook.objects.filter(directory=self) - for book in books: - book.mark_read(user) - - def mark_unread(self, user): - books = ComicBook.objects.filter(directory=self) - for book in books: - book.mark_unread(user) - - def get_thumbnail_url(self): - if self.thumbnail: - return self.thumbnail.url - else: - self.generate_thumbnail() - if self.thumbnail: - return self.thumbnail.url - else: - return static('img/placeholder.png') - - def generate_thumbnail(self): + def generate_thumbnail(self) -> None: book: ComicBook = ComicBook.objects.filter(directory=self).order_by('file_name').first() if not book: return @@ -105,7 +85,7 @@ class Directory(models.Model): else: return Path(path_items[0]) - def get_path_items(self, p: Optional[List] = None) -> List[str]: + def get_path_items(self, p: Optional[List] = None) -> List[Path]: if p is None: p = [] p.append(self.name) @@ -113,7 +93,7 @@ class Directory(models.Model): self.parent.get_path_items(p) return p - def get_path_objects(self, p=None): + def get_path_objects(self, p=None) -> List["Directory"]: if p is None: p = [] p.append(self) @@ -121,14 +101,6 @@ class Directory(models.Model): self.parent.get_path_objects(p) return p - @property - def url_safe_selector(self): - return urlsafe_base64_encode(self.selector.bytes) - - def set_classification(self, form_data): - self.classification = form_data['classification'] - self.save() - class ComicBook(models.Model): file_name = models.TextField() @@ -148,36 +120,17 @@ class ComicBook(models.Model): UniqueConstraint(fields=['directory', 'file_name'], name='one_comic_name_per_directory') ] - def __str__(self): + def __str__(self) -> str: return self.file_name @property - def title(self): + def title(self) -> str: return self.file_name @property - def type(self): + def type(self) -> str: return 'ComicBook' - def mark_read(self, user: User): - status, _ = ComicStatus.objects.get_or_create(comic=self, user=user) - status.mark_read() - - def mark_unread(self, user: User): - status, _ = ComicStatus.objects.get_or_create(comic=self, user=user) - status.mark_unread() - - def mark_previous(self, user): - books = ComicBook.objects.filter(directory=self.directory).order_by('file_name') - for book in books: - if book == self: - break - book.mark_read(user) - - @property - def url_safe_selector(self): - return urlsafe_base64_encode(self.selector.bytes) - def get_pdf(self) -> Path: base_dir = settings.COMIC_BOOK_VOLUME if self.directory: @@ -185,7 +138,7 @@ class ComicBook(models.Model): else: return Path(base_dir, self.file_name) - def get_image(self, page: int): + def get_image(self, page: int) -> Union[Tuple[io.BytesIO, Image_type], Tuple[bool, bool]]: base_dir = settings.COMIC_BOOK_VOLUME if self.directory: archive_path = Path(base_dir, self.directory.path, self.file_name) @@ -196,7 +149,7 @@ class ComicBook(models.Model): except rarfile.NotRarFile: archive = zipfile.ZipFile(archive_path) except zipfile.BadZipfile: - return False + return False, False page_obj = ComicPage.objects.get(Comic=self, index=page) try: @@ -207,39 +160,34 @@ class ComicBook(models.Model): out = (archive.open(page_obj.page_file_name), page_obj.content_type) return out - def get_thumbnail_url(self): - if self.thumbnail: - return self.thumbnail.url - else: - self.generate_thumbnail() - return self.thumbnail.url + def generate_thumbnail_pdf(self, page_index: int = 0) -> Tuple[io.BytesIO, Image_type, str]: + img, pil_data = self._get_pdf_image(page_index if page_index else 0) + return img, pil_data, 'Image/JPEG' - def generate_thumbnail(self, page_index: int = None): + def generate_thumbnail_archive(self, page_index: int = 0) -> Union[Tuple[io.BytesIO, Image_type, str], + Tuple[bool, bool, bool]]: + img, content_type, pil_data = False, False, False + if page_index: + img, content_type = self.get_image(page_index) + pil_data = Image.open(img) + else: + for x in range(ComicPage.objects.filter(Comic=self).count()): + try: + img, content_type = self.get_image(x) + pil_data = Image.open(img) + break + except UnidentifiedImageError: + continue + return img, pil_data, content_type + + def generate_thumbnail(self, page_index: int = None) -> None: if Path(self.file_name).suffix.lower() == '.pdf': - if page_index: - img, pil_data = self._get_pdf_image(page_index) - else: - img, pil_data = self._get_pdf_image(0) - content_type = 'Image/JPEG' + img, pil_data, content_type = self.generate_thumbnail_pdf(page_index if page_index else 0) else: - if page_index: - img, content_type = self.get_image(page_index) - pil_data = Image.open(img) - else: - for x in range(ComicPage.objects.filter(Comic=self).count()): - try: - img, content_type = self.get_image(x) - pil_data = Image.open(img) - break - except UnidentifiedImageError: - continue - try: - img - content_type - pil_data - except NameError: - return + img, pil_data, content_type = self.generate_thumbnail_archive(page_index) + if not img: + return self.thumbnail = InMemoryUploadedFile( img, None, @@ -250,133 +198,25 @@ class ComicBook(models.Model): ) self.save() - def _get_pdf_image(self, page_index: int): - # noinspection PyTypeChecker + def _get_pdf_image(self, page_index: int) -> Tuple[io.BytesIO, Image_type]: + # noinspection PyUnresolvedReferences doc = fitz.open(self.get_pdf()) page = doc[page_index] pix = page.get_pixmap() - mode = "RGBA" if pix.alpha else "RGB" - pil_data = Image.frombytes(mode, [pix.width, pix.height], pix.samples) + mode: Final = "RGBA" if pix.alpha else "RGB" + # noinspection PyTypeChecker + pil_data = Image.frombytes(mode, (pix.width, pix.height), pix.samples) img = io.BytesIO() pil_data.save(img, format="JPEG") img.seek(0) return img, pil_data - def is_last_page(self, page): - if (self.page_count - 1) == page: - return True - return False - @property - def page_count(self): + def page_count(self) -> int: return ComicPage.objects.filter(Comic=self).count() - def nav(self, user): - next_path, next_type = self.nav_get_next_comic(user) - prev_path, prev_type = self.nav_get_prev_comic(user) - return { - "next_path": next_path, - "next_type": next_type, - "prev_path": prev_path, - "prev_type": prev_type, - "cur_path": urlsafe_base64_encode(self.selector.bytes) - } - - def nav_get_prev_comic(self, user) -> str: - base_dir = settings.COMIC_BOOK_VOLUME - if self.directory: - folder = Path(base_dir, self.directory.path) - else: - folder = base_dir - dir_list = ComicBook.get_ordered_dir_list(folder) - comic_index = dir_list.index(self.file_name) - if comic_index == 0: - if self.directory: - comic_path = urlsafe_base64_encode(self.directory.selector.bytes), type(self.directory).__name__ - else: - comic_path = "", None - else: - prev_comic = dir_list[comic_index - 1] - - if Path(folder, prev_comic).is_dir(): - if self.directory: - comic_path = urlsafe_base64_encode(self.directory.selector.bytes), type(self.directory).__name__ - else: - comic_path = "", None - else: - try: - if self.directory: - book = ComicBook.objects.get(file_name=prev_comic, directory=self.directory) - else: - book = ComicBook.objects.get(file_name=prev_comic, directory__isnull=True) - except ComicBook.DoesNotExist: - if self.directory: - book = ComicBook.process_comic_book(Path(prev_comic), self.directory) - else: - book = ComicBook.process_comic_book(Path(prev_comic)) - cs, _ = ComicStatus.objects.get_or_create(comic=book, user=user) - comic_path = urlsafe_base64_encode(book.selector.bytes), type(book).__name__ - - return comic_path - - def nav_get_next_comic(self, user): - base_dir = settings.COMIC_BOOK_VOLUME - if self.directory: - folder = Path(base_dir, self.directory.path) - else: - folder = base_dir - dir_list = ComicBook.get_ordered_dir_list(folder) - comic_index = dir_list.index(self.file_name) - try: - next_comic = dir_list[comic_index + 1] - try: - if self.directory: - book = ComicBook.objects.get(file_name=next_comic, directory=self.directory) - else: - book = ComicBook.objects.get(file_name=next_comic, directory__isnull=True) - except ComicBook.DoesNotExist: - if self.directory: - book = ComicBook.process_comic_book(Path(next_comic), self.directory) - else: - book = ComicBook.process_comic_book(Path(next_comic)) - except ComicBook.MultipleObjectsReturned: - if self.directory: - books = ComicBook.objects.filter(file_name=next_comic, directory=self.directory).order_by('id') - else: - books = ComicBook.objects.get(file_name=next_comic, directory__isnull=True).order_by('id') - book = books.first() - books = books.exclude(id=book.id) - books.delete() - if type(book) is str: - raise IndexError - comic_path = urlsafe_base64_encode(book.selector.bytes), type(book).__name__ - except IndexError: - if self.directory: - comic_path = urlsafe_base64_encode(self.directory.selector.bytes), type(self.directory).__name__ - else: - comic_path = "", None - return comic_path - - class DirFile: - def __init__(self): - self.name = "" - self.isdir = False - self.icon = "" - self.iscb = False - self.location = "" - self.label = "" - self.cur_page = 0 - - def __str__(self): - return self.name - @staticmethod def process_comic_book(comic_file_path: Path, directory: "Directory" = False) -> Union["ComicBook", Path]: - """ - - :type comic_file_path: str - :type directory: Directory - """ try: book = ComicBook.objects.get(file_name=comic_file_path.name, version=0) book.directory = directory @@ -404,19 +244,8 @@ class ComicBook(models.Model): page.save() return book - @staticmethod - def get_ordered_dir_list(folder: Path) -> List[str]: - directories = [] - files = [] - for item in folder.glob('*'): - if item.is_dir(): - directories.append(item) - else: - files.append(item) - return [x.name for x in chain(sorted(directories), sorted(files))] - @property - def get_archive_path(self): + def get_archive_path(self) -> Path: if self.directory: return Path(settings.COMIC_BOOK_VOLUME, self.directory.get_path(), self.file_name) else: @@ -441,13 +270,13 @@ class ComicBook(models.Model): raise NotCompatibleArchive @staticmethod - def get_archive_files(archive): + def get_archive_files(archive) -> List[Tuple[str, str]]: return [ (x, mimetypes.guess_type(x)[0]) for x in sorted(archive.namelist()) if not x.endswith('/') and mimetypes.guess_type(x)[0] ] - def verify_pages(self, pages: Optional["ComicPage"] = None): + def verify_pages(self, pages: Optional["ComicPage"] = None) -> None: if not pages: pages = ComicPage.objects.filter(Comic=self) @@ -493,7 +322,8 @@ class ComicPage(models.Model): class ComicStatus(models.Model): user = models.ForeignKey(User, unique=False, null=False, on_delete=models.CASCADE) - comic = models.ForeignKey(ComicBook, unique=False, blank=False, null=False, on_delete=models.CASCADE, to_field="selector") + comic = models.ForeignKey(ComicBook, unique=False, blank=False, null=False, on_delete=models.CASCADE, + to_field="selector") last_read_page = models.IntegerField(default=0) unread = models.BooleanField(default=True) finished = models.BooleanField(default=False) @@ -503,23 +333,10 @@ class ComicStatus(models.Model): UniqueConstraint(fields=['user', 'comic'], name='one_per_user_per_comic') ] - def mark_read(self): - page_count = ComicPage.objects.filter(Comic=self.comic).count() - self.unread = False - self.finished = True - self.last_read_page = page_count - 1 - self.save() - - def mark_unread(self): - self.unread = True - self.finished = False - self.last_read_page = 0 - self.save() - - def __str__(self): + def __str__(self) -> str: return self.__repr__() - def __repr__(self): + def __repr__(self) -> str: return ( f" - - - - - - - - - - - {% block title %}CB Web Reader{% endblock %} - - - {% sri_static "bootstrap/dist/css/bootstrap.min.css" %} - {% sri_static "datatables.net-bs4/css/dataTables.bootstrap4.min.css" %} - - {% sri_static "css/base.min.css" %} - {% sri_static "@fortawesome/fontawesome-free/css/all.min.css" %} - - - - - - - - {% block content %}{% endblock %} - - - - - - - -{# {% bootstrap_javascript jquery='full' %}#} - - {% sri_static "jquery/dist/jquery.min.js" %} - {% sri_static "bootstrap/dist/js/bootstrap.bundle.js" %} - {% sri_static "datatables.net/js/jquery.dataTables.min.js" %} - {% sri_static "datatables.net-bs4/js/dataTables.bootstrap4.min.js" %} - {% sri_static "js-cookie/src/js.cookie.js" %} - {% sri_static "reveal.js/dist/reveal.js" %} - {% sri_static "reveal.js-menu/menu.js" %} - {% sri_static "hammerjs/hammer.js" %} - {% sri_static "isotope-layout/dist/isotope.pkgd.min.js" %} - - {% block script %} - {% endblock %} - - - - - - - diff --git a/comic/templates/comic/breadcrumbs.html b/comic/templates/comic/breadcrumbs.html deleted file mode 100644 index c3635c0..0000000 --- a/comic/templates/comic/breadcrumbs.html +++ /dev/null @@ -1,7 +0,0 @@ -{% for crumb in breadcrumbs %} - {% if not forloop.last %} - - {% else %} - - {% endif %} -{% endfor %} \ No newline at end of file diff --git a/comic/templates/comic/comic_list.html b/comic/templates/comic/comic_list.html deleted file mode 100644 index bb4023b..0000000 --- a/comic/templates/comic/comic_list.html +++ /dev/null @@ -1,124 +0,0 @@ -{% extends "base.html" %} -{% load sri %} -{% load bootstrap4 %} -{% load static %} -{% block title %}{{ title }}{% endblock %} - -{% block content %} - -
-
-
- -
- - - -
- - -
-
-
-
-
-
-
- {% for file in files %} -
-
- {% if file.item_type == 'Directory' %} - - {% elif file.item_type == 'ComicBook' %} - - {% endif %} - - {% if file.obj.thumbnail %} - {{ file.name }} - {% else %} - {% if file.item_type == 'Directory' %} - {{ file.name }} - {% elif file.item_type == 'ComicBook' %} - {{ file.name }} - {% endif %} - {% endif %} - -
-
- {% if file.item_type == 'Directory' %} - - {% elif file.item_type == 'ComicBook' %} - - {% endif %} - {{ file.name }} - -
-

-

{{ file.total_read }} / {{ file.total }}
-
-
-
-

-
- - -
- - -
- -
-
- {% if file.total_unread and file.item_type == 'Directory' %} - {{ file.total_unread }} - {% endif %} - {{ file.obj.get_classification_display }} - -
-
- {% endfor %} -
-
- -{% endblock %} - -{% block script %} - {{ js_urls|json_script:'js_urls' }} - {% sri_static "js/comic_list.min.js" %} -{% endblock %} \ No newline at end of file diff --git a/comic/templates/comic/read_comic.html b/comic/templates/comic/read_comic.html deleted file mode 100644 index 7767046..0000000 --- a/comic/templates/comic/read_comic.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "base.html" %} -{% load sri %} -{% load static %} -{% block title %}{{ title }}{% endblock %} - -{% block content %} - - -
-
- {% for page in pages %} -
- {% if page.content_type|first in 'image' %} - {{ page.page_file_name }} - {% else %} -

- {% endif %} -
- {% endfor %} -
-
- -{% endblock %} - -{% block script %} - {{ nav|json_script:"nav" }} - {{ status.last_read_page|json_script:"last_read_page" }} - {% sri_static "js/read_comic.min.js" %} -{% endblock %} \ No newline at end of file diff --git a/comic/templates/comic/read_comic_pdf.html b/comic/templates/comic/read_comic_pdf.html deleted file mode 100644 index cf77a72..0000000 --- a/comic/templates/comic/read_comic_pdf.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "base.html" %} -{% load sri %} -{% load static %} -{% block title %}{{ title }}{% endblock %} - -{% block content %} - -
-
-
- - - - - - -
-
-
- -
-
- -{% endblock %} - -{% block script %} - {% sri_static "pdfjs-dist/build/pdf.min.js" %} - {{ nav|json_script:"nav" }} - {{ status.last_read_page|json_script:"last_read_page" }} - {% sri_static 'js/read_comic_pdf.min.js' %} -{% endblock %} \ No newline at end of file diff --git a/comic/templates/comic/recent_comics.html b/comic/templates/comic/recent_comics.html deleted file mode 100644 index 2a76f56..0000000 --- a/comic/templates/comic/recent_comics.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends "base.html" %} -{% load sri %} -{% load static %} -{% block title %}{{ title }}{% endblock %} - -{% block content %} -
-
- {% csrf_token %} - - - - - - - - - - - - - - - - - - - -

Recent Comics - Feed

- mark selected issues as: - -
-
-
File/FolderDate AddedStatus
loading data
-
-
- -{% endblock %} - -{% block script %} -{% sri_static "js/recent_comics.min.js" %} -{% endblock %} \ No newline at end of file diff --git a/comic/templates/comic/settings_page.html b/comic/templates/comic/settings_page.html deleted file mode 100644 index 5640415..0000000 --- a/comic/templates/comic/settings_page.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "base.html" %} -{% load bootstrap4 %} - -{% block title %}{{ title }}{% endblock %} - - -{% block content %} -
- {% if error_message %} - - {% endif %} - {% if success_message %} - - {% endif %} -
- {% csrf_token %} - {% bootstrap_form form %} - {% buttons %} - - {% endbuttons %} -
- {% endblock %} - - {% block content2 %} - {% if error_message %} - - {% endif %} -
- {% csrf_token %} -
- - -
- -
-
-{% endblock %} \ No newline at end of file diff --git a/comic/templates/comic/setup.html b/comic/templates/comic/setup.html deleted file mode 100644 index b2c801e..0000000 --- a/comic/templates/comic/setup.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ title }}{% endblock %} - -{% block content %} -{% if error_message %} - -{% endif %} -{% if success_message %} - -{% endif %} -
- {% csrf_token %} - {% for item in form %} -
- - {{ item }} -
- {% endfor %} - -
-{% endblock %} - -{% block content2 %} -{% if error_message %} - -{% endif %} -
- {% csrf_token %} -
- - -
- -
-{% endblock %} \ No newline at end of file diff --git a/comic/templates/comic/users_page.html b/comic/templates/comic/users_page.html deleted file mode 100644 index 7fc093d..0000000 --- a/comic/templates/comic/users_page.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "base.html" %} - -{% block title %}CBWebReader - Users{% endblock %} - -{% block content %} -
- - - - - - - - - - - - {% for user in users %} - - - - - - - - {% endfor %} - -
#UsernameEmailSuperuserClassification
{{user.id}}{{user.username}}{{user.email}}{{user.is_superuser}}{{ user.usermisc.get_allowed_to_read_display }}
- Add User -
-{% endblock %} -{% block script %} - -{% endblock %} \ No newline at end of file diff --git a/comic/templatetags/__init__.py b/comic/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/comic/templatetags/comic_tags.py b/comic/templatetags/comic_tags.py deleted file mode 100644 index d0b4881..0000000 --- a/comic/templatetags/comic_tags.py +++ /dev/null @@ -1,4 +0,0 @@ -from django import template - -register = template.Library() - diff --git a/comic/tests/test_models.py b/comic/tests/test_models.py deleted file mode 100644 index b1ff949..0000000 --- a/comic/tests/test_models.py +++ /dev/null @@ -1,273 +0,0 @@ -import json -from pathlib import Path - -from django.conf import settings -from django.contrib.auth.models import User -from django.test import Client, TestCase -from django.utils.http import urlsafe_base64_encode - -from comic.models import ComicBook, ComicPage, ComicStatus, Directory -from comic.util import generate_directory - - -# from os import path - - -class ComicBookTests(TestCase): - def setUp(self): - settings.COMIC_BOOK_VOLUME = Path(Path.cwd(), 'test_comics') - User.objects.create_user("test", "test@test.com", "test") - user = User.objects.first() - ComicBook.process_comic_book(Path("test1.rar")) - book = ComicBook.process_comic_book(Path("test2.rar")) - status = ComicStatus(user=user, comic=book, last_read_page=2, unread=False) - status.save() - ComicBook.process_comic_book(Path("test4.rar")) - - def test_comic_processing(self): - book = ComicBook.objects.get(file_name="test1.rar") - self.assertEqual(book.file_name, "test1.rar") - page0 = ComicPage.objects.get(Comic=book, index=0) - self.assertEqual(page0.page_file_name, "img1.jpg") - self.assertEqual(page0.content_type, "image/jpeg") - page1 = ComicPage.objects.get(Comic=book, index=1) - self.assertEqual(page1.page_file_name, "img2.png") - self.assertEqual(page1.content_type, "image/png") - page2 = ComicPage.objects.get(Comic=book, index=2) - self.assertEqual(page2.page_file_name, "img3.gif") - self.assertEqual(page2.content_type, "image/gif") - page3 = ComicPage.objects.get(Comic=book, index=3) - self.assertEqual(page3.page_file_name, "img4.bmp") - self.assertEqual(page3.content_type, "image/bmp") - self.assertEqual(ComicPage.objects.filter(Comic=book).count(), 4) - - def test_page_count(self): - book = ComicBook.objects.get(file_name="test1.rar") - self.assertEqual(book.page_count, 4) - - def test_is_last_page(self): - book = ComicBook.objects.get(file_name="test1.rar") - self.assertEqual(book.is_last_page(3), True) - self.assertEqual(book.is_last_page(2), False) - - def test_get_image(self): - book = ComicBook.objects.get(file_name="test1.rar") - img, content_type = book.get_image(0) - self.assertEqual(content_type, "image/jpeg") - self.assertEqual(img.read(), b"img1.jpg") - - def test_nav_with_folder_above(self): - user = User.objects.get(username="test") - generate_directory(user) - book = ComicBook.objects.get(file_name="test1.rar") - - nav = book.nav(user) - - self.assertEqual(nav['prev_path'], "") - self.assertEqual(nav['cur_path'], urlsafe_base64_encode(book.selector.bytes)) - - def test_nav_with_comic_above(self): - user = User.objects.get(username="test") - generate_directory(user) - prev_book = ComicBook.objects.get(file_name="test1.rar", directory__isnull=True) - book = ComicBook.objects.get(file_name="test2.rar", directory__isnull=True) - next_book = ComicBook.objects.get(file_name="test3.rar", directory__isnull=True) - - nav = book.nav(user) - - self.assertEqual(nav['prev_path'], urlsafe_base64_encode(prev_book.selector.bytes)) - self.assertEqual(nav['cur_path'], urlsafe_base64_encode(book.selector.bytes)) - self.assertEqual(nav['next_path'], urlsafe_base64_encode(next_book.selector.bytes)) - - def test_nav_with_comic_below(self): - user = User.objects.get(username="test") - generate_directory(user) - book = ComicBook.objects.get(file_name="test1.rar", directory__isnull=True) - next_book = ComicBook.objects.get(file_name="test2.rar", directory__isnull=True) - nav = book.nav(user) - - self.assertEqual(nav['cur_path'], urlsafe_base64_encode(book.selector.bytes)) - self.assertEqual(nav['next_path'], urlsafe_base64_encode(next_book.selector.bytes)) - - def test_nav_with_nothing_below(self): - user = User.objects.get(username="test") - generate_directory(user) - book = ComicBook.objects.get(file_name="test4.rar") - nav = book.nav(user) - - self.assertEqual(nav['cur_path'], urlsafe_base64_encode(book.selector.bytes)) - self.assertEqual(nav['next_path'], "") - - def test_generate_directory(self): - user = User.objects.get(username="test") - folders = generate_directory(user) - dir1 = folders[0] - self.assertEqual(dir1.name, "test_folder") - self.assertEqual(dir1.item_type, "Directory") - - dir2 = folders[1] - self.assertEqual(dir2.name, "test1.rar") - self.assertEqual(dir2.item_type, "ComicBook") - - dir3 = folders[2] - self.assertEqual(dir3.name, "test2.rar") - self.assertEqual(dir2.item_type, "ComicBook") - - dir4 = folders[3] - self.assertEqual(dir4.name, "test3.rar") - self.assertEqual(dir4.item_type, "ComicBook") - - def test_pages(self): - book = ComicBook.objects.get(file_name="test1.rar") - pages = [cp for cp in ComicPage.objects.filter(Comic=book).order_by("index")] - self.assertEqual(pages[0].page_file_name, "img1.jpg") - self.assertEqual(pages[0].index, 0) - self.assertEqual(pages[1].page_file_name, "img2.png") - self.assertEqual(pages[1].index, 1) - self.assertEqual(pages[2].page_file_name, "img3.gif") - self.assertEqual(pages[2].index, 2) - self.assertEqual(pages[3].page_file_name, "img4.bmp") - self.assertEqual(pages[3].index, 3) - - - def test_comic_list(self): - c = Client() - response = c.get("/comic/") - self.assertEqual(response.status_code, 302) - c.login(username="test", password="test") - response = c.get("/comic/") - self.assertEqual(response.status_code, 200) - user = User.objects.first() - generate_directory(user) - directory = Directory.objects.first() - response = c.get(f"/comic/{urlsafe_base64_encode(directory.selector.bytes)}/") - self.assertEqual(response.status_code, 200) - - def test_recent_comics(self): - c = Client() - response = c.get("/comic/recent/") - self.assertEqual(response.status_code, 302) - - c.login(username="test", password="test") - - response = c.get("/comic/recent/") - self.assertEqual(response.status_code, 200) - - def test_recent_comics_json(self): - c = Client() - response = c.post("/comic/recent/json/") - self.assertEqual(response.status_code, 302) - - c.login(username="test", password="test") - user = User.objects.get(username="test") - folders = generate_directory(user) - - req_data = {"start": "0", "length": "10", "search[value]": "", "order[0][dir]": "desc"} - response = c.post("/comic/recent/json/", req_data) - self.assertEqual(response.status_code, 200) - req_data["search[value]"] = "test1.rar" - response = c.post("/comic/recent/json/", req_data) - self.assertEqual(response.status_code, 200) - self.maxDiff = None - book = ComicBook.objects.get(file_name="test1.rar") - self.assertDictEqual( - json.loads(response.content), - { - "data": [ - { - "date": book.date_added.strftime("%d/%m/%y-%H:%M"), - "icon": '', - "label": '
Unread
', - "name": "test1.rar", - "selector": urlsafe_base64_encode(book.selector.bytes), - "type": "book", - "url": f"/comic/read/" f"{urlsafe_base64_encode(book.selector.bytes)}/", - } - ], - "recordsFiltered": 1, - "recordsTotal": 4, - }, - ) - req_data["search[value]"] = "" - req_data["order[0][dir]"] = 3 - response = c.post("/comic/recent/json/", req_data) - self.assertEqual(response.status_code, 200) - - def test_comic_edit(self): - c = Client() - book: ComicBook = ComicBook.objects.first() - user = User.objects.get(username="test") - - response = c.get("/comic/edit/") - self.assertEqual(response.status_code, 302) - c.login(username="test", password="test") - - response = c.get("/comic/edit/") - self.assertEqual(response.status_code, 405) - - req_data = {"comic_list_length": 10, "func": "unread", "selected": book.url_safe_selector} - response = c.post("/comic/edit/", req_data) - self.assertEqual(response.status_code, 200) - - status = ComicStatus.objects.get(comic=book, user=user) - self.assertEqual(status.last_read_page, 0) - self.assertTrue(status.unread) - self.assertFalse(status.finished) - - req_data["func"] = "read" - response = c.post("/comic/edit/", req_data) - self.assertEqual(response.status_code, 200) - status.refresh_from_db() - self.assertEqual(status.last_read_page, 3) - self.assertFalse(status.unread) - self.assertTrue(status.finished) - - req_data["func"] = "choose" - response = c.post("/comic/edit/", req_data) - self.assertEqual(response.status_code, 200) - status.refresh_from_db() - self.assertEqual(status.last_read_page, 3) - self.assertFalse(status.unread) - self.assertTrue(status.finished) - - del req_data["selected"] - response = c.post("/comic/edit/", req_data) - self.assertEqual(response.status_code, 200) - - def test_account_page(self): - c = Client() - user = User.objects.get(username="test") - self.assertEqual(user.username, "test") - response = c.get("/comic/account/") - self.assertEqual(response.status_code, 302) - - c.login(username="test", password="test") - - response = c.get("/comic/account/") - self.assertEqual(response.status_code, 200) - - def test_file_not_in_archive(self): - c = Client() - user = User.objects.get(username="test") - book = ComicBook.objects.get(file_name='test1.rar') - page = ComicPage.objects.get(Comic=book, index=0) - page.page_file_name = 'doesnt_exist' - page.save() - generate_directory(user) - c.login(username="test", password="test") - book.verify_pages() - response = c.get(f"/comic/read/{urlsafe_base64_encode(book.selector.bytes)}/0/img") - self.assertEqual(response.status_code, 200) - - def test_duplicate_pages(self): - c = Client() - user = User.objects.get(username="test") - generate_directory(user) - book = ComicBook.objects.get(file_name='test1.rar') - page = ComicPage.objects.get(Comic=book, index=0) - dup_page = ComicPage(Comic=book, index=0, page_file_name=page.page_file_name, content_type=page.content_type) - dup_page.save() - c.login(username="test", password="test") - book.verify_pages() - response = c.get(f"/comic/read/{urlsafe_base64_encode(book.selector.bytes)}/0/img") - self.assertEqual(response.status_code, 200) diff --git a/comic/urls.py b/comic/urls.py index 4411913..e69de29 100644 --- a/comic/urls.py +++ b/comic/urls.py @@ -1,23 +0,0 @@ -from django.urls import path - -from . import feeds, views - -urlpatterns = [ - path("", views.comic_list, name="index"), - path("settings/users/", views.users_page, name="users"), - path("settings/users//", views.user_config_page, name="user_details"), - path("settings/users/add/", views.user_add_page, name="add_users"), - path("account/", views.account_page, name="account"), - path("read//", views.read_comic, name="read_comic"), - path("read//thumb", views.comic_thumbnail, name="comic_thumbnail"), - path("set_page///", views.set_read_page, name="set_read_page"), - path("read///img", views.get_image, name="get_image"), - path("read//pdf", views.get_pdf, name="get_pdf"), - path("recent/", views.recent_comics, name="recent_comics"), - path("recent/json/", views.recent_comics_json, name="recent_comics_json"), - path("edit/", views.comic_edit, name="comic_edit"), - # path("feed//", feeds.RecentComics()), - path("/", views.comic_list, name="comic_list"), - path("/thumb", views.directory_thumbnail, name="directory_thumbnail"), - path("action////", views.perform_action, name="perform_action") -] diff --git a/comic/util.py b/comic/util.py index ceff162..65f333f 100644 --- a/comic/util.py +++ b/comic/util.py @@ -1,50 +1,12 @@ -from collections import OrderedDict from dataclasses import dataclass -from itertools import chain -from pathlib import Path -from typing import Union, Iterable -from django.conf import settings -from django.contrib.auth.models import User -from django.db.models import Count, Q, F, Case, When, PositiveSmallIntegerField -from django.utils.http import urlsafe_base64_encode - -from .models import ComicBook, Directory, ComicStatus - - -def generate_title_from_path(file_path: Path): - if file_path == "Home": - return "CBWebReader" - return f'CBWebReader - {" - ".join(p for p in file_path.parts)}' - - -class Menu: - def __init__(self, user, page=""): - """ - - :type page: str - """ - self.menu_items = OrderedDict() - self.menu_items["Browse"] = "/comic/" - self.menu_items["Recent"] = "/comic/recent/" - self.menu_items["Account"] = "/comic/account/" - if user.is_superuser: - self.menu_items["Users"] = "/comic/settings/users/" - self.menu_items["Logout"] = "/logout/" - self.current_page = page +from .models import ComicBook, Directory +@dataclass() class Breadcrumb: - def __init__(self): - self.name = "Home" - self.url = "/comic/" - self.selector = '' - - def __str__(self): - return self.name - - def __unicode__(self): - return self.name + name: str = 'Home' + selector: str = '' def generate_breadcrumbs_from_path(directory=False, book=False): @@ -59,197 +21,17 @@ def generate_breadcrumbs_from_path(directory=False, book=False): else: folders = [] for item in folders[::-1]: - bc = Breadcrumb() - bc.name = item.name - bc.url = "/comic/" + urlsafe_base64_encode(item.selector.bytes) - bc.selector = item.selector - output.append(bc) + output.append( + Breadcrumb( + name=item.name, + selector=item.selector + ) + ) if book: - bc = Breadcrumb() - bc.name = book.file_name - bc.url = "/read/" + urlsafe_base64_encode(book.selector.bytes) - bc.selector = book.selector - output.append(bc) - - return output - - -def generate_breadcrumbs_from_menu(paths): - output = [Breadcrumb()] - for item in paths: - bc = Breadcrumb() - bc.name = item[0] - bc.url = item[1] - output.append(bc) - return output - - -@dataclass -class DirFile: - obj: Union[Directory, ComicBook] - name: str = '' - item_type: str = '' - percent: int = 0 - selector: str = '' - total: int = 0 - total_read: int = 0 - total_unread: int = 0 - - def __post_init__(self): - self.item_type = type(self.obj).__name__ - if hasattr(self.obj, 'total') and hasattr(self.obj, 'total_read'): - # because pages count from zero. - total_adjustment = 1 - if isinstance(self.obj, Directory): - total_adjustment = 0 - self.total = self.obj.total - total_adjustment - self.total_read = self.obj.total_read - self.total_unread = self.total - self.total_read - try: - self.percent = int((self.obj.total_read / self.total) * 100) - except ZeroDivisionError: - self.percent = 0 - - self.selector = self.obj.url_safe_selector - if isinstance(self.obj, Directory): - self.name = self.obj.name - elif isinstance(self.obj, ComicBook): - self.name = self.obj.file_name - @property - def type(self): - return 'ComicBook' - - @property - def title(self): - return self.name - - @property - def progress(self): - return self.total_read - - @property - def thumbnail(self): - return '/error.jpg' - -def generate_directory(user: User, directory=None): - """ - :type user: User - :type directory: Directory - """ - base_dir = settings.COMIC_BOOK_VOLUME - files = [] - dir_path = Path(base_dir, directory.path) if directory else base_dir - dir_list = [x for x in sorted(dir_path.glob('*')) if x.is_dir()] - - file_list = [x for x in sorted(dir_path.glob('*')) if x.is_file()] - dir_list_obj = Directory.objects.filter(name__in=[x.name for x in dir_list], parent=directory) - file_list_obj = ComicBook.objects.filter(file_name__in=[x.name for x in file_list], directory=directory) - - for file in chain(file_list_obj, dir_list_obj): - if file.thumbnail and not Path(file.thumbnail.path).exists(): - file.thumbnail.delete() - file.save() - - dir_list_obj = dir_list_obj.annotate( - total=Count('comicbook', distinct=True), - total_read=Count('comicbook__comicstatus', Q(comicbook__comicstatus__finished=True, - comicbook__comicstatus__user=user), distinct=True) - ) - - # Create Missing Status - status_list = [x.comic for x in - ComicStatus.objects.filter(comic__in=file_list_obj, user=user).select_related('comic')] - new_status = [ComicStatus(comic=file, user=user) for file in file_list_obj if file not in status_list] - ComicStatus.objects.bulk_create(new_status) - - file_list_obj = file_list_obj.annotate( - total=Count('comicpage', distinct=True), - total_read=F('comicstatus__last_read_page'), - finished=F('comicstatus__finished'), - unread=F('comicstatus__unread'), - user=F('comicstatus__user'), - classification=Case( - When(directory__isnull=True, then=Directory.Classification.C_18), - default=F('directory__classification'), - output_field=PositiveSmallIntegerField(choices=Directory.Classification.choices) + output.append( + Breadcrumb( + name=book.file_name, + selector=book.selector + ) ) - ).filter(Q(user__isnull=True) | Q(user=user.id)) - - for directory_obj in dir_list_obj: - files.append(DirFile(directory_obj)) - dir_list.remove(Path(dir_path, directory_obj.name)) - - for file_obj in file_list_obj: - files.append(DirFile(file_obj)) - file_list.remove(Path(dir_path, file_obj.file_name)) - - for directory_name in dir_list: - if directory: - directory_obj = Directory(name=directory_name.name, parent=directory) - else: - directory_obj = Directory(name=directory_name.name) - directory_obj.save() - directory_obj.total = 0 - directory_obj.total_read = 0 - files.append(DirFile(directory_obj)) - files = [file for file in files if file.obj.classification <= user.usermisc.allowed_to_read] - - comics_to_annotate = [] - for file_name in file_list: - if file_name.suffix.lower() in [".rar", ".zip", ".cbr", ".cbz", ".pdf"]: - book = ComicBook.process_comic_book(file_name, directory) - ComicStatus(user=user, comic=book).save() - comics_to_annotate.append(book.selector) - if comics_to_annotate: - new_comics = ComicBook.objects.filter(selector__in=comics_to_annotate).annotate( - total=Count('comicpage', distinct=True), - total_read=F('comicstatus__last_read_page'), - finished=F('comicstatus__finished'), - unread=F('comicstatus__unread'), - user=F('comicstatus__user'), - classification=Case( - When(directory__isnull=True, then=Directory.Classification.C_18), - default=F('directory__classification'), - output_field=PositiveSmallIntegerField(choices=Directory.Classification.choices) - ) - ).filter(Q(user__isnull=True) | Q(user=user.id)) - - files.extend([DirFile(b) for b in new_comics]) - files.sort(key=lambda x: x.name) - files.sort(key=lambda x: x.item_type, reverse=True) - return files - - -def generate_label(book): - """ - book need to be annotated with the following from ComicStatus - * unread - * finished - * last_read_page - * total_pages - :param book: ComicBook - :return: str - """ - unread_text = '
Unread
' - if not hasattr(book, 'unread'): - label_text = unread_text - elif book.unread or book.unread is None: - label_text = unread_text - elif book.finished: - label_text = '
Read
' - else: - label_text = '
%s/%s
' % ( - book.last_read_page + 1, - book.total_pages, - ) - return label_text - - -def generate_dir_status(total, total_read): - if total == 0: - return '
Empty
' - elif total == total_read: - return '
All Read
' - elif total_read == 0: - return '
Unread
' - return f'
{total_read}/{total}
' + return output diff --git a/comic/views.py b/comic/views.py index a9c827c..e69de29 100644 --- a/comic/views.py +++ b/comic/views.py @@ -1,408 +0,0 @@ -import json -import uuid - -from django.contrib.auth import authenticate, login -from django.contrib.auth.decorators import login_required, user_passes_test -from django.contrib.auth.models import User -from django.db.models import Max, Count, F, Case, When, PositiveSmallIntegerField -from django.db.transaction import atomic -from django.http import HttpResponse, FileResponse -from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse -from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode -from django.views.decorators.clickjacking import xframe_options_sameorigin -from django.views.decorators.csrf import ensure_csrf_cookie -from django.views.decorators.http import require_POST - -from .forms import AccountForm, AddUserForm, EditUserForm, InitialSetupForm, DirectoryEditForm -from .models import ComicBook, ComicPage, ComicStatus, Directory, UserMisc -from .util import ( - Menu, - generate_breadcrumbs_from_menu, - generate_breadcrumbs_from_path, - generate_directory, - generate_label, - generate_title_from_path, -) - - -# noinspection PyTypeChecker -@ensure_csrf_cookie -@login_required -def comic_list(request, directory_selector=False): - if User.objects.all().count() == 0: - return redirect("/comic/settings/") - - directory = None - if directory_selector: - selector = uuid.UUID(bytes=urlsafe_base64_decode(directory_selector)) - directory = Directory.objects.get(selector=selector) - - if directory: - title = generate_title_from_path(directory.path) - breadcrumbs = generate_breadcrumbs_from_path(directory) - else: - title = generate_title_from_path("Home") - breadcrumbs = generate_breadcrumbs_from_path() - - files = generate_directory(request.user, directory) - form = DirectoryEditForm() - - return render( - request, - "comic/comic_list.html", - { - "breadcrumbs": breadcrumbs, - "menu": Menu(request.user, "Browse"), - "title": title, - "files": files, - "form": form, - "selector": directory_selector if directory_selector else 'None', - 'js_urls': { - "perform_action": reverse('perform_action', args=('operation', 'item_type', 'selector')) - } - }, - ) - - -@login_required -def perform_action(request, operation, item_type, selector): - if operation not in ['mark_read', 'mark_unread', 'mark_previous', 'set_classification']: - return HttpResponse(400) - elif operation == 'mark_previous' and item_type == 'Directory': - return HttpResponse(422) - try: - selector_uuid = uuid.UUID(bytes=urlsafe_base64_decode(selector)) - except ValueError: - if item_type == 'Directory': - for book in ComicBook.objects.filter(directory__isnull=True): - getattr(book, operation)(request.user) - return HttpResponse(204) - else: - return HttpResponse(400) - if operation == 'set_classification': - form = DirectoryEditForm(request.POST) - if not (form.is_valid() and item_type == 'Directory'): - return HttpResponse(400) - - if item_type == 'ComicBook': - book = get_object_or_404(ComicBook, selector=selector_uuid) - getattr(book, operation)(request.user) - return HttpResponse(204) - elif item_type == 'Directory': - directory = get_object_or_404(Directory, selector=selector_uuid) - if operation == 'set_classification': - getattr(directory, operation)(form.cleaned_data) - else: - getattr(directory, operation)(request.user) - return HttpResponse(204) - - -@login_required -def recent_comics(request): - return render( - request, - "comic/recent_comics.html", - { - "breadcrumbs": generate_breadcrumbs_from_menu([("Recent", "/comic/recent/")]), - "menu": Menu(request.user, "Recent"), - "title": "Recent Comics", - "feed_id": urlsafe_base64_encode(request.user.usermisc.feed_id.bytes), - }, - ) - - -@login_required -@require_POST -def recent_comics_json(request): - start = int(request.POST["start"]) - end = start + int(request.POST["length"]) - icon = '' - comics = ComicBook.objects.all().annotate(total_pages=Count('comicpage')) - response_data = dict() - response_data["recordsTotal"] = comics.count() - if request.POST["search[value]"]: - comics = comics.filter(file_name__contains=request.POST["search[value]"]) - order_string = "" - # Ordering - if request.POST["order[0][dir]"] == "desc": - order_string += "-" - order_string += "date_added" - comics = comics.order_by(order_string) - comics = comics.annotate( - unread=Case(When(comicstatus__user=request.user, then='comicstatus__unread')), - finished=Case(When(comicstatus__user=request.user, then='comicstatus__finished')), - last_read_page=Case(When(comicstatus__user=request.user, then='comicstatus__last_read_page')), - classification=Case( - When(directory__isnull=True, then=Directory.Classification.C_18), - default=F('directory__classification'), - output_field=PositiveSmallIntegerField(choices=Directory.Classification.choices) - ) - ) - misc, _ = UserMisc.objects.get_or_create(user=request.user) - comics = comics.filter(classification__lte=misc.allowed_to_read) - - response_data["recordsFiltered"] = comics.count() - response_data["data"] = list() - for book in comics[start:end]: - response_data["data"].append( - { - "selector": urlsafe_base64_encode(book.selector.bytes), - "icon": icon, - "type": "book", - "name": book.file_name, - "date": book.date_added.strftime("%d/%m/%y-%H:%M"), - "label": generate_label(book), - "url": "/comic/read/{0}/".format(urlsafe_base64_encode(book.selector.bytes)), - } - ) - return HttpResponse(json.dumps(response_data), content_type="application/json") - - -@login_required -@require_POST -def comic_edit(request): - if "selected" not in request.POST: - return HttpResponse(status=200) - if request.POST["func"] == "choose": - return HttpResponse(status=200) - selected = [uuid.UUID(bytes=urlsafe_base64_decode(item)) for item in request.POST.getlist("selected")] - comics = ComicBook.objects.filter(selector__in=selected) - with atomic(): - for comic in comics: - status, _ = ComicStatus.objects.get_or_create(comic=comic, user=request.user) - if request.POST["func"] == "read": - status.unread = False - status.finished = True - status.last_read_page = comic.page_count - 1 - elif request.POST["func"] == "unread": - status.unread = True - status.finished = False - status.last_read_page = 0 - status.save() - return HttpResponse(status=200) - - -@login_required -def account_page(request): - success_message = [] - if request.POST: - form = AccountForm(request.POST) - if form.is_valid(): - if form.cleaned_data["email"] != request.user.email: - request.user.email = form.cleaned_data["email"] - success_message.append("Email Updated.") - if len(form.cleaned_data["password"]) != 0: - request.user.set_password(form.cleaned_data["password"]) - success_message.append("Password Updated.") - request.user.save() - else: - form = AccountForm(initial={"username": request.user.username, "email": request.user.email}) - crumbs = [("Account", "/comic/account/")] - context = { - "form": form, - "menu": Menu(request.user, "Account"), - "error_message": form.errors, - "success_message": "
".join(success_message), - "breadcrumbs": generate_breadcrumbs_from_menu(crumbs), - "title": "CBWebReader - Account", - } - return render(request, "comic/settings_page.html", context) - - -@user_passes_test(lambda u: u.is_superuser) -def users_page(request): - users = User.objects.all().select_related('usermisc') - crumbs = [("Users", "/comic/settings/users/")] - context = { - "users": users, - "menu": Menu(request.user, "Users"), - "breadcrumbs": generate_breadcrumbs_from_menu(crumbs), - } - return render(request, "comic/users_page.html", context) - - -@user_passes_test(lambda u: u.is_superuser) -def user_config_page(request, user_id): - user = get_object_or_404(User, id=user_id) - success_message = [] - if request.POST: - form = EditUserForm(request.POST) - if form.is_valid(): - if "password" in form.cleaned_data and len(form.cleaned_data["password"]) != 0: - user.set_password(form.cleaned_data["password"]) - success_message.append("Password Updated.") - if form.cleaned_data["email"] != user.email: - user.email = form.cleaned_data["email"] - success_message.append("Email Updated.
") - user.usermisc.allowed_to_read = form.cleaned_data['allowed_to_read'] - user.usermisc.save() - user.save() - else: - form = EditUserForm(initial=EditUserForm.get_initial_values(user)) - - users = User.objects.all() - crumbs = [("Users", "/comic/settings/users/"), (user.username, "/comic/settings/users/" + str(user.id))] - context = { - "form": form, - "users": users, - "menu": Menu(request.user, "Users"), - "error_message": form.errors, - "breadcrumbs": generate_breadcrumbs_from_menu(crumbs), - "success_message": "
".join(success_message), - "title": "CBWebReader - Edit User - " + user.username, - } - return render(request, "comic/settings_page.html", context) - - -@user_passes_test(lambda u: u.is_superuser) -def user_add_page(request): - success_message = "" - if request.POST: - form = AddUserForm(request.POST) - if form.is_valid(): - user = User(username=form.cleaned_data["username"], email=form.cleaned_data["email"]) - user.set_password(form.cleaned_data["password"]) - user.save() - success_message = "User {} created.".format(user.username) - - else: - form = AddUserForm() - crumbs = [("Users", "/comic/settings/users/"), ("Add", "/comic/settings/users/add/")] - context = { - "form": form, - "menu": Menu(request.user, "Users"), - "breadcrumbs": generate_breadcrumbs_from_menu(crumbs), - "error_message": form.errors, - "success_message": success_message, - "title": "CBWebReader - Add User", - } - return render(request, "comic/settings_page.html", context) - - -@login_required -def read_comic(request, comic_selector): - - selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector)) - book = get_object_or_404(ComicBook, selector=selector) - misc, _ = UserMisc.objects.get_or_create(user=request.user) - if book.directory and book.directory.classification > misc.allowed_to_read: - return redirect('index') - - pages = ComicPage.objects.filter(Comic=book) - - status, _ = ComicStatus.objects.get_or_create(comic=book, user=request.user) - title = "CBWebReader - " + book.file_name - context = { - "book": book, - "pages": pages, - "nav": book.nav(request.user), - "status": status, - "breadcrumbs": generate_breadcrumbs_from_path(book.directory, book), - "menu": Menu(request.user), - "title": title, - } - if book.file_name.lower().endswith('pdf'): - context['status'].last_read_page += 1 - return render(request, "comic/read_comic_pdf.html", context) - else: - book.verify_pages(pages) - context['pages'] = ComicPage.objects.filter(Comic=book) - return render(request, "comic/read_comic.html", context) - - -@login_required -def set_read_page(request, comic_selector, page): - page = int(page) - selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector)) - book = get_object_or_404(ComicBook, selector=selector) - status, _ = ComicStatus.objects.get_or_create(comic=book, user=request.user) - status.unread = False - status.last_read_page = page - if ComicPage.objects.filter(Comic=book).aggregate(Max("index"))["index__max"] == status.last_read_page: - status.finished = True - else: - status.finished = False - status.save() - return HttpResponse(status=200) - - -@xframe_options_sameorigin -@login_required -def get_image(request, comic_selector, page): - selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector)) - book = ComicBook.objects.get(selector=selector) - misc, _ = UserMisc.objects.get_or_create(user=request.user) - if book.directory and book.directory.classification > misc.allowed_to_read: - return HttpResponse(status=401) - img, content = book.get_image(int(page)) - return FileResponse(img, content_type=content) - - -@login_required -def get_image_api(request, selector, page): - book = ComicBook.objects.get(selector=selector) - misc, _ = UserMisc.objects.get_or_create(user=request.user) - if book.directory and book.directory.classification > misc.allowed_to_read: - return HttpResponse(status=401) - img, content = book.get_image(int(page)) - return FileResponse(img, content_type=content) - - -@xframe_options_sameorigin -@login_required -def comic_thumbnail(request, comic_selector): - selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector)) - book = ComicBook.objects.get(selector=selector) - misc, _ = UserMisc.objects.get_or_create(user=request.user) - if book.directory and book.directory.classification > misc.allowed_to_read: - return HttpResponse(status=401) - return redirect(book.get_thumbnail_url()) - - -@xframe_options_sameorigin -@login_required -def directory_thumbnail(request, directory_selector): - selector = uuid.UUID(bytes=urlsafe_base64_decode(directory_selector)) - folder = Directory.objects.get(selector=selector) - misc, _ = UserMisc.objects.get_or_create(user=request.user) - if folder.classification > misc.allowed_to_read: - return HttpResponse(status=401) - return redirect(folder.get_thumbnail_url()) - - -@login_required -def get_pdf(request, comic_selector): - selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector)) - book = ComicBook.objects.get(selector=selector) - misc, _ = UserMisc.objects.get_or_create(user=request.user) - if book.directory.classification > misc.allowed_to_read: - return HttpResponse(status=401) - return FileResponse(open(book.get_pdf(), 'rb'), content_type='application/pdf') - - -def initial_setup(request): - if User.objects.all().exists(): - return redirect("/comic/") - if request.POST: - form = InitialSetupForm(request.POST) - if form.is_valid(): - user = User( - username=form.cleaned_data["username"], - email=form.cleaned_data["email"], - is_staff=True, - is_superuser=True, - ) - user.set_password(form.cleaned_data["password"]) - user.save() - user = authenticate(username=form.cleaned_data["username"], password=form.cleaned_data["password"]) - login(request, user) - return redirect("/comic/") - else: - form = InitialSetupForm() - context = {"form": form, "title": "CBWebReader - Setup", "error_message": form.errors} - return render(request, "comic/settings_page.html", context) - - -def comic_redirect(_): - return redirect("/comic/") diff --git a/comic_auth/__init__.py b/comic_auth/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/comic_auth/admin.py b/comic_auth/admin.py deleted file mode 100644 index e69de29..0000000 diff --git a/comic_auth/forms.py b/comic_auth/forms.py deleted file mode 100644 index 008b46c..0000000 --- a/comic_auth/forms.py +++ /dev/null @@ -1,16 +0,0 @@ -from django import forms - - -class LoginForm(forms.Form): - - username = forms.CharField( - max_length=50, - label="", - widget=forms.TextInput( - attrs={"class": "form-control", "placeholder": "Username", "autofocus": True, "required": True} - ), - ) - password = forms.CharField( - label="Password", - widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "Password", "required": True}), - ) diff --git a/comic_auth/migrations/__init__.py b/comic_auth/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/comic_auth/models.py b/comic_auth/models.py deleted file mode 100644 index e69de29..0000000 diff --git a/comic_auth/templates/comic_auth/login.html b/comic_auth/templates/comic_auth/login.html deleted file mode 100644 index 472a9e6..0000000 --- a/comic_auth/templates/comic_auth/login.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "base.html" %} -{% load bootstrap4 %} -{% block title %}CBWebReader - Login{% endblock %} -{% block content %} -
- {% if error %} - - {% endif %} -
- {% csrf_token %} - - {% bootstrap_form form %} - {% buttons %} - - {% endbuttons %} -
-
- -{% endblock %} \ No newline at end of file diff --git a/comic_auth/views.py b/comic_auth/views.py deleted file mode 100644 index 34218bb..0000000 --- a/comic_auth/views.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.contrib.auth import authenticate, login, logout -from django.contrib.auth.models import User -from django.shortcuts import redirect, render -from django.utils.http import url_has_allowed_host_and_scheme - -from comic_auth.forms import LoginForm - - -def comic_login(request): - if request.POST: - form = LoginForm(request.POST) - if form.is_valid(): - user = authenticate(username=form.cleaned_data["username"], password=form.cleaned_data["password"]) - if user is not None: - if user.is_active: - login(request, user) - if "next" in request.GET: - if url_has_allowed_host_and_scheme(request.GET["next"], allowed_hosts=None): - return redirect(request.GET["next"]) - else: - return redirect("/comic/") - else: - return redirect("/comic/") - else: - return render(request, "comic_auth/login.html", {"error": True}) - else: - return render(request, "comic_auth/login.html", {"error": True, "form": form}) - else: - return render(request, "comic_auth/login.html", {"error": True, "form": form}) - else: - if not User.objects.all().exists(): - return redirect("/setup/") - form = LoginForm() - context = {"form": form} - return render(request, "comic_auth/login.html", context) - - -def comic_logout(request): - logout(request) - return redirect("/login/") diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 67e002a..0000000 --- a/package-lock.json +++ /dev/null @@ -1,320 +0,0 @@ -{ - "name": "npm-proj-1661104548614-0.682394455872383331qpFK", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "dependencies": { - "@fortawesome/fontawesome-free": "^5.15.3", - "bootstrap": "^4.6.0", - "datatables.net-bs4": "^1.12.1", - "hammerjs": "^2.0.8", - "isotope-layout": "^3.0.6", - "jquery": "^3.6.0", - "js-cookie": "^2.2.1", - "pdfjs-dist": "^2.15.349", - "reveal.js": "^4.1.0", - "reveal.js-menu": "^2.1.0" - } - }, - "node_modules/@fortawesome/fontawesome-free": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz", - "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==", - "hasInstallScript": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/bootstrap": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", - "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - }, - "peerDependencies": { - "jquery": "1.9.1 - 3", - "popper.js": "^1.16.1" - } - }, - "node_modules/datatables.net": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.11.5.tgz", - "integrity": "sha512-nlFst2xfwSWaQgaOg5sXVG3cxYC0tH8E8d65289w9ROgF2TmLULOOpcdMpyxxUim/qEwVSEem42RjkTWEpr3eA==", - "dependencies": { - "jquery": ">=1.7" - } - }, - "node_modules/datatables.net-bs4": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/datatables.net-bs4/-/datatables.net-bs4-1.12.1.tgz", - "integrity": "sha512-LBeC8zUNVYyQT7ytC2lYqyXDn+k2kYpqvijC83oOjlcnEtb/8Tduzgquox5FrNKUJPcUrj9r+h5B0TDBbob/Gg==", - "dependencies": { - "datatables.net": ">=1.11.3", - "jquery": ">=1.7" - } - }, - "node_modules/desandro-matches-selector": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz", - "integrity": "sha1-cXvu1NwT59jzdi9wem1YpndCGOE=" - }, - "node_modules/dommatrix": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz", - "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" - }, - "node_modules/ev-emitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz", - "integrity": "sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==" - }, - "node_modules/fizzy-ui-utils": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz", - "integrity": "sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg==", - "dependencies": { - "desandro-matches-selector": "^2.0.0" - } - }, - "node_modules/get-size": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/get-size/-/get-size-2.0.3.tgz", - "integrity": "sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q==" - }, - "node_modules/hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/isotope-layout": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/isotope-layout/-/isotope-layout-3.0.6.tgz", - "integrity": "sha512-z2ZKablhocXhoNyWwzJPFd7u7FWbYbVJA51Nvsqsod8jH2ExGc1SwDsSWKE54e3PhXzqf2yZPhFSq/c2MR1arw==", - "dependencies": { - "desandro-matches-selector": "^2.0.0", - "fizzy-ui-utils": "^2.0.4", - "get-size": "^2.0.0", - "masonry-layout": "^4.1.0", - "outlayer": "^2.1.0" - } - }, - "node_modules/jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" - }, - "node_modules/js-cookie": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", - "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" - }, - "node_modules/masonry-layout": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/masonry-layout/-/masonry-layout-4.2.2.tgz", - "integrity": "sha512-iGtAlrpHNyxaR19CvKC3npnEcAwszXoyJiI8ARV2ePi7fmYhIud25MHK8Zx4P0LCC4d3TNO9+rFa1KoK1OEOaA==", - "dependencies": { - "get-size": "^2.0.2", - "outlayer": "^2.1.0" - } - }, - "node_modules/outlayer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/outlayer/-/outlayer-2.1.1.tgz", - "integrity": "sha1-KYY7beEOpdrf/8rfoNcokHOH6aI=", - "dependencies": { - "ev-emitter": "^1.0.0", - "fizzy-ui-utils": "^2.0.0", - "get-size": "^2.0.2" - } - }, - "node_modules/pdfjs-dist": { - "version": "2.15.349", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.15.349.tgz", - "integrity": "sha512-EeCfqj6xi4/aegKNS7Bs+TCg3Y5gmKmG0s/5xXI0PqWW66x+Nm7iFXBpVcup7HnR8sNDm+5NESfFr8T6DeWp9Q==", - "dependencies": { - "dommatrix": "^1.0.3", - "web-streams-polyfill": "^3.2.1" - }, - "peerDependencies": { - "worker-loader": "^3.0.8" - }, - "peerDependenciesMeta": { - "worker-loader": { - "optional": true - } - } - }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/reveal.js": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-4.3.1.tgz", - "integrity": "sha512-1kyEnWeUkaCdBdX//XXq9dtBK95ppvIlSwlHelrP8/wrX6LcsYp4HT9WTFoFEOUBfVqkm8C2aHQ367o+UKfcxw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/reveal.js-menu": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/reveal.js-menu/-/reveal.js-menu-2.1.0.tgz", - "integrity": "sha512-35zp4fHSMyWd15+3CvQ8LrpS+4Gj2qvlkxX3lo5LpITDe6ZkA4A9y1E5fE63YlQl5fp7W1mNgNJr4kCU0s14lA==" - }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "engines": { - "node": ">= 8" - } - } - }, - "dependencies": { - "@fortawesome/fontawesome-free": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz", - "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==" - }, - "bootstrap": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", - "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", - "requires": {} - }, - "datatables.net": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.11.5.tgz", - "integrity": "sha512-nlFst2xfwSWaQgaOg5sXVG3cxYC0tH8E8d65289w9ROgF2TmLULOOpcdMpyxxUim/qEwVSEem42RjkTWEpr3eA==", - "requires": { - "jquery": ">=1.7" - } - }, - "datatables.net-bs4": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/datatables.net-bs4/-/datatables.net-bs4-1.12.1.tgz", - "integrity": "sha512-LBeC8zUNVYyQT7ytC2lYqyXDn+k2kYpqvijC83oOjlcnEtb/8Tduzgquox5FrNKUJPcUrj9r+h5B0TDBbob/Gg==", - "requires": { - "datatables.net": ">=1.11.3", - "jquery": ">=1.7" - } - }, - "desandro-matches-selector": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz", - "integrity": "sha1-cXvu1NwT59jzdi9wem1YpndCGOE=" - }, - "dommatrix": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz", - "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" - }, - "ev-emitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz", - "integrity": "sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==" - }, - "fizzy-ui-utils": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz", - "integrity": "sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg==", - "requires": { - "desandro-matches-selector": "^2.0.0" - } - }, - "get-size": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/get-size/-/get-size-2.0.3.tgz", - "integrity": "sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q==" - }, - "hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" - }, - "isotope-layout": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/isotope-layout/-/isotope-layout-3.0.6.tgz", - "integrity": "sha512-z2ZKablhocXhoNyWwzJPFd7u7FWbYbVJA51Nvsqsod8jH2ExGc1SwDsSWKE54e3PhXzqf2yZPhFSq/c2MR1arw==", - "requires": { - "desandro-matches-selector": "^2.0.0", - "fizzy-ui-utils": "^2.0.4", - "get-size": "^2.0.0", - "masonry-layout": "^4.1.0", - "outlayer": "^2.1.0" - } - }, - "jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" - }, - "js-cookie": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", - "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" - }, - "masonry-layout": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/masonry-layout/-/masonry-layout-4.2.2.tgz", - "integrity": "sha512-iGtAlrpHNyxaR19CvKC3npnEcAwszXoyJiI8ARV2ePi7fmYhIud25MHK8Zx4P0LCC4d3TNO9+rFa1KoK1OEOaA==", - "requires": { - "get-size": "^2.0.2", - "outlayer": "^2.1.0" - } - }, - "outlayer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/outlayer/-/outlayer-2.1.1.tgz", - "integrity": "sha1-KYY7beEOpdrf/8rfoNcokHOH6aI=", - "requires": { - "ev-emitter": "^1.0.0", - "fizzy-ui-utils": "^2.0.0", - "get-size": "^2.0.2" - } - }, - "pdfjs-dist": { - "version": "2.15.349", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.15.349.tgz", - "integrity": "sha512-EeCfqj6xi4/aegKNS7Bs+TCg3Y5gmKmG0s/5xXI0PqWW66x+Nm7iFXBpVcup7HnR8sNDm+5NESfFr8T6DeWp9Q==", - "requires": { - "dommatrix": "^1.0.3", - "web-streams-polyfill": "^3.2.1" - } - }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "peer": true - }, - "reveal.js": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-4.3.1.tgz", - "integrity": "sha512-1kyEnWeUkaCdBdX//XXq9dtBK95ppvIlSwlHelrP8/wrX6LcsYp4HT9WTFoFEOUBfVqkm8C2aHQ367o+UKfcxw==" - }, - "reveal.js-menu": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/reveal.js-menu/-/reveal.js-menu-2.1.0.tgz", - "integrity": "sha512-35zp4fHSMyWd15+3CvQ8LrpS+4Gj2qvlkxX3lo5LpITDe6ZkA4A9y1E5fE63YlQl5fp7W1mNgNJr4kCU0s14lA==" - }, - "web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 5f2af6b..0000000 --- a/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "dependencies": { - "@fortawesome/fontawesome-free": "^5.15.3", - "bootstrap": "^4.6.0", - "datatables.net-bs4": "^1.12.1", - "hammerjs": "^2.0.8", - "isotope-layout": "^3.0.6", - "jquery": "^3.6.0", - "js-cookie": "^2.2.1", - "pdfjs-dist": "^2.15.349", - "reveal.js": "^4.1.0", - "reveal.js-menu": "^2.1.0" - } -} diff --git a/poetry.lock b/poetry.lock index f42a5e0..063d002 100644 --- a/poetry.lock +++ b/poetry.lock @@ -133,7 +133,7 @@ jinja2 = "*" name = "coverage" version = "6.4.4" description = "Code coverage measurement for Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -296,14 +296,6 @@ async = ["django-celery (>=3.0)"] async_dramatiq = ["django-dramatiq (>=0.4.0)"] async_rq = ["django-rq (>=0.6.0)"] -[[package]] -name = "django-node-assets" -version = "0.9.11" -description = "The Django application that allows install and serve assets via Node.js package manager infrastructure." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "django-permissions-policy" version = "4.13.0" @@ -550,17 +542,6 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] dev = ["colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "black (>=19.10b0)", "isort (>=5.1.1)", "Sphinx (>=4.1.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)"] -[[package]] -name = "markdown" -version = "3.4.1" -description = "Python implementation of Markdown." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -testing = ["coverage", "pyyaml"] - [[package]] name = "markupsafe" version = "2.1.1" @@ -1024,7 +1005,7 @@ dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "4a1018e12d5d29a99175c79c0afd14a4faaf846a535f2544b670842739d867e0" +content-hash = "02ffb9bcd22babd30fb99e50a293f2ed1421375c4c36e5ee534c5530b63ca4e8" [metadata.files] appnope = [ @@ -1176,10 +1157,6 @@ django-imagekit = [ {file = "django-imagekit-4.1.0.tar.gz", hash = "sha256:e559aeaae43a33b34f87631a9fa5696455e4451ffa738a42635fde442fedac5c"}, {file = "django_imagekit-4.1.0-py2.py3-none-any.whl", hash = "sha256:87e36f8dc1d8745647881f4366ef4965225f048042dacebbee0dcb87425defef"}, ] -django-node-assets = [ - {file = "django-node-assets-0.9.11.tar.gz", hash = "sha256:df6ca9aeb868aa9692cbf8f6265132b6159798866b15ac95d7d0d4dd5f3cb6da"}, - {file = "django_node_assets-0.9.11-py3-none-any.whl", hash = "sha256:4d37659c07976dc4ebccb6704051c25204e3381aa5e4f98a4a76b57e33cb1776"}, -] django-permissions-policy = [] django-silk = [] django-sri = [] @@ -1216,7 +1193,6 @@ loguru = [ {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"}, {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"}, ] -markdown = [] markupsafe = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, diff --git a/pyproject.toml b/pyproject.toml index 76f29dc..4612970 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ line_length = 119 [tool.poetry] name = "cbwebreader" -version = "1.0.0" +version = "1.0.1" description = "CBR/Z Web Reader" authors = ["ajurna "] license = "Creative Commons Attribution-ShareAlike 4.0 International License" @@ -19,7 +19,6 @@ django-silk = "^5.0.0" mysqlclient = "^2.0.1" psycopg2 = "^2.8.6" rarfile = "^4.0" -coverage = "^6.2" django-extensions = "^3.2.0" Pillow = "^9.1.1" django-imagekit = "^4.0.2" @@ -28,10 +27,8 @@ django-bootstrap4 = "^22.1" django-csp = "^3.7" django-boost = "^2.0" django-sri = "^0.4.0" -django-node-assets = "^0.9.9" django-permissions-policy = "^4.9.0" djangorestframework = "^3.13.1" -Markdown = "^3.3.7" django-filter = "^22.1" django-cors-headers = "^3.13.0" djangorestframework-simplejwt = "^5.2.0" @@ -44,6 +41,7 @@ mypy = "^0.971" Werkzeug = "<2.1" pyOpenSSL = "^22.0.0" ipython = "^8.4.0" +coverage = "^6.2" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/static/css/base.css b/static/css/base.css deleted file mode 100644 index 0eac012..0000000 --- a/static/css/base.css +++ /dev/null @@ -1,21 +0,0 @@ -#comic_list caption { - caption-side: top; -} - -.card_list_card { - width: 200px; -} -.card .unread-badge { - position:absolute; - top:10px; - left:10px; - padding:5px; - color:white; -} -.card .classification-badge { - position:absolute; - top:10px; - right: 10px; - padding:5px; - color:black; -} \ No newline at end of file diff --git a/static/css/base.min.css b/static/css/base.min.css deleted file mode 100644 index 3458dc6..0000000 --- a/static/css/base.min.css +++ /dev/null @@ -1 +0,0 @@ -#comic_list caption{caption-side:top}.card_list_card{width:200px}.card .unread-badge{position:absolute;top:10px;left:10px;padding:5px;color:#fff}.card .classification-badge{position:absolute;top:10px;right:10px;padding:5px;color:#000} \ No newline at end of file diff --git a/static/js/comic_list.js b/static/js/comic_list.js deleted file mode 100644 index 9dfebf0..0000000 --- a/static/js/comic_list.js +++ /dev/null @@ -1,138 +0,0 @@ -let qsRegex; -let buttonFilter; -const js_urls = JSON.parse(document.getElementById('js_urls').textContent) - -let $grid = $('.comic-container').isotope({ - itemSelector: '.grid-item', - layoutMode: 'fitRows', - filter: function() { - let $this = $(this); - let searchResult = qsRegex ? $this.text().match( qsRegex ) : true; - let buttonResult = buttonFilter ? $this.is( buttonFilter ) : true; - return searchResult && buttonResult; - } -}); - -$('#filters').on( 'click', 'button', function() { - if (typeof $( this ).attr('data-filter') === "undefined") {} - else { - buttonFilter = $( this ).attr('data-filter'); - sessionStorage.setItem(window.location.href+"button", buttonFilter); - $grid.isotope(); - } -}); - -let $quicksearch = $('#quicksearch').keyup( debounce( function() { - qsRegex = new RegExp($quicksearch.val(), 'gi'); - sessionStorage.setItem(window.location.href+'text', $quicksearch.val()); - $grid.isotope(); -}) ); - - // debounce so filtering doesn't happen every millisecond -function debounce( fn, threshold ) { - var timeout; - threshold = threshold || 100; - return function debounced() { - clearTimeout( timeout ); - var args = arguments; - var _this = this; - function delayed() { - fn.apply( _this, args ); - } - timeout = setTimeout( delayed, threshold ); - }; -} -setInterval(function (){ - $grid.isotope(); -}, 1000) - -let field = document.getElementById("quicksearch"); - -// See if we have an autosave value -// (this will only happen if the page is accidentally refreshed) -if (sessionStorage.getItem(window.location.href+'text') || sessionStorage.getItem(window.location.href+'button')) { - // Restore the contents of the text field - field.value = sessionStorage.getItem(window.location.href+'text'); - qsRegex = new RegExp($quicksearch.val(), 'gi'); - buttonFilter = sessionStorage.getItem(window.location.href+'button'); - $grid.isotope(); -} - -// Listen for changes in the text field -field.addEventListener("change", function() { - // And save the results into the session storage object - -}); - -function comic_action(selector, item_type, action) { - $.ajax({ - url: '/comic/action/' + action + '/' + item_type + '/' + selector + '/', - success: function (){window.location.reload()} - }) - -} - -$( ".progress-bar" ).each(function( index ) { - let bar = $(this) - bar.css('width', bar.attr('aria-valuenow') + '%') -}); - -let comic_action_elements = document.getElementsByClassName('comic_action') - -comic_action_elements.forEach(el => el.addEventListener('click', event => { - let target = $(event.target).closest('button') - let selector = target.attr('selector') - let item_type = target.attr('itemtype') - let action = target.attr('comic_action') - comic_action(selector, item_type, action) -})); - -let modal_buttons = document.getElementsByClassName('modal-button') - -modal_buttons.forEach(el => el.addEventListener('click', event => { - - let target = $(event.target).closest('button') - let selector = target.attr('selector') - - let modal = $('#editModal') - modal.attr('selector', selector) - modal.attr('itemtype', target.attr('itemtype')) - - let title = $('#editModalLabel') - let title_source = $('.card-title.'+selector) - title.text(title_source.text()) - - let classification = $('select[name="classification"]') - let classification_value = $('.classification-badge.'+selector) - classification.val(classification_value.attr('classification')) - -})) - -let save_button = document.getElementById('save_button') - -save_button.addEventListener('click', function (event){ - let modal = $('#editModal') - let selector = modal.attr('selector') - let itemtype = modal.attr('itemtype') - let classification = $('select[name="classification"]') - let classification_badge = $('.classification-badge.'+selector) - let action_url = js_urls.perform_action.replace('operation', 'set_classification').replace('item_type', itemtype).replace('selector', selector) - $.ajax({ - type: "POST", - url: action_url, - data: { - classification: classification.val(), - csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').attr('value') - }, - success: function (ev){ - classification_badge.text($('select[name="classification"] option:selected').text()) - modal.modal('hide') - }, - }) - - -}) - -$( "img" ).on("error", function() { - $(this).src=$(this).attr('alt_src'); -}) diff --git a/static/js/comic_list.min.js b/static/js/comic_list.min.js deleted file mode 100644 index a9d46af..0000000 --- a/static/js/comic_list.min.js +++ /dev/null @@ -1 +0,0 @@ -let qsRegex;let buttonFilter;const js_urls=JSON.parse(document.getElementById("js_urls").textContent);let $grid=$(".comic-container").isotope({itemSelector:".grid-item",layoutMode:"fitRows",filter:function(){let $this=$(this);let searchResult=qsRegex?$this.text().match(qsRegex):true;let buttonResult=buttonFilter?$this.is(buttonFilter):true;return searchResult&&buttonResult}});$("#filters").on("click","button",function(){if(typeof $(this).attr("data-filter")==="undefined"){}else{buttonFilter=$(this).attr("data-filter");sessionStorage.setItem(window.location.href+"button",buttonFilter);$grid.isotope()}});let $quicksearch=$("#quicksearch").keyup(debounce(function(){qsRegex=new RegExp($quicksearch.val(),"gi");sessionStorage.setItem(window.location.href+"text",$quicksearch.val());$grid.isotope()}));function debounce(fn,threshold){var timeout;threshold=threshold||100;return function debounced(){clearTimeout(timeout);var args=arguments;var _this=this;function delayed(){fn.apply(_this,args)}timeout=setTimeout(delayed,threshold)}}setInterval(function(){$grid.isotope()},1e3);let field=document.getElementById("quicksearch");if(sessionStorage.getItem(window.location.href+"text")||sessionStorage.getItem(window.location.href+"button")){field.value=sessionStorage.getItem(window.location.href+"text");qsRegex=new RegExp($quicksearch.val(),"gi");buttonFilter=sessionStorage.getItem(window.location.href+"button");$grid.isotope()}field.addEventListener("change",function(){});function comic_action(selector,item_type,action){$.ajax({url:"/comic/action/"+action+"/"+item_type+"/"+selector+"/",success:function(){window.location.reload()}})}$(".progress-bar").each(function(index){let bar=$(this);bar.css("width",bar.attr("aria-valuenow")+"%")});let comic_action_elements=document.getElementsByClassName("comic_action");comic_action_elements.forEach(el=>el.addEventListener("click",event=>{let target=$(event.target).closest("button");let selector=target.attr("selector");let item_type=target.attr("itemtype");let action=target.attr("comic_action");comic_action(selector,item_type,action)}));let modal_buttons=document.getElementsByClassName("modal-button");modal_buttons.forEach(el=>el.addEventListener("click",event=>{let target=$(event.target).closest("button");let selector=target.attr("selector");let modal=$("#editModal");modal.attr("selector",selector);modal.attr("itemtype",target.attr("itemtype"));let title=$("#editModalLabel");let title_source=$(".card-title."+selector);title.text(title_source.text());let classification=$('select[name="classification"]');let classification_value=$(".classification-badge."+selector);classification.val(classification_value.attr("classification"))}));let save_button=document.getElementById("save_button");save_button.addEventListener("click",function(event){let modal=$("#editModal");let selector=modal.attr("selector");let itemtype=modal.attr("itemtype");let classification=$('select[name="classification"]');let classification_badge=$(".classification-badge."+selector);let action_url=js_urls.perform_action.replace("operation","set_classification").replace("item_type",itemtype).replace("selector",selector);$.ajax({type:"POST",url:action_url,data:{classification:classification.val(),csrfmiddlewaretoken:$('input[name="csrfmiddlewaretoken"]').attr("value")},success:function(ev){classification_badge.text($('select[name="classification"] option:selected').text());modal.modal("hide")}})});$("img").on("error",function(){$(this).src=$(this).attr("alt_src")}); \ No newline at end of file diff --git a/static/js/read_comic.js b/static/js/read_comic.js deleted file mode 100644 index b8f1a9c..0000000 --- a/static/js/read_comic.js +++ /dev/null @@ -1,70 +0,0 @@ -const nav = JSON.parse(document.getElementById('nav').textContent); -const last_read_page = JSON.parse(document.getElementById('last_read_page').textContent); - -Reveal.initialize({ - controls: false, - hash: true, - width: "100%", - height: "100%", - margin: 0, - minScale: 1, - maxScale: 1, - disableLayout: true, - progress: true, - keyboard: { - 37: () => {prevPage()}, - 39: () => {nextPage()}, - 38: () => {window.scrollTo({ top: window.scrollY-window.innerHeight*.6, left: 0, behavior: 'smooth' })}, - 40: () => {window.scrollTo({ top: window.scrollY+window.innerHeight*.6, left: 0, behavior: 'smooth' })}, - }, - touch: false, - transition: 'slide', - plugins: [ RevealMenu ] -}).then(() => { - Reveal.slide(last_read_page) - -}); - -Reveal.on( 'slidechanged', event => { - setTimeout(() =>{document.getElementsByClassName('slides')[0].scrollIntoView({behavior: 'smooth'})}, 100) - $.ajax({url: "/comic/set_page/" + nav.cur_path + "/" + event.indexh + "/"}) -}); - -const hammertime = new Hammer(document.getElementById('comic_box'), {}); -hammertime.on('swipeleft', function (ev) { - nextPage() -}); -hammertime.on('swiperight', function (ev) { - prevPage() -}); - -function prevPage() { - if (Reveal.isFirstSlide()){ - if (nav.prev_type === 'ComicBook'){ - window.location = "/comic/read/"+ nav.prev_path +"/" - } else { - window.location = "/comic/"+ nav.prev_path +"/" - } - } else { - Reveal.prev(); - } -} -function nextPage() { - if (Reveal.isLastSlide()){ - if (nav.next_type === 'ComicBook'){ - window.location = "/comic/read/"+ nav.next_path +"/" - } else { - window.location = "/comic/"+ nav.next_path +"/" - } - } else { - Reveal.next() - } -} -let slides_div = document.getElementById('slides_div') -slides_div.addEventListener('click', nextPage) - -let embeds = document.getElementsByClassName('comic_embed') - -embeds.forEach(function (embed){ - embed.addEventListener('click', nextPage) -}) diff --git a/static/js/read_comic.min.js b/static/js/read_comic.min.js deleted file mode 100644 index 7e8485e..0000000 --- a/static/js/read_comic.min.js +++ /dev/null @@ -1 +0,0 @@ -const nav=JSON.parse(document.getElementById("nav").textContent);const last_read_page=JSON.parse(document.getElementById("last_read_page").textContent);Reveal.initialize({controls:false,hash:true,width:"100%",height:"100%",margin:0,minScale:1,maxScale:1,disableLayout:true,progress:true,keyboard:{37:()=>{prevPage()},39:()=>{nextPage()},38:()=>{window.scrollTo({top:window.scrollY-window.innerHeight*.6,left:0,behavior:"smooth"})},40:()=>{window.scrollTo({top:window.scrollY+window.innerHeight*.6,left:0,behavior:"smooth"})}},touch:false,transition:"slide",plugins:[RevealMenu]}).then(()=>{Reveal.slide(last_read_page)});Reveal.on("slidechanged",event=>{setTimeout(()=>{document.getElementsByClassName("slides")[0].scrollIntoView({behavior:"smooth"})},100);$.ajax({url:"/comic/set_page/"+nav.cur_path+"/"+event.indexh+"/"})});const hammertime=new Hammer(document.getElementById("comic_box"),{});hammertime.on("swipeleft",function(ev){nextPage()});hammertime.on("swiperight",function(ev){prevPage()});function prevPage(){if(Reveal.isFirstSlide()){if(nav.prev_type==="ComicBook"){window.location="/comic/read/"+nav.prev_path+"/"}else{window.location="/comic/"+nav.prev_path+"/"}}else{Reveal.prev()}}function nextPage(){if(Reveal.isLastSlide()){if(nav.next_type==="ComicBook"){window.location="/comic/read/"+nav.next_path+"/"}else{window.location="/comic/"+nav.next_path+"/"}}else{Reveal.next()}}let slides_div=document.getElementById("slides_div");slides_div.addEventListener("click",nextPage);let embeds=document.getElementsByClassName("comic_embed");embeds.forEach(function(embed){embed.addEventListener("click",nextPage)}); \ No newline at end of file diff --git a/static/js/read_comic_pdf.js b/static/js/read_comic_pdf.js deleted file mode 100644 index 8525458..0000000 --- a/static/js/read_comic_pdf.js +++ /dev/null @@ -1,158 +0,0 @@ -// If absolute URL from the remote server is provided, configure the CORS -// header on that server. -const nav = JSON.parse(document.getElementById('nav').textContent); -const last_read_page = JSON.parse(document.getElementById('last_read_page').textContent); -var url = "/comic/read/" + nav.cur_path + "/pdf" - -// Loaded via