Compare commits

...

12 Commits

Author SHA1 Message Date
6a21a9d094 design 2023-11-22 21:09:59 +01:00
b4c12def13 work on templates 2023-11-22 17:33:51 +01:00
36ae12af3d fix: static 2023-11-02 22:10:41 +01:00
0a86d4e0a3 add statics 2023-11-02 22:09:49 +01:00
a53aebb5b8 rm static 2023-11-02 22:07:27 +01:00
1af0348c89 add vue files 2023-11-02 22:03:55 +01:00
8ab8ef5b1c add missing files 2023-11-02 21:59:30 +01:00
bf9da835b2 add missing files 2023-11-02 21:58:13 +01:00
7b28149d7e add radiocampus app 2023-11-02 21:56:22 +01:00
87a2ee5a45 feat: work on date menu 2023-11-02 21:54:15 +01:00
ab231e9a89 work on design 2023-10-27 21:09:58 +02:00
1661601caf work on design: items, components 2023-10-24 18:29:34 +02:00
72 changed files with 23460 additions and 5546 deletions

View File

@ -30,9 +30,9 @@ class DiffusionAdmin(DiffusionBaseAdmin, admin.ModelAdmin):
end_date.short_description = _("end") end_date.short_description = _("end")
list_display = ("episode", "start_date", "end_date", "type", "initial") list_display = ("episode", "start", "end", "type", "initial")
list_filter = ("type", "start", "program") list_filter = ("type", "start", "program")
list_editable = ("type",) list_editable = ("type", "start", "end")
ordering = ("-start", "id") ordering = ("-start", "id")
fields = ("type", "start", "end", "initial", "program", "schedule") fields = ("type", "start", "end", "initial", "program", "schedule")

View File

@ -10,7 +10,7 @@ class PageFilters(filters.FilterSet):
class Meta: class Meta:
model = Page model = Page
fields = { fields = {
"category__id": ["in"], "category__id": ["in", "exact"],
"pub_date": ["exact", "gte", "lte"], "pub_date": ["exact", "gte", "lte"],
} }

View File

@ -8,6 +8,7 @@ __all__ = ("Article",)
class Article(Page): class Article(Page):
detail_url_name = "article-detail" detail_url_name = "article-detail"
template_prefix = "article"
objects = ProgramChildQuerySet.as_manager() objects = ProgramChildQuerySet.as_manager()

View File

@ -137,8 +137,6 @@ class Diffusion(Rerun):
# help_text = _('use this input port'), # help_text = _('use this input port'),
# ) # )
item_template_name = "aircox/widgets/diffusion_item.html"
class Meta: class Meta:
verbose_name = _("Diffusion") verbose_name = _("Diffusion")
verbose_name_plural = _("Diffusions") verbose_name_plural = _("Diffusions")
@ -208,6 +206,11 @@ class Diffusion(Rerun):
and self.end >= now and self.end >= now
) )
@property
def is_today(self):
"""True if diffusion is currently today."""
return self.start.date() == datetime.date.today()
@property @property
def is_live(self): def is_live(self):
"""True if Diffusion is live (False if there are sounds files).""" """True if Diffusion is live (False if there are sounds files)."""

View File

@ -13,11 +13,15 @@ __all__ = ("Episode",)
class Episode(Page): class Episode(Page):
objects = ProgramChildQuerySet.as_manager() objects = ProgramChildQuerySet.as_manager()
detail_url_name = "episode-detail" detail_url_name = "episode-detail"
item_template_name = "aircox/widgets/episode_item.html" template_prefix = "episode"
@property @property
def program(self): def program(self):
return getattr(self.parent, "program", None) return self.parent_subclass
@program.setter
def program(self, value):
self.parent = value
@cached_property @cached_property
def podcasts(self): def podcasts(self):
@ -40,10 +44,6 @@ class Episode(Page):
podcasts[index]["page_title"] = self.title podcasts[index]["page_title"] = self.title
return podcasts return podcasts
@program.setter
def program(self, value):
self.parent = value
class Meta: class Meta:
verbose_name = _("Episode") verbose_name = _("Episode")
verbose_name_plural = _("Episodes") verbose_name_plural = _("Episodes")

View File

@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
from .diffusion import Diffusion from .diffusion import Diffusion
from .sound import Sound, Track from .sound import Sound, Track
from .station import Station from .station import Station
from .page import Renderable
logger = logging.getLogger("aircox") logger = logging.getLogger("aircox")
@ -54,13 +55,15 @@ class LogQuerySet(models.QuerySet):
return self.filter(track__isnull=not with_it) return self.filter(track__isnull=not with_it)
class Log(models.Model): class Log(Renderable, models.Model):
"""Log sounds and diffusions that are played on the station. """Log sounds and diffusions that are played on the station.
This only remember what has been played on the outputs, not on each This only remember what has been played on the outputs, not on each
source; Source designate here which source is responsible of that. source; Source designate here which source is responsible of that.
""" """
template_prefix = "log"
TYPE_STOP = 0x00 TYPE_STOP = 0x00
"""Source has been stopped, e.g. manually.""" """Source has been stopped, e.g. manually."""
# Rule: \/ diffusion != null \/ sound != null # Rule: \/ diffusion != null \/ sound != null

View File

@ -16,6 +16,7 @@ from model_utils.managers import InheritanceQuerySet
from .station import Station from .station import Station
__all__ = ( __all__ = (
"Renderable",
"Category", "Category",
"PageQuerySet", "PageQuerySet",
"Page", "Page",
@ -30,6 +31,17 @@ headline_re = re.compile(
) )
class Renderable:
template_prefix = "page"
template_name = "aircox/widgets/{prefix}.html"
def get_template_name(self, widget):
"""Return template name for the provided widget."""
return self.template_name.format(
prefix=self.template_prefix, widget=widget
)
class Category(models.Model): class Category(models.Model):
title = models.CharField(_("title"), max_length=64) title = models.CharField(_("title"), max_length=64)
slug = models.SlugField(_("slug"), max_length=64, db_index=True) slug = models.SlugField(_("slug"), max_length=64, db_index=True)
@ -52,6 +64,9 @@ class BasePageQuerySet(InheritanceQuerySet):
def trash(self): def trash(self):
return self.filter(status=Page.STATUS_TRASH) return self.filter(status=Page.STATUS_TRASH)
def by_last(self):
return self.order_by("-pub_date")
def parent(self, parent=None, id=None): def parent(self, parent=None, id=None):
"""Return pages having this parent.""" """Return pages having this parent."""
return ( return (
@ -68,7 +83,7 @@ class BasePageQuerySet(InheritanceQuerySet):
return self.filter(title__icontains=q) return self.filter(title__icontains=q)
class BasePage(models.Model): class BasePage(Renderable, models.Model):
"""Base class for publishable content.""" """Base class for publishable content."""
STATUS_DRAFT = 0x00 STATUS_DRAFT = 0x00
@ -112,7 +127,6 @@ class BasePage(models.Model):
objects = BasePageQuerySet.as_manager() objects = BasePageQuerySet.as_manager()
detail_url_name = None detail_url_name = None
item_template_name = "aircox/widgets/page_item.html"
class Meta: class Meta:
abstract = True abstract = True
@ -152,14 +166,14 @@ class BasePage(models.Model):
@property @property
def display_title(self): def display_title(self):
if self.is_published(): if self.is_published:
return self.title return self.title
return self.parent.display_title() return self.parent and self.parent.display_title or ""
@cached_property @cached_property
def headline(self): def display_headline(self):
if not self.content: if not self.content or not self.is_published:
return "" return self.parent and self.parent.display_headline or ""
content = bleach.clean(self.content, tags=[], strip=True) content = bleach.clean(self.content, tags=[], strip=True)
headline = headline_re.search(content) headline = headline_re.search(content)
return mark_safe(headline.groupdict()["headline"]) if headline else "" return mark_safe(headline.groupdict()["headline"]) if headline else ""
@ -207,6 +221,12 @@ class Page(BasePage):
objects = PageQuerySet.as_manager() objects = PageQuerySet.as_manager()
@cached_property
def parent_subclass(self):
if self.parent_id:
return Page.objects.get_subclass(id=self.parent_id)
return None
class Meta: class Meta:
verbose_name = _("Publication") verbose_name = _("Publication")
verbose_name_plural = _("Publications") verbose_name_plural = _("Publications")
@ -265,7 +285,7 @@ class StaticPage(BasePage):
return super().get_absolute_url() return super().get_absolute_url()
class Comment(models.Model): class Comment(Renderable, models.Model):
page = models.ForeignKey( page = models.ForeignKey(
Page, Page,
models.CASCADE, models.CASCADE,
@ -278,7 +298,7 @@ class Comment(models.Model):
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
content = models.TextField(_("content"), max_length=1024) content = models.TextField(_("content"), max_length=1024)
item_template_name = "aircox/widgets/comment_item.html" template_prefix = "comment"
@cached_property @cached_property
def parent(self): def parent(self):
@ -286,7 +306,7 @@ class Comment(models.Model):
return Page.objects.select_subclasses().filter(id=self.page_id).first() return Page.objects.select_subclasses().filter(id=self.page_id).first()
def get_absolute_url(self): def get_absolute_url(self):
return self.parent.get_absolute_url() return self.parent.get_absolute_url() + f"#comment-{self.pk}"
class Meta: class Meta:
verbose_name = _("Comment") verbose_name = _("Comment")

View File

@ -43,6 +43,7 @@ def user_default_groups(sender, instance, created, *args, **kwargs):
@receiver(signals.post_save, sender=Page) @receiver(signals.post_save, sender=Page)
def page_post_save(sender, instance, created, *args, **kwargs): def page_post_save(sender, instance, created, *args, **kwargs):
return
if not created and instance.cover: if not created and instance.cover:
Page.objects.filter(parent=instance, cover__isnull=True).update( Page.objects.filter(parent=instance, cover__isnull=True).update(
cover=instance.cover cover=instance.cover
@ -67,6 +68,7 @@ def program_post_save(sender, instance, created, *args, **kwargs):
@receiver(signals.pre_save, sender=Schedule) @receiver(signals.pre_save, sender=Schedule)
def schedule_pre_save(sender, instance, *args, **kwargs): def schedule_pre_save(sender, instance, *args, **kwargs):
return
if getattr(instance, "pk") is not None: if getattr(instance, "pk") is not None:
instance._initial = Schedule.objects.get(pk=instance.pk) instance._initial = Schedule.objects.get(pk=instance.pk)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{% extends "admin/index.html" %} {% extends "admin/index.html" %}
{% load i18n thumbnail %} {% load i18n thumbnail aircox %}
{% block app %} {% block app %}
@ -62,10 +62,12 @@
<span>{% translate "Latest comments" %}</span> <span>{% translate "Latest comments" %}</span>
</h1> </h1>
{% if comments %} {% if comments %}
{% include "aircox/widgets/page_list.html" with object_list=comments with_title=True %} {% for object in comments %}
<div class="has-text-centered"> {% page_widget "preview" object with_title=True %}
<a href="{% url "admin:aircox_comment_changelist" %}" class="float-center">{% translate "All comments" %}</a> {% endfor %}
</div> <div class="has-text-centered">
<a href="{% url "admin:aircox_comment_changelist" %}" class="float-center">{% translate "All comments" %}</a>
</div>
{% else %} {% else %}
<p class="block has-text-centered">{% trans "No comment posted yet" %}</p> <p class="block has-text-centered">{% trans "No comment posted yet" %}</p>
{% endif %} {% endif %}

View File

@ -48,120 +48,48 @@ Usefull context:
}) })
</script> </script>
<div id="app"> <div id="app">
{% block top-nav-container %}
<nav class="navbar has-shadow" role="navigation" aria-label="main navigation">
<div class="container">
<div class="navbar-brand">
<a href="/" title="{% translate "Home" %}" class="navbar-item">
<img src="{{ station.logo.url }}" class="logo"/>
</a>
</div>
<div class="navbar-menu">
<div class="navbar-start">
{% block top-nav %}
{% nav_items "top" css_class="navbar-item" active_class="is-active" as items %}
{% for item, render in items %}
{{ render }}
{% endfor %}
{% endblock %}
</div>
<div class="navbar-end">
{% block top-nav-tools %}
{% endblock %}
{% block top-nav-end %}
<div class="navbar-item">
<form action="{% url 'page-list' %}" method="GET">
<div class="control has-icons-left">
<span class="icon is-small is-left">
<i class="fa fa-search"></i>
</span>
<input type="text" name="q" class="input"
placeholder="{% translate "Search" %}" />
</div>
</form>
</div>
{% endblock %}
</div>
</div>
</div>
</nav>
{% endblock %}
<div class="container"> <div class="container">
<div class="columns is-desktop"> {% block nav %}
<main class="column page"> <nav class="nav primary" role="navigation" aria-label="main navigation">
<header class="header"> {% block nav-primary %}
{% block header %} <a class="nav-brand" href="{% url "home" %}">
<h1 class="title is-1"> <img src="{{ station.logo.url }}">
{% block title %} </a>
{% if page and page.title %} <div class="nav-menu">
{{ page.title }} {% block nav-primary-menu %}
{% endif %} {% nav_items "top" css_class="nav-item" active_class="active" as items %}
{% endblock %} {% for item, render in items %}
</h1> {{ render }}
{% endfor %}
<h3 class="subtitle is-3">
{% block subtitle %}{% endblock %}
</h3>
<div class="columns is-size-4">
{% block header_nav %}
<span class="column">
{% block header_crumbs %}
{% if parent %}
<a href="{{ parent.get_absolute_url }}">
{{ parent.title }}</a></li>
{% endif %}
{% endblock %}
</span>
{% endblock %}
</div>
{% endblock %}
</header>
{% block main %}
{% block content %}
{% if page and page.content %}
<section class="page-content mb-2">{{ page.content|safe }}</section>
{% endif %}
{% endblock %}
{% endblock main %}
</main>
{% if has_sidebar %}
{% comment %}Translators: main sidebar {% endcomment %}
<aside class="column is-one-third-desktop">
{# FIXME: block cover into sidebar one #}
{% block cover %}
{% if page and page.cover %}
<img class="cover mb-4" src="{{ page.cover.url }}" class="cover"/>
{% endif %}
{% endblock %} {% endblock %}
</div>
{% with is_thin=True %} {% endblock %}
{% block sidebar %} </nav>
{% if sidebar_object_list %} {% endblock %}
{% with object_list=sidebar_object_list %} {% block secondary-nav %}{% endblock %}
{% with list_url=sidebar_list_url %}
{% with has_headline=False %}
<section>
<h4 class="title is-4">
{% block sidebar_title %}{% translate "Recently" %}{% endblock %}
</h4>
{% include "aircox/widgets/page_list.html" %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endif %}
</section>
{% endblock %}
{% endwith %}
</aside>
{% endif %}
</div>
</div> </div>
<hr> {% block main-container %}
<main class="page">
{% block main %}
<header class="container header {% if cover %}has-cover{% endif %}">
{% block header %}
{% include header_template_name|default:"./widgets/header.html" %}
{% endblock %}
</header>
{% block content-container %}
<div class="container content">
{% block content %}
{% if page and page.content %}
<section class="page-content">{{ page.content|safe }}</section>
{% endif %}
{% endblock %}
</div>
{% endblock %}
{% endblock %}
</main>
{% endblock %}
</div> </div>
{% block player-container %} {% block player-container %}
<div id="player">{% include "aircox/widgets/player.html" %}</div> <div id="player">{% include "aircox/widgets/player.html" %}</div>

View File

@ -1,4 +1,4 @@
{% extends "aircox/base.html" %} {% extends "./base.html" %}
{% comment %}Display a list of BasePages{% endcomment %} {% comment %}Display a list of BasePages{% endcomment %}
{% load i18n aircox %} {% load i18n aircox %}
@ -22,20 +22,24 @@
{{ station.name }} {{ station.name }}
{% endblock %} {% endblock %}
{% block main %}{{ block.super }}
{% block content-container %}
{{ block.super }}
<div class="container">
{% block before_list %}{% endblock %} {% block before_list %}{% endblock %}
<section role="list"> <section role="list">
{% block pages_list %} {% block pages_list %}
{% with has_headline=True %} {% with has_headline=True %}
{% for object in object_list %} {% for object in object_list %}
{% block list_object %} {% block list_object %}
{% include object.item_template_name|default:item_template_name %} {% page_widget item_widget|default:"item" object %}
{% endblock %} {% endblock %}
{% empty %} {% empty %}
{% blocktranslate %}There is nothing published here...{% endblocktranslate %} {% blocktranslate %}There is nothing published here...{% endblocktranslate %}
{% endfor %} {% endfor %}
{% endwith %} {% endwith %}
{% endblock %} {% endblock %}
</section> </section>
@ -83,4 +87,5 @@
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</div>
{% endblock %} {% endblock %}

View File

@ -12,18 +12,24 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %} {% block header %}
{% with "./widgets/dated_list_header.html" as header_template_name %}
{% block before_list %} {{ block.super }}
{% with "diffusion-list" as url_name %}
{% include "aircox/widgets/dates_menu.html" %}
{% endwith %} {% endwith %}
{% endblock %} {% endblock %}
{% block secondary-nav %}
<nav class="nav secondary">
{% include "./widgets/dates_menu.html" with url_name="diffusion-list" %}
</nav>
{% endblock %}
{% block pages_list %} {% block pages_list %}
{% with hide_schedule=True %} {% with hide_schedule=True %}
<section role="list"> <section role="list" class="list">
{% include 'aircox/widgets/diffusion_list.html' %} {% for object in object_list %}
{% page_widget "item" object.episode diffusion=object timetable=True %}
{% endfor %}
</section> </section>
{% endwith %} {% endwith %}
{% endblock %} {% endblock %}

View File

@ -2,79 +2,50 @@
{% comment %}List of a show's episodes for a specific{% endcomment %} {% comment %}List of a show's episodes for a specific{% endcomment %}
{% load i18n aircox %} {% load i18n aircox %}
{% include "aircox/program_sidebar.html" %} {% block header %}
{% with "aircox/widgets/episode_header.html" as header_template_name %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block content %} {% block content %}
<a-episode :page="{title: &quot;{{ page.title }}&quot;, podcasts: {{ object.podcasts|json }}}"> <a-episode :page="{title: &quot;{{ page.title }}&quot;, podcasts: {{ object.podcasts|json }}}">
<template v-slot="{podcasts,page}"> <template v-slot="{podcasts,page}">
{{ block.super }} {{ block.super }}
{% if object.podcasts %} {% if tracks %}
<section> <section>
<a-playlist v-if="page" :set="podcasts" <h3 class="title is-3">{% translate "Playlist" %}</h3>
name="{{ page.title }}" <table class="table is-hoverable is-fullwidth">
list-class="menu-list" item-class="menu-item" <tbody>
:player="player" :actions="['play']" {% for track in tracks %}
@select="player.playItems('queue', $event.item)"> <tr>
<template v-slot:header> <td>{{ forloop.counter }}</td>
<h4 class="title is-4">{% translate "Podcasts" %}</h4> <td>{{ track.artist }}</td>
</template> <td>{{ track.title }}</td>
</a-playlist> <td>{{ track.info|default:"" }}</td>
{% comment %} </tr>
{% for object in podcasts %} {% endfor %}
{% include "aircox/widgets/podcast_item.html" %} </tbody>
{% endfor %} </table>
{% endcomment %} <ol>
</section> </ol>
{% endif %} </section>
{% endif %}
{% if tracks %} {% if object.podcasts %}
<section> <section>
<h4 class="title is-4">{% translate "Playlist" %}</h4> <a-playlist v-if="page" :set="podcasts"
<ol> name="{{ page.title }}"
{% for track in tracks %} list-class="menu-list" item-class="menu-item"
<li><span>{{ track.title }}</span> :player="player" :actions="['play']"
<span class="has-text-grey-dark has-text-weight-light"> @select="player.playItems('queue', $event.item)">
&mdash; {{ track.artist }} <template v-slot:header>
{% if track.info %}(<i>{{ track.info }}</i>){% endif %} <h3 class="title is-3">{% translate "Podcasts" %}</h3>
</span> </template>
</li> </a-playlist>
{% endfor %} </section>
</ol> {% endif %}
</section> </template>
{% endif %} </a-episode>
</template></a-episode>
{% endblock %}
{% block sidebar %}
<section>
<h4 class="title is-4">{% translate "Diffusions" %}</h4>
<ul>
{% for diffusion in object.diffusion_set.all %}
<li>
{% with diffusion.start as start %}
{% with diffusion.end as end %}
<time datetime="{{ start }}">{{ start|date:"D. d F Y, H:i" }}</time>
&mdash;
<time datetime="{{ end }}">{{ end|date:"H:i" }}</time>
{% endwith %}
{% endwith %}
<small>
{% if diffusion.initial %}
{% with diffusion.initial.date as date %}
<span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
({% translate "rerun" %})
</span>
{% endwith %}
{% endif %}
</small>
<br>
</li>
{% endfor %}
</ul>
</section>
{{ block.super }}
{% endblock %} {% endblock %}

View File

@ -1,85 +1,57 @@
{% extends "aircox/page_list.html" %} {% extends "aircox/page_list.html" %}
{% comment %}Home page{% endcomment %} {% load i18n aircox %}
{% load i18n %}
{% block head_title %}{{ station.name }}{% endblock %} {% block head_title %}{{ station.name }}{% endblock %}
{% block title %} {% block content-container %}
{% if not page or not page.title %}{{ station.name }} {{ block.super }}
{% else %}{{ block.super }}
{% endif %}
{% endblock %}
{% block before_list %}{% endblock %}
{% block pages_list %}
{% if page and page.content %}<hr/>{% endif %}
{% if next_diffs %} {% if next_diffs %}
<div class="columns"> <section class="container">
{% with render_card=True %} <h2 class="title is-3 p-2">
{% for object in next_diffs %} {% with station.name as station %}
{% with is_primary=object.is_now %} {% blocktrans %}
<div class="column is-relative"> Today on {{ station }}
<h4 class="card-super-title" title="{{ object.start }}"> {% endblocktrans %}
{% if is_primary %} {% endwith %}
<span class="fas fa-play"></span> </h2>
<time datetime="{{ object.start }}">
{% translate "Currently" %}
</time>
{% else %}
{{ object.start|date:"H:i" }}
{% endif %}
{% if object.episode.category %} <div class="mb-3">
// {{ object.episode.category.title }} {% with next_diffs.0 as obj %}
{% endif %} {% page_widget "wide" obj.episode diffusion=obj timetable=True %}
</h4> {% endwith %}
{% include object.item_template_name %}
</div> </div>
{% endwith %}
{% endfor %} <div class="card-grid">
{% endwith %} {% for obj in next_diffs|slice:"1:4" %}
</div> {% if object != diffusion %}
{% page_widget "card" obj.episode diffusion=obj timetable=True %}
{% endif %}
{% endfor %}
</div>
</section>
{% endif %} {% endif %}
{% if object_list %} {% if logs %}
<h4 class="title is-4">{% translate "Today" %}</h4> <section class="container">
<section role="list"> <h2 class="title is-3 p-2">{% translate "It just happened" %}</h2>
{% include 'aircox/widgets/diffusion_list.html' %} {% for object in logs %}
{% include "./widgets/log.html" with widget="item" %}
{% endfor %}
</section>
{% endif %}
{% if last_publications %}
<section class="container">
<h2 class="title is-3 p-2">{% translate "Last publications" %}</h2>
<div role="list">
{% for object in last_publications %}
{% page_widget "item" object open=True %}
{% endfor %}
</div>
</section> </section>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block pagination %} {% block pages_list %}{% endblock %}
<ul class="pagination-list">
<li>
<a href="{% url "page-list" %}" class="pagination-link"
aria-label="{% translate "Show all publication" %}">
{% translate "More publications..." %}
</a>
</li>
</ul>
{% endblock %}
{% block sidebar %}
<section>
<h4 class="title is-4">{% translate "Previously on air" %}</h4>
{% with has_cover=False %}
{% with logs as object_list %}
{% include "aircox/widgets/log_list.html" %}
{% endwith %}
{% endwith %}
</section>
<section>
<h4 class="title is-4">{% translate "Last publications" %}</h4>
{% with hide_schedule=True %}
{% with has_headline=False %}
{% for object in last_publications %}
{% include object.item_template_name|default:'aircox/widgets/page_item.html' %}
{% endfor %}
{% endwith %}
{% endwith %}
</section>
{% endblock %}

View File

@ -15,15 +15,37 @@
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %} {% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
{% block before_list %} {% block header %}
{% with "log-list" as url_name %} {% with "./widgets/dated_list_header.html" as header_template_name %}
{% include "aircox/widgets/dates_menu.html" %} {{ block.super }}
{% endwith %} {% endwith %}
{% endblock %} {% endblock %}
{% block pages_list %}
{% block secondary-nav %}
<nav class="nav secondary">
{% include "./widgets/dates_menu.html" with url_name="log-list" %}
</nav>
{% endblock %}
{% block pages_list_ %}
<section> <section>
{# <h4 class="subtitle size-4">{{ date }}</h4> #} {# <h4 class="subtitle size-4">{{ date }}</h4> #}
{% include "aircox/widgets/log_list.html" %} {% include "aircox/widgets/log_list.html" %}
</section> </section>
{% endblock %} {% endblock %}
{% block pages_list %}
{% with hide_schedule=True %}
<section role="list" class="list">
{% for object in object_list %}
{% if object.episode %}
{% page_widget "item" object.episode diffusion=object timetable=True %}
{% else %}
{% page_widget "item" object timetable=True %}
{% endif %}
{% endfor %}
</section>
{% endwith %}
{% endblock %}

View File

@ -32,57 +32,43 @@ Context:
{{ block.super }} {{ block.super }}
{% block comments %} {% block comments %}
{% if comments or comment_form %} {% if comments %}
<section class="mt-6"> <section class="container">
<h4 class="title is-4">{% translate "Comments" %}</h4> <h2 class="title">{% translate "Comments" %}</h2>
{% for comment in comments %} {% for object in comments %}
<div class="media box"> <div id="comment-{{ object.pk }}">
<div class="media-content"> {% page_widget "item" object %}
<p>
<strong class="mr-2">{{ comment.nickname }}</strong>
<time datetime="{{ comment.date }}" title="{{ comment.date }}">
<small>{{ comment.date|naturaltime }}</small>
</time>
<br>
{{ comment.content }}
</p>
</div>
</div> </div>
{% endfor %} {% endfor %}
</section>
{% endif %}
{% if comment_form %} {% if comment_form %}
<section class="container">
<form method="POST"> <form method="POST">
<h5 class="title is-5">{% translate "Post a comment" %}</h5> <h2 class="title">{% translate "Post a comment" %}</h2>
{% csrf_token %} {% csrf_token %}
{% render_honeypot_field "website" %} {% render_honeypot_field "website" %}
{% for field in comment_form %} {% for field in comment_form %}
<div class="field is-horizontal"> <div class="field">
<div class="field-label is-normal"> <label class="label">{{ field.label_tag }}</label>
<label class="label"> <div class="control">{{ field }}</div>
{{ field.label_tag }} {% if field.errors %}
</label> <p class="help is-danger">{{ field.errors }}</p>
</div> {% endif %}
<div class="field-body"> {% if field.help_text %}
<div class="field"> <p class="help">{{ field.help_text|safe }}</p>
<p class="control is-expanded">{{ field }}</p> {% endif %}
{% if field.errors %}
<p class="help is-danger">{{ field.errors }}</p>
{% endif %}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
</div>
</div> </div>
{% endfor %} {% endfor %}
<div class="has-text-right"> <div class="has-text-right">
<button type="reset" class="button is-danger">{% translate "Reset" %}</button> <button type="reset" class="button is-danger mr-3">{% translate "Reset" %}</button>
<button type="submit" class="button is-success">{% translate "Post comment" %}</button> <button type="submit" class="button is-success">{% translate "Post comment" %}</button>
</div> </div>
</form> </form>
{% endif %}
</section> </section>
{% endif %} {% endif %}

View File

@ -2,61 +2,21 @@
{% comment %}Display a list of Pages{% endcomment %} {% comment %}Display a list of Pages{% endcomment %}
{% load i18n aircox %} {% load i18n aircox %}
{% block before_list %} {% block secondary-nav %}
{{ block.super }} <form class="nav secondary">
{% for id, title in view.categories.items %}
{% if view.has_filters and object_list %} <a class="nav-item{% if category_id == id %} active{% endif %}"
<form method="GET" action="" class="media"> href="?category__id={{ id }}">{{ title }}</a>
<div class="media-content"> {% endfor %}
{% block filters %}
<div class="field is-horizontal">
<div class="field-label">
<label class="label">{% translate "Search" %}</label>
</div>
<div class="field-body">
<div class="field">
<div class="control has-icons-left">
<span class="icon is-small is-left">
<i class="fa fa-search"></i>
</span>
<input class="input" type="text" name="q"
value="{{ filterset_data.q }}"
placeholder="{% translate "Search content" %}">
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">{% translate "Categories" %}</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
{% for label, value in categories %}
<label class="checkbox">
<input type="checkbox" class="checkbox" name="category__id__in"
value="{{ value }}"
{% if value in filterset_data.category__id__in %}checked{% endif %} />
{{ label }}
</label>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}
</div>
<div class="media-right">
<div class="field is-grouped is-grouped-right">
<div class="control">
<button class="button is-primary"/>{% translate "Apply" %}</button>
</div>
<div class="control">
<a href="?" class="button is-secondary">{% translate "Reset" %}</a>
</div>
</div>
</div>
</form> </form>
{% endblock %}
{% block header %}
{% if page and not object %}
{% with page as object %}
{{ block.super }}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -1,41 +1,54 @@
{% extends "aircox/page_detail.html" %} {% extends "aircox/page_detail.html" %}
{% comment %}Detail page of a show{% endcomment %} {% comment %}Detail page of a show{% endcomment %}
{% load i18n %} {% load i18n aircox %}
{% include "aircox/program_sidebar.html" %} {% include "aircox/program_sidebar.html" %}
{% block header_nav %} {% block content-container %}
{% endblock %}
{% block content %}
{{ block.super }} {{ block.super }}
<br> {% if episodes %}
{% with has_headline=False %} <section class="container">
{% if articles %} <h4 class="title is-3">{% translate "Last Episodes" %}</h4>
<section> <section class="card-grid">
<h4 class="title is-4">{% translate "Articles" %}</h4> {% for object in episodes|slice:":3" %}
{% page_widget "card" object %}
{% endfor %}
</section>
{% for object in articles %} <nav class="has-text-right">
{% include "aircox/widgets/page_item.html" %} <li class="nav-item">
{% endfor %} <a href="{% url "episode-list" parent_slug=program.slug %}"
class="button action">
<br> {% translate "All episodes" %}
<nav class="pagination is-centered"> </a>
<ul class="pagination-list"> </li>
<li> </nav>
<a href="{% url "article-list" parent_slug=program.slug %}" </section>
class="pagination-link" {% endif %}
aria-label="{% translate "Show all program's articles" %}">
{% translate "More articles" %}
</a> {% if articles %}
</li> <section class="container">
</ul> <h4 class="title is-4">{% translate "Last Articles" %}</h4>
<section class="card-grid">
{% for object in articles|slice:3 %}
{% page_widget "card" object %}
{% endfor %}
</section>
<nav class="has-text-right">
<li class="nav-item">
<a href="{% url "article-list" parent_slug=program.slug %}"
class="button action"
aria-label="{% translate "Show all program's articles" %}">
{% translate "All articles" %}
</a>
</li>
</nav> </nav>
</section> </section>
{% endif %} {% endif %}
{% endwith %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "./page.html" %}
{% load humanize %}
{% block subtitle %}{{ object.pub_date.date }}{% endblock %}

View File

@ -11,63 +11,48 @@ Context variables:
- is_thin (=False): if True, smaller cover and display less info - is_thin (=False): if True, smaller cover and display less info
{% endcomment %} {% endcomment %}
{% if render_card %} {% block outer %}
<article class="card {% if is_primary %}is-primary{% endif %}"> <article class="preview preview-item{% if is_primary %}is-primary{% endif %}{% block card_class %}{% endblock %}">
<header class="card-image"> {% block inner %}
<a href="{{ object.get_absolute_url }}"> <header class="headings"
<figure class="image is-4by3"> style="background-image: url({{ object.cover.url }})">
<img src="{% thumbnail object.cover|default:station.default_cover 480x480 %}"> {% block headings %}
</figure> <div>
</a> <span class="heading subtitle">{% block subtitle %}{% endblock %}</span>
</div>
{% endblock %}
</header> </header>
<div class="card-header">
<h4 class="title">
<a href="{{ object.get_absolute_url }}">
{% block card_title %}{{ object.title }}{% endblock %}
</a>
</h4>
</div>
</article>
<div class="">
<div>
<h2 class="heading title">{% block title %}{% endblock %}</h2>
</div>
{% else %} <summary class="heading-container">
<article class="media item {% block css %}{% endblock%}"> {% block content %}
{% if has_cover|default_if_none:True %} {% if content and with_content %}
<div class="media-left"> {% autoescape off %}
{% if is_thin %} {{ content|striptags|truncatewords:64|linebreaks }}
<img src="{% thumbnail object.cover|default:station.default_cover 64x64 crop=scale %}" {% endautoescape %}
class="cover is-tiny">
{% else %}
<img src="{% thumbnail object.cover|default:station.default_cover 128x128 crop=scale %}"
class="cover is-small">
{% endif %}
</div>
{% endif %}
<div class="media-content">
<h5 class="title is-5 has-text-weight-normal">
{% block title %}
{% if object.is_published %}
<a href="{{ object.get_absolute_url }}">{{ object.title }}</a>
{% else %}
{{ object.title }}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
</h5> </summary>
<div class="subtitle is-6 has-text-weight-light">
{% block subtitle %}
{% if object.category %}{{ object.category.title }}{% endif %}
{% endblock %}
</div>
{% if has_headline|default_if_none:True %} <div class="actions">
<div class="headline"> {% block actions %}
{% block headline %}{{ object.headline }}{% endblock %} <a class="button btn-hg float-right" href="{{ object.get_absolute_url|escape }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
<label>{% translate "More infos" %}</label>
</a>
{% endblock %}
</div> </div>
{% endif %}
</div> </div>
{% endblock %}
{% if not no_actions %} {% if with_container %}
{% block actions %}{% endblock %} </div>
{% endif %} {% endif %}
</article> </article>
{% endif %} {% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "./preview.html" %}
{% load i18n %}
{% block tag-class %}{{ block.super }} preview-card{% endblock %}
{% block tag-extra %}
{{ block.super }}
{% if cover %}
style="background-image: url({{ cover }});"
{% endif %}
{% endblock %}
{% block headings-class %}{{ block.super }} preview-card-headings{% endblock %}
{% block inner %}
{% block headings-container %}
{{ block.super }}
{% block actions-container %}{{ block.super }}{% endblock %}
{% endblock %}
{% endblock %}

View File

@ -0,0 +1,7 @@
{% load aircox %}
<div class="card-grid">
{% for object in object_list %}
{% page_widget "card" object.episode diffusion=object %}
{% endfor %}
</div>

View File

@ -0,0 +1,50 @@
{% extends "./page.html" %}
{% load i18n humanize aircox %}
{% block tag-class %}{{ block.super }} comment{% endblock %}
{% block outer %}
{% if with_title %}
{% with url=object.parent.get_absolute_url %}
{{ block.super }}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block title %}
{{ object.nickname }} &mdash; {{ object.date }}
{% endblock %}
{% block subtitle %}
{% if with_title %}
{{ object.parent.title }}
{% endif %}
{% endblock %}
{% block content %}{{ object.content }}{% endblock %}
{% block actions %}
{{ block.super }}
{% if request.user.is_staff %}
<span class="float-right">
<a href="{% url "admin:aircox_comment_change" object.pk %}"
title="{% trans "Edit comment" %}"
aria-label="{% trans "Edit comment" %}">
<span class="fa fa-edit"></span>
</a>
<a class="has-text-danger"
title="{% trans "Delete comment" %}"
aria-label="{% trans "Delete comment" %}"
href="{% url "admin:aircox_comment_delete" object.pk %}">
<span class="fa fa-trash-alt"></span>
</a>
{# <a href="mailto:{{ object.email }}">{{ object.nickname }}</a> #}
</span>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "./header.html" %}
{% block outer %}
{% with date|date:"l d F Y" as subtitle %}
{{ block.super }}
{% endwith %}
{% endblock %}

View File

@ -11,36 +11,26 @@ An empty date results to a title or a separator
{% endcomment %} {% endcomment %}
{% load i18n %} {% load i18n %}
<div class="media" role="menu" {% for day in dates %}
aria-label="{% translate "pick a date" %}"> <a href="{% url url_name date=day %}" class="nav-item {% if day == date %}active{% endif %}">
<div class="media-content"> {{ day|date:"l d" }}
<div class="tabs is-toggle"> </a>
<ul> {% endfor %}
{% for day in dates %}
<li class="{% if day == date %}is-active{% endif %}">
<a href="{% url url_name date=day %}">
{{ day|date:"D. d" }}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="media-right"> <a-dropdown class="nav-item align-right flex-grow-0 dropdown is-right"
<form action="{% url url_name %}" method="GET" class="navbar-body" content-class="dropdown-menu"
aria-label="{% translate "Jump to date" %}"> button-tag="span" button-class="dropdown-trigger"
<div class="field has-addons"> button-icon-open="fa-solid fa-plus" button-icon-close="fa-solid fa-minus">
<div class="control has-icons-left"> <template #default>
<span class="icon is-small is-left"><span class="far fa-calendar"></span></span> <div class="dropdown-content">
<input type="{{ date_input|default:"date" }}" class="input date" <div class="dropdown-item">
name="date" value="{{ date|date:"Y-m-d" }}"> <h4>{% translate "Pick a date" %}</h4>
</div> <v-calendar mode="date" borderless
<div class="control"> :initial-page="{month: {{date.month}}, year: {{date.year}}}"
{% comment %}Translators: form button to select a date{% endcomment %} @dayclick="(event) => window.aircox.pickDate({% url url_name %}, event)"
<button class="button is-primary">{% translate "Go" %}</button> color="yellow"
</div> />
</div> </div>
</form> </div>
</div> </template>
</div> </a-dropdown>

View File

@ -3,19 +3,4 @@ Context:
- object_list: object list - object_list: object list
- date: date for list - date: date for list
{% endcomment %} {% endcomment %}
<table id="timetable{% if date %}-{{ date|date:"Y-m-d" }}{% endif %}" class="timetable"> {% load aircox %}
{% for diffusion in object_list %}
<tr class="{% if diffusion.is_now %}has-background-primary{% endif %}">
<td class="pr-2 pb-2">
<time datetime="{{ diffusion.start|date:"c" }}">
{{ diffusion.start|date:"H:i" }} - {{ diffusion.end|date:"H:i" }}
</time>
</td>
<td class="pb-2">
{% with diffusion.episode as object %}
{% include "aircox/widgets/episode_item.html" %}
{% endwith %}
</td>
</tr>
{% endfor %}
</table>

View File

@ -0,0 +1,32 @@
{% extends "./page.html" %}
{% load i18n humanize aircox %}
{% block subtitle %}
{% if diffusion %}
{% if timetable %}
{{ diffusion.start|date:"H:i" }}
&mdash;
{{ diffusion.end|date:"H:i" }}
{% else %}
{{ diffusion.start|naturalday }},
{{ diffusion.start|date:"H:i" }}
{% endif %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block actions %}
{{ block.super }}
{% if object.sound_set.count %}
<button class="button action" @click="player.playButtonClick($event)"
data-sounds="{{ object.podcasts|json }}">
<span class="icon is-small">
<span class="fas fa-play"></span>
</span>
<label>{% translate "Listen" %}</label>
</button>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends "aircox/widgets/header.html" %}
{% block header-nav %}
{{ block.super }}
<div>
<a class="heading" href="{{ object.program.get_absolute_url }}">
{{ object.program.display_title }}
</a>
</div>
{% endblock %}

View File

@ -1,58 +1,36 @@
{% extends "aircox/widgets/page_item.html" %} {% extends "./basepage_item.html" %}
{% comment %} {% load i18n humanize %}
List item for an episode.
Context variables:
- object: episode
- diffusion: episode's diffusion
- hide_schedule: if True, do not display start time
{% endcomment %}
{% load i18n easy_thumbnails_tags aircox %}
{% block title %} {% block title %}
{% if not object.is_published and object.program.is_published %} {% if not object.is_published and object.program.is_published %}
<a href="{{ object.program.get_absolute_url }}"> <a href="{{ object.program.get_absolute_url }}">
{{ object.program.title }} {{ object.program.title }}
{% if diffusion %} </a>
&mdash;
{{ diffusion.start|date:"d F" }}
{% endif %}
</a>
{% else %} {% else %}
{{ block.super }} {{ block.super }}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block class %}
{% if object.is_now %}is-active{% endif %}
{% endblock %}
{% block subtitle %} {% block subtitle %}
{{ block.super }}
{% if diffusion %} {% if diffusion %}
{% if not hide_schedule %} {{ diffusion.start|naturalday }},
{% if object.category %}&mdash;{% endif %} {{ diffusion.start|date:"g:i" }}
<time datetime="{{ diffusion.start|date:"c" }}" title="{{ diffusion.start }}"> {% else %}
{{ diffusion.start|date:"d M, H:i" }} {{ block.super }}
</time> {% endif %}
{% endif %} {% endblock %}
{% if diffusion.initial %}
{% with diffusion.initial.date as date %} {% block content %}
<span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}"> {% if not object.content %}
{% translate "(rerun)" %} {% with object.parent.content as content %}
</span> {{ block.super }}
{% endwith %} {% endwith %}
{% endif %} {% else %}
{% endif %} {{ block.super }}
{% endblock %}
{% block actions %}
{% if object.sound_set.public.count %}
<button class="button" @click="player.playButtonClick($event)"
data-sounds="{{ object.podcasts|json }}">
<span class="icon is-small">
<span class="fas fa-play"></span>
</span>
</button>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,20 @@
{% extends "./card.html" %}
{% block tag-class %}preview-header{% endblock %}
{% block headings-class %}{{ block.super }} preview-card-headings{% endblock %}
{% block headings %}
{{ block.super }}
{% if parent and parent.is_published %}
<div>
<a href="{{ parent.get_absolute_url|escape }}" class="heading subtitle">
<span class="icon">
<i class="fa fa-angles-right"></i>
</span>
{{ parent.title }}
</a>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,42 @@
{% extends "./preview.html" %}
{% load i18n aircox %}
{% block tag-class %}list-item is-fullwidth{% endblock %}
{% block headings-class %} columns{% endblock %}
{% block headings %}
<div class="column">
<a href="{{ url|escape }}" class="heading title {% block title-class %}{% endblock %}">{% block title %}{{ title|default:"" }}{% endblock %}</a>
</div>
<div>
<span class="heading subtitle {% block subtitle-class %}{% endblock %}">{% block subtitle %}{{ subtitle|default:"" }}{% endblock %}</span>
</div>
{% endblock %}
{% block inner %}
{% block headings-container %}{{ block.super }}{% endblock %}
{% block content-container %}
<div class="media">
{% if object.cover %}
<div class="media-left preview-cover small"
style="background-image: url({{ object.cover.url }})">
</div>
{% endif %}
<div class="media-content">
<section class="content">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|striptags|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</section>
{% block actions-container %}
{{ block.super }}
{% endblock %}
</div>
{% endblock %}
{% endblock %}

View File

@ -11,12 +11,10 @@ for design review.
{% endcomment %} {% endcomment %}
{% block outer %}
{% if object|is_diffusion %} {% if object|is_diffusion %}
{% with object as diffusion %} {% page_widget widget object.episode diffusion=object %}
{% include "aircox/widgets/diffusion_item.html" %}
{% endwith %}
{% else %} {% else %}
{% with object.track as object %} {% include "./track_item.html" with object=object.track log=object %}
{% include "aircox/widgets/track_item.html" %}
{% endwith %}
{% endif %} {% endif %}
{% endblock %}

View File

@ -1,30 +0,0 @@
{% comment %}
Render list of logs (as widget).
Context:
- object_list: list of logs to display
- is_thin: if True, hide some information in order to fit in a thin container
{% endcomment %}
{% load aircox %}
{% with True as hide_schedule %}
<table class="table is-striped is-hoverable is-fullwidth" role="list">
{% for object in object_list %}
<tr {% if object|is_diffusion and object.is_now %}class="is-selected"{% endif %}>
<td>
{% if object|is_diffusion %}
<time datetime="{{ object.start }}" title="{{ object.start }}">
{{ object.start|date:"H:i" }}
{% if not is_thin %} - {{ object.end|date:"H:i" }}{% endif %}
</time>
{% else %}
<time datetime="{{ object.date }}" title="{{ object.date }}">
{{ object.date|date:"H:i" }}
</time>
{% endif %}
</td>
<td>{% include "aircox/widgets/log_item.html" %}</td>
</tr>
{% endfor %}
</table>
{% endwith %}

View File

@ -0,0 +1,30 @@
{% extends widget_template %}
{% load i18n %}
{% block outer %}
{% with object.get_absolute_url as url %}
{% with object.cover.url as cover %}
{{ block.super }}
{% endwith %}
{% endwith %}
{% endblock %}
{% block title %}
{% if title %}
{{ block.super }}
{% elif object %}
{{ object.display_title }}
{% endif %}
{% endblock %}
{% block content %}
{% if content %}
{{ content }}
{% elif object %}
{{ block.super }}
{{ object.display_headline }}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends widget|default:"./card.html" %}
{% block outer %}
{% if object %}
{% with content=object.get_display_excerpt() %}
{% with title=object.get_display_title() %}
{{ block.super }}
{% endwith %}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}

View File

@ -3,3 +3,11 @@
{% block card_title %} {% block card_title %}
{% block title %}{{ block.super }}{% endblock %} {% block title %}{{ block.super }}{% endblock %}
{% endblock %} {% endblock %}
{% block card_subtitle %}
{% block subtitle %}{{ block.super }}{% endblock %}
{% endblock %}
{% block card_class %}
{% block class %}{{ block.super }}{% endblock %}
{% endblock %}

View File

@ -5,10 +5,10 @@ Context:
- object_list: object list - object_list: object list
- list_url: url to complete list page - list_url: url to complete list page
{% endcomment %} {% endcomment %}
{% load i18n %} {% load i18n aircox %}
{% for object in object_list %} {% for object in object_list %}
{% include object.item_template_name %} {% page_widget "item" object %}
{% endfor %} {% endfor %}
{% if list_url %} {% if list_url %}

View File

@ -0,0 +1,52 @@
{% load i18n %}
{% comment %}
Content related context:
- object: object to display
- cover: cover
- title: title
- subtitle: subtitle
- content: content to display
Styling related context:
- tag_class: css class to set to main tag
- tag_style: css styles to set to main tag
- child_tag_class: css class to set to direct component node children
{% endcomment %}
{% block outer %}
<article class="preview {% if not cover %}no-cover {% endif %}{% if is_active %}is-active{% endif %}{% block tag-class %}{{ tag_class|default:"" }}{% endblock %}" {% block tag-extra %}{% endblock %}>
{% block inner %}
{% block headings-container %}
<header class="headings{% block headings-class %}{% endblock %}"{% block headings-tag-extra %}{% endblock %}>
{% block headings %}
<div>
<a href="{{ url|escape }}" class="heading title {% block title-class %}{% endblock %}">{% block title %}{{ title|default:"" }}{% endblock %}</a>
</div>
<div>
<span class="heading subtitle {% block subtitle-class %}{% endblock %}">{% block subtitle %}{{ subtitle|default:"" }}{% endblock %}</span>
</div>
{% endblock %}
</header>
{% endblock %}
{% block content-container %}
<section class="content headings-container">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|striptags|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</section>
{% endblock %}
{% block actions-container %}
<div class="actions">
{% block actions %}{% endblock %}
</div>
{% endblock %}
{% endblock %}
</article>
{% endblock %}

View File

@ -5,9 +5,16 @@ Context:
- object: track to render - object: track to render
{% endcomment %} {% endcomment %}
<span class="has-text-info is-size-5">&#9836;</span> <span class="content">
<span>{{ object.title }}</span> <span class="has-text-info">&#9836;</span>
<span class="has-text-grey-dark has-text-weight-light"> {% if log %}
&mdash; {{ object.artist }} <span>{{ log.date|date:"H:i" }} &mdash; </span>
{% if object.info %}(<i>{{ object.info }}</i>){% endif %} {% endif %}
<span class="has-text-weight-bold">{{ object.title }}</span>
{% if object.artist and object.artist != object.title %}
<span>
&mdash; {{ object.artist }}
{% if object.info %}(<i>{{ object.info }}</i>){% endif %}
</span>
{% endif %}
</span> </span>

View File

@ -0,0 +1,20 @@
{% extends "./preview.html" %}
{% load i18n %}
{% block tag-class %}{{ block.super }} preview-wide columns{% endblock %}
{% block headings-class %}{{ block.super }} preview-card-headings{% endblock %}
{% block headings-tag-extra %}
{{ block.super }}
{% if cover %}
style="background-image: url({{ cover }});"
{% endif %}
{% endblock %}
{% block inner %}
{% block headings-container %}{{ block.super }}{% endblock %}
<div class="is-flex-direction-column">
{% block content-container %}{{ block.super }}{% endblock %}
{% block actions-container %}{{ block.super }}{% endblock %}
</div>
{% endblock %}

View File

@ -3,14 +3,32 @@ import random
from django import template from django import template
from django.contrib.admin.templatetags.admin_urls import admin_urlname from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from aircox.models import Diffusion, Log from aircox.models import Diffusion, Log
random.seed() random.seed()
register = template.Library() register = template.Library()
@register.simple_tag(name="page_widget", takes_context=True)
def do_page_widget(context, widget, object, dir="aircox/widgets", **ctx):
"""Render widget for the provided page and context."""
ctx["request"] = context["request"]
ctx["object"] = object
ctx["widget"] = widget
ctx["widget_template"] = f"{dir}/{widget}.html"
return render_to_string(object.get_template_name(widget), ctx)
@register.filter(name="page_template")
def do_page_template(self, page, component):
"""For a provided page object and component name, return template name."""
return page.get_template(component)
@register.filter(name="admin_url") @register.filter(name="admin_url")
def do_admin_url(obj, arg, pass_id=True): def do_admin_url(obj, arg, pass_id=True):
"""Reverse admin url for object.""" """Reverse admin url for object."""

View File

@ -57,9 +57,6 @@ class TestBaseView:
"station": station, "station": station,
"page": None, # get_page() returns None "page": None, # get_page() returns None
"has_sidebar": base_view.has_sidebar, "has_sidebar": base_view.has_sidebar,
"has_filters": False,
"sidebar_object_list": published_pages[: base_view.list_count],
"sidebar_list_url": base_view.get_sidebar_url(),
"audio_streams": station.streams, "audio_streams": station.streams,
"model": base_view.model, "model": base_view.model,
} }

View File

@ -5,7 +5,6 @@ __all__ = ["ArticleDetailView", "ArticleListView"]
class ArticleDetailView(PageDetailView): class ArticleDetailView(PageDetailView):
has_sidebar = True
model = Article model = Article
def get_sidebar_queryset(self): def get_sidebar_queryset(self):

View File

@ -2,18 +2,12 @@ from django.http import HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
from django.views.generic.base import ContextMixin, TemplateResponseMixin from django.views.generic.base import ContextMixin, TemplateResponseMixin
from ..models import Page
__all__ = ("BaseView", "BaseAPIView") __all__ = ("BaseView", "BaseAPIView")
class BaseView(TemplateResponseMixin, ContextMixin): class BaseView(TemplateResponseMixin, ContextMixin):
has_sidebar = True header_template_name = "aircox/widgets/header.html"
"""Show side navigation."""
has_filters = False
"""Show filters nav."""
list_count = 5
"""Item count for small lists displayed on page."""
@property @property
def station(self): def station(self):
@ -22,31 +16,13 @@ class BaseView(TemplateResponseMixin, ContextMixin):
# def get_queryset(self): # def get_queryset(self):
# return super().get_queryset().station(self.station) # return super().get_queryset().station(self.station)
def get_sidebar_queryset(self):
"""Return a queryset of items to render on the side nav."""
return (
Page.objects.select_subclasses().published().order_by("-pub_date")
)
def get_sidebar_url(self):
return reverse("page-list")
def get_page(self): def get_page(self):
return None return None
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs.setdefault("station", self.station) kwargs.setdefault("station", self.station)
kwargs.setdefault("page", self.get_page()) kwargs.setdefault("page", self.get_page())
kwargs.setdefault("has_filters", self.has_filters) kwargs.setdefault("header_template_name", self.header_template_name)
has_sidebar = kwargs.setdefault("has_sidebar", self.has_sidebar)
if has_sidebar and "sidebar_object_list" not in kwargs:
sidebar_object_list = self.get_sidebar_queryset()
if sidebar_object_list is not None:
kwargs["sidebar_object_list"] = sidebar_object_list[
: self.list_count
]
kwargs["sidebar_list_url"] = self.get_sidebar_url()
if "audio_streams" not in kwargs: if "audio_streams" not in kwargs:
kwargs["audio_streams"] = self.station.streams kwargs["audio_streams"] = self.station.streams
@ -59,6 +35,11 @@ class BaseView(TemplateResponseMixin, ContextMixin):
) )
kwargs["model"] = model kwargs["model"] = model
page = kwargs.get("page")
if page:
kwargs["title"] = page.display_title
kwargs["cover"] = page.cover and page.cover.url
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def dispatch(self, *args, **kwargs): def dispatch(self, *args, **kwargs):

View File

@ -13,7 +13,6 @@ class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView):
"""View for timetables.""" """View for timetables."""
model = Diffusion model = Diffusion
has_filters = True
redirect_date_url = "diffusion-list" redirect_date_url = "diffusion-list"
attach_to_value = StaticPage.ATTACH_TO_DIFFUSIONS attach_to_value = StaticPage.ATTACH_TO_DIFFUSIONS

View File

@ -5,16 +5,16 @@ from django.views.generic import ListView
from ..models import Diffusion, Log, Page, StaticPage from ..models import Diffusion, Log, Page, StaticPage
from .base import BaseView from .base import BaseView
from .mixins import AttachedToMixin
class HomeView(BaseView, ListView): class HomeView(AttachedToMixin, BaseView, ListView):
template_name = "aircox/home.html" template_name = "aircox/home.html"
model = Diffusion
attach_to_value = StaticPage.ATTACH_TO_HOME attach_to_value = StaticPage.ATTACH_TO_HOME
model = Diffusion
queryset = Diffusion.objects.on_air().select_related("episode") queryset = Diffusion.objects.on_air().select_related("episode")
logs_count = 5 logs_count = 5
publications_count = 5 publications_count = 7
has_filters = False
def get_queryset(self): def get_queryset(self):
return super().get_queryset().date(date.today()) return super().get_queryset().date(date.today())
@ -27,14 +27,15 @@ class HomeView(BaseView, ListView):
def get_next_diffs(self): def get_next_diffs(self):
now = tz.now() now = tz.now()
current_diff = Diffusion.objects.on_air().now(now).first() query = Diffusion.objects.on_air().select_related("episode")
next_diffs = Diffusion.objects.on_air().after(now) current_diff = query.now(now).first()
next_diffs = query.after(now)
if current_diff: if current_diff:
diffs = [current_diff] + list( diffs = [current_diff] + list(
next_diffs.exclude(pk=current_diff.pk)[:2] next_diffs.exclude(pk=current_diff.pk)[:9]
) )
else: else:
diffs = next_diffs[:3] diffs = next_diffs[:10]
return diffs return diffs
def get_last_publications(self): def get_last_publications(self):
@ -52,8 +53,16 @@ class HomeView(BaseView, ListView):
return items return items
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) next_diffs = self.get_next_diffs()
context["logs"] = self.get_logs(context["object_list"]) current_diff = next_diffs and next_diffs[0]
context["next_diffs"] = self.get_next_diffs()
context["last_publications"] = self.get_last_publications()[:5] kwargs.update(
return context {
"object": current_diff.episode,
"diffusion": current_diff,
"logs": self.get_logs(self.object_list),
"next_diffs": next_diffs,
"last_publications": self.get_last_publications(),
}
)
return super().get_context_data(**kwargs)

View File

@ -72,7 +72,6 @@ class LogListView(AttachedToMixin, BaseView, LogListMixin, ListView):
`request.GET`, defaults to today).""" `request.GET`, defaults to today)."""
redirect_date_url = "log-list" redirect_date_url = "log-list"
has_filters = True
attach_to_value = StaticPage.ATTACH_TO_LOGS attach_to_value = StaticPage.ATTACH_TO_LOGS
def get_date(self): def get_date(self):

View File

@ -71,7 +71,7 @@ class ParentMixin:
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
self.parent = kwargs.setdefault("parent", self.parent) self.parent = kwargs.setdefault("parent", self.parent)
if self.parent is not None: if self.parent is not None:
kwargs.setdefault("cover", self.parent.cover) kwargs.setdefault("cover", self.parent.cover.url)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -22,8 +22,6 @@ class BasePageListView(AttachedToMixin, ParentMixin, BaseView, ListView):
"""Base view class for BasePage list.""" """Base view class for BasePage list."""
template_name = "aircox/basepage_list.html" template_name = "aircox/basepage_list.html"
item_template_name = "aircox/widgets/page_item.html"
has_sidebar = True
paginate_by = 30 paginate_by = 30
has_headline = True has_headline = True
@ -41,9 +39,23 @@ class BasePageListView(AttachedToMixin, ParentMixin, BaseView, ListView):
) )
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs.setdefault("item_template_name", self.item_template_name)
kwargs.setdefault("has_headline", self.has_headline) kwargs.setdefault("has_headline", self.has_headline)
return super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
parent = context.get("parent")
if not context.get("page"):
if not context.get("title"):
model = self.model._meta.verbose_name_plural
if parent:
parent = parent.display_title
title = _("{model} of {parent}")
else:
title = _("{model}")
context["title"] = title.format(model=model, parent=parent)
if not context.get("cover") and parent and parent.cover:
context["cover"] = parent.cover.url
return context
class BasePageDetailView(BaseView, DetailView): class BasePageDetailView(BaseView, DetailView):
@ -51,7 +63,13 @@ class BasePageDetailView(BaseView, DetailView):
template_name = "aircox/basepage_detail.html" template_name = "aircox/basepage_detail.html"
context_object_name = "page" context_object_name = "page"
has_filters = False
def get_context_data(self, **kwargs):
if self.object.cover:
kwargs.setdefault("cover", self.object.cover.url)
if self.object.title:
kwargs.setdefault("title", self.object.display_title)
return super().get_context_data(**kwargs)
def get_queryset(self): def get_queryset(self):
return super().get_queryset().select_related("cover") return super().get_queryset().select_related("cover")
@ -84,7 +102,6 @@ class PageListView(FiltersMixin, BasePageListView):
filterset_class = PageFilters filterset_class = PageFilters
template_name = None template_name = None
has_filters = True
categories = None categories = None
filters = None filters = None
@ -106,12 +123,17 @@ class PageListView(FiltersMixin, BasePageListView):
return qs return qs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs["categories"] = ( self.categories = {
self.model.objects.published() id: title
for title, id in self.model.objects.published()
.filter(category__isnull=False) .filter(category__isnull=False)
.values_list("category__title", "category__id") .values_list("category__title", "category__id")
.distinct() .distinct()
) }
cat_id = self.request.GET.get("category__id")
if cat_id:
cat_id = int(cat_id)
kwargs["category_id"] = cat_id
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
@ -120,7 +142,6 @@ class PageDetailView(BasePageDetailView):
template_name = None template_name = None
context_object_name = "page" context_object_name = "page"
has_filters = False
def get_template_names(self): def get_template_names(self):
return super().get_template_names() + ["aircox/page_detail.html"] return super().get_template_names() + ["aircox/page_detail.html"]
@ -134,6 +155,8 @@ class PageDetailView(BasePageDetailView):
kwargs["comments"] = Comment.objects.filter(page=self.object).order_by( kwargs["comments"] = Comment.objects.filter(page=self.object).order_by(
"-date" "-date"
) )
if self.object.parent_subclass:
kwargs["parent"] = self.object.parent_subclass
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
@classmethod @classmethod

View File

@ -1,6 +1,6 @@
from django.urls import reverse from django.urls import reverse
from ..models import Page, Program, StaticPage from ..models import Article, Page, Program, StaticPage, Episode
from .mixins import ParentMixin from .mixins import ParentMixin
from .page import PageDetailView, PageListView from .page import PageDetailView, PageListView
@ -25,8 +25,20 @@ class BaseProgramMixin:
class ProgramDetailView(BaseProgramMixin, PageDetailView): class ProgramDetailView(BaseProgramMixin, PageDetailView):
model = Program model = Program
def get_sidebar_queryset(self): def get_context_data(self, **kwargs):
return super().get_sidebar_queryset().filter(parent=self.program) episodes = (
Episode.objects.program(self.object)
.published()
.order_by("-pub_date")
)
articles = (
Article.objects.parent(self.object)
.published()
.order_by("-pub_date")
)
return super().get_context_data(
articles=articles, episodes=episodes, **kwargs
)
class ProgramListView(PageListView): class ProgramListView(PageListView):

View File

@ -10,9 +10,12 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.0.0", "@fortawesome/fontawesome-free": "^6.0.0",
"@popperjs/core": "^2.11.8",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"vue": "^3.2.13" "v-calendar": "^3.1.2",
"vue": "^3.2.13",
"vue3-carousel": "^0.3.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
@ -20,7 +23,7 @@
"@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"bulma": "^0.9.3", "bulma": "^0.9.4",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3", "eslint-plugin-vue": "^8.0.3",
"sass": "^1.49.9", "sass": "^1.49.9",

View File

@ -1,13 +1,51 @@
import {Calendar, DatePicker} from 'v-calendar';
import components from './components' import components from './components'
import { Carousel, Pagination, Navigation, Slide } from 'vue3-carousel'
const App = { const App = {
el: '#app', el: '#app',
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
components: {...components}, components: {
...components,
Slide,
Carousel,
Pagination,
Navigation,
...{
VCalendar: Calendar,
VDatepicker: DatePicker
},
},
computed: { computed: {
player() { return window.aircox.player; }, player() { return window.aircox.player; },
}, },
data() {
return {
carouselBreakpoints: {
400: {
itemsToShow: 1
},
600: {
itemsToShow: 1.75
},
800: {
itemsToShow: 3
},
1024: {
itemsToShow: 4
},
1280: {
itemsToShow: 4
},
1380: {
itemsToShow: 5
},
}
}
}
} }
export const PlayerApp = { export const PlayerApp = {

View File

@ -15,14 +15,17 @@ export default class Builder {
/** /**
* Fetch app from remote and mount application. * Fetch app from remote and mount application.
*/ */
fetch(url, {el='#app', ...options}={}) { fetch(url, {el='#app', historySave=true, ...options}={}) {
return fetch(url, options).then(response => response.text()) const fut = fetch(url, options).then(response => response.text())
.then(content => { .then(content => {
let doc = new DOMParser().parseFromString(content, 'text/html') let doc = new DOMParser().parseFromString(content, 'text/html')
let app = doc.querySelector(el) let app = doc.querySelector(el)
content = app ? app.innerHTML : content content = app ? app.innerHTML : content
return this.mount({content, title: doc.title, reset:true, url }) return this.mount({content, title: doc.title, reset:true, url })
}) })
if(historySave)
fut.then(() => this.historySave(url))
return fut
} }
/** /**
@ -61,7 +64,9 @@ export default class Builder {
container.innerHTML = content container.innerHTML = content
if(title) if(title)
document.title = title document.title = title
return createApp(config, props) const app = createApp(config, props)
app.config.globalProperties.window = window
return app
} }
unmount() { unmount() {
@ -106,7 +111,7 @@ export default class Builder {
else else
options = {...options, method: target.method, body: formData} options = {...options, method: target.method, body: formData}
} }
this.fetch(url, options).then(() => this.historySave(url)) this.fetch(url, options)
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }

View File

@ -1,13 +1,50 @@
@charset "utf-8"; @charset "utf-8";
@import "~bulma/sass/utilities/_all.sass"; $font-special: "bagnard";
@import "~bulma/sass/components/dropdown.sass"; $font-special-url: url("assets/Bagnard.otf");
$body-background-color: $light; $black: #000;
$menu-item-hover-background-color: #dfdfdf; $white: #fff;
$menu-item-active-background-color: #d2d2d2;
$mp-1: 0.2em;
$mp-2: 0.4em;
$mp-3: 0.8em;
$mp-4: 1.2em;
$mp-5: 1.6em;
$mp-6: 2em;
$mp-7: 4em;
$text-size-small: 0.6em;
$text-size-smaller: 0.8em;
$text-size: 1em;
$text-size-medium: 1.4em;
$text-size-bigger: 1.6em;
$text-size-big: 2em;
$h1-size: 40px;
$h2-size: 32px;
$h3-size: 28px;
$h4-size: 24px;
$h5-size: 20px;
$h6-size: 14px;
$weight-light: 100;
$weight-lighter: 300;
$weight-normal: 400;
$weight-bolder: 500;
$weight-bold: 700;
$screen-very-small: 400px;
//TODO: switch small & smaller
$screen-small: 600px;
$screen-smaller: 800px;
$screen-normal: 1024px;
$screen-wider: 1280px;
$screen-wide: 1380px;
@import "./vendor";
@import "~bulma";
//-- helpers/modifiers //-- helpers/modifiers
.is-fullwidth { width: 100%; } .is-fullwidth { width: 100%; }
@ -65,10 +102,6 @@ input.half-field:not(:active):not(:hover) {
//-- navbar //-- navbar
.navbar + .container {
margin-top: 1em;
}
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow { .navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
box-shadow: 0em 0em 1em rgba(0,0,0,0.1); box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
} }
@ -78,6 +111,10 @@ a.navbar-item.is-active {
} }
.navbar { .navbar {
& + .container {
margin-top: 1em;
}
.navbar-dropdown { .navbar-dropdown {
z-index: 2000; z-index: 2000;
} }
@ -112,93 +149,6 @@ a.navbar-item.is-active {
} }
} }
//-- cards
.card {
.title {
a {
color: $dark;
}
padding: 0.2em;
font-size: $size-5;
font-weight: $weight-medium;
}
&.is-primary {
box-shadow: 0em 0em 0.5em $black
}
}
.card-super-title {
position: absolute;
z-index: 1000;
font-size: $size-6;
font-weight: $weight-bold;
padding: 0.2em;
top: 1em;
background-color: #ffffffc7;
max-width: 90%;
.fas {
padding: 0.1em;
font-size: 0.8em;
}
}
//-- page
.page {
& > .cover {
float: right;
max-width: 45%;
}
.header {
margin-bottom: 1.5em;
}
.headline {
font-size: 1.4em;
padding: 0.2em 0em;
}
p { padding: 0.4em 0em; }
hr { background-color: $grey-light; }
.page-content {
h1 { font-size: $size-1; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
h2 { font-size: $size-3; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
h3 { font-size: $size-4; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
h4 { font-size: $size-5; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
h5 { font-size: $size-6; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
h6 { font-size: $size-6; margin-top:0.4em; margin-bottom:0.2em; }
}
}
.media.item .headline {
line-height: 1.2em;
max-height: calc(1.2em * 3);
overflow: hidden;
& + .headline-overflow {
position: relative;
width: 100%;
height: 2em;
margin-top: -2em;
}
& + .headline-overflow:before {
content:'';
width:100%;
height:100%;
position:absolute;
left:0;
bottom:0;
background:linear-gradient(transparent 1em, $body-background-color);
}
}
//-- player //-- player
.player { .player {
@ -267,18 +217,31 @@ a.navbar-item.is-active {
} }
//-- media //-- general
.media { :root {
.subtitle { --text-color: black;
margin-bottom: 0.4em; --highlight-color: rgba(255, 255, 0, 1);
} --highlight-color-alpha: rgba(255, 255, 0, 0.6);
.media-content .headline { --highlight-color-2: rgb(0, 0, 254);
font-size: 1em; --highlight-color-2-alpha: rgb(0, 0, 254, 0.6);
font-weight: 400;
} --header-height: 30em;
--heading-height: 30em;
--heading-title-bg-color: rgba(255, 255, 0, 1);
--heading-bg-color: var(--highlight-color);
--heading-bg-highlight-color: var(--highlight-color-2);
--preview-media-height: 10em;
--preview-media-cover-size: 10em;
--preview-cover-size: 24em;
--preview-cover-small-size: 10em;
--heading-font-family: default;
} }
//-- general
body { body {
background-color: $body-background-color; background-color: $body-background-color;
} }
@ -290,12 +253,6 @@ section > .toolbar {
} }
main {
.cover.is-small { width: 10em; }
.cover.is-tiny { height: 2em; }
}
aside { aside {
& > section { & > section {
margin-bottom: 2em; margin-bottom: 2em;
@ -321,7 +278,445 @@ aside {
} }
.timetable { // ---- main theme & layout
width: 100%; h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
border: none; font-family: var(--heading-font-family);
}
.page {
padding-bottom: 5em;
a {
background-color: var(--highlight-color-alpha);
color: var(--highlight-color-2);
text-decoration: none;
}
.content {
font-size: $text-size-big;
}
section.container {
padding-top: $mp-6;
> .title {
margin-top: unset;
padding-top: unset !important;
margin-bottom: $mp-4;
}
&:not(:first-child) {
margin-top: $mp-6;
border-top: 1px solid black;
}
}
}
// ---- helpers
.d-inline { display: inline; }
.d-block { display: block; }
.d-inline-block { display: inline-block; }
.p-relative { position: relative }
.p-absolute { position: absolute }
.p-fixed { position: fixed }
.p-sticky { position: sticky }
.p-static { position: static }
.align-right { text-align: right; justify-content: right; }
.height-full { height: 100%; }
.flex-push-right { margin-left: auto; }
.flex-grow-0 { flex-grow: 0 !important; }
.is-clickable { cursor: pointer; }
// ---- components
.btn-hg, .btn-outline-hg {
border: 0.1em var(--highlight-color) solid;
background-color: var(--highlight-color-alpha) !important;
border-radius: 0.2em;
}
.button {
&.action {
background-color: var(--highlight-color);
justify-content: center;
padding: $mp-2 !important;
min-width: 2em;
.icon { margin: 0em !important; }
label {
margin-left: $mp-2;
}
}
.dropdown-trigger {
border-radius: 1.5em;
}
}
.list-filters {
text-align: right;
}
.title {
text-transform: uppercase;
&.is-3 {
margin-top: $mp-3;
}
}
.heading {
display: inline-block;
&:not(:empty) {
background-color: var(--heading-bg-color);
padding: $mp-2;
margin-top: 0em !important;
vertical-align: top;
&.highlight {
background-color: var(--heading-bg-highlight-color);
color: var(--highlight-color);
}
}
&.title {
background-color: var(--heading-title-bg-color);
}
}
.actions {
&.no-label label {
display: none;
}
}
.dropdown-item {
font-size: unset !important
}
.vc-weekday-1, .vc-weekday-7 {
color: var(--highlight-color-2) !important;
}
// ---- main navigation
.nav {
display: flex;
background-color: var(--highlight-color);
.nav-item {
padding: $mp-3;
flex-grow: 1;
flex-shrink: 1;
text-align: center;
font-family: var(--heading-font-family);
text-transform: uppercase;
&.active {
background-color: var(--highlight-color-2);
color: var(--highlight-color);
}
}
&.primary {
.nav-brand {
display: inline-block;
margin-right: $mp-3;
padding: $mp-3;
img {
width: 14em !important;
}
}
.nav-menu {
display: flex;
flex-grow: 1;
}
.nav-item {
font-size: $text-size-medium;
font-weight: $weight-bold;
}
}
&.secondary {
justify-content: right;
.nav-item {
font-size: 1.2em;
}
}
}
nav li {
list-style: none;
a, .button {
font-size: $text-size-medium;
}
}
// ---- ---- detail
.page-content {
margin-top: $mp-6;
margin-bottom: $mp-6;
}
// ---- ---- previews & page items
.preview {
position: relative;
background-size: cover;
margin-bottom: $mp-4 !important;
&.preview-card {
&:not(.wide) {
max-width: 30em;
}
}
&.preview-item {
width: 100%;
}
&.columns, .headings.columns {
margin-left: 0em;
margin-right: 0em;
.column { padding: 0em; }
}
.title {
font-weight: $weight-bold;
font-size: $text-size-bigger;
}
.subtitle {
font-weight: $weight-bolder;
font-size: $text-size-bigger;
}
//.content, .actions {
// font-size: $text-size-bigger;
//}
.headings {
background-size: cover;
* { margin: 0em; }
.column { padding: 0em; }
a {
color: var(--text-color);
}
}
}
.preview.comment {
.title { font-size: $text-size-bigger; }
.subtitle { font-size: $text-size; }
}
.list-item {
width: 100%;
&:not(:first-child) {
margin-top: calc($mp-4 / 2);
}
.headings {
padding-top: 0em;
margin-bottom: $mp-4 !important;
background-color: var(--heading-bg-color);
}
.subtitle {
text-align: right;
}
.media-content {
display: flex;
flex-direction: column;
min-height: var(--preview-cover-small-size);
.content {
flex-grow: 1;
margin-bottom: auto;
}
.actions {
flex-grow: unset;
text-align: right;
margin-top: auto;
}
}
}
// ---- cards
.preview-wide {
height: var(--preview-cover-size);
&:not(.header) .headings {
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
}
& .headings {
width: var(--preview-cover-size);
flex-grow: 0;
margin-right: $mp-4;
}
& .content {
font-size: $text-size-big;
flex-grow: 1;
}
}
.preview-card {
height: var(--preview-cover-size);
min-width: var(--preview-cover-size);
&:not(.header) {
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
}
.card-grid & {
min-width: unset;
}
.actions {
position: absolute;
bottom: $mp-3;
right: $mp-3;
label {
display: none;
}
}
}
.preview-cover {
background-size: cover;
height: var(--preview-cover-size);
width: var(--preview-cover-size);
&.small {
min-width: unset;
height: var(--preview-cover-small-size);
width: var(--preview-cover-small-size) !important;
}
}
.preview-card-headings {
width: 100%;
min-width: var(--preview-cover-size);
min-height: 100%;
padding-top: $mp-3;
& > div:not(:last-child),
& .column > div {
margin-bottom: $mp-3;
}
preview-header:not(.no-cover) & .heading {
margin-bottom: $mp-3;
}
}
.preview-header {
width: 100%;
&:not(.no-cover) {
min-height: var(--header-height);
}
&.no-cover {
height: unset;
}
.headings {
padding-top: $mp-6;
}
.headings, > .container {
width: 100%;
}
> .container, {
height: 100%;
}
}
.header {
background-size: cover;
.preview-card {
max-width: unset;
}
.title {
font-size: $h1-size;
}
.subtitle {
font-size: $h2-size;
}
.content {
display: inline !important;
font-size: $text-size-medium;
font-weight: $weight-bolder;
text-align: right;
p {
margin-bottom: $mp-3
}
}
}
// -- program grid
.card-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: $mp-4;
}
@media screen and (max-width: $screen-wide) {
.preview-card:not(.preview-header) {
height: 20em !important;
}
.card-grid .preview-card {
height: 20em;
}
}
@media screen and (max-width: $screen-normal) {
.card-grid {
grid-template-columns: 1fr 1fr;
.preview-card:nth-child(3) {
display: none;
}
}
} }

View File

@ -0,0 +1,31 @@
@import 'v-calendar/style.css';
@import "~bulma/sass/utilities/_all.sass";
$body-background-color: $light;
$menu-item-hover-background-color: #dfdfdf;
$menu-item-active-background-color: #d2d2d2;
@import "~bulma/sass/base/_all";
@import "~bulma/sass/components/breadcrumb";
@import "~bulma/sass/components/dropdown";
@import "~bulma/sass/components/navbar";
@import "~bulma/sass/components/media";
@import "~bulma/sass/components/message";
@import "~bulma/sass/components/modal";
@import "~bulma/sass/components/pagination";
@import "~bulma/sass/form/_all";
@import "~bulma/sass/grid/_all";
@import "~bulma/sass/helpers/_all";
@import "~bulma/sass/layout/_all";
@import "~bulma/sass/elements/box";
@import "~bulma/sass/elements/button";
@import "~bulma/sass/elements/container";
@import "~bulma/sass/elements/content";
@import "~bulma/sass/elements/icon";
// @import "~bulma/sass/elements/image";
// @import "~bulma/sass/elements/notification";
@import "~bulma/sass/elements/progress";
@import "~bulma/sass/elements/table";
@import "~bulma/sass/elements/tag";
@import "~bulma/sass/elements/title";

View File

@ -0,0 +1,45 @@
<template>
<component :is="tag" :class="[itemClass, active ? activeClass : '']">
<slot name="before-button"></slot>
<slot name="button" :toggle="toggle" :active="active">
<component :is="buttonTag" :class="buttonClass" @click="toggle()">
<span class="icon">
<i v-if="!active" :class="buttonIconOpen"></i>
<i v-if="active" :class="buttonIconClose"></i>
</span>
{{ label }}
</component>
</slot>
<div :class="contentClass" v-show="active">
<slot></slot>
</div>
</component>
</template>
<script>
export default {
data() {
return {
active: this.open,
}
},
props: {
tag: {type: String, default: "div"},
label: {type: String, default: ""},
buttonTag: {type: String, default: "button"},
activeClass: {type: String, default: "is-active"},
buttonClass: {type: String, default: "button"},
buttonIconOpen: { type: String, default:"fa fa-angle-down"},
buttonIconClose: { type: String, default:"fa fa-angle-up"},
contentClass: String,
open: {type: Boolean, default: false},
noButton: {type: Boolean, default: false},
},
methods: {
toggle() {
this.active = !this.active
}
},
}
</script>

View File

@ -1,4 +1,5 @@
import AAutocomplete from './AAutocomplete.vue' import AAutocomplete from './AAutocomplete.vue'
import ADropdown from "./ADropdown.vue"
import AEpisode from './AEpisode.vue' import AEpisode from './AEpisode.vue'
import AList from './AList.vue' import AList from './AList.vue'
import APage from './APage.vue' import APage from './APage.vue'
@ -14,7 +15,7 @@ import AStreamer from './AStreamer.vue'
* Core components * Core components
*/ */
export const base = { export const base = {
AAutocomplete, AEpisode, AList, APage, APlayer, APlaylist, AAutocomplete, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
AProgress, ASoundItem, AProgress, ASoundItem,
} }

View File

@ -68,5 +68,10 @@ window.aircox = {
else else
for(let item of container.querySelectorAll('a.navbar-item')) for(let item of container.querySelectorAll('a.navbar-item'))
item.style.display = null; item.style.display = null;
},
pickDate(url, date) {
url = `${url}?date=${date.id}`
this.builder.fetch(url)
} }
} }

View File

@ -189,6 +189,7 @@ THUMBNAIL_PROCESSORS = (
# Enabled applications # Enabled applications
INSTALLED_APPS = ( INSTALLED_APPS = (
"radiocampus",
"aircox.apps.AircoxConfig", "aircox.apps.AircoxConfig",
"aircox.apps.AircoxAdminConfig", "aircox.apps.AircoxAdminConfig",
"aircox_streamer.apps.AircoxStreamerConfig", "aircox_streamer.apps.AircoxStreamerConfig",

0
radiocampus/__init__.py Normal file
View File

6
radiocampus/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class RadiocampusConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "radiocampus"

View File

View File

@ -0,0 +1,16 @@
{% extends "aircox/base.html" %}
{% load static %}
{% block head_extra %}
{{ block.super }}
<style>
:root {
--heading-font-family: "campus_heading";
}
@font-face {
font-family: 'campus_heading';
src: url('{% static "radiocampus/fonts/CampusGroteskv12-Regular.otf" %}');
}
</style>
{% endblock %}