Implement article page layout with responsive design
This commit is contained in:
parent
c5b2f18177
commit
299624832d
@ -23,6 +23,19 @@ BLOCK_SIZE = _get_env_int("HOMEPAGE_BLOCK_SIZE", 7) # Default to 7 articles in b
|
|||||||
HORIZON_SIZE = _get_env_int("HOMEPAGE_HORIZON_SIZE", 4) # Default to 4 articles in horizon layout
|
HORIZON_SIZE = _get_env_int("HOMEPAGE_HORIZON_SIZE", 4) # Default to 4 articles in horizon layout
|
||||||
PAGE_SIZE = _get_env_int("HOMEPAGE_PAGE_SIZE", 10) # Default to 10 articles per page for pagination
|
PAGE_SIZE = _get_env_int("HOMEPAGE_PAGE_SIZE", 10) # Default to 10 articles per page for pagination
|
||||||
CATEGORY_HOT_SIZE = _get_env_int("CATEGORY_HOT_SIZE", 6) # Default to 4 articles in category hot layout
|
CATEGORY_HOT_SIZE = _get_env_int("CATEGORY_HOT_SIZE", 6) # Default to 4 articles in category hot layout
|
||||||
|
RELATED_ARTICLES_SIZE = _get_env_int("RELATED_ARTICLES_SIZE", 4) # Default to 4 related articles
|
||||||
|
|
||||||
|
class BreadcrumbMixin:
|
||||||
|
def build_breadcrumbs(self):
|
||||||
|
site = self.get_site()
|
||||||
|
site_root = site.root_page if site else None
|
||||||
|
if site_root:
|
||||||
|
ancestors = self.get_ancestors().specific().filter(depth__gt=site_root.depth)
|
||||||
|
else:
|
||||||
|
ancestors = self.get_ancestors().specific()
|
||||||
|
breadcrumbs = list(ancestors) + [self]
|
||||||
|
return breadcrumbs, site_root
|
||||||
|
|
||||||
|
|
||||||
# Mixin for Category-related functionality
|
# Mixin for Category-related functionality
|
||||||
class CategoryMixin:
|
class CategoryMixin:
|
||||||
@ -70,17 +83,6 @@ class CategoryMixin:
|
|||||||
)
|
)
|
||||||
return blocks
|
return blocks
|
||||||
|
|
||||||
# Build breadcrumbs
|
|
||||||
def build_breadcrumbs(self):
|
|
||||||
site = self.get_site()
|
|
||||||
site_root = site.root_page if site else None
|
|
||||||
if site_root:
|
|
||||||
ancestors = self.get_ancestors().specific().filter(depth__gt=site_root.depth)
|
|
||||||
else:
|
|
||||||
ancestors = self.get_ancestors().specific()
|
|
||||||
breadcrumbs = list(ancestors) + [self]
|
|
||||||
return breadcrumbs, site_root
|
|
||||||
|
|
||||||
# Get latest articles
|
# Get latest articles
|
||||||
def get_latest_articles(self, request=None):
|
def get_latest_articles(self, request=None):
|
||||||
latest_page = LatestPage.objects.first()
|
latest_page = LatestPage.objects.first()
|
||||||
@ -196,7 +198,7 @@ class HomePage(Page, CategoryMixin):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class LatestPage(Page, CategoryMixin):
|
class LatestPage(Page, CategoryMixin, BreadcrumbMixin):
|
||||||
template = "home/category_page.html"
|
template = "home/category_page.html"
|
||||||
|
|
||||||
def get_context(self, request):
|
def get_context(self, request):
|
||||||
@ -208,7 +210,7 @@ class LatestPage(Page, CategoryMixin):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class TrendingPage(Page, CategoryMixin):
|
class TrendingPage(Page, CategoryMixin, BreadcrumbMixin):
|
||||||
template = "home/category_page.html"
|
template = "home/category_page.html"
|
||||||
|
|
||||||
def get_context(self, request):
|
def get_context(self, request):
|
||||||
@ -220,7 +222,7 @@ class TrendingPage(Page, CategoryMixin):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class CategoryPage(Page, CategoryMixin):
|
class CategoryPage(Page, CategoryMixin, BreadcrumbMixin):
|
||||||
@property
|
@property
|
||||||
def has_subcategories(self):
|
def has_subcategories(self):
|
||||||
return self.get_children().type(CategoryPage).live().exists()
|
return self.get_children().type(CategoryPage).live().exists()
|
||||||
@ -254,7 +256,7 @@ class ArticlePageTag(TaggedItemBase):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
|
|
||||||
class ArticlePage(Page):
|
class ArticlePage(Page, BreadcrumbMixin):
|
||||||
cover_image = models.ForeignKey(
|
cover_image = models.ForeignKey(
|
||||||
"wagtailimages.Image",
|
"wagtailimages.Image",
|
||||||
null=True,
|
null=True,
|
||||||
@ -308,6 +310,16 @@ class ArticlePage(Page):
|
|||||||
def get_context(self, request):
|
def get_context(self, request):
|
||||||
context = super().get_context(request)
|
context = super().get_context(request)
|
||||||
|
|
||||||
|
breadcrumbs, site_root = self.build_breadcrumbs()
|
||||||
|
context["breadcrumbs"] = breadcrumbs
|
||||||
|
context["breadcrumb_root"] = site_root
|
||||||
|
category_crumbs = [
|
||||||
|
crumb
|
||||||
|
for crumb in breadcrumbs
|
||||||
|
if crumb.id != self.id and (not site_root or crumb.id != site_root.id)
|
||||||
|
]
|
||||||
|
context["article_category"] = category_crumbs[-1] if category_crumbs else None
|
||||||
|
|
||||||
tag_ids = list(self.tags.values_list("id", flat=True))
|
tag_ids = list(self.tags.values_list("id", flat=True))
|
||||||
if tag_ids:
|
if tag_ids:
|
||||||
related_articles = (
|
related_articles = (
|
||||||
@ -315,7 +327,7 @@ class ArticlePage(Page):
|
|||||||
.exclude(id=self.id)
|
.exclude(id=self.id)
|
||||||
.filter(tags__id__in=tag_ids)
|
.filter(tags__id__in=tag_ids)
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by("-date", "-id")[:4]
|
.order_by("-date", "-id")[:RELATED_ARTICLES_SIZE]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
related_articles = ArticlePage.objects.none()
|
related_articles = ArticlePage.objects.none()
|
||||||
|
|||||||
396
innovedus_cms/home/static/css/article_page.css
Normal file
396
innovedus_cms/home/static/css/article_page.css
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
.article-page {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 718px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-breadcrumbs {
|
||||||
|
margin: 12px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-breadcrumbs ol {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-breadcrumbs li {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #0e1b4266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-breadcrumbs li + li::before {
|
||||||
|
content: "/";
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-breadcrumbs a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-title {
|
||||||
|
background-color: #0e1b42;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content img {
|
||||||
|
max-width: 718px;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content h1 {
|
||||||
|
font-size: 36px;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content .intro {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content .body iframe {
|
||||||
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content .body blockquote.instagram-media,
|
||||||
|
.article-content .body blockquote.twitter-tweet,
|
||||||
|
.article-content .body .fb-post,
|
||||||
|
.article-content .body .fb-video {
|
||||||
|
max-width: 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content .date {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #0e1b4266;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content .date::after {
|
||||||
|
content: "";
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
background: #0e1b4266;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-tags {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-tags .tag-chips {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chips li {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chips a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 96px;
|
||||||
|
height: 25px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chips li:nth-child(3n + 1) a {
|
||||||
|
background: #00abf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chips li:nth-child(3n + 2) a {
|
||||||
|
background: #f4a41c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chips li:nth-child(3n) a {
|
||||||
|
background: #ff461c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-share {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 64px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-share .icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
--fill-0: #0e1b4266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-share .icon .icon-cutout {
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-articles {
|
||||||
|
margin-top: 32px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-articles h2 {
|
||||||
|
margin: 0 0 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #0e1b4266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-articles-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-articles-list > hr {
|
||||||
|
width: 100%;
|
||||||
|
margin: 12px 0;
|
||||||
|
margin-inline: 0;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #0e1b4266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 194px 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-image {
|
||||||
|
display: block;
|
||||||
|
width: 194px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-image img {
|
||||||
|
width: 194px;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 133px;
|
||||||
|
display: block;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-date {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #0e1b4266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-title {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chips {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-tags {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top {
|
||||||
|
position: fixed;
|
||||||
|
right: var(--back-to-top-right, 24px);
|
||||||
|
bottom: 24px;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #0e1b42;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateY(8px);
|
||||||
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top__icon {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d9deea;
|
||||||
|
box-shadow: 0 3px 8px rgba(14, 27, 66, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top__icon::before,
|
||||||
|
.back-to-top__icon::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
width: 14px;
|
||||||
|
height: 2px;
|
||||||
|
background: #0e1b42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top__icon::before {
|
||||||
|
left: 12px;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top__icon::after {
|
||||||
|
right: 12px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top__label {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) and (max-width: 1023px) {
|
||||||
|
.article-page {
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content img {
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 575px) and (max-width: 767px) {
|
||||||
|
.article-page {
|
||||||
|
max-width: 426px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content img {
|
||||||
|
max-width: 426px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top {
|
||||||
|
bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 574px) {
|
||||||
|
.article-page {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content img {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content .intro {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content .date {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-tags {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-item {
|
||||||
|
grid-template-columns: 139px 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-image {
|
||||||
|
width: 139px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-image img {
|
||||||
|
width: 139px;
|
||||||
|
height: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-date {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-title {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chips a {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-article-tags {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top {
|
||||||
|
bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top__label {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.article-breadcrumbs {
|
||||||
|
margin: 8px 0 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,3 @@
|
|||||||
.block-title {
|
|
||||||
display: inline-block;
|
|
||||||
width: 197px;
|
|
||||||
height: 87px;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-title span {
|
|
||||||
padding-left: 21px;
|
|
||||||
line-height: 87px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-title {
|
.category-title {
|
||||||
background-color: #00abf5;
|
background-color: #00abf5;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@ -50,17 +36,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 574px) {
|
@media (max-width: 574px) {
|
||||||
.block-title {
|
|
||||||
width: 139px;
|
|
||||||
height: 55px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-title span {
|
|
||||||
padding-left: 14px;
|
|
||||||
line-height: 55px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subcategory-title span {
|
.subcategory-title span {
|
||||||
width: 139px;
|
width: 139px;
|
||||||
height: 55px;
|
height: 55px;
|
||||||
|
|||||||
@ -13,20 +13,6 @@
|
|||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-title {
|
|
||||||
display: inline-block;
|
|
||||||
width: 197px;
|
|
||||||
height: 87px;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-title span {
|
|
||||||
padding-left: 21px;
|
|
||||||
line-height: 87px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-title-divider {
|
.block-title-divider {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
@ -78,17 +64,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 574px) {
|
@media (max-width: 574px) {
|
||||||
.block-title {
|
|
||||||
width: 139px;
|
|
||||||
height: 55px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-title span {
|
|
||||||
padding-left: 14px;
|
|
||||||
line-height: 55px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-title-divider {
|
.block-title-divider {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|||||||
67
innovedus_cms/home/static/js/article_page.js
Normal file
67
innovedus_cms/home/static/js/article_page.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
(function () {
|
||||||
|
const button = document.querySelector(".back-to-top");
|
||||||
|
const articlePage = document.querySelector(".article-page");
|
||||||
|
const articleBody = document.querySelector(".article-content .body");
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
const toggleVisibility = () => {
|
||||||
|
button.classList.toggle("is-visible", window.scrollY > 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncHorizontalPosition = () => {
|
||||||
|
if (!articlePage) return;
|
||||||
|
const rightGap = Math.max(0, window.innerWidth - articlePage.getBoundingClientRect().right);
|
||||||
|
button.style.setProperty("--back-to-top-right", `${rightGap}px`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeEmbedsResponsive = () => {
|
||||||
|
if (!articleBody) return;
|
||||||
|
|
||||||
|
articleBody.querySelectorAll("iframe").forEach((iframe) => {
|
||||||
|
const src = (iframe.getAttribute("src") || "").toLowerCase();
|
||||||
|
const isFixedRatioPlayer =
|
||||||
|
src.includes("youtube.com") ||
|
||||||
|
src.includes("youtube-nocookie.com") ||
|
||||||
|
src.includes("m.youtube.com") ||
|
||||||
|
src.includes("youtu.be") ||
|
||||||
|
src.includes("vimeo.com") ||
|
||||||
|
src.includes("player.vimeo.com") ||
|
||||||
|
src.includes("dailymotion.com");
|
||||||
|
const width = Number(iframe.getAttribute("width"));
|
||||||
|
const height = Number(iframe.getAttribute("height"));
|
||||||
|
|
||||||
|
iframe.style.width = "100%";
|
||||||
|
iframe.style.maxWidth = "100%";
|
||||||
|
|
||||||
|
if (isFixedRatioPlayer) {
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
iframe.style.aspectRatio = `${width} / ${height}`;
|
||||||
|
} else {
|
||||||
|
iframe.style.aspectRatio = "16 / 9";
|
||||||
|
}
|
||||||
|
iframe.style.height = "auto";
|
||||||
|
} else {
|
||||||
|
iframe.style.removeProperty("aspect-ratio");
|
||||||
|
iframe.style.removeProperty("height");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("scroll", toggleVisibility, { passive: true });
|
||||||
|
window.addEventListener("resize", syncHorizontalPosition);
|
||||||
|
|
||||||
|
toggleVisibility();
|
||||||
|
syncHorizontalPosition();
|
||||||
|
makeEmbedsResponsive();
|
||||||
|
|
||||||
|
if (articleBody && "MutationObserver" in window) {
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
makeEmbedsResponsive();
|
||||||
|
});
|
||||||
|
observer.observe(articleBody, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener("click", function () {
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
});
|
||||||
|
})();
|
||||||
@ -1,35 +1,125 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load wagtailcore_tags wagtailimages_tags %}
|
{% load wagtailcore_tags wagtailimages_tags static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'css/article_page.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<article class="article-page">
|
||||||
<h1>{{ page.title }}</h1>
|
{% if breadcrumbs %}
|
||||||
{% if page.banner_image %}
|
<nav class="article-breadcrumbs" aria-label="breadcrumb">
|
||||||
{% image page.banner_image original as banner %}
|
<ol>
|
||||||
<img src="{{ banner.url }}" alt="{{ page.title }}">
|
<li>
|
||||||
|
{% if breadcrumb_root %}
|
||||||
|
<a href="{{ breadcrumb_root.url }}">首頁</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="/">首頁</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% for crumb in breadcrumbs %}
|
||||||
|
{% if not breadcrumb_root or crumb.id != breadcrumb_root.id %}
|
||||||
|
{% if crumb.id != page.id %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="date">{{ page.date }}</p>
|
<div class="article-category">
|
||||||
<div class="intro">{{ page.intro }}</div>
|
<div class="block-title category-title"><span>{% if article_category %}{{ article_category.title }}{% endif %}</span></div>
|
||||||
<div class="body">
|
|
||||||
{{ page.body }}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="article-content">
|
||||||
|
{% if page.cover_image %}
|
||||||
|
{% image page.cover_image original as cover %}
|
||||||
|
<img src="{{ cover.url }}" alt="{{ page.title }}">
|
||||||
|
{% endif %}
|
||||||
|
<h1>{{ page.title }}</h1>
|
||||||
|
<p class="date">{{ page.date|date:"Y/m/d" }}</p>
|
||||||
|
<div class="intro">{{ page.intro }}</div>
|
||||||
|
<div class="body">
|
||||||
|
{{ page.body }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% with share_url=request.build_absolute_uri|urlencode share_text=page.title|urlencode %}
|
||||||
|
<div class="article-share" aria-label="分享文章">
|
||||||
|
<a href="https://www.facebook.com/sharer/sharer.php?u={{ share_url }}" target="_blank" rel="noopener noreferrer" aria-label="分享到 Facebook">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" role="img">
|
||||||
|
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0ZM23.12 8.37H21.18C19.27 8.37 18.67 9.56 18.67 10.78V13.67H22.94L22.26 18.12H18.67V28.89H13.85V18.12H9.94V13.67H13.85V10.28C13.85 6.42 16.15 4.29 19.67 4.29C21.36 4.29 23.12 4.59 23.12 4.59V8.38V8.37Z" fill="var(--fill-0, #0E1B42)"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/intent/tweet?url={{ share_url }}&text={{ share_text }}" target="_blank" rel="noopener noreferrer" aria-label="分享到 X">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" role="img">
|
||||||
|
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0Z" fill="var(--fill-0, #0E1B42)"/>
|
||||||
|
<path class="icon-cutout" d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z" transform="translate(7.59 7.59) scale(1.125)"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.threads.net/intent/post?text={{ share_text }}%20{{ share_url }}" target="_blank" rel="noopener noreferrer" aria-label="分享到 Threads">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.18 33.18" role="img">
|
||||||
|
<path d="M16.59 0C7.43 0 0 7.43 0 16.59C0 25.75 7.43 33.18 16.59 33.18C25.75 33.18 33.18 25.75 33.18 16.59C33.18 7.43 25.75 0 16.59 0Z" fill="var(--fill-0, #0E1B42)"/>
|
||||||
|
<path class="icon-cutout" d="M6.321 6.016c-.27-.18-1.166-.802-1.166-.802.756-1.081 1.753-1.502 3.132-1.502.975 0 1.803.327 2.394.948s.928 1.509 1.005 2.644q.492.207.905.484c1.109.745 1.719 1.86 1.719 3.137 0 2.716-2.226 5.075-6.256 5.075C4.594 16 1 13.987 1 7.994 1 2.034 4.482 0 8.044 0 9.69 0 13.55.243 15 5.036l-1.36.353C12.516 1.974 10.163 1.43 8.006 1.43c-3.565 0-5.582 2.171-5.582 6.79 0 4.143 2.254 6.343 5.63 6.343 2.777 0 4.847-1.443 4.847-3.556 0-1.438-1.208-2.127-1.27-2.127-.236 1.234-.868 3.31-3.644 3.31-1.618 0-3.013-1.118-3.013-2.582 0-2.09 1.984-2.847 3.55-2.847.586 0 1.294.04 1.663.114 0-.637-.54-1.728-1.9-1.728-1.25 0-1.566.405-1.967.868ZM8.716 8.19c-2.04 0-2.304.87-2.304 1.416 0 .878 1.043 1.168 1.6 1.168 1.02 0 2.067-.282 2.232-2.423a6.2 6.2 0 0 0-1.528-.161" transform="translate(7.59 7.59) scale(1.125)"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
{% with tags=page.tags.all %}
|
{% with tags=page.tags.all %}
|
||||||
{% if tags %}
|
{% if tags %}
|
||||||
<div class="tags">
|
<div class="article-tags">
|
||||||
<span>Hashtags:</span>
|
<ul class="tag-chips">
|
||||||
<ul>
|
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<li><a href="{% url 'hashtag_search' tag.slug %}">#{{ tag }}</a></li>
|
<li><a href="{% url 'hashtag_search' tag.slug %}">{{ tag }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if related_articles %}
|
{% if related_articles %}
|
||||||
|
<hr/>
|
||||||
<section class="related-articles">
|
<section class="related-articles">
|
||||||
<h2>相關文章</h2>
|
<h2>相關文章</h2>
|
||||||
{% include "home/includes/article_list.html" with items=related_articles %}
|
<div class="related-articles-list">
|
||||||
|
{% for related in related_articles %}
|
||||||
|
<article class="related-article-item">
|
||||||
|
<div class="related-article-left">
|
||||||
|
<a class="related-article-image" href="{{ related.url }}">
|
||||||
|
{% if related.cover_image %}
|
||||||
|
{% image related.cover_image max-194x133 as related_cover %}
|
||||||
|
<img src="{{ related_cover.url }}" alt="{{ related.title }}">
|
||||||
|
{% else %}
|
||||||
|
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ related.title }}">
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
<p class="related-article-date">{{ related.date|date:"Y/m/d" }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="related-article-right">
|
||||||
|
<h3 class="related-article-title"><a href="{{ related.url }}">{{ related.title }}</a></h3>
|
||||||
|
{% with related_tags=related.tags.all %}
|
||||||
|
{% if related_tags %}
|
||||||
|
<ul class="related-article-tags tag-chips">
|
||||||
|
{% for tag in related_tags %}
|
||||||
|
<li><a href="{% url 'hashtag_search' tag.slug %}">{{ tag }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<hr/>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
|
<button class="back-to-top" type="button" aria-label="回到頁首">
|
||||||
|
<span class="back-to-top__icon" aria-hidden="true"></span>
|
||||||
|
<span class="back-to-top__label">TOP</span>
|
||||||
|
</button>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
{{ block.super }}
|
||||||
|
<script type="text/javascript" src="{% static 'js/article_page.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -369,6 +369,20 @@ footer .footer-sections {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block-title {
|
||||||
|
display: inline-block;
|
||||||
|
width: 197px;
|
||||||
|
height: 87px;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-title span {
|
||||||
|
padding-left: 21px;
|
||||||
|
line-height: 87px;
|
||||||
|
}
|
||||||
|
|
||||||
.template-darkbackground .site-hero-band {
|
.template-darkbackground .site-hero-band {
|
||||||
background-color: #0e1b42;
|
background-color: #0e1b42;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@ -578,6 +592,17 @@ footer .footer-sections {
|
|||||||
.main-nav {
|
.main-nav {
|
||||||
right: 16px;
|
right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block-title {
|
||||||
|
width: 139px;
|
||||||
|
height: 55px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-title span {
|
||||||
|
padding-left: 14px;
|
||||||
|
line-height: 55px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer figreset {
|
@layer figreset {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user