Compare commits
6 Commits
bc2d4aebeb
...
24dd74ae77
| Author | SHA1 | Date | |
|---|---|---|---|
| 24dd74ae77 | |||
| 899f2060f3 | |||
| 59707d3572 | |||
| 2b6234e2f8 | |||
| 6e3685b4ca | |||
| d484835f5b |
13
app.py
13
app.py
@@ -2,9 +2,10 @@ import os
|
||||
from datetime import datetime
|
||||
from flask import Flask, render_template, abort, jsonify, request
|
||||
from comics_data import (
|
||||
COMICS, FULL_WIDTH_DEFAULT, PLAIN_DEFAULT, HEADER_IMAGE, FOOTER_IMAGE,
|
||||
COMPACT_FOOTER, USE_COMIC_NAV_ICONS, USE_HEADER_NAV_ICONS,
|
||||
USE_FOOTER_SOCIAL_ICONS, SOCIAL_INSTAGRAM, SOCIAL_YOUTUBE, SOCIAL_EMAIL
|
||||
COMICS, COMIC_NAME, SITE_URL, FULL_WIDTH_DEFAULT, PLAIN_DEFAULT, HEADER_IMAGE, FOOTER_IMAGE,
|
||||
COMPACT_FOOTER, ARCHIVE_FULL_WIDTH, USE_COMIC_NAV_ICONS, USE_HEADER_NAV_ICONS,
|
||||
USE_FOOTER_SOCIAL_ICONS, SOCIAL_INSTAGRAM, SOCIAL_YOUTUBE, SOCIAL_EMAIL,
|
||||
API_SPEC_LINK
|
||||
)
|
||||
import markdown
|
||||
|
||||
@@ -18,15 +19,19 @@ app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'your-secret-key')
|
||||
def inject_global_settings():
|
||||
"""Make global settings available to all templates"""
|
||||
return {
|
||||
'comic_name': COMIC_NAME,
|
||||
'site_url': SITE_URL,
|
||||
'header_image': HEADER_IMAGE,
|
||||
'footer_image': FOOTER_IMAGE,
|
||||
'compact_footer': COMPACT_FOOTER,
|
||||
'archive_full_width': ARCHIVE_FULL_WIDTH,
|
||||
'use_comic_nav_icons': USE_COMIC_NAV_ICONS,
|
||||
'use_header_nav_icons': USE_HEADER_NAV_ICONS,
|
||||
'use_footer_social_icons': USE_FOOTER_SOCIAL_ICONS,
|
||||
'social_instagram': SOCIAL_INSTAGRAM,
|
||||
'social_youtube': SOCIAL_YOUTUBE,
|
||||
'social_email': SOCIAL_EMAIL
|
||||
'social_email': SOCIAL_EMAIL,
|
||||
'api_spec_link': API_SPEC_LINK
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
# Comic data
|
||||
# Edit this file to add, remove, or modify comics
|
||||
|
||||
# Global setting: The name of your comic/website
|
||||
COMIC_NAME = 'Sunday Comics'
|
||||
|
||||
# Global setting: Your website's domain (used for RSS feed, Open Graph tags, etc.)
|
||||
# Update this to your production domain when deploying
|
||||
SITE_URL = 'http://localhost:3000'
|
||||
|
||||
# Global setting: Set to True to make all comics full-width by default
|
||||
# Individual comics can override this with 'full_width': False
|
||||
FULL_WIDTH_DEFAULT = False
|
||||
@@ -12,7 +19,7 @@ PLAIN_DEFAULT = False
|
||||
# Global setting: Path to header image (relative to static/images/)
|
||||
# Set to None to disable header image
|
||||
# Example: HEADER_IMAGE = 'title.jpg' will use static/images/title.jpg
|
||||
HEADER_IMAGE = 'title.png'
|
||||
HEADER_IMAGE = None
|
||||
|
||||
# Global setting: Path to footer image (relative to static/images/)
|
||||
# Set to None to disable footer image
|
||||
@@ -21,7 +28,11 @@ FOOTER_IMAGE = None # 'footer.jpg'
|
||||
|
||||
# Global setting: Set to True to display footer in compact mode
|
||||
# Compact mode: single line, no border, horizontal layout
|
||||
COMPACT_FOOTER = True
|
||||
COMPACT_FOOTER = False
|
||||
|
||||
# Global setting: Set to True to make archive page full-width with 4 columns (2 on mobile)
|
||||
# Full-width archive shows square thumbnails with only dates, no titles
|
||||
ARCHIVE_FULL_WIDTH = True
|
||||
|
||||
# Global setting: Set to True to use icon images for comic navigation buttons
|
||||
# Icons should be in static/images/icons/ (first.png, previous.png, next.png, latest.png)
|
||||
@@ -40,6 +51,10 @@ SOCIAL_INSTAGRAM = None # e.g., 'https://instagram.com/yourhandle'
|
||||
SOCIAL_YOUTUBE = None # e.g., 'https://youtube.com/@yourchannel'
|
||||
SOCIAL_EMAIL = None # e.g., 'mailto:your@email.com'
|
||||
|
||||
# API documentation link - set to None to hide the link
|
||||
# Path is relative to static/ directory
|
||||
API_SPEC_LINK = None # Set to 'openapi.yml' to enable
|
||||
|
||||
COMICS = [
|
||||
{
|
||||
'number': 1,
|
||||
|
||||
@@ -10,11 +10,10 @@ from xml.dom import minidom
|
||||
|
||||
# Add parent directory to path so we can import comics_data
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from comics_data import COMICS
|
||||
from comics_data import COMICS, COMIC_NAME, SITE_URL
|
||||
|
||||
# Configuration - update these for your site
|
||||
SITE_URL = "http://localhost:3000" # Change to your actual domain
|
||||
SITE_TITLE = "Sunday Comics"
|
||||
# Configuration
|
||||
SITE_TITLE = COMIC_NAME
|
||||
SITE_DESCRIPTION = "A webcomic about life, the universe, and everything"
|
||||
SITE_LANGUAGE = "en-us"
|
||||
|
||||
|
||||
@@ -490,6 +490,38 @@ main {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* Full-width Archive Mode */
|
||||
.page-header-fullwidth {
|
||||
padding: 0 var(--space-xl);
|
||||
}
|
||||
|
||||
.archive-content-fullwidth {
|
||||
max-width: 100%;
|
||||
padding: 0 var(--space-xl);
|
||||
}
|
||||
|
||||
.archive-grid-fullwidth {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.archive-item-fullwidth img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.archive-item-fullwidth .archive-info {
|
||||
text-align: center;
|
||||
padding: var(--space-sm);
|
||||
}
|
||||
|
||||
.archive-item-fullwidth .archive-date {
|
||||
font-size: var(--font-size-md);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.nav-brand a {
|
||||
@@ -537,6 +569,19 @@ main {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.archive-grid-fullwidth {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.page-header-fullwidth {
|
||||
padding: 0 var(--space-md);
|
||||
}
|
||||
|
||||
.archive-content-fullwidth {
|
||||
padding: 0 var(--space-md);
|
||||
}
|
||||
|
||||
.comic-container {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
@@ -686,6 +731,18 @@ footer {
|
||||
letter-spacing: var(--letter-spacing-tight);
|
||||
}
|
||||
|
||||
.footer-bottom a.api-link {
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
font-size: var(--font-size-md);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--letter-spacing-tight);
|
||||
}
|
||||
|
||||
.footer-bottom a.api-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Compact Footer Mode */
|
||||
footer.compact-footer {
|
||||
border-top: none;
|
||||
@@ -698,6 +755,7 @@ footer.compact-footer .container {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
footer.compact-footer .footer-content {
|
||||
@@ -705,7 +763,7 @@ footer.compact-footer .footer-content {
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
margin-bottom: 0;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
footer.compact-footer .footer-section {
|
||||
@@ -745,6 +803,8 @@ footer.compact-footer .footer-bottom {
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
footer.compact-footer .footer-bottom::before {
|
||||
@@ -758,6 +818,16 @@ footer.compact-footer .footer-bottom p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
footer.compact-footer .footer-bottom a.api-link {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
footer.compact-footer .footer-bottom a.api-link::before {
|
||||
content: '|';
|
||||
margin: 0 var(--space-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Add separator between sections in compact mode */
|
||||
footer.compact-footer .footer-section:not(:last-child)::after {
|
||||
content: '|';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
'use strict';
|
||||
|
||||
let totalComics = 0;
|
||||
let comicName = ''; // Will be extracted from initial page title
|
||||
|
||||
// Fetch and display a comic
|
||||
async function loadComic(comicId) {
|
||||
@@ -109,7 +110,7 @@
|
||||
updateNavButtons(comic.number, comic.formatted_date);
|
||||
|
||||
// Update page title
|
||||
document.title = `${title} - Sunday Comics`;
|
||||
document.title = `${title} - ${comicName}`;
|
||||
|
||||
// Update URL without reload
|
||||
history.pushState({ comicId: comic.number }, '', `/comic/${comic.number}`);
|
||||
@@ -231,6 +232,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract comic name from initial page title (e.g., "Latest Comic - Sunday Comics" -> "Sunday Comics")
|
||||
const titleParts = document.title.split(' - ');
|
||||
if (titleParts.length > 1) {
|
||||
comicName = titleParts[titleParts.length - 1];
|
||||
}
|
||||
|
||||
// Get total comics count from the page
|
||||
totalComics = parseInt(document.querySelector('.comic-container').dataset.totalComics || 0);
|
||||
|
||||
|
||||
170
static/openapi.yaml
Normal file
170
static/openapi.yaml
Normal file
@@ -0,0 +1,170 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Webcomic API
|
||||
description: API for accessing webcomic data
|
||||
version: 1.0.0
|
||||
contact:
|
||||
url: http://127.0.0.1:3000
|
||||
|
||||
servers:
|
||||
- url: http://127.0.0.1:3000
|
||||
description: Development server
|
||||
- url: https://your-production-domain.com
|
||||
description: Production server (update with your actual domain)
|
||||
|
||||
paths:
|
||||
/api/comics:
|
||||
get:
|
||||
summary: Get all comics
|
||||
description: Returns a list of all comics with enriched metadata including formatted dates and author notes
|
||||
operationId: getAllComics
|
||||
tags:
|
||||
- Comics
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response with array of comics
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Comic'
|
||||
example:
|
||||
- number: 1
|
||||
title: "First Comic"
|
||||
filename: "comic-001.jpg"
|
||||
mobile_filename: "comic-001-mobile.jpg"
|
||||
date: "2025-01-01"
|
||||
alt_text: "The very first comic"
|
||||
author_note: "This is where your comic journey begins!"
|
||||
full_width: true
|
||||
plain: true
|
||||
formatted_date: "Wednesday, January 1, 2025"
|
||||
author_note_is_html: false
|
||||
- number: 2
|
||||
filename: "comic-002.jpg"
|
||||
date: "2025-01-08"
|
||||
alt_text: "The second comic"
|
||||
full_width: true
|
||||
plain: true
|
||||
formatted_date: "Wednesday, January 8, 2025"
|
||||
author_note_is_html: false
|
||||
|
||||
/api/comics/{comic_id}:
|
||||
get:
|
||||
summary: Get a specific comic
|
||||
description: Returns a single comic by its number/ID with enriched metadata
|
||||
operationId: getComicById
|
||||
tags:
|
||||
- Comics
|
||||
parameters:
|
||||
- name: comic_id
|
||||
in: path
|
||||
description: Comic number/ID
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 1
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response with comic data
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Comic'
|
||||
example:
|
||||
number: 1
|
||||
title: "First Comic"
|
||||
filename: "comic-001.jpg"
|
||||
mobile_filename: "comic-001-mobile.jpg"
|
||||
date: "2025-01-01"
|
||||
alt_text: "The very first comic"
|
||||
author_note: "This is where your comic journey begins!"
|
||||
full_width: true
|
||||
plain: true
|
||||
formatted_date: "Wednesday, January 1, 2025"
|
||||
author_note_is_html: false
|
||||
'404':
|
||||
description: Comic not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
example:
|
||||
error: "Comic not found"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Comic:
|
||||
type: object
|
||||
required:
|
||||
- number
|
||||
- filename
|
||||
- date
|
||||
- alt_text
|
||||
- full_width
|
||||
- plain
|
||||
- formatted_date
|
||||
- author_note_is_html
|
||||
properties:
|
||||
number:
|
||||
type: integer
|
||||
description: Sequential comic number (unique identifier)
|
||||
minimum: 1
|
||||
example: 1
|
||||
title:
|
||||
type: string
|
||||
description: Comic title (optional, defaults to "#X" if not provided)
|
||||
example: "First Comic"
|
||||
filename:
|
||||
type: string
|
||||
description: Image filename in static/images/comics/
|
||||
example: "comic-001.jpg"
|
||||
mobile_filename:
|
||||
type: string
|
||||
description: Optional mobile version of the comic image
|
||||
example: "comic-001-mobile.jpg"
|
||||
date:
|
||||
type: string
|
||||
format: date
|
||||
description: Publication date in YYYY-MM-DD format
|
||||
example: "2025-01-01"
|
||||
alt_text:
|
||||
type: string
|
||||
description: Accessibility text for the comic image
|
||||
example: "The very first comic"
|
||||
author_note:
|
||||
type: string
|
||||
description: Author's note about the comic (plain text or HTML from markdown)
|
||||
example: "This is where your comic journey begins!"
|
||||
full_width:
|
||||
type: boolean
|
||||
description: Whether the comic should display in full-width mode (computed from global default and per-comic override)
|
||||
example: true
|
||||
plain:
|
||||
type: boolean
|
||||
description: Whether the comic should display in plain mode without header/borders (computed from global default and per-comic override)
|
||||
example: true
|
||||
formatted_date:
|
||||
type: string
|
||||
description: Human-readable formatted date (computed field)
|
||||
example: "Wednesday, January 1, 2025"
|
||||
author_note_is_html:
|
||||
type: boolean
|
||||
description: Indicates whether author_note contains HTML (from markdown) or plain text (computed field)
|
||||
example: false
|
||||
|
||||
Error:
|
||||
type: object
|
||||
required:
|
||||
- error
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Error message
|
||||
example: "Comic not found"
|
||||
|
||||
tags:
|
||||
- name: Comics
|
||||
description: Operations for accessing comic data
|
||||
@@ -1,21 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
{% if archive_full_width %}
|
||||
</div> {# Close container for full-width mode #}
|
||||
{% endif %}
|
||||
|
||||
<div class="page-header{% if archive_full_width %} page-header-fullwidth{% endif %}">
|
||||
<h1>Comic Archive</h1>
|
||||
<p>Browse all {{ comics|length }} comics</p>
|
||||
</div>
|
||||
|
||||
<section class="archive-content">
|
||||
<div class="archive-grid">
|
||||
<section class="archive-content{% if archive_full_width %} archive-content-fullwidth{% endif %}">
|
||||
<div class="archive-grid{% if archive_full_width %} archive-grid-fullwidth{% endif %}">
|
||||
{% for comic in comics %}
|
||||
<div class="archive-item">
|
||||
<div class="archive-item{% if archive_full_width %} archive-item-fullwidth{% endif %}">
|
||||
<a href="{{ url_for('comic', comic_id=comic.number) }}">
|
||||
<img src="{{ url_for('static', filename='images/thumbs/' + comic.filename) }}"
|
||||
onerror="this.onerror=null; this.src='{{ url_for('static', filename='images/thumbs/default.jpg') }}';"
|
||||
alt="{{ comic.title if comic.title else '#' ~ comic.number }}">
|
||||
<div class="archive-info">
|
||||
{% if not archive_full_width %}
|
||||
<h3>#{{ comic.number }}{% if comic.title %}: {{ comic.title }}{% endif %}</h3>
|
||||
{% endif %}
|
||||
<p class="archive-date">{{ comic.date }}</p>
|
||||
</div>
|
||||
</a>
|
||||
@@ -23,4 +29,8 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% if archive_full_width %}
|
||||
<div class="container"> {# Reopen container for footer #}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}{{ title }}{% endblock %} - Sunday Comics</title>
|
||||
<title>{% block title %}{{ title }}{% endblock %} - {{ comic_name }}</title>
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<meta name="description" content="{% block meta_description %}A webcomic about life, the universe, and everything{% endblock %}">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{% block meta_url %}{{ request.url }}{% endblock %}">
|
||||
<meta property="og:title" content="{% block og_title %}{{ self.title() }} - Sunday Comics{% endblock %}">
|
||||
<meta property="og:url" content="{% block meta_url %}{{ site_url }}{{ request.path }}{% endblock %}">
|
||||
<meta property="og:title" content="{% block og_title %}{{ self.title() }} - {{ comic_name }}{% endblock %}">
|
||||
<meta property="og:description" content="{% block og_description %}{{ self.meta_description() }}{% endblock %}">
|
||||
<meta property="og:image" content="{% block og_image %}{{ request.url_root }}static/images/default-preview.png{% endblock %}">
|
||||
<meta property="og:image" content="{% block og_image %}{{ site_url }}/static/images/default-preview.png{% endblock %}">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
@@ -29,13 +29,13 @@
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='apple-touch-icon.png') }}">
|
||||
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="Sunday Comics RSS Feed" href="{{ url_for('static', filename='feed.rss') }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ comic_name }} RSS Feed" href="{{ url_for('static', filename='feed.rss') }}">
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% if header_image %}
|
||||
<div class="site-header-image">
|
||||
<img src="{{ url_for('static', filename='images/' + header_image) }}" alt="Sunday Comics Header">
|
||||
<img src="{{ url_for('static', filename='images/' + header_image) }}" alt="{{ comic_name }} Header">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<div class="container">
|
||||
{% if not header_image %}
|
||||
<div class="nav-brand">
|
||||
<a href="{{ url_for('index') }}">Sunday Comics</a>
|
||||
<a href="{{ url_for('index') }}">{{ comic_name }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<ul class="nav-links">
|
||||
@@ -114,6 +114,9 @@
|
||||
RSS Feed
|
||||
{% endif %}
|
||||
</a>
|
||||
{% if api_spec_link %}
|
||||
<a href="{{ url_for('static', filename=api_spec_link) }}" aria-label="API">API</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -129,14 +132,14 @@
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Sunday Comics. All rights reserved.</p>
|
||||
<p>© 2025 {{ comic_name }}. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{% if footer_image %}
|
||||
<div class="site-footer-image">
|
||||
<img src="{{ url_for('static', filename='images/' + footer_image) }}" alt="Sunday Comics Footer">
|
||||
<img src="{{ url_for('static', filename='images/' + footer_image) }}" alt="{{ comic_name }} Footer">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block meta_description %}{{ comic.alt_text }}{% if comic.author_note %} - {{ comic.author_note }}{% endif %}{% endblock %}
|
||||
|
||||
{% block og_image %}{{ request.url_root }}static/images/thumbs/{{ comic.filename }}{% endblock %}
|
||||
{% block og_image %}{{ site_url }}/static/images/thumbs/{{ comic.filename }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="comic-container{% if comic.full_width %} comic-container-fullwidth{% endif %}{% if comic.plain %} comic-container-plain{% endif %}" data-comic-number="{{ comic.number }}" data-total-comics="{{ total_comics }}">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% if comic %}
|
||||
{% block meta_description %}{{ comic.alt_text }}{% if comic.author_note %} - {{ comic.author_note }}{% endif %}{% endblock %}
|
||||
|
||||
{% block og_image %}{{ request.url_root }}static/images/thumbs/{{ comic.filename }}{% endblock %}
|
||||
{% block og_image %}{{ site_url }}/static/images/thumbs/{{ comic.filename }}{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
Reference in New Issue
Block a user