WIP: Design #135
|
@ -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")
|
||||||
|
|
|
@ -50,7 +50,9 @@ class BasePageAdmin(admin.ModelAdmin):
|
||||||
change_form_template = "admin/aircox/page_change_form.html"
|
change_form_template = "admin/aircox/page_change_form.html"
|
||||||
|
|
||||||
def cover_thumb(self, obj):
|
def cover_thumb(self, obj):
|
||||||
return mark_safe('<img src="{}"/>'.format(obj.cover.icons["64"])) if obj.cover else ""
|
if obj.cover and obj.cover.thumbnails:
|
||||||
|
return mark_safe('<img src="{}"/>'.format(obj.cover.icons["64"]))
|
||||||
|
return ""
|
||||||
|
|
||||||
def get_changeform_initial_data(self, request):
|
def get_changeform_initial_data(self, request):
|
||||||
data = super().get_changeform_initial_data(request)
|
data = super().get_changeform_initial_data(request)
|
||||||
|
@ -95,6 +97,7 @@ class PageAdmin(BasePageAdmin):
|
||||||
@admin.register(StaticPage)
|
@admin.register(StaticPage)
|
||||||
class StaticPageAdmin(BasePageAdmin):
|
class StaticPageAdmin(BasePageAdmin):
|
||||||
list_display = BasePageAdmin.list_display + ("attach_to",)
|
list_display = BasePageAdmin.list_display + ("attach_to",)
|
||||||
|
list_editable = BasePageAdmin.list_editable + ("attach_to",)
|
||||||
fieldsets = deepcopy(BasePageAdmin.fieldsets)
|
fieldsets = deepcopy(BasePageAdmin.fieldsets)
|
||||||
|
|
||||||
fieldsets[1][1]["fields"] += ("attach_to",)
|
fieldsets[1][1]["fields"] += ("attach_to",)
|
||||||
|
|
|
@ -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"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,7 @@ class AircoxMiddleware(object):
|
||||||
"""Middleware used to get default info for the given website.
|
"""Middleware used to get default info for the given website.
|
||||||
|
|
||||||
It provide following request attributes:
|
It provide following request attributes:
|
||||||
|
- ``mobile``: set to True if mobile device is detected
|
||||||
- ``station``: current Station
|
- ``station``: current Station
|
||||||
|
|
||||||
This middleware must be set after the middleware
|
This middleware must be set after the middleware
|
||||||
|
@ -24,6 +25,11 @@ class AircoxMiddleware(object):
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def is_mobile(self, request):
|
||||||
|
if agent := request.META.get("HTTP_USER_AGENT"):
|
||||||
|
return " Mobi" in agent
|
||||||
|
return False
|
||||||
|
|
||||||
def get_station(self, request):
|
def get_station(self, request):
|
||||||
"""Return station for the provided request."""
|
"""Return station for the provided request."""
|
||||||
host = request.get_host()
|
host = request.get_host()
|
||||||
|
@ -45,6 +51,7 @@ class AircoxMiddleware(object):
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
self.init_timezone(request)
|
self.init_timezone(request)
|
||||||
request.station = self.get_station(request)
|
request.station = self.get_station(request)
|
||||||
|
request.is_mobile = self.is_mobile(request)
|
||||||
try:
|
try:
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
except Redirect:
|
except Redirect:
|
||||||
|
|
|
@ -0,0 +1,641 @@
|
||||||
|
# Generated by Django 4.2.1 on 2023-11-24 21:11
|
||||||
|
|
||||||
|
import aircox.models.schedule
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("aircox", "0014_alter_schedule_timezone"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="schedule",
|
||||||
|
name="timezone",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("Africa/Abidjan", "Africa/Abidjan"),
|
||||||
|
("Africa/Accra", "Africa/Accra"),
|
||||||
|
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
|
||||||
|
("Africa/Algiers", "Africa/Algiers"),
|
||||||
|
("Africa/Asmara", "Africa/Asmara"),
|
||||||
|
("Africa/Asmera", "Africa/Asmera"),
|
||||||
|
("Africa/Bamako", "Africa/Bamako"),
|
||||||
|
("Africa/Bangui", "Africa/Bangui"),
|
||||||
|
("Africa/Banjul", "Africa/Banjul"),
|
||||||
|
("Africa/Bissau", "Africa/Bissau"),
|
||||||
|
("Africa/Blantyre", "Africa/Blantyre"),
|
||||||
|
("Africa/Brazzaville", "Africa/Brazzaville"),
|
||||||
|
("Africa/Bujumbura", "Africa/Bujumbura"),
|
||||||
|
("Africa/Cairo", "Africa/Cairo"),
|
||||||
|
("Africa/Casablanca", "Africa/Casablanca"),
|
||||||
|
("Africa/Ceuta", "Africa/Ceuta"),
|
||||||
|
("Africa/Conakry", "Africa/Conakry"),
|
||||||
|
("Africa/Dakar", "Africa/Dakar"),
|
||||||
|
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
|
||||||
|
("Africa/Djibouti", "Africa/Djibouti"),
|
||||||
|
("Africa/Douala", "Africa/Douala"),
|
||||||
|
("Africa/El_Aaiun", "Africa/El_Aaiun"),
|
||||||
|
("Africa/Freetown", "Africa/Freetown"),
|
||||||
|
("Africa/Gaborone", "Africa/Gaborone"),
|
||||||
|
("Africa/Harare", "Africa/Harare"),
|
||||||
|
("Africa/Johannesburg", "Africa/Johannesburg"),
|
||||||
|
("Africa/Juba", "Africa/Juba"),
|
||||||
|
("Africa/Kampala", "Africa/Kampala"),
|
||||||
|
("Africa/Khartoum", "Africa/Khartoum"),
|
||||||
|
("Africa/Kigali", "Africa/Kigali"),
|
||||||
|
("Africa/Kinshasa", "Africa/Kinshasa"),
|
||||||
|
("Africa/Lagos", "Africa/Lagos"),
|
||||||
|
("Africa/Libreville", "Africa/Libreville"),
|
||||||
|
("Africa/Lome", "Africa/Lome"),
|
||||||
|
("Africa/Luanda", "Africa/Luanda"),
|
||||||
|
("Africa/Lubumbashi", "Africa/Lubumbashi"),
|
||||||
|
("Africa/Lusaka", "Africa/Lusaka"),
|
||||||
|
("Africa/Malabo", "Africa/Malabo"),
|
||||||
|
("Africa/Maputo", "Africa/Maputo"),
|
||||||
|
("Africa/Maseru", "Africa/Maseru"),
|
||||||
|
("Africa/Mbabane", "Africa/Mbabane"),
|
||||||
|
("Africa/Mogadishu", "Africa/Mogadishu"),
|
||||||
|
("Africa/Monrovia", "Africa/Monrovia"),
|
||||||
|
("Africa/Nairobi", "Africa/Nairobi"),
|
||||||
|
("Africa/Ndjamena", "Africa/Ndjamena"),
|
||||||
|
("Africa/Niamey", "Africa/Niamey"),
|
||||||
|
("Africa/Nouakchott", "Africa/Nouakchott"),
|
||||||
|
("Africa/Ouagadougou", "Africa/Ouagadougou"),
|
||||||
|
("Africa/Porto-Novo", "Africa/Porto-Novo"),
|
||||||
|
("Africa/Sao_Tome", "Africa/Sao_Tome"),
|
||||||
|
("Africa/Timbuktu", "Africa/Timbuktu"),
|
||||||
|
("Africa/Tripoli", "Africa/Tripoli"),
|
||||||
|
("Africa/Tunis", "Africa/Tunis"),
|
||||||
|
("Africa/Windhoek", "Africa/Windhoek"),
|
||||||
|
("America/Adak", "America/Adak"),
|
||||||
|
("America/Anchorage", "America/Anchorage"),
|
||||||
|
("America/Anguilla", "America/Anguilla"),
|
||||||
|
("America/Antigua", "America/Antigua"),
|
||||||
|
("America/Araguaina", "America/Araguaina"),
|
||||||
|
("America/Argentina/Buenos_Aires", "America/Argentina/Buenos_Aires"),
|
||||||
|
("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
|
||||||
|
("America/Argentina/ComodRivadavia", "America/Argentina/ComodRivadavia"),
|
||||||
|
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
|
||||||
|
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
|
||||||
|
("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
|
||||||
|
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
|
||||||
|
("America/Argentina/Rio_Gallegos", "America/Argentina/Rio_Gallegos"),
|
||||||
|
("America/Argentina/Salta", "America/Argentina/Salta"),
|
||||||
|
("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
|
||||||
|
("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
|
||||||
|
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
|
||||||
|
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
|
||||||
|
("America/Aruba", "America/Aruba"),
|
||||||
|
("America/Asuncion", "America/Asuncion"),
|
||||||
|
("America/Atikokan", "America/Atikokan"),
|
||||||
|
("America/Atka", "America/Atka"),
|
||||||
|
("America/Bahia", "America/Bahia"),
|
||||||
|
("America/Bahia_Banderas", "America/Bahia_Banderas"),
|
||||||
|
("America/Barbados", "America/Barbados"),
|
||||||
|
("America/Belem", "America/Belem"),
|
||||||
|
("America/Belize", "America/Belize"),
|
||||||
|
("America/Blanc-Sablon", "America/Blanc-Sablon"),
|
||||||
|
("America/Boa_Vista", "America/Boa_Vista"),
|
||||||
|
("America/Bogota", "America/Bogota"),
|
||||||
|
("America/Boise", "America/Boise"),
|
||||||
|
("America/Buenos_Aires", "America/Buenos_Aires"),
|
||||||
|
("America/Cambridge_Bay", "America/Cambridge_Bay"),
|
||||||
|
("America/Campo_Grande", "America/Campo_Grande"),
|
||||||
|
("America/Cancun", "America/Cancun"),
|
||||||
|
("America/Caracas", "America/Caracas"),
|
||||||
|
("America/Catamarca", "America/Catamarca"),
|
||||||
|
("America/Cayenne", "America/Cayenne"),
|
||||||
|
("America/Cayman", "America/Cayman"),
|
||||||
|
("America/Chicago", "America/Chicago"),
|
||||||
|
("America/Chihuahua", "America/Chihuahua"),
|
||||||
|
("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
|
||||||
|
("America/Coral_Harbour", "America/Coral_Harbour"),
|
||||||
|
("America/Cordoba", "America/Cordoba"),
|
||||||
|
("America/Costa_Rica", "America/Costa_Rica"),
|
||||||
|
("America/Creston", "America/Creston"),
|
||||||
|
("America/Cuiaba", "America/Cuiaba"),
|
||||||
|
("America/Curacao", "America/Curacao"),
|
||||||
|
("America/Danmarkshavn", "America/Danmarkshavn"),
|
||||||
|
("America/Dawson", "America/Dawson"),
|
||||||
|
("America/Dawson_Creek", "America/Dawson_Creek"),
|
||||||
|
("America/Denver", "America/Denver"),
|
||||||
|
("America/Detroit", "America/Detroit"),
|
||||||
|
("America/Dominica", "America/Dominica"),
|
||||||
|
("America/Edmonton", "America/Edmonton"),
|
||||||
|
("America/Eirunepe", "America/Eirunepe"),
|
||||||
|
("America/El_Salvador", "America/El_Salvador"),
|
||||||
|
("America/Ensenada", "America/Ensenada"),
|
||||||
|
("America/Fort_Nelson", "America/Fort_Nelson"),
|
||||||
|
("America/Fort_Wayne", "America/Fort_Wayne"),
|
||||||
|
("America/Fortaleza", "America/Fortaleza"),
|
||||||
|
("America/Glace_Bay", "America/Glace_Bay"),
|
||||||
|
("America/Godthab", "America/Godthab"),
|
||||||
|
("America/Goose_Bay", "America/Goose_Bay"),
|
||||||
|
("America/Grand_Turk", "America/Grand_Turk"),
|
||||||
|
("America/Grenada", "America/Grenada"),
|
||||||
|
("America/Guadeloupe", "America/Guadeloupe"),
|
||||||
|
("America/Guatemala", "America/Guatemala"),
|
||||||
|
("America/Guayaquil", "America/Guayaquil"),
|
||||||
|
("America/Guyana", "America/Guyana"),
|
||||||
|
("America/Halifax", "America/Halifax"),
|
||||||
|
("America/Havana", "America/Havana"),
|
||||||
|
("America/Hermosillo", "America/Hermosillo"),
|
||||||
|
("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
|
||||||
|
("America/Indiana/Knox", "America/Indiana/Knox"),
|
||||||
|
("America/Indiana/Marengo", "America/Indiana/Marengo"),
|
||||||
|
("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
|
||||||
|
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
|
||||||
|
("America/Indiana/Vevay", "America/Indiana/Vevay"),
|
||||||
|
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
|
||||||
|
("America/Indiana/Winamac", "America/Indiana/Winamac"),
|
||||||
|
("America/Indianapolis", "America/Indianapolis"),
|
||||||
|
("America/Inuvik", "America/Inuvik"),
|
||||||
|
("America/Iqaluit", "America/Iqaluit"),
|
||||||
|
("America/Jamaica", "America/Jamaica"),
|
||||||
|
("America/Jujuy", "America/Jujuy"),
|
||||||
|
("America/Juneau", "America/Juneau"),
|
||||||
|
("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
|
||||||
|
("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
|
||||||
|
("America/Knox_IN", "America/Knox_IN"),
|
||||||
|
("America/Kralendijk", "America/Kralendijk"),
|
||||||
|
("America/La_Paz", "America/La_Paz"),
|
||||||
|
("America/Lima", "America/Lima"),
|
||||||
|
("America/Los_Angeles", "America/Los_Angeles"),
|
||||||
|
("America/Louisville", "America/Louisville"),
|
||||||
|
("America/Lower_Princes", "America/Lower_Princes"),
|
||||||
|
("America/Maceio", "America/Maceio"),
|
||||||
|
("America/Managua", "America/Managua"),
|
||||||
|
("America/Manaus", "America/Manaus"),
|
||||||
|
("America/Marigot", "America/Marigot"),
|
||||||
|
("America/Martinique", "America/Martinique"),
|
||||||
|
("America/Matamoros", "America/Matamoros"),
|
||||||
|
("America/Mazatlan", "America/Mazatlan"),
|
||||||
|
("America/Mendoza", "America/Mendoza"),
|
||||||
|
("America/Menominee", "America/Menominee"),
|
||||||
|
("America/Merida", "America/Merida"),
|
||||||
|
("America/Metlakatla", "America/Metlakatla"),
|
||||||
|
("America/Mexico_City", "America/Mexico_City"),
|
||||||
|
("America/Miquelon", "America/Miquelon"),
|
||||||
|
("America/Moncton", "America/Moncton"),
|
||||||
|
("America/Monterrey", "America/Monterrey"),
|
||||||
|
("America/Montevideo", "America/Montevideo"),
|
||||||
|
("America/Montreal", "America/Montreal"),
|
||||||
|
("America/Montserrat", "America/Montserrat"),
|
||||||
|
("America/Nassau", "America/Nassau"),
|
||||||
|
("America/New_York", "America/New_York"),
|
||||||
|
("America/Nipigon", "America/Nipigon"),
|
||||||
|
("America/Nome", "America/Nome"),
|
||||||
|
("America/Noronha", "America/Noronha"),
|
||||||
|
("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
|
||||||
|
("America/North_Dakota/Center", "America/North_Dakota/Center"),
|
||||||
|
("America/North_Dakota/New_Salem", "America/North_Dakota/New_Salem"),
|
||||||
|
("America/Nuuk", "America/Nuuk"),
|
||||||
|
("America/Ojinaga", "America/Ojinaga"),
|
||||||
|
("America/Panama", "America/Panama"),
|
||||||
|
("America/Pangnirtung", "America/Pangnirtung"),
|
||||||
|
("America/Paramaribo", "America/Paramaribo"),
|
||||||
|
("America/Phoenix", "America/Phoenix"),
|
||||||
|
("America/Port-au-Prince", "America/Port-au-Prince"),
|
||||||
|
("America/Port_of_Spain", "America/Port_of_Spain"),
|
||||||
|
("America/Porto_Acre", "America/Porto_Acre"),
|
||||||
|
("America/Porto_Velho", "America/Porto_Velho"),
|
||||||
|
("America/Puerto_Rico", "America/Puerto_Rico"),
|
||||||
|
("America/Punta_Arenas", "America/Punta_Arenas"),
|
||||||
|
("America/Rainy_River", "America/Rainy_River"),
|
||||||
|
("America/Rankin_Inlet", "America/Rankin_Inlet"),
|
||||||
|
("America/Recife", "America/Recife"),
|
||||||
|
("America/Regina", "America/Regina"),
|
||||||
|
("America/Resolute", "America/Resolute"),
|
||||||
|
("America/Rio_Branco", "America/Rio_Branco"),
|
||||||
|
("America/Rosario", "America/Rosario"),
|
||||||
|
("America/Santa_Isabel", "America/Santa_Isabel"),
|
||||||
|
("America/Santarem", "America/Santarem"),
|
||||||
|
("America/Santiago", "America/Santiago"),
|
||||||
|
("America/Santo_Domingo", "America/Santo_Domingo"),
|
||||||
|
("America/Sao_Paulo", "America/Sao_Paulo"),
|
||||||
|
("America/Scoresbysund", "America/Scoresbysund"),
|
||||||
|
("America/Shiprock", "America/Shiprock"),
|
||||||
|
("America/Sitka", "America/Sitka"),
|
||||||
|
("America/St_Barthelemy", "America/St_Barthelemy"),
|
||||||
|
("America/St_Johns", "America/St_Johns"),
|
||||||
|
("America/St_Kitts", "America/St_Kitts"),
|
||||||
|
("America/St_Lucia", "America/St_Lucia"),
|
||||||
|
("America/St_Thomas", "America/St_Thomas"),
|
||||||
|
("America/St_Vincent", "America/St_Vincent"),
|
||||||
|
("America/Swift_Current", "America/Swift_Current"),
|
||||||
|
("America/Tegucigalpa", "America/Tegucigalpa"),
|
||||||
|
("America/Thule", "America/Thule"),
|
||||||
|
("America/Thunder_Bay", "America/Thunder_Bay"),
|
||||||
|
("America/Tijuana", "America/Tijuana"),
|
||||||
|
("America/Toronto", "America/Toronto"),
|
||||||
|
("America/Tortola", "America/Tortola"),
|
||||||
|
("America/Vancouver", "America/Vancouver"),
|
||||||
|
("America/Virgin", "America/Virgin"),
|
||||||
|
("America/Whitehorse", "America/Whitehorse"),
|
||||||
|
("America/Winnipeg", "America/Winnipeg"),
|
||||||
|
("America/Yakutat", "America/Yakutat"),
|
||||||
|
("America/Yellowknife", "America/Yellowknife"),
|
||||||
|
("Antarctica/Casey", "Antarctica/Casey"),
|
||||||
|
("Antarctica/Davis", "Antarctica/Davis"),
|
||||||
|
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
|
||||||
|
("Antarctica/Macquarie", "Antarctica/Macquarie"),
|
||||||
|
("Antarctica/Mawson", "Antarctica/Mawson"),
|
||||||
|
("Antarctica/McMurdo", "Antarctica/McMurdo"),
|
||||||
|
("Antarctica/Palmer", "Antarctica/Palmer"),
|
||||||
|
("Antarctica/Rothera", "Antarctica/Rothera"),
|
||||||
|
("Antarctica/South_Pole", "Antarctica/South_Pole"),
|
||||||
|
("Antarctica/Syowa", "Antarctica/Syowa"),
|
||||||
|
("Antarctica/Troll", "Antarctica/Troll"),
|
||||||
|
("Antarctica/Vostok", "Antarctica/Vostok"),
|
||||||
|
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
|
||||||
|
("Asia/Aden", "Asia/Aden"),
|
||||||
|
("Asia/Almaty", "Asia/Almaty"),
|
||||||
|
("Asia/Amman", "Asia/Amman"),
|
||||||
|
("Asia/Anadyr", "Asia/Anadyr"),
|
||||||
|
("Asia/Aqtau", "Asia/Aqtau"),
|
||||||
|
("Asia/Aqtobe", "Asia/Aqtobe"),
|
||||||
|
("Asia/Ashgabat", "Asia/Ashgabat"),
|
||||||
|
("Asia/Ashkhabad", "Asia/Ashkhabad"),
|
||||||
|
("Asia/Atyrau", "Asia/Atyrau"),
|
||||||
|
("Asia/Baghdad", "Asia/Baghdad"),
|
||||||
|
("Asia/Bahrain", "Asia/Bahrain"),
|
||||||
|
("Asia/Baku", "Asia/Baku"),
|
||||||
|
("Asia/Bangkok", "Asia/Bangkok"),
|
||||||
|
("Asia/Barnaul", "Asia/Barnaul"),
|
||||||
|
("Asia/Beirut", "Asia/Beirut"),
|
||||||
|
("Asia/Bishkek", "Asia/Bishkek"),
|
||||||
|
("Asia/Brunei", "Asia/Brunei"),
|
||||||
|
("Asia/Calcutta", "Asia/Calcutta"),
|
||||||
|
("Asia/Chita", "Asia/Chita"),
|
||||||
|
("Asia/Choibalsan", "Asia/Choibalsan"),
|
||||||
|
("Asia/Chongqing", "Asia/Chongqing"),
|
||||||
|
("Asia/Chungking", "Asia/Chungking"),
|
||||||
|
("Asia/Colombo", "Asia/Colombo"),
|
||||||
|
("Asia/Dacca", "Asia/Dacca"),
|
||||||
|
("Asia/Damascus", "Asia/Damascus"),
|
||||||
|
("Asia/Dhaka", "Asia/Dhaka"),
|
||||||
|
("Asia/Dili", "Asia/Dili"),
|
||||||
|
("Asia/Dubai", "Asia/Dubai"),
|
||||||
|
("Asia/Dushanbe", "Asia/Dushanbe"),
|
||||||
|
("Asia/Famagusta", "Asia/Famagusta"),
|
||||||
|
("Asia/Gaza", "Asia/Gaza"),
|
||||||
|
("Asia/Harbin", "Asia/Harbin"),
|
||||||
|
("Asia/Hebron", "Asia/Hebron"),
|
||||||
|
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
|
||||||
|
("Asia/Hong_Kong", "Asia/Hong_Kong"),
|
||||||
|
("Asia/Hovd", "Asia/Hovd"),
|
||||||
|
("Asia/Irkutsk", "Asia/Irkutsk"),
|
||||||
|
("Asia/Istanbul", "Asia/Istanbul"),
|
||||||
|
("Asia/Jakarta", "Asia/Jakarta"),
|
||||||
|
("Asia/Jayapura", "Asia/Jayapura"),
|
||||||
|
("Asia/Jerusalem", "Asia/Jerusalem"),
|
||||||
|
("Asia/Kabul", "Asia/Kabul"),
|
||||||
|
("Asia/Kamchatka", "Asia/Kamchatka"),
|
||||||
|
("Asia/Karachi", "Asia/Karachi"),
|
||||||
|
("Asia/Kashgar", "Asia/Kashgar"),
|
||||||
|
("Asia/Kathmandu", "Asia/Kathmandu"),
|
||||||
|
("Asia/Katmandu", "Asia/Katmandu"),
|
||||||
|
("Asia/Khandyga", "Asia/Khandyga"),
|
||||||
|
("Asia/Kolkata", "Asia/Kolkata"),
|
||||||
|
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
|
||||||
|
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
|
||||||
|
("Asia/Kuching", "Asia/Kuching"),
|
||||||
|
("Asia/Kuwait", "Asia/Kuwait"),
|
||||||
|
("Asia/Macao", "Asia/Macao"),
|
||||||
|
("Asia/Macau", "Asia/Macau"),
|
||||||
|
("Asia/Magadan", "Asia/Magadan"),
|
||||||
|
("Asia/Makassar", "Asia/Makassar"),
|
||||||
|
("Asia/Manila", "Asia/Manila"),
|
||||||
|
("Asia/Muscat", "Asia/Muscat"),
|
||||||
|
("Asia/Nicosia", "Asia/Nicosia"),
|
||||||
|
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
|
||||||
|
("Asia/Novosibirsk", "Asia/Novosibirsk"),
|
||||||
|
("Asia/Omsk", "Asia/Omsk"),
|
||||||
|
("Asia/Oral", "Asia/Oral"),
|
||||||
|
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
|
||||||
|
("Asia/Pontianak", "Asia/Pontianak"),
|
||||||
|
("Asia/Pyongyang", "Asia/Pyongyang"),
|
||||||
|
("Asia/Qatar", "Asia/Qatar"),
|
||||||
|
("Asia/Qostanay", "Asia/Qostanay"),
|
||||||
|
("Asia/Qyzylorda", "Asia/Qyzylorda"),
|
||||||
|
("Asia/Rangoon", "Asia/Rangoon"),
|
||||||
|
("Asia/Riyadh", "Asia/Riyadh"),
|
||||||
|
("Asia/Saigon", "Asia/Saigon"),
|
||||||
|
("Asia/Sakhalin", "Asia/Sakhalin"),
|
||||||
|
("Asia/Samarkand", "Asia/Samarkand"),
|
||||||
|
("Asia/Seoul", "Asia/Seoul"),
|
||||||
|
("Asia/Shanghai", "Asia/Shanghai"),
|
||||||
|
("Asia/Singapore", "Asia/Singapore"),
|
||||||
|
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
|
||||||
|
("Asia/Taipei", "Asia/Taipei"),
|
||||||
|
("Asia/Tashkent", "Asia/Tashkent"),
|
||||||
|
("Asia/Tbilisi", "Asia/Tbilisi"),
|
||||||
|
("Asia/Tehran", "Asia/Tehran"),
|
||||||
|
("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
|
||||||
|
("Asia/Thimbu", "Asia/Thimbu"),
|
||||||
|
("Asia/Thimphu", "Asia/Thimphu"),
|
||||||
|
("Asia/Tokyo", "Asia/Tokyo"),
|
||||||
|
("Asia/Tomsk", "Asia/Tomsk"),
|
||||||
|
("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
|
||||||
|
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
|
||||||
|
("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
|
||||||
|
("Asia/Urumqi", "Asia/Urumqi"),
|
||||||
|
("Asia/Ust-Nera", "Asia/Ust-Nera"),
|
||||||
|
("Asia/Vientiane", "Asia/Vientiane"),
|
||||||
|
("Asia/Vladivostok", "Asia/Vladivostok"),
|
||||||
|
("Asia/Yakutsk", "Asia/Yakutsk"),
|
||||||
|
("Asia/Yangon", "Asia/Yangon"),
|
||||||
|
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
|
||||||
|
("Asia/Yerevan", "Asia/Yerevan"),
|
||||||
|
("Atlantic/Azores", "Atlantic/Azores"),
|
||||||
|
("Atlantic/Bermuda", "Atlantic/Bermuda"),
|
||||||
|
("Atlantic/Canary", "Atlantic/Canary"),
|
||||||
|
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
|
||||||
|
("Atlantic/Faeroe", "Atlantic/Faeroe"),
|
||||||
|
("Atlantic/Faroe", "Atlantic/Faroe"),
|
||||||
|
("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
|
||||||
|
("Atlantic/Madeira", "Atlantic/Madeira"),
|
||||||
|
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
|
||||||
|
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
|
||||||
|
("Atlantic/St_Helena", "Atlantic/St_Helena"),
|
||||||
|
("Atlantic/Stanley", "Atlantic/Stanley"),
|
||||||
|
("Australia/ACT", "Australia/ACT"),
|
||||||
|
("Australia/Adelaide", "Australia/Adelaide"),
|
||||||
|
("Australia/Brisbane", "Australia/Brisbane"),
|
||||||
|
("Australia/Broken_Hill", "Australia/Broken_Hill"),
|
||||||
|
("Australia/Canberra", "Australia/Canberra"),
|
||||||
|
("Australia/Currie", "Australia/Currie"),
|
||||||
|
("Australia/Darwin", "Australia/Darwin"),
|
||||||
|
("Australia/Eucla", "Australia/Eucla"),
|
||||||
|
("Australia/Hobart", "Australia/Hobart"),
|
||||||
|
("Australia/LHI", "Australia/LHI"),
|
||||||
|
("Australia/Lindeman", "Australia/Lindeman"),
|
||||||
|
("Australia/Lord_Howe", "Australia/Lord_Howe"),
|
||||||
|
("Australia/Melbourne", "Australia/Melbourne"),
|
||||||
|
("Australia/NSW", "Australia/NSW"),
|
||||||
|
("Australia/North", "Australia/North"),
|
||||||
|
("Australia/Perth", "Australia/Perth"),
|
||||||
|
("Australia/Queensland", "Australia/Queensland"),
|
||||||
|
("Australia/South", "Australia/South"),
|
||||||
|
("Australia/Sydney", "Australia/Sydney"),
|
||||||
|
("Australia/Tasmania", "Australia/Tasmania"),
|
||||||
|
("Australia/Victoria", "Australia/Victoria"),
|
||||||
|
("Australia/West", "Australia/West"),
|
||||||
|
("Australia/Yancowinna", "Australia/Yancowinna"),
|
||||||
|
("Brazil/Acre", "Brazil/Acre"),
|
||||||
|
("Brazil/DeNoronha", "Brazil/DeNoronha"),
|
||||||
|
("Brazil/East", "Brazil/East"),
|
||||||
|
("Brazil/West", "Brazil/West"),
|
||||||
|
("CET", "CET"),
|
||||||
|
("CST6CDT", "CST6CDT"),
|
||||||
|
("Canada/Atlantic", "Canada/Atlantic"),
|
||||||
|
("Canada/Central", "Canada/Central"),
|
||||||
|
("Canada/Eastern", "Canada/Eastern"),
|
||||||
|
("Canada/Mountain", "Canada/Mountain"),
|
||||||
|
("Canada/Newfoundland", "Canada/Newfoundland"),
|
||||||
|
("Canada/Pacific", "Canada/Pacific"),
|
||||||
|
("Canada/Saskatchewan", "Canada/Saskatchewan"),
|
||||||
|
("Canada/Yukon", "Canada/Yukon"),
|
||||||
|
("Chile/Continental", "Chile/Continental"),
|
||||||
|
("Chile/EasterIsland", "Chile/EasterIsland"),
|
||||||
|
("Cuba", "Cuba"),
|
||||||
|
("EET", "EET"),
|
||||||
|
("EST", "EST"),
|
||||||
|
("EST5EDT", "EST5EDT"),
|
||||||
|
("Egypt", "Egypt"),
|
||||||
|
("Eire", "Eire"),
|
||||||
|
("Etc/GMT", "Etc/GMT"),
|
||||||
|
("Etc/GMT+0", "Etc/GMT+0"),
|
||||||
|
("Etc/GMT+1", "Etc/GMT+1"),
|
||||||
|
("Etc/GMT+10", "Etc/GMT+10"),
|
||||||
|
("Etc/GMT+11", "Etc/GMT+11"),
|
||||||
|
("Etc/GMT+12", "Etc/GMT+12"),
|
||||||
|
("Etc/GMT+2", "Etc/GMT+2"),
|
||||||
|
("Etc/GMT+3", "Etc/GMT+3"),
|
||||||
|
("Etc/GMT+4", "Etc/GMT+4"),
|
||||||
|
("Etc/GMT+5", "Etc/GMT+5"),
|
||||||
|
("Etc/GMT+6", "Etc/GMT+6"),
|
||||||
|
("Etc/GMT+7", "Etc/GMT+7"),
|
||||||
|
("Etc/GMT+8", "Etc/GMT+8"),
|
||||||
|
("Etc/GMT+9", "Etc/GMT+9"),
|
||||||
|
("Etc/GMT-0", "Etc/GMT-0"),
|
||||||
|
("Etc/GMT-1", "Etc/GMT-1"),
|
||||||
|
("Etc/GMT-10", "Etc/GMT-10"),
|
||||||
|
("Etc/GMT-11", "Etc/GMT-11"),
|
||||||
|
("Etc/GMT-12", "Etc/GMT-12"),
|
||||||
|
("Etc/GMT-13", "Etc/GMT-13"),
|
||||||
|
("Etc/GMT-14", "Etc/GMT-14"),
|
||||||
|
("Etc/GMT-2", "Etc/GMT-2"),
|
||||||
|
("Etc/GMT-3", "Etc/GMT-3"),
|
||||||
|
("Etc/GMT-4", "Etc/GMT-4"),
|
||||||
|
("Etc/GMT-5", "Etc/GMT-5"),
|
||||||
|
("Etc/GMT-6", "Etc/GMT-6"),
|
||||||
|
("Etc/GMT-7", "Etc/GMT-7"),
|
||||||
|
("Etc/GMT-8", "Etc/GMT-8"),
|
||||||
|
("Etc/GMT-9", "Etc/GMT-9"),
|
||||||
|
("Etc/GMT0", "Etc/GMT0"),
|
||||||
|
("Etc/Greenwich", "Etc/Greenwich"),
|
||||||
|
("Etc/UCT", "Etc/UCT"),
|
||||||
|
("Etc/UTC", "Etc/UTC"),
|
||||||
|
("Etc/Universal", "Etc/Universal"),
|
||||||
|
("Etc/Zulu", "Etc/Zulu"),
|
||||||
|
("Europe/Amsterdam", "Europe/Amsterdam"),
|
||||||
|
("Europe/Andorra", "Europe/Andorra"),
|
||||||
|
("Europe/Astrakhan", "Europe/Astrakhan"),
|
||||||
|
("Europe/Athens", "Europe/Athens"),
|
||||||
|
("Europe/Belfast", "Europe/Belfast"),
|
||||||
|
("Europe/Belgrade", "Europe/Belgrade"),
|
||||||
|
("Europe/Berlin", "Europe/Berlin"),
|
||||||
|
("Europe/Bratislava", "Europe/Bratislava"),
|
||||||
|
("Europe/Brussels", "Europe/Brussels"),
|
||||||
|
("Europe/Bucharest", "Europe/Bucharest"),
|
||||||
|
("Europe/Budapest", "Europe/Budapest"),
|
||||||
|
("Europe/Busingen", "Europe/Busingen"),
|
||||||
|
("Europe/Chisinau", "Europe/Chisinau"),
|
||||||
|
("Europe/Copenhagen", "Europe/Copenhagen"),
|
||||||
|
("Europe/Dublin", "Europe/Dublin"),
|
||||||
|
("Europe/Gibraltar", "Europe/Gibraltar"),
|
||||||
|
("Europe/Guernsey", "Europe/Guernsey"),
|
||||||
|
("Europe/Helsinki", "Europe/Helsinki"),
|
||||||
|
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
|
||||||
|
("Europe/Istanbul", "Europe/Istanbul"),
|
||||||
|
("Europe/Jersey", "Europe/Jersey"),
|
||||||
|
("Europe/Kaliningrad", "Europe/Kaliningrad"),
|
||||||
|
("Europe/Kiev", "Europe/Kiev"),
|
||||||
|
("Europe/Kirov", "Europe/Kirov"),
|
||||||
|
("Europe/Kyiv", "Europe/Kyiv"),
|
||||||
|
("Europe/Lisbon", "Europe/Lisbon"),
|
||||||
|
("Europe/Ljubljana", "Europe/Ljubljana"),
|
||||||
|
("Europe/London", "Europe/London"),
|
||||||
|
("Europe/Luxembourg", "Europe/Luxembourg"),
|
||||||
|
("Europe/Madrid", "Europe/Madrid"),
|
||||||
|
("Europe/Malta", "Europe/Malta"),
|
||||||
|
("Europe/Mariehamn", "Europe/Mariehamn"),
|
||||||
|
("Europe/Minsk", "Europe/Minsk"),
|
||||||
|
("Europe/Monaco", "Europe/Monaco"),
|
||||||
|
("Europe/Moscow", "Europe/Moscow"),
|
||||||
|
("Europe/Nicosia", "Europe/Nicosia"),
|
||||||
|
("Europe/Oslo", "Europe/Oslo"),
|
||||||
|
("Europe/Paris", "Europe/Paris"),
|
||||||
|
("Europe/Podgorica", "Europe/Podgorica"),
|
||||||
|
("Europe/Prague", "Europe/Prague"),
|
||||||
|
("Europe/Riga", "Europe/Riga"),
|
||||||
|
("Europe/Rome", "Europe/Rome"),
|
||||||
|
("Europe/Samara", "Europe/Samara"),
|
||||||
|
("Europe/San_Marino", "Europe/San_Marino"),
|
||||||
|
("Europe/Sarajevo", "Europe/Sarajevo"),
|
||||||
|
("Europe/Saratov", "Europe/Saratov"),
|
||||||
|
("Europe/Simferopol", "Europe/Simferopol"),
|
||||||
|
("Europe/Skopje", "Europe/Skopje"),
|
||||||
|
("Europe/Sofia", "Europe/Sofia"),
|
||||||
|
("Europe/Stockholm", "Europe/Stockholm"),
|
||||||
|
("Europe/Tallinn", "Europe/Tallinn"),
|
||||||
|
("Europe/Tirane", "Europe/Tirane"),
|
||||||
|
("Europe/Tiraspol", "Europe/Tiraspol"),
|
||||||
|
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
|
||||||
|
("Europe/Uzhgorod", "Europe/Uzhgorod"),
|
||||||
|
("Europe/Vaduz", "Europe/Vaduz"),
|
||||||
|
("Europe/Vatican", "Europe/Vatican"),
|
||||||
|
("Europe/Vienna", "Europe/Vienna"),
|
||||||
|
("Europe/Vilnius", "Europe/Vilnius"),
|
||||||
|
("Europe/Volgograd", "Europe/Volgograd"),
|
||||||
|
("Europe/Warsaw", "Europe/Warsaw"),
|
||||||
|
("Europe/Zagreb", "Europe/Zagreb"),
|
||||||
|
("Europe/Zaporozhye", "Europe/Zaporozhye"),
|
||||||
|
("Europe/Zurich", "Europe/Zurich"),
|
||||||
|
("Factory", "Factory"),
|
||||||
|
("GB", "GB"),
|
||||||
|
("GB-Eire", "GB-Eire"),
|
||||||
|
("GMT", "GMT"),
|
||||||
|
("GMT+0", "GMT+0"),
|
||||||
|
("GMT-0", "GMT-0"),
|
||||||
|
("GMT0", "GMT0"),
|
||||||
|
("Greenwich", "Greenwich"),
|
||||||
|
("HST", "HST"),
|
||||||
|
("Hongkong", "Hongkong"),
|
||||||
|
("Iceland", "Iceland"),
|
||||||
|
("Indian/Antananarivo", "Indian/Antananarivo"),
|
||||||
|
("Indian/Chagos", "Indian/Chagos"),
|
||||||
|
("Indian/Christmas", "Indian/Christmas"),
|
||||||
|
("Indian/Cocos", "Indian/Cocos"),
|
||||||
|
("Indian/Comoro", "Indian/Comoro"),
|
||||||
|
("Indian/Kerguelen", "Indian/Kerguelen"),
|
||||||
|
("Indian/Mahe", "Indian/Mahe"),
|
||||||
|
("Indian/Maldives", "Indian/Maldives"),
|
||||||
|
("Indian/Mauritius", "Indian/Mauritius"),
|
||||||
|
("Indian/Mayotte", "Indian/Mayotte"),
|
||||||
|
("Indian/Reunion", "Indian/Reunion"),
|
||||||
|
("Iran", "Iran"),
|
||||||
|
("Israel", "Israel"),
|
||||||
|
("Jamaica", "Jamaica"),
|
||||||
|
("Japan", "Japan"),
|
||||||
|
("Kwajalein", "Kwajalein"),
|
||||||
|
("Libya", "Libya"),
|
||||||
|
("MET", "MET"),
|
||||||
|
("MST", "MST"),
|
||||||
|
("MST7MDT", "MST7MDT"),
|
||||||
|
("Mexico/BajaNorte", "Mexico/BajaNorte"),
|
||||||
|
("Mexico/BajaSur", "Mexico/BajaSur"),
|
||||||
|
("Mexico/General", "Mexico/General"),
|
||||||
|
("NZ", "NZ"),
|
||||||
|
("NZ-CHAT", "NZ-CHAT"),
|
||||||
|
("Navajo", "Navajo"),
|
||||||
|
("PRC", "PRC"),
|
||||||
|
("PST8PDT", "PST8PDT"),
|
||||||
|
("Pacific/Apia", "Pacific/Apia"),
|
||||||
|
("Pacific/Auckland", "Pacific/Auckland"),
|
||||||
|
("Pacific/Bougainville", "Pacific/Bougainville"),
|
||||||
|
("Pacific/Chatham", "Pacific/Chatham"),
|
||||||
|
("Pacific/Chuuk", "Pacific/Chuuk"),
|
||||||
|
("Pacific/Easter", "Pacific/Easter"),
|
||||||
|
("Pacific/Efate", "Pacific/Efate"),
|
||||||
|
("Pacific/Enderbury", "Pacific/Enderbury"),
|
||||||
|
("Pacific/Fakaofo", "Pacific/Fakaofo"),
|
||||||
|
("Pacific/Fiji", "Pacific/Fiji"),
|
||||||
|
("Pacific/Funafuti", "Pacific/Funafuti"),
|
||||||
|
("Pacific/Galapagos", "Pacific/Galapagos"),
|
||||||
|
("Pacific/Gambier", "Pacific/Gambier"),
|
||||||
|
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
|
||||||
|
("Pacific/Guam", "Pacific/Guam"),
|
||||||
|
("Pacific/Honolulu", "Pacific/Honolulu"),
|
||||||
|
("Pacific/Johnston", "Pacific/Johnston"),
|
||||||
|
("Pacific/Kanton", "Pacific/Kanton"),
|
||||||
|
("Pacific/Kiritimati", "Pacific/Kiritimati"),
|
||||||
|
("Pacific/Kosrae", "Pacific/Kosrae"),
|
||||||
|
("Pacific/Kwajalein", "Pacific/Kwajalein"),
|
||||||
|
("Pacific/Majuro", "Pacific/Majuro"),
|
||||||
|
("Pacific/Marquesas", "Pacific/Marquesas"),
|
||||||
|
("Pacific/Midway", "Pacific/Midway"),
|
||||||
|
("Pacific/Nauru", "Pacific/Nauru"),
|
||||||
|
("Pacific/Niue", "Pacific/Niue"),
|
||||||
|
("Pacific/Norfolk", "Pacific/Norfolk"),
|
||||||
|
("Pacific/Noumea", "Pacific/Noumea"),
|
||||||
|
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
|
||||||
|
("Pacific/Palau", "Pacific/Palau"),
|
||||||
|
("Pacific/Pitcairn", "Pacific/Pitcairn"),
|
||||||
|
("Pacific/Pohnpei", "Pacific/Pohnpei"),
|
||||||
|
("Pacific/Ponape", "Pacific/Ponape"),
|
||||||
|
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
|
||||||
|
("Pacific/Rarotonga", "Pacific/Rarotonga"),
|
||||||
|
("Pacific/Saipan", "Pacific/Saipan"),
|
||||||
|
("Pacific/Samoa", "Pacific/Samoa"),
|
||||||
|
("Pacific/Tahiti", "Pacific/Tahiti"),
|
||||||
|
("Pacific/Tarawa", "Pacific/Tarawa"),
|
||||||
|
("Pacific/Tongatapu", "Pacific/Tongatapu"),
|
||||||
|
("Pacific/Truk", "Pacific/Truk"),
|
||||||
|
("Pacific/Wake", "Pacific/Wake"),
|
||||||
|
("Pacific/Wallis", "Pacific/Wallis"),
|
||||||
|
("Pacific/Yap", "Pacific/Yap"),
|
||||||
|
("Poland", "Poland"),
|
||||||
|
("Portugal", "Portugal"),
|
||||||
|
("ROC", "ROC"),
|
||||||
|
("ROK", "ROK"),
|
||||||
|
("Singapore", "Singapore"),
|
||||||
|
("Turkey", "Turkey"),
|
||||||
|
("UCT", "UCT"),
|
||||||
|
("US/Alaska", "US/Alaska"),
|
||||||
|
("US/Aleutian", "US/Aleutian"),
|
||||||
|
("US/Arizona", "US/Arizona"),
|
||||||
|
("US/Central", "US/Central"),
|
||||||
|
("US/East-Indiana", "US/East-Indiana"),
|
||||||
|
("US/Eastern", "US/Eastern"),
|
||||||
|
("US/Hawaii", "US/Hawaii"),
|
||||||
|
("US/Indiana-Starke", "US/Indiana-Starke"),
|
||||||
|
("US/Michigan", "US/Michigan"),
|
||||||
|
("US/Mountain", "US/Mountain"),
|
||||||
|
("US/Pacific", "US/Pacific"),
|
||||||
|
("US/Samoa", "US/Samoa"),
|
||||||
|
("UTC", "UTC"),
|
||||||
|
("Universal", "Universal"),
|
||||||
|
("W-SU", "W-SU"),
|
||||||
|
("WET", "WET"),
|
||||||
|
("Zulu", "Zulu"),
|
||||||
|
],
|
||||||
|
default=aircox.models.schedule.current_timezone_key,
|
||||||
|
help_text="timezone used for the date",
|
||||||
|
max_length=100,
|
||||||
|
verbose_name="timezone",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="staticpage",
|
||||||
|
name="attach_to",
|
||||||
|
field=models.SmallIntegerField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
(0, "Home page"),
|
||||||
|
(1, "Diffusions page"),
|
||||||
|
(2, "Logs page"),
|
||||||
|
(3, "Programs list"),
|
||||||
|
(4, "Episodes list"),
|
||||||
|
(5, "Articles list"),
|
||||||
|
(6, "Publications list"),
|
||||||
|
],
|
||||||
|
help_text="display this page content to related element",
|
||||||
|
null=True,
|
||||||
|
verbose_name="attach to",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
32
aircox/migrations/0016_alter_staticpage_attach_to.py
Normal file
32
aircox/migrations/0016_alter_staticpage_attach_to.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 4.2.1 on 2023-11-28 01:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("aircox", "0015_alter_schedule_timezone_alter_staticpage_attach_to"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="staticpage",
|
||||||
|
name="attach_to",
|
||||||
|
field=models.SmallIntegerField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
(0, "Home page"),
|
||||||
|
(1, "Diffusions page"),
|
||||||
|
(2, "Logs page"),
|
||||||
|
(3, "Programs list"),
|
||||||
|
(4, "Episodes list"),
|
||||||
|
(5, "Articles list"),
|
||||||
|
(6, "Publications list"),
|
||||||
|
(7, "Podcasts list"),
|
||||||
|
],
|
||||||
|
help_text="display this page content to related element",
|
||||||
|
null=True,
|
||||||
|
verbose_name="attach to",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Generated by Django 4.2.1 on 2023-12-12 16:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("aircox", "0016_alter_staticpage_attach_to"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="navitem",
|
||||||
|
name="text",
|
||||||
|
field=models.CharField(blank=True, max_length=64, null=True, verbose_name="title"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="staticpage",
|
||||||
|
name="attach_to",
|
||||||
|
field=models.SmallIntegerField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
(0, "Home page"),
|
||||||
|
(1, "Diffusions page"),
|
||||||
|
(3, "Programs list"),
|
||||||
|
(4, "Episodes list"),
|
||||||
|
(5, "Articles list"),
|
||||||
|
(6, "Publications list"),
|
||||||
|
(7, "Podcasts list"),
|
||||||
|
],
|
||||||
|
help_text="display this page content to related element",
|
||||||
|
null=True,
|
||||||
|
verbose_name="attach to",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
32
aircox/migrations/0018_alter_staticpage_attach_to.py
Normal file
32
aircox/migrations/0018_alter_staticpage_attach_to.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 4.2.1 on 2023-12-12 18:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("aircox", "0017_alter_navitem_text_alter_staticpage_attach_to"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="staticpage",
|
||||||
|
name="attach_to",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("", "Home Page"),
|
||||||
|
("timetable-list", "Timetable"),
|
||||||
|
("program-list", "Programs list"),
|
||||||
|
("episode-list", "Episodes list"),
|
||||||
|
("article-list", "Articles list"),
|
||||||
|
("page-list", "Publications list"),
|
||||||
|
("podcast-list", "Podcasts list"),
|
||||||
|
],
|
||||||
|
help_text="display this page content to related element",
|
||||||
|
max_length=32,
|
||||||
|
null=True,
|
||||||
|
verbose_name="attach to",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 4.2.1 on 2024-02-01 18:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("aircox", "0018_alter_staticpage_attach_to"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="station",
|
||||||
|
name="music_stream_title",
|
||||||
|
field=models.CharField(
|
||||||
|
default="Music stream",
|
||||||
|
max_length=64,
|
||||||
|
verbose_name="Music stream's title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="staticpage",
|
||||||
|
name="attach_to",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("", "None"),
|
||||||
|
("home", "Home Page"),
|
||||||
|
("timetable-list", "Timetable"),
|
||||||
|
("program-list", "Programs list"),
|
||||||
|
("episode-list", "Episodes list"),
|
||||||
|
("article-list", "Articles list"),
|
||||||
|
("page-list", "Publications list"),
|
||||||
|
("podcast-list", "Podcasts list"),
|
||||||
|
],
|
||||||
|
help_text="display this page content to related element",
|
||||||
|
max_length=32,
|
||||||
|
null=True,
|
||||||
|
verbose_name="attach to",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,8 @@ class Diffusion(Rerun):
|
||||||
- stop: the diffusion has been manually stopped
|
- stop: the diffusion has been manually stopped
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
list_url_name = "timetable-list"
|
||||||
|
|
||||||
objects = DiffusionQuerySet.as_manager()
|
objects = DiffusionQuerySet.as_manager()
|
||||||
|
|
||||||
TYPE_ON_AIR = 0x00
|
TYPE_ON_AIR = 0x00
|
||||||
|
@ -127,8 +129,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")
|
||||||
|
@ -192,6 +192,11 @@ class Diffusion(Rerun):
|
||||||
now = tz.now()
|
now = tz.now()
|
||||||
return self.type == self.TYPE_ON_AIR and self.start <= now and self.end >= now
|
return self.type == self.TYPE_ON_AIR and self.start <= 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)."""
|
||||||
|
|
|
@ -10,18 +10,29 @@ from .program import ProgramChildQuerySet
|
||||||
__all__ = ("Episode",)
|
__all__ = ("Episode",)
|
||||||
|
|
||||||
|
|
||||||
|
class EpisodeQuerySet(ProgramChildQuerySet):
|
||||||
|
def with_podcasts(self):
|
||||||
|
return self.filter(sound__is_public=True).distinct()
|
||||||
|
|
||||||
|
|
||||||
class Episode(Page):
|
class Episode(Page):
|
||||||
objects = ProgramChildQuerySet.as_manager()
|
objects = EpisodeQuerySet.as_manager()
|
||||||
detail_url_name = "episode-detail"
|
detail_url_name = "episode-detail"
|
||||||
item_template_name = "aircox/widgets/episode_item.html"
|
list_url_name = "episode-list"
|
||||||
|
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):
|
||||||
"""Return serialized data about podcasts."""
|
"""Return serialized data about podcasts."""
|
||||||
|
from .sound import Sound
|
||||||
from ..serializers import PodcastSerializer
|
from ..serializers import PodcastSerializer
|
||||||
|
|
||||||
podcasts = [PodcastSerializer(s).data for s in self.sound_set.public().order_by("type")]
|
podcasts = [PodcastSerializer(s).data for s in self.sound_set.public().order_by("type")]
|
||||||
|
@ -31,16 +42,19 @@ class Episode(Page):
|
||||||
else:
|
else:
|
||||||
cover = None
|
cover = None
|
||||||
|
|
||||||
|
archive_index = 1
|
||||||
for index, podcast in enumerate(podcasts):
|
for index, podcast in enumerate(podcasts):
|
||||||
|
if podcast["type"] == Sound.TYPE_ARCHIVE:
|
||||||
|
if archive_index > 1:
|
||||||
|
podcast["name"] = f"{self.title} - {archive_index}"
|
||||||
|
else:
|
||||||
|
podcast["name"] = self.title
|
||||||
|
|
||||||
podcasts[index]["cover"] = cover
|
podcasts[index]["cover"] = cover
|
||||||
podcasts[index]["page_url"] = self.get_absolute_url()
|
podcasts[index]["page_url"] = self.get_absolute_url()
|
||||||
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")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import operator
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -9,6 +10,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")
|
||||||
|
|
||||||
|
@ -46,13 +48,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
|
||||||
|
@ -160,21 +164,22 @@ class Log(models.Model):
|
||||||
object_list += [cls(obj) for obj in items]
|
object_list += [cls(obj) for obj in items]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def merge_diffusions(cls, logs, diffs, count=None):
|
def merge_diffusions(cls, logs, diffs, count=None, diff_count=None, group_logs=False):
|
||||||
"""Merge logs and diffusions together.
|
"""Merge logs and diffusions together.
|
||||||
|
|
||||||
`logs` can either be a queryset or a list ordered by `Log.date`.
|
`logs` can either be a queryset or a list ordered by `Log.date`.
|
||||||
"""
|
"""
|
||||||
# TODO: limit count
|
|
||||||
# FIXME: log may be iterable (in stats view)
|
|
||||||
if isinstance(logs, models.QuerySet):
|
if isinstance(logs, models.QuerySet):
|
||||||
logs = list(logs.order_by("-date"))
|
logs = list(logs.order_by("-date"))
|
||||||
diffs = deque(diffs.on_air().before().order_by("-start"))
|
diffs = diffs.on_air().order_by("-start")
|
||||||
|
if diff_count:
|
||||||
|
diffs = diffs[:diff_count]
|
||||||
|
diffs = deque(diffs)
|
||||||
object_list = []
|
object_list = []
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if not len(diffs):
|
if not len(diffs):
|
||||||
object_list += logs
|
cls._append_logs(object_list, logs, len(logs), group=group_logs)
|
||||||
break
|
break
|
||||||
|
|
||||||
if not len(logs):
|
if not len(logs):
|
||||||
|
@ -184,13 +189,8 @@ class Log(models.Model):
|
||||||
diff = diffs.popleft()
|
diff = diffs.popleft()
|
||||||
|
|
||||||
# - takes all logs after diff start
|
# - takes all logs after diff start
|
||||||
index = next(
|
index = cls._next_index(logs, diff.end, len(logs), pred=operator.le)
|
||||||
(i for i, v in enumerate(logs) if v.date <= diff.end),
|
cls._append_logs(object_list, logs, index, group=group_logs)
|
||||||
len(logs),
|
|
||||||
)
|
|
||||||
if index is not None and index > 0:
|
|
||||||
object_list += logs[:index]
|
|
||||||
logs = logs[index:]
|
|
||||||
|
|
||||||
if len(logs):
|
if len(logs):
|
||||||
# FIXME
|
# FIXME
|
||||||
|
@ -199,10 +199,7 @@ class Log(models.Model):
|
||||||
# object_list.append(logs[0])
|
# object_list.append(logs[0])
|
||||||
|
|
||||||
# - skips logs while diff is running
|
# - skips logs while diff is running
|
||||||
index = next(
|
index = cls._next_index(logs, diff.start, len(logs))
|
||||||
(i for i, v in enumerate(logs) if v.date < diff.start),
|
|
||||||
len(logs),
|
|
||||||
)
|
|
||||||
if index is not None and index > 0:
|
if index is not None and index > 0:
|
||||||
logs = logs[index:]
|
logs = logs[index:]
|
||||||
|
|
||||||
|
@ -211,6 +208,40 @@ class Log(models.Model):
|
||||||
|
|
||||||
return object_list if count is None else object_list[:count]
|
return object_list if count is None else object_list[:count]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _next_index(cls, items, date, default, pred=operator.lt):
|
||||||
|
iter = (i for i, v in enumerate(items) if pred(v.date, date))
|
||||||
|
return next(iter, default)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _append_logs(cls, object_list, logs, count, group=False):
|
||||||
|
logs = logs[:count]
|
||||||
|
if not logs:
|
||||||
|
return object_list
|
||||||
|
|
||||||
|
if group:
|
||||||
|
grouped = cls._group_logs_by_time(logs)
|
||||||
|
object_list.extend(grouped)
|
||||||
|
else:
|
||||||
|
object_list += logs
|
||||||
|
return object_list
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _group_logs_by_time(cls, logs):
|
||||||
|
last_time = -1
|
||||||
|
cum = []
|
||||||
|
for log in logs:
|
||||||
|
hour = log.date.time().hour
|
||||||
|
if hour != last_time:
|
||||||
|
if cum:
|
||||||
|
yield cum
|
||||||
|
cum = []
|
||||||
|
last_time = hour
|
||||||
|
# reverse from lowest to highest date
|
||||||
|
cum.insert(0, log)
|
||||||
|
if cum:
|
||||||
|
yield cum
|
||||||
|
|
||||||
def print(self):
|
def print(self):
|
||||||
r = []
|
r = []
|
||||||
if self.diffusion:
|
if self.diffusion:
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -25,7 +26,17 @@ __all__ = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
headline_re = re.compile(r"(<p>)?" r"(?P<headline>[^\n]{1,140}(\n|[^\.]*?\.))" r"(</p>)?")
|
headline_clean_re = re.compile(r"\n(\s| )+", re.MULTILINE)
|
||||||
|
headline_re = re.compile(r"(?P<headline>([\S+]|\s+){1,240}\S+)", re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
@ -50,6 +61,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 self.filter(parent=parent) if id is None else self.filter(parent__id=id)
|
return self.filter(parent=parent) if id is None else self.filter(parent__id=id)
|
||||||
|
@ -60,7 +74,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
|
||||||
|
@ -102,11 +116,14 @@ 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
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cover_url(self):
|
||||||
|
return self.cover_id and self.cover.url
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}".format(self.title or self.pk)
|
return "{}".format(self.title or self.pk)
|
||||||
|
|
||||||
|
@ -117,12 +134,17 @@ class BasePage(models.Model):
|
||||||
if count:
|
if count:
|
||||||
self.slug += "-" + str(count)
|
self.slug += "-" + str(count)
|
||||||
|
|
||||||
if self.parent and not self.cover:
|
if self.parent:
|
||||||
|
if self.parent == self:
|
||||||
|
self.parent = None
|
||||||
|
if not self.cover:
|
||||||
self.cover = self.parent.cover
|
self.cover = self.parent.cover
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(self.detail_url_name, kwargs={"slug": self.slug}) if self.is_published else "#"
|
if self.is_published:
|
||||||
|
return reverse(self.detail_url_name, kwargs={"slug": self.slug})
|
||||||
|
return ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_draft(self):
|
def is_draft(self):
|
||||||
|
@ -138,17 +160,28 @@ 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.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)
|
||||||
|
content = headline_clean_re.sub("\n", content)
|
||||||
|
if content.startswith("\n"):
|
||||||
|
content = content[1:]
|
||||||
headline = headline_re.search(content)
|
headline = headline_re.search(content)
|
||||||
return mark_safe(headline.groupdict()["headline"]) if headline else ""
|
if not headline:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
headline = headline.groupdict()["headline"]
|
||||||
|
suffix = "<b>...</b>" if len(headline) < len(content) else ""
|
||||||
|
|
||||||
|
headline = headline.split("\n")[:3]
|
||||||
|
headline[-1] += suffix
|
||||||
|
return mark_safe(" ".join(headline))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_init_kwargs_from(cls, page, **kwargs):
|
def get_init_kwargs_from(cls, page, **kwargs):
|
||||||
|
@ -188,6 +221,23 @@ class Page(BasePage):
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = PageQuerySet.as_manager()
|
objects = PageQuerySet.as_manager()
|
||||||
|
detail_url_name = ""
|
||||||
|
list_url_name = "page-list"
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def parent_subclass(self):
|
||||||
|
if self.parent_id:
|
||||||
|
return Page.objects.get_subclass(id=self.parent_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
if not self.is_published and self.parent_subclass:
|
||||||
|
return self.parent_subclass.get_absolute_url()
|
||||||
|
return super().get_absolute_url()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_list_url(cls, kwargs={}):
|
||||||
|
return reverse(cls.list_url_name, kwargs=kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Publication")
|
verbose_name = _("Publication")
|
||||||
|
@ -209,45 +259,37 @@ class StaticPage(BasePage):
|
||||||
|
|
||||||
detail_url_name = "static-page-detail"
|
detail_url_name = "static-page-detail"
|
||||||
|
|
||||||
ATTACH_TO_HOME = 0x00
|
class Target(models.TextChoices):
|
||||||
ATTACH_TO_DIFFUSIONS = 0x01
|
NONE = "", _("None")
|
||||||
ATTACH_TO_LOGS = 0x02
|
HOME = "home", _("Home Page")
|
||||||
ATTACH_TO_PROGRAMS = 0x03
|
TIMETABLE = "timetable-list", _("Timetable")
|
||||||
ATTACH_TO_EPISODES = 0x04
|
PROGRAMS = "program-list", _("Programs list")
|
||||||
ATTACH_TO_ARTICLES = 0x05
|
EPISODES = "episode-list", _("Episodes list")
|
||||||
|
ARTICLES = "article-list", _("Articles list")
|
||||||
|
PAGES = "page-list", _("Publications list")
|
||||||
|
PODCASTS = "podcast-list", _("Podcasts list")
|
||||||
|
|
||||||
ATTACH_TO_CHOICES = (
|
attach_to = models.CharField(
|
||||||
(ATTACH_TO_HOME, _("Home page")),
|
|
||||||
(ATTACH_TO_DIFFUSIONS, _("Diffusions page")),
|
|
||||||
(ATTACH_TO_LOGS, _("Logs page")),
|
|
||||||
(ATTACH_TO_PROGRAMS, _("Programs list")),
|
|
||||||
(ATTACH_TO_EPISODES, _("Episodes list")),
|
|
||||||
(ATTACH_TO_ARTICLES, _("Articles list")),
|
|
||||||
)
|
|
||||||
VIEWS = {
|
|
||||||
ATTACH_TO_HOME: "home",
|
|
||||||
ATTACH_TO_DIFFUSIONS: "diffusion-list",
|
|
||||||
ATTACH_TO_LOGS: "log-list",
|
|
||||||
ATTACH_TO_PROGRAMS: "program-list",
|
|
||||||
ATTACH_TO_EPISODES: "episode-list",
|
|
||||||
ATTACH_TO_ARTICLES: "article-list",
|
|
||||||
}
|
|
||||||
|
|
||||||
attach_to = models.SmallIntegerField(
|
|
||||||
_("attach to"),
|
_("attach to"),
|
||||||
choices=ATTACH_TO_CHOICES,
|
choices=Target.choices,
|
||||||
|
max_length=32,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_("display this page content to related element"),
|
help_text=_("display this page content to related element"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_related_view(self):
|
||||||
|
from ..views import attached
|
||||||
|
|
||||||
|
return self.attach_to and attached.get(self.attach_to) or None
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
if self.attach_to:
|
if self.attach_to:
|
||||||
return reverse(self.VIEWS[self.attach_to])
|
return reverse(self.attach_to)
|
||||||
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,
|
||||||
|
@ -260,7 +302,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):
|
||||||
|
@ -268,7 +310,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")
|
||||||
|
@ -281,7 +323,7 @@ class NavItem(models.Model):
|
||||||
station = models.ForeignKey(Station, models.CASCADE, verbose_name=_("station"))
|
station = models.ForeignKey(Station, models.CASCADE, verbose_name=_("station"))
|
||||||
menu = models.SlugField(_("menu"), max_length=24)
|
menu = models.SlugField(_("menu"), max_length=24)
|
||||||
order = models.PositiveSmallIntegerField(_("order"))
|
order = models.PositiveSmallIntegerField(_("order"))
|
||||||
text = models.CharField(_("title"), max_length=64)
|
text = models.CharField(_("title"), max_length=64, blank=True, null=True)
|
||||||
url = models.CharField(_("url"), max_length=256, blank=True, null=True)
|
url = models.CharField(_("url"), max_length=256, blank=True, null=True)
|
||||||
page = models.ForeignKey(
|
page = models.ForeignKey(
|
||||||
StaticPage,
|
StaticPage,
|
||||||
|
@ -300,14 +342,21 @@ class NavItem(models.Model):
|
||||||
def get_url(self):
|
def get_url(self):
|
||||||
return self.url if self.url else self.page.get_absolute_url() if self.page else None
|
return self.url if self.url else self.page.get_absolute_url() if self.page else None
|
||||||
|
|
||||||
|
def get_label(self):
|
||||||
|
if self.text:
|
||||||
|
return self.text
|
||||||
|
elif self.page:
|
||||||
|
return self.page.title
|
||||||
|
|
||||||
def render(self, request, css_class="", active_class=""):
|
def render(self, request, css_class="", active_class=""):
|
||||||
url = self.get_url()
|
url = self.get_url()
|
||||||
|
label = self.get_label()
|
||||||
if active_class and request.path.startswith(url):
|
if active_class and request.path.startswith(url):
|
||||||
css_class += " " + active_class
|
css_class += " " + active_class
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
return self.text
|
return label
|
||||||
elif not css_class:
|
elif not css_class:
|
||||||
return format_html('<a href="{}">{}</a>', url, self.text)
|
return format_html('<a href="{}">{}</a>', url, label)
|
||||||
else:
|
else:
|
||||||
return format_html('<a href="{}" class="{}">{}</a>', url, css_class, self.text)
|
return format_html('<a href="{}" class="{}">{}</a>', url, css_class, label)
|
||||||
|
|
|
@ -61,6 +61,7 @@ class Program(Page):
|
||||||
|
|
||||||
objects = ProgramQuerySet.as_manager()
|
objects = ProgramQuerySet.as_manager()
|
||||||
detail_url_name = "program-detail"
|
detail_url_name = "program-detail"
|
||||||
|
list_url_name = "program-list"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self):
|
def path(self):
|
||||||
|
|
|
@ -42,6 +42,7 @@ class Schedule(Rerun):
|
||||||
second_and_fourth = 0b001010, _("2nd and 4th {day} of the month")
|
second_and_fourth = 0b001010, _("2nd and 4th {day} of the month")
|
||||||
every = 0b011111, _("{day}")
|
every = 0b011111, _("{day}")
|
||||||
one_on_two = 0b100000, _("one {day} on two")
|
one_on_two = 0b100000, _("one {day} on two")
|
||||||
|
# every_weekday = 0b10000000 _("from Monday to Friday")
|
||||||
|
|
||||||
date = models.DateField(
|
date = models.DateField(
|
||||||
_("date"),
|
_("date"),
|
||||||
|
@ -71,6 +72,10 @@ class Schedule(Rerun):
|
||||||
verbose_name = _("Schedule")
|
verbose_name = _("Schedule")
|
||||||
verbose_name_plural = _("Schedules")
|
verbose_name_plural = _("Schedules")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._initial = kwargs
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{} - {}, {}".format(
|
return "{} - {}, {}".format(
|
||||||
self.program.title,
|
self.program.title,
|
||||||
|
@ -110,16 +115,28 @@ class Schedule(Rerun):
|
||||||
date = tz.datetime.combine(date, self.time)
|
date = tz.datetime.combine(date, self.time)
|
||||||
return date.replace(tzinfo=self.tz)
|
return date.replace(tzinfo=self.tz)
|
||||||
|
|
||||||
def dates_of_month(self, date):
|
def dates_of_month(self, date, frequency=None, sched_date=None):
|
||||||
"""Return normalized diffusion dates of provided date's month."""
|
"""Return normalized diffusion dates of provided date's month.
|
||||||
if self.frequency == Schedule.Frequency.ponctual:
|
|
||||||
|
:param Date date: date of the month to get dates from;
|
||||||
|
:param Schedule.Frequency frequency: frequency (defaults to ``self.frequency``)
|
||||||
|
:param Date sched_date: schedule start date (defaults to ``self.date``)
|
||||||
|
:return list of diffusion dates
|
||||||
|
"""
|
||||||
|
if frequency is None:
|
||||||
|
frequency = self.frequency
|
||||||
|
|
||||||
|
if sched_date is None:
|
||||||
|
sched_date = self.date
|
||||||
|
|
||||||
|
if frequency == Schedule.Frequency.ponctual:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
sched_wday, freq = self.date.weekday(), self.frequency
|
sched_wday = sched_date.weekday()
|
||||||
date = date.replace(day=1)
|
date = date.replace(day=1)
|
||||||
|
|
||||||
# last of the month
|
# last of the month
|
||||||
if freq == Schedule.Frequency.last:
|
if frequency == Schedule.Frequency.last:
|
||||||
date = date.replace(day=calendar.monthrange(date.year, date.month)[1])
|
date = date.replace(day=calendar.monthrange(date.year, date.month)[1])
|
||||||
date_wday = date.weekday()
|
date_wday = date.weekday()
|
||||||
|
|
||||||
|
@ -134,33 +151,42 @@ class Schedule(Rerun):
|
||||||
date_wday, month = date.weekday(), date.month
|
date_wday, month = date.weekday(), date.month
|
||||||
date += tz.timedelta(days=(7 if date_wday > sched_wday else 0) - date_wday + sched_wday)
|
date += tz.timedelta(days=(7 if date_wday > sched_wday else 0) - date_wday + sched_wday)
|
||||||
|
|
||||||
if freq == Schedule.Frequency.one_on_two:
|
if frequency == Schedule.Frequency.one_on_two:
|
||||||
# - adjust date with modulo 14 (= 2 weeks in days)
|
# - adjust date with modulo 14 (= 2 weeks in days)
|
||||||
# - there are max 3 "weeks on two" per month
|
# - there are max 3 "weeks on two" per month
|
||||||
if (date - self.date).days % 14:
|
if (date - sched_date).days % 14:
|
||||||
date += tz.timedelta(days=7)
|
date += tz.timedelta(days=7)
|
||||||
dates = (date + tz.timedelta(days=14 * i) for i in range(0, 3))
|
dates = (date + tz.timedelta(days=14 * i) for i in range(0, 3))
|
||||||
else:
|
else:
|
||||||
dates = (date + tz.timedelta(days=7 * week) for week in range(0, 5) if freq & (0b1 << week))
|
dates = (date + tz.timedelta(days=7 * week) for week in range(0, 5) if frequency & (0b1 << week))
|
||||||
|
|
||||||
return [self.normalize(date) for date in dates if date.month == month]
|
return [self.normalize(date) for date in dates if date.month == month]
|
||||||
|
|
||||||
def diffusions_of_month(self, date):
|
def diffusions_of_month(self, date, frequency=None, sched_date=None):
|
||||||
"""Get episodes and diffusions for month of provided date, including
|
"""Get episodes and diffusions for month of provided date, including
|
||||||
reruns.
|
reruns.
|
||||||
|
|
||||||
|
:param Date date: date of the month to get diffusions from;
|
||||||
|
:param Schedule.Frequency frequency: frequency (defaults to ``self.frequency``)
|
||||||
|
:param Date sched_date: schedule start date (defaults to ``self.date``)
|
||||||
:returns: tuple([Episode], [Diffusion])
|
:returns: tuple([Episode], [Diffusion])
|
||||||
"""
|
"""
|
||||||
from .diffusion import Diffusion
|
from .diffusion import Diffusion
|
||||||
from .episode import Episode
|
from .episode import Episode
|
||||||
|
|
||||||
if self.initial is not None or self.frequency == Schedule.Frequency.ponctual:
|
if frequency is None:
|
||||||
|
frequency = self.frequency
|
||||||
|
|
||||||
|
if sched_date is None:
|
||||||
|
sched_date = self.date
|
||||||
|
|
||||||
|
if self.initial is not None or frequency == Schedule.Frequency.ponctual:
|
||||||
return [], []
|
return [], []
|
||||||
|
|
||||||
# dates for self and reruns as (date, initial)
|
# dates for self and reruns as (date, initial)
|
||||||
reruns = [(rerun, rerun.date - self.date) for rerun in self.rerun_set.all()]
|
reruns = [(rerun, rerun.date - sched_date) for rerun in self.rerun_set.all()]
|
||||||
|
|
||||||
dates = {date: None for date in self.dates_of_month(date)}
|
dates = {date: None for date in self.dates_of_month(date, frequency, sched_date)}
|
||||||
dates.update(
|
dates.update(
|
||||||
(rerun.normalize(date.date() + delta), date) for date in list(dates.keys()) for rerun, delta in reruns
|
(rerun.normalize(date.date() + delta), date) for date in list(dates.keys()) for rerun, delta in reruns
|
||||||
)
|
)
|
||||||
|
|
|
@ -41,6 +41,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(cover=instance.cover)
|
Page.objects.filter(parent=instance, cover__isnull=True).update(cover=instance.cover)
|
||||||
|
|
||||||
|
@ -59,6 +60,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)
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class Station(models.Model):
|
||||||
max_length=2048,
|
max_length=2048,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Audio streams urls used by station's player. One url " "a line."),
|
help_text=_("Audio streams urls used by station's player. One url a line."),
|
||||||
)
|
)
|
||||||
default_cover = FilerImageField(
|
default_cover = FilerImageField(
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
@ -76,6 +76,11 @@ class Station(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="+",
|
related_name="+",
|
||||||
)
|
)
|
||||||
|
music_stream_title = models.CharField(
|
||||||
|
_("Music stream's title"),
|
||||||
|
max_length=64,
|
||||||
|
default=_("Music stream"),
|
||||||
|
)
|
||||||
|
|
||||||
objects = StationQuerySet.as_manager()
|
objects = StationQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<title>Vue App</title>
|
|
||||||
<script defer src="js/chunk-vendors.js"></script><script defer src="js/chunk-common.js"></script><script defer src="js/core.js"></script><link href="css/chunk-vendors.css" rel="stylesheet"><link href="css/chunk-common.css" rel="stylesheet"></head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
7569
aircox/static/aircox/css/public.css
Normal file
7569
aircox/static/aircox/css/public.css
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -16,7 +16,7 @@
|
||||||
\**********************/
|
\**********************/
|
||||||
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _assets_styles_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./assets/styles.scss */ \"./src/assets/styles.scss\");\n/* harmony import */ var _assets_admin_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./assets/admin.scss */ \"./src/assets/admin.scss\");\n/* harmony import */ var _index_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./index.js */ \"./src/index.js\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./components */ \"./src/components/index.js\");\n/* harmony import */ var _track__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./track */ \"./src/track.js\");\n\n\n\n\n\n\nconst AdminApp = {\n ..._app__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n components: {\n ..._app__WEBPACK_IMPORTED_MODULE_3__[\"default\"].components,\n ..._components__WEBPACK_IMPORTED_MODULE_4__.admin\n },\n data() {\n return {\n ...super.data,\n Track: _track__WEBPACK_IMPORTED_MODULE_5__[\"default\"]\n };\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (AdminApp);\nwindow.App = AdminApp;\n\n//# sourceURL=webpack://aircox-assets/./src/admin.js?");
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _assets_admin_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./assets/admin.scss */ \"./src/assets/admin.scss\");\n/* harmony import */ var _index_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.js */ \"./src/index.js\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./components */ \"./src/components/index.js\");\n/* harmony import */ var _track__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./track */ \"./src/track.js\");\n\n\n\n\n\nconst AdminApp = {\n ..._app__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n components: {\n ..._app__WEBPACK_IMPORTED_MODULE_2__[\"default\"].components,\n ..._components__WEBPACK_IMPORTED_MODULE_3__.admin\n },\n data() {\n return {\n ...super.data,\n Track: _track__WEBPACK_IMPORTED_MODULE_4__[\"default\"]\n };\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (AdminApp);\nwindow.App = AdminApp;\n\n//# sourceURL=webpack://aircox-assets/./src/admin.js?");
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -10,13 +10,23 @@
|
||||||
/******/ "use strict";
|
/******/ "use strict";
|
||||||
/******/ var __webpack_modules__ = ({
|
/******/ var __webpack_modules__ = ({
|
||||||
|
|
||||||
/***/ "./src/core.js":
|
/***/ "./src/public.js":
|
||||||
/*!*********************!*\
|
/*!***********************!*\
|
||||||
!*** ./src/core.js ***!
|
!*** ./src/public.js ***!
|
||||||
\*********************/
|
\***********************/
|
||||||
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _index_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.js */ \"./src/index.js\");\n/* harmony import */ var _app_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./app.js */ \"./src/app.js\");\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (_app_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]);\nwindow.App = _app_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"];\n\n//# sourceURL=webpack://aircox-assets/./src/core.js?");
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _assets_public_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./assets/public.scss */ \"./src/assets/public.scss\");\n/* harmony import */ var _index_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.js */ \"./src/index.js\");\n/* harmony import */ var _app_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./app.js */ \"./src/app.js\");\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (_app_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\nwindow.App = _app_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"];\n\n//# sourceURL=webpack://aircox-assets/./src/public.js?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./src/assets/public.scss":
|
||||||
|
/*!********************************!*\
|
||||||
|
!*** ./src/assets/public.scss ***!
|
||||||
|
\********************************/
|
||||||
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://aircox-assets/./src/assets/public.scss?");
|
||||||
|
|
||||||
/***/ })
|
/***/ })
|
||||||
|
|
||||||
|
@ -156,7 +166,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ind
|
||||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||||
/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
|
/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
|
||||||
/******/ var installedChunks = {
|
/******/ var installedChunks = {
|
||||||
/******/ "core": 0
|
/******/ "public": 0
|
||||||
/******/ };
|
/******/ };
|
||||||
/******/
|
/******/
|
||||||
/******/ // no chunk on demand loading
|
/******/ // no chunk on demand loading
|
||||||
|
@ -208,7 +218,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ind
|
||||||
/******/ // startup
|
/******/ // startup
|
||||||
/******/ // Load entry module and return exports
|
/******/ // Load entry module and return exports
|
||||||
/******/ // This entry module depends on other loaded chunks and execution need to be delayed
|
/******/ // This entry module depends on other loaded chunks and execution need to be delayed
|
||||||
/******/ var __webpack_exports__ = __webpack_require__.O(undefined, ["chunk-vendors","chunk-common"], function() { return __webpack_require__("./src/core.js"); })
|
/******/ var __webpack_exports__ = __webpack_require__.O(undefined, ["chunk-vendors","chunk-common"], function() { return __webpack_require__("./src/public.js"); })
|
||||||
/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__);
|
/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__);
|
||||||
/******/
|
/******/
|
||||||
/******/ })()
|
/******/ })()
|
16095
aircox/static/aircox/vue.esm-browser.js
Normal file
16095
aircox/static/aircox/vue.esm-browser.js
Normal file
File diff suppressed because it is too large
Load Diff
1
aircox/static/aircox/vue.esm-browser.prod.js
Normal file
1
aircox/static/aircox/vue.esm-browser.prod.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -7,7 +7,7 @@
|
||||||
<li>
|
<li>
|
||||||
{% if choice.type %}
|
{% if choice.type %}
|
||||||
<form method="GET" action="?{{ choice.query_string }}"
|
<form method="GET" action="?{{ choice.query_string }}"
|
||||||
onsubmit="return this.{{ choice.name }}.value ? true : false"">
|
onsubmit="return this.{{ choice.name }}.value ? true : false">
|
||||||
<label for="filter-{{ choice.name }}">{{ choice.label }}: </label>
|
<label for="filter-{{ choice.name }}">{{ choice.label }}: </label>
|
||||||
<input id="filter-{{ choice.name }}" type="{{ choice.type }}" name="{{ choice.name }}"
|
<input id="filter-{{ choice.name }}" type="{{ choice.type }}" name="{{ choice.name }}"
|
||||||
value="{{ choice.value }}" {{ choice.extra }}/>
|
value="{{ choice.value }}" {{ choice.extra }}/>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<template #title>
|
<template #title>
|
||||||
<h5 class="title is-4">{% trans "Playlist" %}</h5>
|
<h5 class="title is-4">{% trans "Playlist" %}</h5>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:top="{items}">
|
<template #top="{items}">
|
||||||
<input type="hidden" name="{{ formset.prefix }}-TOTAL_FORMS"
|
<input type="hidden" name="{{ formset.prefix }}-TOTAL_FORMS"
|
||||||
:value="items.length || 0"/>
|
:value="items.length || 0"/>
|
||||||
<input type="hidden" name="{{ formset.prefix }}-INITIAL_FORMS"
|
<input type="hidden" name="{{ formset.prefix }}-INITIAL_FORMS"
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<head>
|
<head>
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
|
||||||
<!-- <link rel="stylesheet" type="text/css" href="{% static "aircox/vendor.css" %}"> -->
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "admin/css/base.css" %}">
|
<link rel="stylesheet" type="text/css" href="{% static "admin/css/base.css" %}">
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-common.css" %}"/>
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-common.css" %}"/>
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-vendors.css" %}"/>
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-vendors.css" %}"/>
|
||||||
|
@ -33,23 +32,26 @@
|
||||||
function vuePre(selector) {
|
function vuePre(selector) {
|
||||||
const elms = document.querySelectorAll(selector)
|
const elms = document.querySelectorAll(selector)
|
||||||
for(const elm of elms) {
|
for(const elm of elms) {
|
||||||
elm.setAttribute('v-pre', true)
|
// elm.setAttribute('v-pre', "")
|
||||||
|
elm.parentNode.setAttribute('v-pre', '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
{% block init-scripts %}
|
{% block init-scripts %}
|
||||||
|
vuePre(".django-ckeditor-widget")
|
||||||
|
vuePre("fieldset")
|
||||||
|
window.source_ = document.body.innerHTML
|
||||||
aircox.init(null, {
|
aircox.init(null, {
|
||||||
hotReload: false,
|
|
||||||
{% if not init_app %}
|
{% if not init_app %}
|
||||||
initBuilder: false,
|
initApp: false,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if init_el %}
|
{% if init_el %}
|
||||||
el: "{{ init_el }}",
|
el: "{{ init_el }}",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
})
|
})
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
})
|
}, true)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Container -->
|
<!-- Container -->
|
||||||
|
@ -71,7 +73,7 @@
|
||||||
</span>
|
</span>
|
||||||
<div class="navbar-dropdown is-boxed">
|
<div class="navbar-dropdown is-boxed">
|
||||||
{% for diffusion in diffusions %}
|
{% for diffusion in diffusions %}
|
||||||
<a class="navbar-item {% if diffusion.is_now %}has-background-primary{% endif %}" href="{% url "admin:aircox_episode_change" diffusion.episode.pk %}">
|
<a class="navbar-item {% if diffusion.is_now %}active{% endif %}" href="{% url "admin:aircox_episode_change" diffusion.episode.pk %}">
|
||||||
{{ diffusion.start|time }} |
|
{{ diffusion.start|time }} |
|
||||||
{{ diffusion.episode.title }}
|
{{ diffusion.episode.title }}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "admin/index.html" %}
|
{% extends "admin/index.html" %}
|
||||||
{% load i18n thumbnail %}
|
{% load i18n thumbnail aircox %}
|
||||||
|
|
||||||
|
|
||||||
{% block app %}
|
{% block app %}
|
||||||
|
@ -12,43 +12,11 @@
|
||||||
<span>{% translate "Today" %}</span>
|
<span>{% translate "Today" %}</span>
|
||||||
</h1>
|
</h1>
|
||||||
{% if diffusions %}
|
{% if diffusions %}
|
||||||
<table class="table is-fullwidth is-striped">
|
<div class="grid-3">
|
||||||
<tbody>
|
{% for obj in diffusions %}
|
||||||
{% for diffusion in diffusions %}
|
{% page_widget "card" obj.episode diffusion=obj timetable=True admin=True tag_class="" %}
|
||||||
{% with episode=diffusion.episode %}
|
|
||||||
<tr {% if diffusion.is_now %}class="is-selected"{% endif %}>
|
|
||||||
<td>{{ diffusion.start|time }} - {{ diffusion.end|time }}</td>
|
|
||||||
<td><img src="{% thumbnail episode.cover 64x64 crop %}"/></td>
|
|
||||||
<td>
|
|
||||||
<a href="{% url "admin:aircox_episode_change" episode.pk %}">{{ episode.title }}</a>
|
|
||||||
|
|
||||||
{% if diffusion.type == diffusion.TYPE_ON_AIR %}
|
|
||||||
<span class="tag is-info">
|
|
||||||
<span class="icon is-small">
|
|
||||||
{% if diffusion.is_live %}
|
|
||||||
<i class="fa fa-microphone"
|
|
||||||
title="{% translate "Live diffusion" %}"></i>
|
|
||||||
{% else %}
|
|
||||||
<i class="fa fa-music"
|
|
||||||
title="{% translate "Differed diffusion" %}"></i>
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{{ diffusion.get_type_display }}
|
|
||||||
</span>
|
|
||||||
{% elif diffusion.type == diffusion.TYPE_CANCEL %}
|
|
||||||
<span class="tag is-danger">
|
|
||||||
{{ diffusion.get_type_display }}</span>
|
|
||||||
{% elif diffusion.type == diffusion.TYPE_UNCONFIRMED %}
|
|
||||||
<span class="tag is-warning">
|
|
||||||
{{ diffusion.get_type_display }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="block has-text-centered">
|
<div class="block has-text-centered">
|
||||||
{% trans "No diffusion is scheduled for today." %}
|
{% trans "No diffusion is scheduled for today." %}
|
||||||
|
@ -62,7 +30,9 @@
|
||||||
<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|slice:":5" %}
|
||||||
|
{% page_widget "item" object with_title=True %}
|
||||||
|
{% endfor %}
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<a href="{% url "admin:aircox_comment_changelist" %}" class="float-center">{% translate "All comments" %}</a>
|
<a href="{% url "admin:aircox_comment_changelist" %}" class="float-center">{% translate "All comments" %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,30 +1,2 @@
|
||||||
{% extends "aircox/page_detail.html" %}
|
{% extends "aircox/page_detail.html" %}
|
||||||
{% comment %}Detail page for regular articles{% endcomment %}
|
{% comment %}Detail page for regular articles{% endcomment %}
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
{% if sidebar_object_list %}
|
|
||||||
<section>
|
|
||||||
{% comment %}Translators: in page detail sidebar{% endcomment %}
|
|
||||||
<h4 class="title is-4">{% translate "Latest news" %}</h4>
|
|
||||||
|
|
||||||
{% for object in sidebar_object_list %}
|
|
||||||
{% include "aircox/widgets/page_item.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<nav class="pagination is-centered">
|
|
||||||
<ul class="pagination-list">
|
|
||||||
<li>
|
|
||||||
<a href="{% url "article-list" %}" class="pagination-link"
|
|
||||||
aria-label="{% translate "Show all news" %}">
|
|
||||||
{% translate "More news" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -24,9 +24,10 @@ Usefull context:
|
||||||
{% block assets %}
|
{% block assets %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-common.css" %}"/>
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-common.css" %}"/>
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-vendors.css" %}"/>
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-vendors.css" %}"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/public.css" %}"/>
|
||||||
<script src="{% static "aircox/js/chunk-common.js" %}"></script>
|
<script src="{% static "aircox/js/chunk-common.js" %}"></script>
|
||||||
<script src="{% static "aircox/js/chunk-vendors.js" %}"></script>
|
<script src="{% static "aircox/js/chunk-vendors.js" %}"></script>
|
||||||
<script src="{% static "aircox/js/core.js" %}"></script>
|
<script src="{% static "aircox/js/public.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<title>
|
<title>
|
||||||
|
@ -39,7 +40,7 @@ Usefull context:
|
||||||
|
|
||||||
{% block head_extra %}{% endblock %}
|
{% block head_extra %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body {% if request.is_mobile %}class="mobile"{% endif %}>
|
||||||
<script id="init-script">
|
<script id="init-script">
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
{% block init-scripts %}
|
{% block init-scripts %}
|
||||||
|
@ -48,120 +49,92 @@ Usefull context:
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
{% block top-nav-container %}
|
<div class="navs">
|
||||||
<nav class="navbar has-shadow" role="navigation" aria-label="main navigation">
|
{% block nav %}
|
||||||
<div class="container">
|
<nav class="nav primary" role="navigation" aria-label="main navigation">
|
||||||
<div class="navbar-brand">
|
{% block nav-primary %}
|
||||||
<a href="/" title="{% translate "Home" %}" class="navbar-item">
|
<a class="nav-brand" href="{% url "home" %}">
|
||||||
<img src="{{ station.logo.url }}" class="logo"/>
|
<img src="{{ station.logo.url }}">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<a-switch class="button burger"
|
||||||
<div class="navbar-menu">
|
el=".nav.primary .nav-menu" group="nav"
|
||||||
<div class="navbar-start">
|
aria-label="{% translate "Main menu" %}">
|
||||||
{% block top-nav %}
|
</a-switch>
|
||||||
{% nav_items "top" css_class="navbar-item" active_class="is-active" as items %}
|
<div class="nav-menu">
|
||||||
|
{% block nav-primary-menu %}
|
||||||
|
{% nav_items "top" css_class="nav-item" active_class="active" as items %}
|
||||||
{% for item, render in items %}
|
{% for item, render in items %}
|
||||||
{{ render }}
|
{{ render }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if user.is_staff %}
|
||||||
|
<a class="nav-item" href="{% url "admin:index" %}" target="new">
|
||||||
|
{% translate "Admin" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
|
||||||
{% block top-nav-tools %}
|
|
||||||
{% endblock %}
|
{% 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>
|
</nav>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block secondary-nav %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
{% block main-container %}
|
||||||
<div class="columns is-desktop">
|
<main class="page">
|
||||||
<main class="column page">
|
{% block main %}
|
||||||
<header class="header">
|
{% spaceless %}
|
||||||
{% block header %}
|
{% block breadcrumbs-container %}
|
||||||
<h1 class="title is-1">
|
<div class="breadcrumbs container">
|
||||||
{% block title %}
|
{% block breadcrumbs %}{% endblock %}
|
||||||
{% if page and page.title %}
|
</div>
|
||||||
{{ page.title }}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</h1>
|
{% endspaceless %}
|
||||||
|
|
||||||
<h3 class="subtitle is-3">
|
{% block header-container %}
|
||||||
{% block subtitle %}{% endblock %}
|
{% if page or cover or title %}
|
||||||
</h3>
|
<header class="container header preview preview-header {% if cover %}has-cover{% endif %}">
|
||||||
|
{% block header %}
|
||||||
<div class="columns is-size-4">
|
{% if cover %}
|
||||||
{% block header_nav %}
|
<figure class="header-cover">
|
||||||
<span class="column">
|
<img src="{{ cover }}" class="cover">
|
||||||
{% block header_crumbs %}
|
</figure>
|
||||||
{% if parent %}
|
{% endif %}
|
||||||
<a href="{{ parent.get_absolute_url }}">
|
<div class="headings preview-card-headings">
|
||||||
{{ parent.title }}</a></li>
|
{% block headings %}
|
||||||
|
<div>
|
||||||
|
<h1 class="title is-1 {% block title-class %}{% endblock %}">{% block title %}{{ title|default:"" }}{% endblock %}</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{% spaceless %}
|
||||||
|
<span class="subtitle is-2">
|
||||||
|
{% block subtitle %}
|
||||||
|
{% if subtitle %}
|
||||||
|
{{ subtitle }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</span>
|
</span>
|
||||||
|
{% endspaceless %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</header>
|
</header>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block content-container %}
|
||||||
{% block content %}
|
|
||||||
{% if page and page.content %}
|
{% if page and page.content %}
|
||||||
<section class="page-content mb-2">{{ page.content|safe }}</section>
|
<section class="container content page-content">
|
||||||
{% endif %}
|
{% block content %}
|
||||||
|
{{ page.content|safe }}
|
||||||
{% endblock %}
|
{% 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 %}
|
|
||||||
|
|
||||||
{% with is_thin=True %}
|
|
||||||
{% block sidebar %}
|
|
||||||
{% if sidebar_object_list %}
|
|
||||||
{% with object_list=sidebar_object_list %}
|
|
||||||
{% 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>
|
</section>
|
||||||
{% endblock %}
|
|
||||||
{% endwith %}
|
|
||||||
</aside>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock %}
|
||||||
</div>
|
{% endblock %}
|
||||||
|
</main>
|
||||||
<hr>
|
{% 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>
|
||||||
|
|
|
@ -6,3 +6,5 @@
|
||||||
—
|
—
|
||||||
{{ station.name }}
|
{{ station.name }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}{% if page %}{{ block.super }}{% endif %}{% endblock %}
|
||||||
|
|
|
@ -1,37 +1,26 @@
|
||||||
{% 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 %}
|
||||||
|
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if not page or not page.title %}
|
{{ block.super }}
|
||||||
{% if not parent %}{{ view.model|verbose_name:True|title }}
|
|
||||||
{% else %}
|
|
||||||
{% with parent.title as title %}
|
|
||||||
{% with model|default:"Publications"|verbose_name:True|capfirst as model %}
|
|
||||||
{% comment %}Translators: title when pages are filtered for a specific parent page, e.g.: Articles of My Incredible Show{% endcomment %}
|
|
||||||
{% blocktranslate %}{{ model }} of {{ title }}{% endblocktranslate %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
{% else %}{{ block.super }}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
—
|
—
|
||||||
{{ station.name }}
|
{{ station.name }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}{{ block.super }}
|
{% block main %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
{% block before_list %}{% endblock %}
|
{% block list-container %}
|
||||||
|
<section class="container clear-both list grid {{ list_class|default:"" }}" role="list">
|
||||||
<section role="list">
|
{% block 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 %}
|
||||||
|
@ -40,47 +29,44 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
{% block list-pagination %}
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
<hr/>
|
<hr/>
|
||||||
{% update_query request.GET.copy page=None as GET %}
|
{% update_query request.GET.copy page=None as GET %}
|
||||||
{% with GET.urlencode as GET %}
|
{% with GET.urlencode as GET %}
|
||||||
<nav class="pagination is-centered" role="pagination" aria-label="{% translate "pagination" %}">
|
<nav class="nav-urls is-centered" role="pagination" aria-label="{% translate "pagination" %}">
|
||||||
{% block pagination %}
|
<ul class="urls">
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<a href="?{{ GET }}&page={{ page_obj.previous_page_number }}" class="pagination-previous">
|
|
||||||
{% else %}
|
|
||||||
<a class="pagination-previous" disabled>
|
|
||||||
{% endif %}
|
|
||||||
{% comment %}Translators: Bottom of the list, "previous page"{% endcomment %}
|
{% comment %}Translators: Bottom of the list, "previous page"{% endcomment %}
|
||||||
{% translate "Previous" %}</a>
|
<a href="?{{ GET }}&page={{ page_obj.previous_page_number }}" class="left"
|
||||||
|
title="{% translate "Previous" %}"
|
||||||
|
aria-label="{% translate "Previous" %}">
|
||||||
|
<span class="icon"><i class="fa fa-chevron-left"></i></span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}
|
||||||
|
</span>
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<a href="?{{ GET }}&page={{ page_obj.next_page_number }}" class="pagination-next">
|
|
||||||
{% else %}
|
|
||||||
<a class="pagination-next" disabled>
|
|
||||||
{% endif %}
|
|
||||||
{% comment %}Translators: Bottom of the list, "Nextpage"{% endcomment %}
|
{% comment %}Translators: Bottom of the list, "Nextpage"{% endcomment %}
|
||||||
{% translate "Next" %}</a>
|
<a href="?{{ GET }}&page={{ page_obj.next_page_number }}" class="right"
|
||||||
|
title="{% translate "Next" %}"
|
||||||
<ul class="pagination-list">
|
aria-label="{% translate "Next" %}">
|
||||||
{% for i in paginator.page_range %}
|
<span class="icon"><i class="fa fa-chevron-right"></i></span>
|
||||||
<li>
|
</a>
|
||||||
{% comment %}
|
{% endif %}
|
||||||
<form action="?{{ GET }}">
|
|
||||||
{% for get in GET %}
|
|
||||||
<input type="hidden" name="{{ get.0 }}" value="{{ get.1 }}" />
|
|
||||||
{% endfor %}
|
|
||||||
<input type="number" name="page" value="{{ page_obj.number }}" />
|
|
||||||
</form>
|
|
||||||
{% endcomment %}
|
|
||||||
<a class="pagination-link {% if page_obj.number == i %}is-current{% endif %}"
|
|
||||||
href="?{{ GET }}&page={{ i }}">{{ i }}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
|
||||||
</nav>
|
</nav>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -14,16 +14,18 @@
|
||||||
|
|
||||||
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
|
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
|
||||||
|
|
||||||
{% block before_list %}
|
{% block secondary-nav %}
|
||||||
{% with "diffusion-list" as url_name %}
|
<nav class="nav secondary">
|
||||||
{% include "aircox/widgets/dates_menu.html" %}
|
{% include "./widgets/dates_menu.html" with url_name="diffusion-list" %}
|
||||||
{% endwith %}
|
</nav>
|
||||||
{% endblock %}
|
{% 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 %}
|
||||||
|
|
|
@ -2,79 +2,51 @@
|
||||||
{% 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 content-container %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<a-episode :page="{title: "{{ page.title }}", podcasts: {{ object.podcasts|json }}}">
|
<a-episode :page="{title: "{{ page.title }}", podcasts: {{ object.podcasts|json }}}">
|
||||||
<template v-slot="{podcasts,page}">
|
<template v-slot="{podcasts,page}">
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
{% if object.podcasts %}
|
{% if object.podcasts %}
|
||||||
<section>
|
{% spaceless %}
|
||||||
|
<section class="container no-border">
|
||||||
|
<h3 class="title">{% translate "Podcasts" %}</h3>
|
||||||
<a-playlist v-if="page" :set="podcasts"
|
<a-playlist v-if="page" :set="podcasts"
|
||||||
name="{{ page.title }}"
|
name="{{ page.title }}"
|
||||||
list-class="menu-list" item-class="menu-item"
|
list-class="menu-list" item-class="menu-item"
|
||||||
:player="player" :actions="['play']"
|
:player="player" :actions="['play']"
|
||||||
@select="player.playItems('queue', $event.item)">
|
@select="player.playItems('queue', $event.item)">
|
||||||
<template v-slot:header>
|
|
||||||
<h4 class="title is-4">{% translate "Podcasts" %}</h4>
|
|
||||||
</template>
|
|
||||||
</a-playlist>
|
</a-playlist>
|
||||||
{% comment %}
|
|
||||||
{% for object in podcasts %}
|
|
||||||
{% include "aircox/widgets/podcast_item.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endcomment %}
|
|
||||||
</section>
|
</section>
|
||||||
|
{% endspaceless %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if tracks %}
|
{% if tracks %}
|
||||||
<section>
|
<section class="container">
|
||||||
<h4 class="title is-4">{% translate "Playlist" %}</h4>
|
<h3 class="title">{% translate "Playlist" %}</h3>
|
||||||
<ol>
|
<table class="table is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>{% translate "Artist" %}</th>
|
||||||
|
<th>{% translate "Title" %}</th>
|
||||||
|
<th></th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
{% for track in tracks %}
|
{% for track in tracks %}
|
||||||
<li><span>{{ track.title }}</span>
|
<tr>
|
||||||
<span class="has-text-grey-dark has-text-weight-light">
|
<td>{{ forloop.counter }}</td>
|
||||||
— {{ track.artist }}
|
<td>{{ track.artist }}</td>
|
||||||
{% if track.info %}(<i>{{ track.info }}</i>){% endif %}
|
<td>{{ track.title }}</td>
|
||||||
</span>
|
<td>{{ track.info|default:"" }}</td>
|
||||||
</li>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</tbody>
|
||||||
|
</table>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</template>
|
||||||
</template></a-episode>
|
</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>
|
|
||||||
—
|
|
||||||
<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 %}
|
||||||
|
|
|
@ -1,85 +1,73 @@
|
||||||
{% extends "aircox/page_list.html" %}
|
{% extends "aircox/base.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 title %}{% if page %}{{ block.super }}{% endif %}{% endblock %}
|
||||||
{% if not page or not page.title %}{{ station.name }}
|
|
||||||
{% else %}{{ block.super }}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block before_list %}{% endblock %}
|
|
||||||
|
|
||||||
{% block pages_list %}
|
{% block breadcrumbs-container %}{% endblock %}
|
||||||
{% if page and page.content %}<hr/>{% endif %}
|
|
||||||
|
{% block content-container %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
{% if next_diffs %}
|
{% if next_diffs %}
|
||||||
<div class="columns">
|
<section class="container">
|
||||||
{% with render_card=True %}
|
<h2 class="title">
|
||||||
{% for object in next_diffs %}
|
{% with station.name as station %}
|
||||||
{% with is_primary=object.is_now %}
|
{% blocktrans %}Today on {{ station }}{% endblocktrans %}
|
||||||
<div class="column is-relative">
|
|
||||||
<h4 class="card-super-title" title="{{ object.start }}">
|
|
||||||
{% if is_primary %}
|
|
||||||
<span class="fas fa-play"></span>
|
|
||||||
<time datetime="{{ object.start }}">
|
|
||||||
{% translate "Currently" %}
|
|
||||||
</time>
|
|
||||||
{% else %}
|
|
||||||
{{ object.start|date:"H:i" }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if object.episode.category %}
|
|
||||||
// {{ object.episode.category.title }}
|
|
||||||
{% endif %}
|
|
||||||
</h4>
|
|
||||||
{% include object.item_template_name %}
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
{% with next_diffs.0 as obj %}
|
||||||
|
{% page_widget "wide" obj.episode diffusion=obj timetable=True %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<a-carousel section-class="card-grid">
|
||||||
|
{% for obj in next_diffs|slice:"1:" %}
|
||||||
|
{% if object != diffusion %}
|
||||||
|
{% page_widget "card" obj.episode diffusion=obj timetable=True %}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endwith %}
|
</a-carousel>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if object_list %}
|
|
||||||
<h4 class="title is-4">{% translate "Today" %}</h4>
|
|
||||||
<section role="list">
|
|
||||||
{% include 'aircox/widgets/diffusion_list.html' %}
|
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pagination %}
|
{% if logs %}
|
||||||
<ul class="pagination-list">
|
<section class="container">
|
||||||
<li>
|
<h2 class="title">{% translate "It just happened" %}</h2>
|
||||||
<a href="{% url "page-list" %}" class="pagination-link"
|
|
||||||
aria-label="{% translate "Show all publication" %}">
|
<div class="grid" role="list">
|
||||||
{% translate "More publications..." %}
|
{% include "./widgets/logs.html" with object_list=logs %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="nav-urls">
|
||||||
|
<a href="{% url "timetable-list" %}"
|
||||||
|
aria-label="{% translate "Show all program's for today" %}">
|
||||||
|
{% translate "Today" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</nav>
|
||||||
</ul>
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if podcasts %}
|
||||||
|
<section class="container">
|
||||||
|
<h2 class="title is-3 p-2">{% translate "Last podcasts" %}</h2>
|
||||||
|
{% include "./widgets/carousel.html" with objects=podcasts url_name="podcast-list" url_label=_("All podcasts") %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if publications %}
|
||||||
|
<section class="container">
|
||||||
|
<h2 class="title">{% translate "Last publications" %}</h2>
|
||||||
|
{% include "./widgets/carousel.html" with objects=publications url_name="page-list" url_label=_("All publications") %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block pages_list %}{% 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 %}
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
{% extends "aircox/page_list.html" %}
|
|
||||||
{% comment %}List of logs for a specific date{% endcomment %}
|
|
||||||
{% load i18n humanize aircox %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% if not page or not page.title %}
|
|
||||||
{% with station.name as station %}
|
|
||||||
{% blocktranslate %}That happened on {{ station }}{% endblocktranslate %}
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
|
|
||||||
|
|
||||||
{% block before_list %}
|
|
||||||
{% with "log-list" as url_name %}
|
|
||||||
{% include "aircox/widgets/dates_menu.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pages_list %}
|
|
||||||
<section>
|
|
||||||
{# <h4 class="subtitle size-4">{{ date }}</h4> #}
|
|
||||||
{% include "aircox/widgets/log_list.html" %}
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
|
@ -6,15 +6,10 @@ Base template used to display a Page
|
||||||
Context:
|
Context:
|
||||||
- page: page
|
- page: page
|
||||||
- parent: parent page
|
- parent: parent page
|
||||||
|
- related_objects: list of object to display as related publications
|
||||||
|
- related_url: url to the full list of related_objects
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% block header_crumbs %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if page.category %}
|
|
||||||
{% if parent %} / {% endif %} {{ page.category.title }}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block top-nav-tools %}
|
{% block top-nav-tools %}
|
||||||
{% has_perm page "change" as can_edit %}
|
{% has_perm page "change" as can_edit %}
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
|
@ -28,61 +23,80 @@ Context:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{% if parent %}
|
||||||
|
{% include "./widgets/breadcrumbs.html" with page=parent %}
|
||||||
|
{% if page %}
|
||||||
|
<a href="{% url page.list_url_name parent_slug=parent.slug %}">
|
||||||
|
{{ page|verbose_name:True }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% elif page %}
|
||||||
|
{% include "./widgets/breadcrumbs.html" with page=page no_title=True %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
{% block comments %}
|
{% block related %}
|
||||||
{% if comments or comment_form %}
|
{% if related_objects %}
|
||||||
<section class="mt-6">
|
<section class="container">
|
||||||
<h4 class="title is-4">{% translate "Comments" %}</h4>
|
{% with models=object|verbose_name:True %}
|
||||||
|
<h3 class="title">{% blocktranslate %}Related {{models}}{% endblocktranslate %}</h3>
|
||||||
|
|
||||||
{% for comment in comments %}
|
{% include "./widgets/carousel.html" with objects=related_objects url_name=object.list_url_name url_category=object.category %}
|
||||||
<div class="media box">
|
{% endwith %}
|
||||||
<div class="media-content">
|
</section>
|
||||||
<p>
|
{% endif %}
|
||||||
<strong class="mr-2">{{ comment.nickname }}</strong>
|
{% endblock %}
|
||||||
<time datetime="{{ comment.date }}" title="{{ comment.date }}">
|
|
||||||
<small>{{ comment.date|naturaltime }}</small>
|
{% block comments %}
|
||||||
</time>
|
{% if comments %}
|
||||||
<br>
|
<section class="container">
|
||||||
{{ comment.content }}
|
<h2 class="title">{% translate "Comments" %}</h2>
|
||||||
</p>
|
|
||||||
</div>
|
{% for object in comments %}
|
||||||
</div>
|
{% page_widget "item" object %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if comment_form %}
|
{% if comment_form %}
|
||||||
|
<section class="container">
|
||||||
|
<h2 class="title">{% translate "Post a comment" %}</h2>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<h5 class="title is-5">{% translate "Post a comment" %}</h5>
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% render_honeypot_field "website" %}
|
{% render_honeypot_field "website" %}
|
||||||
|
|
||||||
{% for field in comment_form %}
|
|
||||||
<div class="field is-horizontal">
|
|
||||||
<div class="field-label is-normal">
|
|
||||||
<label class="label">
|
|
||||||
{{ field.label_tag }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<p class="control is-expanded">{{ field }}</p>
|
<div class="control">
|
||||||
|
{{ comment_form.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for field in comment_form %}
|
||||||
|
{% if field.name != "content" %}
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<label class="label">{{ field.label }}</label>
|
||||||
|
<div class="control">{{ field }}</div>
|
||||||
|
</div>
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<p class="help is-danger">{{ field.errors }}</p>
|
<p class="help is-danger">{{ field.errors }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.help_text %}
|
{% if field.help_text %}
|
||||||
<p class="help">{{ field.help_text|safe }}</p>
|
<p class="help">{{ field.help_text|safe }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endif %}
|
||||||
</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="submit" class="button">{% translate "Post comment" %}</button>
|
||||||
<button type="submit" class="button is-success">{% translate "Post comment" %}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -2,61 +2,60 @@
|
||||||
{% 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 }}
|
{% if not parent and categories %}
|
||||||
|
<nav class="nav secondary">
|
||||||
{% if view.has_filters and object_list %}
|
<div class="nav-menu nav-categories">
|
||||||
<form method="GET" action="" class="media">
|
{% for cat in categories %}
|
||||||
<div class="media-content">
|
<a class="nav-item{% if cat == category %} active{% endif %}"
|
||||||
{% block filters %}
|
href="{% url request.resolver_match.url_name category_slug=cat.slug %}">
|
||||||
<div class="field is-horizontal">
|
{{ cat.title }}
|
||||||
<div class="field-label">
|
</a>
|
||||||
<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 %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<a-switch class="button burger"
|
||||||
</div>
|
el=".nav-categories" group="nav" icon="fas fa-tags"
|
||||||
</div>
|
aria-label="{% translate "Categories" %}">
|
||||||
{% endblock %}
|
</a-switch>
|
||||||
</div>
|
</nav>
|
||||||
<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>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if parent %}{{ parent.title }}
|
||||||
|
{% else %}{{ block.super }}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{% if page and not object %}
|
||||||
|
{% with page as object %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{% if parent and model.list_url_name %}
|
||||||
|
{% include "./widgets/breadcrumbs.html" with page=parent %}
|
||||||
|
<a href="{% url model.list_url_name %}">{{ model|verbose_name:True }}</a>
|
||||||
|
{% elif page and model.list_url_name %}
|
||||||
|
<a href="{% url model.list_url_name %}">{{ page.title }}</a>
|
||||||
|
{% if category %}
|
||||||
|
<a href="{% url request.resolver_match.url_name category_slug=category.slug %}">
|
||||||
|
{{ category.title }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url request.resolver_match.url_name %}">{{ model|verbose_name:True }}</a>
|
||||||
|
{% if category %}
|
||||||
|
<a href="{% url request.resolver_match.url_name category_slug=category.slug %}">
|
||||||
|
{{ category.title }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content-container %}{% endblock %}
|
||||||
|
|
|
@ -1,49 +1,15 @@
|
||||||
{% 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" %}
|
{% block content-container %}
|
||||||
|
{% with schedules=program.schedule_set.all %}
|
||||||
|
{% if schedules %}
|
||||||
{% block header_nav %}
|
<header class="container schedules">
|
||||||
{% endblock %}
|
{% for schedule in schedules %}
|
||||||
|
<div class="schedule">
|
||||||
|
<div class="heading">
|
||||||
{% block content %}
|
<span class="day">{{ schedule.get_frequency_display }}</span>
|
||||||
{{ block.super }}
|
|
||||||
<br>
|
|
||||||
{% with has_headline=False %}
|
|
||||||
{% if articles %}
|
|
||||||
<section>
|
|
||||||
<h4 class="title is-4">{% translate "Articles" %}</h4>
|
|
||||||
|
|
||||||
{% for object in articles %}
|
|
||||||
{% include "aircox/widgets/page_item.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<nav class="pagination is-centered">
|
|
||||||
<ul class="pagination-list">
|
|
||||||
<li>
|
|
||||||
<a href="{% url "article-list" parent_slug=program.slug %}"
|
|
||||||
class="pagination-link"
|
|
||||||
aria-label="{% translate "Show all program's articles" %}">
|
|
||||||
{% translate "More articles" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
<section>
|
|
||||||
<h4 class="title is-4">{% translate "Diffusions" %}</h4>
|
|
||||||
{% for schedule in program.schedule_set.all %}
|
|
||||||
{{ schedule.get_frequency_display }}
|
|
||||||
{% with schedule.start|date:"H:i" as start %}
|
{% with schedule.start|date:"H:i" as start %}
|
||||||
{% with schedule.end|date:"H:i" as end %}
|
{% with schedule.end|date:"H:i" as end %}
|
||||||
<time datetime="{{ start }}">{{ start }}</time>
|
<time datetime="{{ start }}">{{ start }}</time>
|
||||||
|
@ -52,7 +18,7 @@
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<small>
|
<small>
|
||||||
{% if schedule.initial %}
|
{% if schedule.is_rerun %}
|
||||||
{% with schedule.initial.date as date %}
|
{% with schedule.initial.date as date %}
|
||||||
<span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
|
<span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
|
||||||
({% translate "Rerun" %})
|
({% translate "Rerun" %})
|
||||||
|
@ -60,8 +26,28 @@
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</small>
|
</small>
|
||||||
<br>
|
</div>
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</header>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
{% if episodes %}
|
||||||
|
<section class="container">
|
||||||
|
<h3 class="title">{% translate "Last Episodes" %}</h3>
|
||||||
|
{% include "./widgets/carousel.html" with objects=episodes url_name="episode-list" url_parent=object url_label=_("All episodes") %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if articles %}
|
||||||
|
<section class="container">
|
||||||
|
<h3 class="title">{% translate "Last Articles" %}</h3>
|
||||||
|
{% include "./widgets/carousel.html" with objects=articles url_name="article-list" url_parent=object url_label=_("All articles") %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
|
|
||||||
{% block sidebar_title %}
|
|
||||||
{% with program.title as program %}
|
|
||||||
{% blocktranslate %}Recently on {{ program }}{% endblocktranslate %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
28
aircox/templates/aircox/timetable_list.html
Normal file
28
aircox/templates/aircox/timetable_list.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends "aircox/page_list.html" %}
|
||||||
|
{% comment %}List of diffusions as a timetable{% endcomment %}
|
||||||
|
{% load i18n aircox humanize %}
|
||||||
|
|
||||||
|
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
|
||||||
|
|
||||||
|
{% block secondary-nav %}
|
||||||
|
<nav class="nav secondary">
|
||||||
|
{% include "./widgets/dates_menu.html" with url_name=view.redirect_date_url %}
|
||||||
|
</nav>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
<a href="{% url "timetable-list" date=date %}">{{ date|date:"l d F Y" }}</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block list-container %}
|
||||||
|
{% with list_class="grid" %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block list %}
|
||||||
|
{% include "./widgets/logs.html" with object_list=object_list timetable=True %}
|
||||||
|
{% endblock %}
|
4
aircox/templates/aircox/widgets/article.html
Normal file
4
aircox/templates/aircox/widgets/article.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends "./page.html" %}
|
||||||
|
{% load humanize %}
|
||||||
|
|
||||||
|
{% block subtitle %}{{ object.pub_date.date }}{% endblock %}
|
|
@ -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">
|
<div class="">
|
||||||
<a href="{{ object.get_absolute_url }}">
|
<div>
|
||||||
{% block card_title %}{{ object.title }}{% endblock %}
|
<h2 class="heading title">{% block title %}{% endblock %}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<summary class="heading-container">
|
||||||
|
{% block content %}
|
||||||
|
{% if content and with_content %}
|
||||||
|
{% autoescape off %}
|
||||||
|
{{ content|striptags|truncatewords:64|linebreaks }}
|
||||||
|
{% endautoescape %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
{% block actions %}
|
||||||
|
<a class="button 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>
|
</a>
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<article class="media item {% block css %}{% endblock%}">
|
|
||||||
{% if has_cover|default_if_none:True %}
|
|
||||||
<div class="media-left">
|
|
||||||
{% if is_thin %}
|
|
||||||
<img src="{% thumbnail object.cover|default:station.default_cover 64x64 crop=scale %}"
|
|
||||||
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 %}
|
|
||||||
{% endblock %}
|
|
||||||
</h5>
|
|
||||||
<div class="subtitle is-6 has-text-weight-light">
|
|
||||||
{% block subtitle %}
|
|
||||||
{% if object.category %}{{ object.category.title }}{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if has_headline|default_if_none:True %}
|
|
||||||
<div class="headline">
|
|
||||||
{% block headline %}{{ object.headline }}{% endblock %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% if not no_actions %}
|
{% if with_container %}
|
||||||
{% block actions %}{% endblock %}
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
{% endif %}
|
{% endblock %}
|
||||||
|
|
15
aircox/templates/aircox/widgets/breadcrumbs.html
Normal file
15
aircox/templates/aircox/widgets/breadcrumbs.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% load aircox %}
|
||||||
|
|
||||||
|
<a href="{% url page.list_url_name %}">
|
||||||
|
{{ page|verbose_name:True }}
|
||||||
|
</a>
|
||||||
|
{% if page.category and not no_cat %}
|
||||||
|
<a href="{% url page.list_url_name category_slug=page.category.slug %}">
|
||||||
|
{{ page.category.title }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if not no_title %}
|
||||||
|
<a href="{{ page.get_absolute_url }}">
|
||||||
|
{{ page.title|truncatechars:24 }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
23
aircox/templates/aircox/widgets/card.html
Normal file
23
aircox/templates/aircox/widgets/card.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "./preview.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block tag-class %}{{ block.super }} preview-card{% endblock %}
|
||||||
|
|
||||||
|
{% block inner %}
|
||||||
|
<div class="card-content">
|
||||||
|
{% if cover %}
|
||||||
|
{% if url %}<a href="{{ url }}">{% endif %}
|
||||||
|
<figure style="background-image: url({{ cover }});" class="preview-cover">
|
||||||
|
<img src="{{ cover }}" class="hide">
|
||||||
|
</figure>
|
||||||
|
{% if url %}</a>{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<footer class="actions">
|
||||||
|
{% block actions %}{{ block.super }}{% endblock %}
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block headings-container %}{{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% endblock %}
|
28
aircox/templates/aircox/widgets/carousel.html
Normal file
28
aircox/templates/aircox/widgets/carousel.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{% load aircox %}
|
||||||
|
{% comment %}
|
||||||
|
Context:
|
||||||
|
- objects: list of objects to display
|
||||||
|
- url_name: url name to show the full list
|
||||||
|
- url_parent: parent page for the full list
|
||||||
|
- url_label: label of url button
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
<a-carousel>
|
||||||
|
{% for object in objects %}
|
||||||
|
{% page_widget "card" object %}
|
||||||
|
{% endfor %}
|
||||||
|
</a-carousel>
|
||||||
|
|
||||||
|
{% if url_name %}
|
||||||
|
<nav class="nav-urls">
|
||||||
|
{% if url_parent %}
|
||||||
|
<a href="{% url url_name parent_slug=url_parent.slug %}">
|
||||||
|
{% elif url_category %}
|
||||||
|
<a href="{% url url_name category_slug=url_category.slug %}">
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url url_name %}">
|
||||||
|
{% endif %}
|
||||||
|
{{ url_label|default:_("Show all") }}
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
51
aircox/templates/aircox/widgets/comment.html
Normal file
51
aircox/templates/aircox/widgets/comment.html
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{% extends "./page.html" %}
|
||||||
|
{% load i18n humanize aircox %}
|
||||||
|
|
||||||
|
{% block tag-class %}{{ block.super }} comment{% endblock %}
|
||||||
|
{% block tag-extra %} id="comment-{{ object.pk }}"{% endblock %}
|
||||||
|
|
||||||
|
{% block outer %}
|
||||||
|
{% with url=object.get_absolute_url %}
|
||||||
|
{% if with_title %}
|
||||||
|
{{ block.super }}
|
||||||
|
{{ block.super }}
|
||||||
|
{% else %}
|
||||||
|
{{ block.super }}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ object.nickname }} — {{ 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 %}
|
||||||
|
<a href="{% url "admin:aircox_comment_change" object.pk %}" class="button"
|
||||||
|
title="{% trans "Edit comment" %}"
|
||||||
|
aria-label="{% trans "Edit comment" %}">
|
||||||
|
<span class="fa fa-edit"></span>
|
||||||
|
</a>
|
||||||
|
<a class="button is-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> #}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -11,36 +11,33 @@ An empty date results to a title or a separator
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<div class="media" role="menu"
|
<a-switch class="button burger"
|
||||||
aria-label="{% translate "pick a date" %}">
|
el=".nav-dates" icon="far fa-calendar" group="nav"
|
||||||
<div class="media-content">
|
aria-label="{% translate "Dates" %}">
|
||||||
<div class="tabs is-toggle">
|
</a-switch>
|
||||||
<ul>
|
|
||||||
{% 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">
|
<div class="nav-menu nav-dates">
|
||||||
<form action="{% url url_name %}" method="GET" class="navbar-body"
|
{% for day in dates %}
|
||||||
aria-label="{% translate "Jump to date" %}">
|
<a href="{% url url_name date=day %}" class="nav-item {% if day == date %}active{% endif %}">
|
||||||
<div class="field has-addons">
|
{{ day|date:"l d" }}
|
||||||
<div class="control has-icons-left">
|
</a>
|
||||||
<span class="icon is-small is-left"><span class="far fa-calendar"></span></span>
|
{% endfor %}
|
||||||
<input type="{{ date_input|default:"date" }}" class="input date"
|
|
||||||
name="date" value="{{ date|date:"Y-m-d" }}">
|
<a-dropdown class="nav-item align-right flex-grow-0 dropdown is-right"
|
||||||
</div>
|
content-class="dropdown-menu"
|
||||||
<div class="control">
|
button-tag="span" button-class="dropdown-trigger"
|
||||||
{% comment %}Translators: form button to select a date{% endcomment %}
|
button-icon-open="fa-solid fa-plus" button-icon-close="fa-solid fa-minus">
|
||||||
<button class="button is-primary">{% translate "Go" %}</button>
|
<template #default>
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<div class="dropdown-item">
|
||||||
|
<h4>{% translate "Pick a date" %}</h4>
|
||||||
|
<v-calendar mode="date" borderless
|
||||||
|
:initial-page="{month: {{date.month}}, year: {{date.year}}}"
|
||||||
|
@dayclick="(event) => window.aircox.pickDate({% url url_name %}, event)"
|
||||||
|
color="yellow"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</template>
|
||||||
</div>
|
</a-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
68
aircox/templates/aircox/widgets/episode.html
Normal file
68
aircox/templates/aircox/widgets/episode.html
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{% extends "./page.html" %}
|
||||||
|
{% load i18n humanize aircox %}
|
||||||
|
|
||||||
|
{% block outer %}
|
||||||
|
{% with diffusion.is_now as is_active %}
|
||||||
|
{% if admin %}
|
||||||
|
{% with object|admin_url:"change" as url %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{% if diffusion %}
|
||||||
|
{% if timetable %}
|
||||||
|
{{ diffusion.start|date:"H:i" }}
|
||||||
|
—
|
||||||
|
{{ 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 admin and diffusion %}
|
||||||
|
{% if diffusion.type == diffusion.TYPE_ON_AIR %}
|
||||||
|
<span class="tag is-info">
|
||||||
|
<span class="icon is-small">
|
||||||
|
{% if diffusion.is_live %}
|
||||||
|
<i class="fa fa-microphone"
|
||||||
|
title="{% translate "Live diffusion" %}"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fa fa-music"
|
||||||
|
title="{% translate "Differed diffusion" %}"></i>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{ diffusion.get_type_display }}
|
||||||
|
</span>
|
||||||
|
{% elif diffusion.type == diffusion.TYPE_CANCEL %}
|
||||||
|
<span class="tag is-danger">
|
||||||
|
{{ diffusion.get_type_display }}</span>
|
||||||
|
{% elif diffusion.type == diffusion.TYPE_UNCONFIRMED %}
|
||||||
|
<span class="tag is-warning">
|
||||||
|
{{ diffusion.get_type_display }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% 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 %}
|
|
@ -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 %}
|
|
||||||
—
|
|
||||||
{{ diffusion.start|date:"d F" }}
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
</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 %}—{% 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 %}
|
||||||
|
|
42
aircox/templates/aircox/widgets/item.html
Normal file
42
aircox/templates/aircox/widgets/item.html
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{% extends "./preview.html" %}
|
||||||
|
{% load i18n aircox %}
|
||||||
|
|
||||||
|
{% block tag-class %}{{ block.super }} list-item is-fullwidth{% endblock %}
|
||||||
|
|
||||||
|
{% block headings %}
|
||||||
|
<a href="{{ url|escape }}" class="heading title {% block title-class %}{% endblock %}">
|
||||||
|
{% block title %}{{ title|default:"" }}{% endblock %}
|
||||||
|
</a>
|
||||||
|
<span class="heading subtitle {% block subtitle-class %}{% endblock %}">
|
||||||
|
{% block subtitle %}{{ subtitle|default:"" }}{% endblock %}
|
||||||
|
</span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block inner %}
|
||||||
|
{% block headings-container %}{{ block.super }}{% endblock %}
|
||||||
|
{% block content-container %}
|
||||||
|
<div class="media">
|
||||||
|
{% if object.cover %}
|
||||||
|
<a href="{{ object.get_absolute_url }}"
|
||||||
|
class="media-left preview-cover small"
|
||||||
|
style="background-image: url({{ object.cover.url }})">
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<div class="media-content flex-column">
|
||||||
|
<section class="content flex-grow-1">
|
||||||
|
{% block content %}
|
||||||
|
{% if content and with_content %}
|
||||||
|
{% autoescape off %}
|
||||||
|
{{ content|striptags|linebreaks }}
|
||||||
|
{% endautoescape %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</section>
|
||||||
|
{% block actions-container %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% endblock %}
|
23
aircox/templates/aircox/widgets/log.html
Normal file
23
aircox/templates/aircox/widgets/log.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% load i18n aircox %}
|
||||||
|
{% comment %}
|
||||||
|
List item for a log, either for a logged track or diffusion (as diffusion).
|
||||||
|
|
||||||
|
Context objects:
|
||||||
|
- object: object to render
|
||||||
|
- hide_schedule: if true, hide the schedule
|
||||||
|
|
||||||
|
In case of modification, you might want to check on `assets/vue/player.vue`
|
||||||
|
for design review.
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% block outer %}
|
||||||
|
{% if object|is_diffusion %}
|
||||||
|
{% page_widget widget object.episode diffusion=object timetable=timetable|default:False %}
|
||||||
|
{% elif object|is_log %}
|
||||||
|
{% include "./track_item.html" with object=object.track log=object timetable=timetable|default:False %}
|
||||||
|
{% else %}
|
||||||
|
{% for obj in object %}
|
||||||
|
{% include "./track_item.html" with object=obj.track log=obj timetable=timetable|default:False %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -1,22 +0,0 @@
|
||||||
{% load i18n aircox %}
|
|
||||||
{% comment %}
|
|
||||||
List item for a log, either for a logged track or diffusion (as diffusion).
|
|
||||||
|
|
||||||
Context objects:
|
|
||||||
- object: object to render
|
|
||||||
- hide_schedule: if true, hide the schedule
|
|
||||||
|
|
||||||
In case of modification, you might want to check on `assets/vue/player.vue`
|
|
||||||
for design review.
|
|
||||||
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% if object|is_diffusion %}
|
|
||||||
{% with object as diffusion %}
|
|
||||||
{% include "aircox/widgets/diffusion_item.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
{% with object.track as object %}
|
|
||||||
{% include "aircox/widgets/track_item.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
|
@ -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 %}
|
|
35
aircox/templates/aircox/widgets/logs.html
Normal file
35
aircox/templates/aircox/widgets/logs.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{% comment %}
|
||||||
|
Context:
|
||||||
|
- object_list: list of logs
|
||||||
|
- timetable: defaults to False
|
||||||
|
- widget: defaults to "item"
|
||||||
|
{% endcomment %}
|
||||||
|
{% load aircox %}
|
||||||
|
|
||||||
|
{% with timetable|default:False as timetable %}
|
||||||
|
{% with widget|default:"item" as widget %}
|
||||||
|
{% for object in object_list %}
|
||||||
|
{% if object.episode %}
|
||||||
|
{% page_widget widget object.episode diffusion=object timetable=True %}
|
||||||
|
{% elif object|is_log %}
|
||||||
|
{% include "./track_item.html" with object=object.track log=object timetable=True %}
|
||||||
|
{% else %}
|
||||||
|
<div class="preview list-item logs">
|
||||||
|
<header class="headings">
|
||||||
|
<span class="heading title">
|
||||||
|
<span class="icon pr-2">
|
||||||
|
<i class="fas fa-music"></i>
|
||||||
|
</span>
|
||||||
|
{{ station.music_stream_title }}
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
<div class="media d-block content">
|
||||||
|
{% for obj in object %}
|
||||||
|
{% include "./track_item.html" with object=obj.track log=obj timetable=True %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endwith %}
|
43
aircox/templates/aircox/widgets/page.html
Normal file
43
aircox/templates/aircox/widgets/page.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{% extends widget_template %}
|
||||||
|
{% load i18n aircox %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block outer %}
|
||||||
|
{% with cover|default:object.cover_url as cover %}
|
||||||
|
{% if admin %}
|
||||||
|
{% with object|admin_url:"change" as url %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
{% with url|default:object.get_absolute_url as url %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% 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 %}
|
||||||
|
|
||||||
|
{% block actions %}
|
||||||
|
{% if url and "card" not in widget_template %}
|
||||||
|
<a href="{{ url }}">{% translate "Show" %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
13
aircox/templates/aircox/widgets/page_card.html
Normal file
13
aircox/templates/aircox/widgets/page_card.html
Normal 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 %}
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -5,7 +5,7 @@ The audio player
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="box is-fullwidth is-fixed-bottom is-paddingless player"
|
<div class="is-fullwidth is-fixed-bottom is-paddingless player-container"
|
||||||
role="{% translate "player" %}"
|
role="{% translate "player" %}"
|
||||||
aria-description="{% translate "Audio player used to listen to the radio and podcasts" %}">
|
aria-description="{% translate "Audio player used to listen to the radio and podcasts" %}">
|
||||||
<noscript>
|
<noscript>
|
||||||
|
@ -22,24 +22,29 @@ The audio player
|
||||||
:live-args="{% player_live_attr %}"
|
:live-args="{% player_live_attr %}"
|
||||||
button-title="{% translate "Play or pause audio" %}">
|
button-title="{% translate "Play or pause audio" %}">
|
||||||
<template v-slot:content="{ loaded, live, current }">
|
<template v-slot:content="{ loaded, live, current }">
|
||||||
<h4 v-if="loaded" class="title is-4">
|
<h4 v-if="loaded" class="title">
|
||||||
|
<a v-if="current?.data?.page_url" :href="current.data.page_url">
|
||||||
[[ loaded.name ]]
|
[[ loaded.name ]]
|
||||||
|
</a>
|
||||||
|
<template v-else>[[ loaded.name ]]</template>
|
||||||
</h4>
|
</h4>
|
||||||
<h4 v-else-if="current && current.data.type == 'track'"
|
<h4 v-else-if="current && current.data.type == 'track'"
|
||||||
class="title is-4" aria-description="{% translate "Track currently on air" %}">
|
class="title" aria-description="{% translate "Track currently on air" %}">
|
||||||
<span class="has-text-info is-size-3">♬</span>
|
<span class="icon secondary-color mr-3">
|
||||||
|
<i class="fas fa-music"></i>
|
||||||
|
</span>
|
||||||
<span>[[ current.data.title ]]</span>
|
<span>[[ current.data.title ]]</span>
|
||||||
<span class="has-text-grey-dark has-text-weight-light">
|
<span class="has-text-grey-dark has-text-weight-light">
|
||||||
— [[ current.data.artist ]]
|
— [[ current.data.artist ]]
|
||||||
<i v-if="current.data.info">([[ current.data.info ]])</i>
|
<i v-if="current.data.info">([[ current.data.info ]])</i>
|
||||||
</span>
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
<div v-else-if="live && current && current.data.type == 'diffusion'">
|
<h4 v-else-if="live && current && current.data.type == 'diffusion'"
|
||||||
<h4 class="title is-4" aria-description="{% translate "Diffusion currently on air" %}">
|
class="title"
|
||||||
<a :href="current.data.url">[[ current.data.title ]]</a>
|
aria-description="{% translate "Diffusion currently on air" %}">
|
||||||
|
<a :href="current.data.url" v-if="current.data.url">[[ current.data.title ]]</a>
|
||||||
|
<template v-else>[[ current.data.title ]]</template>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="">[[ current.data.info ]]</div>
|
|
||||||
</div>
|
|
||||||
<h4 v-else class="title is-4" aria-description="{% translate "Currently playing" %}">
|
<h4 v-else class="title is-4" aria-description="{% translate "Currently playing" %}">
|
||||||
{{ request.station.name }}
|
{{ request.station.name }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
59
aircox/templates/aircox/widgets/preview.html
Normal file
59
aircox/templates/aircox/widgets/preview.html
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% comment %}
|
||||||
|
Content related context:
|
||||||
|
- object: object to display
|
||||||
|
- cover: cover
|
||||||
|
- title: title
|
||||||
|
- subtitle: subtitle
|
||||||
|
- content: content to display
|
||||||
|
|
||||||
|
Styling related context:
|
||||||
|
- is_active: add "active" css class
|
||||||
|
- is_small: add "small" css class
|
||||||
|
- is_tiny: add "tiny" css class
|
||||||
|
- tag
|
||||||
|
- tag_class: css class to set to main tag
|
||||||
|
- tag_extra: extra tag attributes
|
||||||
|
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% block outer %}
|
||||||
|
<{{ tag|default:"article" }} class="preview {% if not cover %}no-cover {% endif %}{% if is_active %}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 %}
|
||||||
|
{% block title-container %}
|
||||||
|
<a href="{{ url|escape }}" class="heading title {% block title-class %}{% endblock %}"{% if title %} title="{{ title|escape }}"{% endif %}>
|
||||||
|
{% block title %}{{ title|default:"" }}{% endblock %}
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
||||||
|
{% block subtitle-container %}
|
||||||
|
<span class="heading subtitle {% block subtitle-class %}{% endblock %}">
|
||||||
|
{% block subtitle %}{{ subtitle|default:"" }}{% endblock %}
|
||||||
|
</span>
|
||||||
|
{% endblock %}
|
||||||
|
{% 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 %}
|
||||||
|
</{{ tag|default:"article" }}>
|
||||||
|
{% endblock %}
|
|
@ -5,9 +5,20 @@ Context:
|
||||||
- object: track to render
|
- object: track to render
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
<span class="has-text-info is-size-5">♬</span>
|
<span class="track">
|
||||||
<span>{{ object.title }}</span>
|
<span class="icon secondary-color">
|
||||||
<span class="has-text-grey-dark has-text-weight-light">
|
<i class="fas fa-music"></i>
|
||||||
|
</span>
|
||||||
|
<label>
|
||||||
|
{% if log %}
|
||||||
|
<span>{{ log.date|date:"H:i" }} — </span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="has-text-weight-bold">{{ object.title }}</span>
|
||||||
|
{% if object.artist and object.artist != object.title %}
|
||||||
|
<span>
|
||||||
— {{ object.artist }}
|
— {{ object.artist }}
|
||||||
{% if object.info %}(<i>{{ object.info }}</i>){% endif %}
|
{% if object.info %}(<i>{{ object.info }}</i>){% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
|
43
aircox/templates/aircox/widgets/wide.html
Normal file
43
aircox/templates/aircox/widgets/wide.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{% extends "./preview.html" %}
|
||||||
|
{% load i18n aircox %}
|
||||||
|
|
||||||
|
{% block tag-class %}{{ block.super }} list-item wide is-fullwidth{% endblock %}
|
||||||
|
|
||||||
|
{% block headings %}
|
||||||
|
<a href="{{ url|escape }}" class="heading title {% block title-class %}{% endblock %}">
|
||||||
|
{% block title %}{{ title|default:"" }}{% endblock %}
|
||||||
|
</a>
|
||||||
|
<span class="heading subtitle {% block subtitle-class %}{% endblock %}">
|
||||||
|
{% block subtitle %}{{ subtitle|default:"" }}{% endblock %}
|
||||||
|
</span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block inner %}
|
||||||
|
{% block content-container %}
|
||||||
|
<div class="media">
|
||||||
|
{% if object.cover %}
|
||||||
|
<a href="{{ object.get_absolute_url }}"
|
||||||
|
class="media-left preview-cover"
|
||||||
|
style="background-image: url({{ object.cover.url }})">
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<div class="media-content">
|
||||||
|
{% block headings-container %}{{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
<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 %}
|
|
@ -3,14 +3,41 @@ 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.filter(name="admin_url")
|
||||||
|
def admin_url(obj, action):
|
||||||
|
meta = obj._meta
|
||||||
|
return reverse(f"admin:{meta.app_label}_{meta.model_name}_{action}", args=[obj.id])
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
if object.pk and not ctx.get("tag_id"):
|
||||||
|
model = type(object)._meta.model_name
|
||||||
|
ctx["tag_id"] = f"{widget}_{model}_{object.pk}"
|
||||||
|
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."""
|
||||||
|
@ -43,6 +70,12 @@ def do_is_diffusion(obj):
|
||||||
return isinstance(obj, Diffusion)
|
return isinstance(obj, Diffusion)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="is_log")
|
||||||
|
def do_is_log(obj):
|
||||||
|
"""Return True if object is a Diffusion."""
|
||||||
|
return isinstance(obj, Log)
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name="json")
|
@register.filter(name="json")
|
||||||
def do_json(obj, fields=""):
|
def do_json(obj, fields=""):
|
||||||
"""Return object as json."""
|
"""Return object as json."""
|
||||||
|
@ -70,9 +103,19 @@ def do_nav_items(context, menu, **kwargs):
|
||||||
return [(item, item.render(request, **kwargs)) for item in station.navitem_set.filter(menu=menu)]
|
return [(item, item.render(request, **kwargs)) for item in station.navitem_set.filter(menu=menu)]
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="nav_active")
|
||||||
|
def do_nav_active(obj, request):
|
||||||
|
if request.path.startswith(obj.get_url()):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(name="update_query")
|
@register.simple_tag(name="update_query")
|
||||||
def do_update_query(obj, **kwargs):
|
def do_update_query(obj, **kwargs):
|
||||||
"""Replace provided querydict's values with **kwargs."""
|
"""Replace provided querydict's values with **kwargs.
|
||||||
|
|
||||||
|
Values set to ``None`` will be dropped.
|
||||||
|
"""
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
if v is not None:
|
if v is not None:
|
||||||
obj[k] = list(v) if hasattr(v, "__iter__") else [v]
|
obj[k] = list(v) if hasattr(v, "__iter__") else [v]
|
||||||
|
@ -85,4 +128,6 @@ def do_update_query(obj, **kwargs):
|
||||||
def do_verbose_name(obj, plural=False):
|
def do_verbose_name(obj, plural=False):
|
||||||
"""Return model's verbose name (singular or plural) or `obj` if it is a
|
"""Return model's verbose name (singular or plural) or `obj` if it is a
|
||||||
string (can act for default values)."""
|
string (can act for default values)."""
|
||||||
return obj if isinstance(obj, str) else obj._meta.verbose_name_plural if plural else obj._meta.verbose_name
|
if isinstance(obj, str):
|
||||||
|
return obj
|
||||||
|
return obj._meta.verbose_name_plural if plural else obj._meta.verbose_name
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from aircox import models
|
from aircox import models
|
||||||
from aircox.test import Interface
|
from aircox.test import Interface
|
||||||
|
@ -37,29 +36,13 @@ class TestBaseView:
|
||||||
def test_station(self, base_view, station):
|
def test_station(self, base_view, station):
|
||||||
assert base_view.station == station
|
assert base_view.station == station
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_get_sidebar_queryset(self, base_view, pages, published_pages):
|
|
||||||
query = base_view.get_sidebar_queryset().values_list("id", flat=True)
|
|
||||||
page_ids = {r.id for r in published_pages}
|
|
||||||
assert set(query) == page_ids
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_get_sidebar_url(self, base_view):
|
|
||||||
assert base_view.get_sidebar_url() == reverse("page-list")
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_context_data(self, base_view, station, published_pages):
|
def test_get_context_data(self, base_view, station, published_pages):
|
||||||
base_view.has_sidebar = True
|
|
||||||
base_view.get_sidebar_queryset = lambda: published_pages
|
|
||||||
context = base_view.get_context_data()
|
context = base_view.get_context_data()
|
||||||
assert context == {
|
assert context == {
|
||||||
"view": base_view,
|
"view": base_view,
|
||||||
"station": station,
|
"station": station,
|
||||||
"page": None, # get_page() returns None
|
"page": None, # get_page() returns None
|
||||||
"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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,45 +38,40 @@ api = [
|
||||||
urls = [
|
urls = [
|
||||||
path("", views.HomeView.as_view(), name="home"),
|
path("", views.HomeView.as_view(), name="home"),
|
||||||
path("api/", include((api, "aircox"), namespace="api")),
|
path("api/", include((api, "aircox"), namespace="api")),
|
||||||
# path('', views.PageDetailView.as_view(model=models.Article),
|
# ---- ---- objects views
|
||||||
# name='home'),
|
# ---- articles
|
||||||
|
path(
|
||||||
|
_("articles/<slug:slug>/"),
|
||||||
|
views.ArticleDetailView.as_view(),
|
||||||
|
name="article-detail",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
_("articles/"),
|
_("articles/"),
|
||||||
views.ArticleListView.as_view(model=models.Article),
|
views.ArticleListView.as_view(model=models.Article),
|
||||||
name="article-list",
|
name="article-list",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
_("articles/<slug:slug>/"),
|
_("articles/c/<slug:category_slug>/"),
|
||||||
views.ArticleDetailView.as_view(),
|
views.ArticleListView.as_view(model=models.Article),
|
||||||
name="article-detail",
|
name="article-list",
|
||||||
),
|
),
|
||||||
path(_("episodes/"), views.EpisodeListView.as_view(), name="episode-list"),
|
# ---- timetable
|
||||||
|
path(_("timetable/"), views.TimeTableView.as_view(), name="timetable-list"),
|
||||||
path(
|
path(
|
||||||
_("episodes/<slug:slug>/"),
|
_("timetable/<date:date>/"),
|
||||||
views.EpisodeDetailView.as_view(),
|
views.TimeTableView.as_view(),
|
||||||
name="episode-detail",
|
name="timetable-list",
|
||||||
),
|
),
|
||||||
path(_("week/"), views.DiffusionListView.as_view(), name="diffusion-list"),
|
# ---- pages
|
||||||
path(
|
|
||||||
_("week/<date:date>/"),
|
|
||||||
views.DiffusionListView.as_view(),
|
|
||||||
name="diffusion-list",
|
|
||||||
),
|
|
||||||
path(_("logs/"), views.LogListView.as_view(), name="log-list"),
|
|
||||||
path(_("logs/<date:date>/"), views.LogListView.as_view(), name="log-list"),
|
|
||||||
# path('<page_path:path>', views.route_page, name='page'),
|
|
||||||
path(
|
path(
|
||||||
_("publications/"),
|
_("publications/"),
|
||||||
views.PageListView.as_view(model=models.Page),
|
views.PageListView.as_view(model=models.Page, attach_to_value=models.StaticPage.Target.PAGES),
|
||||||
name="page-list",
|
name="page-list",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
_("pages/"),
|
_("publications/c/<slug:category_slug>"),
|
||||||
views.BasePageListView.as_view(
|
views.PageListView.as_view(model=models.Page, attach_to_value=models.StaticPage.Target.PAGES),
|
||||||
model=models.StaticPage,
|
name="page-list",
|
||||||
queryset=models.StaticPage.objects.filter(attach_to__isnull=True),
|
|
||||||
),
|
|
||||||
name="static-page-list",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
_("pages/<slug:slug>/"),
|
_("pages/<slug:slug>/"),
|
||||||
|
@ -86,27 +81,42 @@ urls = [
|
||||||
),
|
),
|
||||||
name="static-page-detail",
|
name="static-page-detail",
|
||||||
),
|
),
|
||||||
path(_("programs/"), views.ProgramListView.as_view(), name="program-list"),
|
|
||||||
path(
|
path(
|
||||||
_("programs/<slug:slug>/"),
|
_("pages/"),
|
||||||
|
views.BasePageListView.as_view(
|
||||||
|
model=models.StaticPage,
|
||||||
|
queryset=models.StaticPage.objects.filter(attach_to__isnull=True),
|
||||||
|
),
|
||||||
|
name="static-page-list",
|
||||||
|
),
|
||||||
|
# ---- programs
|
||||||
|
path(_("programs/"), views.ProgramListView.as_view(), name="program-list"),
|
||||||
|
path(_("programs/c/<slug:category_slug>/"), views.ProgramListView.as_view(), name="program-list"),
|
||||||
|
path(
|
||||||
|
_("programs/<slug:slug>"),
|
||||||
views.ProgramDetailView.as_view(),
|
views.ProgramDetailView.as_view(),
|
||||||
name="program-detail",
|
name="program-detail",
|
||||||
),
|
),
|
||||||
|
path(_("programs/<slug:parent_slug>/articles"), views.ArticleListView.as_view(), name="article-list"),
|
||||||
|
path(_("programs/<slug:parent_slug>/podcasts"), views.PodcastListView.as_view(), name="podcast-list"),
|
||||||
|
path(_("programs/<slug:parent_slug>/episodes"), views.EpisodeListView.as_view(), name="episode-list"),
|
||||||
|
path(_("programs/<slug:parent_slug>/diffusions"), views.DiffusionListView.as_view(), name="diffusion-list"),
|
||||||
path(
|
path(
|
||||||
_("programs/<slug:parent_slug>/episodes/"),
|
_("programs/<slug:parent_slug>/publications"),
|
||||||
views.EpisodeListView.as_view(),
|
views.PageListView.as_view(model=models.Page, attach_to_value=models.StaticPage.Target.PAGES),
|
||||||
name="episode-list",
|
name="page-list",
|
||||||
),
|
),
|
||||||
|
# ---- episodes
|
||||||
|
path(_("programs/episodes/"), views.EpisodeListView.as_view(), name="episode-list"),
|
||||||
|
path(_("programs/episodes/c/<slug:category_slug>"), views.EpisodeListView.as_view(), name="episode-list"),
|
||||||
path(
|
path(
|
||||||
_("programs/<slug:parent_slug>/articles/"),
|
_("programs/episodes/<slug:slug>"),
|
||||||
views.ArticleListView.as_view(),
|
views.EpisodeDetailView.as_view(),
|
||||||
name="article-list",
|
name="episode-detail",
|
||||||
),
|
|
||||||
path(
|
|
||||||
_("programs/<slug:parent_slug>/publications/"),
|
|
||||||
views.ProgramPageListView.as_view(),
|
|
||||||
name="program-page-list",
|
|
||||||
),
|
),
|
||||||
|
path(_("podcasts/"), views.PodcastListView.as_view(), name="podcast-list"),
|
||||||
|
path(_("podcasts/c/<slug:category_slug>/"), views.PodcastListView.as_view(), name="podcast-list"),
|
||||||
|
# ---- others
|
||||||
path(
|
path(
|
||||||
"errors/no-station",
|
"errors/no-station",
|
||||||
views.errors.NoStationErrorView.as_view(),
|
views.errors.NoStationErrorView.as_view(),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from . import admin, errors
|
from . import admin, errors
|
||||||
from .article import ArticleDetailView, ArticleListView
|
from .article import ArticleDetailView, ArticleListView
|
||||||
from .base import BaseAPIView, BaseView
|
from .base import BaseAPIView, BaseView
|
||||||
from .diffusion import DiffusionListView
|
from .diffusion import DiffusionListView, TimeTableView
|
||||||
from .episode import EpisodeDetailView, EpisodeListView
|
from .episode import EpisodeDetailView, EpisodeListView, PodcastListView
|
||||||
from .home import HomeView
|
from .home import HomeView
|
||||||
from .log import LogListAPIView, LogListView
|
from .log import LogListAPIView, LogListView
|
||||||
from .page import (
|
from .page import (
|
||||||
|
@ -26,8 +26,10 @@ __all__ = (
|
||||||
"BaseAPIView",
|
"BaseAPIView",
|
||||||
"BaseView",
|
"BaseView",
|
||||||
"DiffusionListView",
|
"DiffusionListView",
|
||||||
|
"TimeTableView",
|
||||||
"EpisodeDetailView",
|
"EpisodeDetailView",
|
||||||
"EpisodeListView",
|
"EpisodeListView",
|
||||||
|
"PodcastListView",
|
||||||
"HomeView",
|
"HomeView",
|
||||||
"LogListAPIView",
|
"LogListAPIView",
|
||||||
"LogListView",
|
"LogListView",
|
||||||
|
@ -39,4 +41,15 @@ __all__ = (
|
||||||
"ProgramListView",
|
"ProgramListView",
|
||||||
"ProgramPageDetailView",
|
"ProgramPageDetailView",
|
||||||
"ProgramPageListView",
|
"ProgramPageListView",
|
||||||
|
"attached",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
attached = {}
|
||||||
|
|
||||||
|
for key in __all__:
|
||||||
|
view = globals().get(key)
|
||||||
|
if key == "attached":
|
||||||
|
continue
|
||||||
|
if attach := getattr(view, "attach_to_value", None):
|
||||||
|
attached[attach] = view
|
||||||
|
|
|
@ -5,16 +5,10 @@ __all__ = ["ArticleDetailView", "ArticleListView"]
|
||||||
|
|
||||||
|
|
||||||
class ArticleDetailView(PageDetailView):
|
class ArticleDetailView(PageDetailView):
|
||||||
has_sidebar = True
|
|
||||||
model = Article
|
model = Article
|
||||||
|
|
||||||
def get_sidebar_queryset(self):
|
|
||||||
qs = Article.objects.published().select_related("cover").order_by("-pub_date")
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class ArticleListView(PageListView):
|
class ArticleListView(PageListView):
|
||||||
model = Article
|
model = Article
|
||||||
has_headline = True
|
|
||||||
parent_model = Program
|
parent_model = Program
|
||||||
attach_to_value = StaticPage.ATTACH_TO_ARTICLES
|
attach_to_value = StaticPage.Target.ARTICLES
|
||||||
|
|
|
@ -2,18 +2,14 @@ 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."""
|
related_count = 4
|
||||||
has_filters = False
|
related_carousel_count = 8
|
||||||
"""Show filters nav."""
|
|
||||||
list_count = 5
|
|
||||||
"""Item count for small lists displayed on page."""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def station(self):
|
def station(self):
|
||||||
|
@ -22,12 +18,33 @@ 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):
|
def get_nav_menu(self):
|
||||||
"""Return a queryset of items to render on the side nav."""
|
menu = []
|
||||||
return Page.objects.select_subclasses().published().order_by("-pub_date")
|
for item in self.station.navitem_set.all():
|
||||||
|
try:
|
||||||
|
if item.page:
|
||||||
|
view = item.page.get_related_view()
|
||||||
|
secondary = view and view.get_secondary_nav()
|
||||||
|
else:
|
||||||
|
secondary = None
|
||||||
|
menu.append((item, secondary))
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
|
||||||
def get_sidebar_url(self):
|
traceback.print_exc()
|
||||||
return reverse("page-list")
|
raise
|
||||||
|
return menu
|
||||||
|
|
||||||
|
def get_secondary_nav(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_related_queryset(self):
|
||||||
|
"""Return a queryset of related pages or None."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_related_url(self):
|
||||||
|
"""Return an url to the list of related pages."""
|
||||||
|
return None
|
||||||
|
|
||||||
def get_page(self):
|
def get_page(self):
|
||||||
return None
|
return None
|
||||||
|
@ -35,14 +52,7 @@ class BaseView(TemplateResponseMixin, ContextMixin):
|
||||||
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
|
||||||
|
@ -51,6 +61,13 @@ class BaseView(TemplateResponseMixin, ContextMixin):
|
||||||
model = getattr(self, "model", None) or hasattr(self, "object") and type(self.object)
|
model = getattr(self, "model", None) or hasattr(self, "object") and type(self.object)
|
||||||
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
|
||||||
|
|
||||||
|
if "nav_menu" not in kwargs:
|
||||||
|
kwargs["nav_menu"] = self.get_nav_menu()
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def dispatch(self, *args, **kwargs):
|
def dispatch(self, *args, **kwargs):
|
||||||
|
|
|
@ -1,30 +1,55 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
|
|
||||||
from aircox.models import Diffusion, StaticPage
|
from aircox.models import Diffusion, Log, StaticPage
|
||||||
from .base import BaseView
|
from .base import BaseView
|
||||||
from .mixins import AttachedToMixin, GetDateMixin
|
from .mixins import AttachedToMixin, GetDateMixin
|
||||||
|
|
||||||
__all__ = ("DiffusionListView",)
|
__all__ = ("DiffusionListView", "TimeTableView")
|
||||||
|
|
||||||
|
|
||||||
class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView):
|
class BaseDiffusionListView(AttachedToMixin, BaseView, ListView):
|
||||||
|
model = Diffusion
|
||||||
|
queryset = Diffusion.objects.on_air().order_by("-start")
|
||||||
|
|
||||||
|
|
||||||
|
class DiffusionListView(BaseDiffusionListView):
|
||||||
"""View for timetables."""
|
"""View for timetables."""
|
||||||
|
|
||||||
model = Diffusion
|
model = Diffusion
|
||||||
has_filters = True
|
|
||||||
redirect_date_url = "diffusion-list"
|
|
||||||
attach_to_value = StaticPage.ATTACH_TO_DIFFUSIONS
|
class TimeTableView(GetDateMixin, BaseDiffusionListView):
|
||||||
|
model = Diffusion
|
||||||
|
redirect_date_url = "timetable-list"
|
||||||
|
attach_to_value = StaticPage.Target.TIMETABLE
|
||||||
|
template_name = "aircox/timetable_list.html"
|
||||||
|
|
||||||
def get_date(self):
|
def get_date(self):
|
||||||
date = super().get_date()
|
date = super().get_date()
|
||||||
return date if date is not None else datetime.date.today()
|
return date if date is not None else datetime.date.today()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_logs(self, date):
|
||||||
return super().get_queryset().date(self.date).order_by("start")
|
return Log.objects.on_air().date(self.date).filter(track__isnull=False)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().date(self.date)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_secondary_nav(cls):
|
||||||
|
date = datetime.date.today()
|
||||||
|
start = date - datetime.timedelta(days=date.weekday())
|
||||||
|
dates = [start + datetime.timedelta(days=i) for i in range(0, 7)]
|
||||||
|
return tuple((date.strftime("%A %d"), reverse("timetable-list", kwargs={"date": date})) for date in dates)
|
||||||
|
|
||||||
|
def get_context_data(self, object_list=None, **kwargs):
|
||||||
start = self.date - datetime.timedelta(days=self.date.weekday())
|
start = self.date - datetime.timedelta(days=self.date.weekday())
|
||||||
dates = [start + datetime.timedelta(days=i) for i in range(0, 7)]
|
dates = [start + datetime.timedelta(days=i) for i in range(0, 7)]
|
||||||
return super().get_context_data(date=self.date, dates=dates, **kwargs)
|
|
||||||
|
if object_list is None:
|
||||||
|
logs = self.get_logs(self.date)
|
||||||
|
object_list = Log.merge_diffusions(logs, self.object_list, group_logs=True)
|
||||||
|
object_list = list(reversed(object_list))
|
||||||
|
return super().get_context_data(date=self.date, dates=dates, object_list=object_list, **kwargs)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
from ..filters import EpisodeFilters
|
from ..filters import EpisodeFilters
|
||||||
from ..models import Episode, Program, StaticPage
|
from ..models import Episode, Program, StaticPage
|
||||||
from .page import PageListView
|
from .page import PageListView
|
||||||
|
@ -6,6 +8,7 @@ from .program import ProgramPageDetailView
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"EpisodeDetailView",
|
"EpisodeDetailView",
|
||||||
"EpisodeListView",
|
"EpisodeListView",
|
||||||
|
"PodcastListView",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,11 +20,22 @@ class EpisodeDetailView(ProgramPageDetailView):
|
||||||
kwargs["tracks"] = self.object.track_set.order_by("position")
|
kwargs["tracks"] = self.object.track_set.order_by("position")
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def get_related_queryset(self):
|
||||||
|
return (
|
||||||
|
self.get_queryset().parent(self.object.parent).exclude(pk=self.object.pk).published().order_by("-pub_date")
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_related_url(self):
|
||||||
|
return reverse("episode-list", kwargs={"parent_slug": self.object.parent.slug})
|
||||||
|
|
||||||
|
|
||||||
class EpisodeListView(PageListView):
|
class EpisodeListView(PageListView):
|
||||||
model = Episode
|
model = Episode
|
||||||
filterset_class = EpisodeFilters
|
filterset_class = EpisodeFilters
|
||||||
item_template_name = "aircox/widgets/episode_item.html"
|
|
||||||
has_headline = True
|
|
||||||
parent_model = Program
|
parent_model = Program
|
||||||
attach_to_value = StaticPage.ATTACH_TO_EPISODES
|
attach_to_value = StaticPage.Target.EPISODES
|
||||||
|
|
||||||
|
|
||||||
|
class PodcastListView(EpisodeListView):
|
||||||
|
attach_to_value = StaticPage.Target.PODCASTS
|
||||||
|
queryset = Episode.objects.published().with_podcasts().order_by("-pub_date")
|
||||||
|
|
|
@ -1,43 +1,56 @@
|
||||||
from datetime import date
|
from datetime import date, datetime, timedelta
|
||||||
|
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
|
|
||||||
from ..models import Diffusion, Log, Page, StaticPage
|
from ..models import Diffusion, Episode, 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"
|
||||||
|
attach_to_value = StaticPage.Target.HOME
|
||||||
model = Diffusion
|
model = Diffusion
|
||||||
attach_to_value = StaticPage.ATTACH_TO_HOME
|
queryset = Diffusion.objects.on_air().select_related("episode").order_by("-start")
|
||||||
queryset = Diffusion.objects.on_air().select_related("episode")
|
|
||||||
logs_count = 5
|
publications_queryset = Page.objects.select_subclasses().published().order_by("-pub_date")
|
||||||
publications_count = 5
|
podcasts_queryset = Episode.objects.published().with_podcasts().order_by("-pub_date")
|
||||||
has_filters = False
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().date(date.today())
|
now = datetime.now()
|
||||||
|
return super().get_queryset().after(now - timedelta(hours=24)).before(now).order_by("-start")
|
||||||
|
|
||||||
def get_logs(self, diffusions):
|
def get_logs(self, diffusions):
|
||||||
today = date.today()
|
today = date.today()
|
||||||
logs = Log.objects.on_air().date(today).filter(track__isnull=False)
|
now = datetime.now()
|
||||||
# diffs = Diffusion.objects.on_air().date(today)
|
# diffs = Diffusion.objects.on_air().date(today)
|
||||||
return Log.merge_diffusions(logs, diffusions, self.logs_count)
|
object_list = self.object_list
|
||||||
|
diffs = list(object_list[: self.related_count])
|
||||||
|
logs = Log.objects.on_air().filter(track__isnull=False, date__lte=now)
|
||||||
|
if diffs:
|
||||||
|
min_date = diffs[-1].start - timedelta(hours=1)
|
||||||
|
logs = logs.after(min_date)
|
||||||
|
else:
|
||||||
|
logs = logs.date(today)
|
||||||
|
return Log.merge_diffusions(
|
||||||
|
logs, object_list, diff_count=self.related_count, count=self.related_count + 2, group_logs=True
|
||||||
|
)
|
||||||
|
|
||||||
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(next_diffs.exclude(pk=current_diff.pk)[:2])
|
diffs = [current_diff] + list(next_diffs.exclude(pk=current_diff.pk)[:9])
|
||||||
else:
|
else:
|
||||||
diffs = next_diffs[:3]
|
diffs = next_diffs[: self.related_carousel_count]
|
||||||
return diffs
|
return diffs
|
||||||
|
|
||||||
def get_last_publications(self):
|
def get_publications(self):
|
||||||
# note: with postgres db, possible to use distinct()
|
# note: with postgres db, possible to use distinct()
|
||||||
qs = Page.objects.select_subclasses().published().order_by("-pub_date")
|
qs = self.publications_queryset.all()
|
||||||
parents = set()
|
parents = set()
|
||||||
items = []
|
items = []
|
||||||
for publication in qs:
|
for publication in qs:
|
||||||
|
@ -45,13 +58,25 @@ class HomeView(BaseView, ListView):
|
||||||
if parent_id is not None and parent_id in parents:
|
if parent_id is not None and parent_id in parents:
|
||||||
continue
|
continue
|
||||||
items.append(publication)
|
items.append(publication)
|
||||||
if len(items) == self.publications_count:
|
if len(items) == self.related_count:
|
||||||
break
|
break
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def get_podcasts(self):
|
||||||
|
return self.podcasts_queryset.all()[: self.related_carousel_count]
|
||||||
|
|
||||||
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,
|
||||||
|
"publications": self.get_publications(),
|
||||||
|
"podcasts": self.get_podcasts(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -6,12 +6,12 @@ from django.views.decorators.cache import cache_page
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from rest_framework.generics import ListAPIView
|
from rest_framework.generics import ListAPIView
|
||||||
|
|
||||||
from ..models import Diffusion, Log, StaticPage
|
from ..models import Diffusion, Log
|
||||||
from ..serializers import LogInfo, LogInfoSerializer
|
from ..serializers import LogInfo, LogInfoSerializer
|
||||||
from .base import BaseAPIView, BaseView
|
from .base import BaseAPIView, BaseView
|
||||||
from .mixins import AttachedToMixin, GetDateMixin
|
from .mixins import AttachedToMixin, GetDateMixin
|
||||||
|
|
||||||
__all__ = ["LogListMixin", "LogListView"]
|
__all__ = ("LogListMixin", "LogListView", "LogListAPIView")
|
||||||
|
|
||||||
|
|
||||||
class LogListMixin(GetDateMixin):
|
class LogListMixin(GetDateMixin):
|
||||||
|
@ -37,7 +37,8 @@ class LogListMixin(GetDateMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_diffusions_queryset(self):
|
def get_diffusions_queryset(self):
|
||||||
qs = Diffusion.objects.station(self.station).on_air().filter(start__lte=tz.now())
|
qs = Diffusion.objects.station(self.station).on_air().filter(start__lte=tz.now()).before()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
qs.date(self.date)
|
qs.date(self.date)
|
||||||
if self.date is not None
|
if self.date is not None
|
||||||
|
@ -62,8 +63,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
|
|
||||||
|
|
||||||
def get_date(self):
|
def get_date(self):
|
||||||
date = super().get_date()
|
date = super().get_date()
|
||||||
|
|
|
@ -44,16 +44,16 @@ class ParentMixin:
|
||||||
parent = None
|
parent = None
|
||||||
"""Parent page object."""
|
"""Parent page object."""
|
||||||
|
|
||||||
def get_parent(self, request, *args, **kwargs):
|
def get_parent(self, request, **kwargs):
|
||||||
if self.parent_model is None or self.parent_url_kwarg not in kwargs:
|
if self.parent_model is None or self.parent_url_kwarg not in kwargs:
|
||||||
return
|
return
|
||||||
|
|
||||||
lookup = {self.parent_field: kwargs[self.parent_url_kwarg]}
|
lookup = {self.parent_field: kwargs[self.parent_url_kwarg]}
|
||||||
return get_object_or_404(self.parent_model.objects.select_related("cover"), **lookup)
|
return get_object_or_404(self.parent_model.objects.select_related("cover"), **lookup)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.parent = self.get_parent(request, *args, **kwargs)
|
self.parent = self.get_parent(request, **kwargs)
|
||||||
return super().get(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.parent is not None:
|
if self.parent is not None:
|
||||||
|
@ -61,9 +61,10 @@ class ParentMixin:
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.parent = kwargs.setdefault("parent", self.parent)
|
parent = kwargs.setdefault("parent", self.parent)
|
||||||
if self.parent is not None:
|
|
||||||
kwargs.setdefault("cover", self.parent.cover)
|
if parent is not None:
|
||||||
|
kwargs.setdefault("cover", parent.cover.url)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
|
from django.urls import reverse
|
||||||
from honeypot.decorators import check_honeypot
|
from honeypot.decorators import check_honeypot
|
||||||
|
|
||||||
from ..filters import PageFilters
|
from ..filters import PageFilters
|
||||||
from ..forms import CommentForm
|
from ..forms import CommentForm
|
||||||
from ..models import Comment
|
from ..models import Comment, Category
|
||||||
from ..utils import Redirect
|
|
||||||
from .base import BaseView
|
from .base import BaseView
|
||||||
from .mixins import AttachedToMixin, FiltersMixin, ParentMixin
|
from .mixins import AttachedToMixin, FiltersMixin, ParentMixin
|
||||||
|
|
||||||
|
@ -18,55 +18,84 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BasePageListView(AttachedToMixin, ParentMixin, BaseView, ListView):
|
class BasePageMixin:
|
||||||
"""Base view class for BasePage list."""
|
category = None
|
||||||
|
|
||||||
template_name = "aircox/basepage_list.html"
|
|
||||||
item_template_name = "aircox/widgets/page_item.html"
|
|
||||||
has_sidebar = True
|
|
||||||
|
|
||||||
paginate_by = 30
|
|
||||||
has_headline = True
|
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
return super().get(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_subclasses().published().select_related("cover")
|
return super().get_queryset().select_subclasses().published().select_related("cover")
|
||||||
|
|
||||||
|
def get_category(self, page, **kwargs):
|
||||||
|
if page:
|
||||||
|
if getattr(page, "category_id", None):
|
||||||
|
return page.category
|
||||||
|
if page.parent_id:
|
||||||
|
return self.get_category(page.parent_subclass)
|
||||||
|
if slug := self.kwargs.get("category_slug"):
|
||||||
|
return Category.objects.get(slug=slug)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
kwargs.setdefault("category", self.category)
|
||||||
|
return super().get_context_data(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class BasePageListView(AttachedToMixin, BasePageMixin, ParentMixin, BaseView, ListView):
|
||||||
|
"""Base view class for BasePage list."""
|
||||||
|
|
||||||
|
template_name = "aircox/basepage_list.html"
|
||||||
|
|
||||||
|
paginate_by = 30
|
||||||
|
has_headline = True
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
self.category = self.get_category(self.parent)
|
||||||
|
return super().get(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
query = super().get_queryset()
|
||||||
|
if self.category:
|
||||||
|
query = query.filter(category=self.category)
|
||||||
|
return query
|
||||||
|
|
||||||
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
|
||||||
|
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(BasePageMixin, BaseView, DetailView):
|
||||||
"""Base view class for BasePage."""
|
"""Base view class for BasePage."""
|
||||||
|
|
||||||
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_queryset(self):
|
def get_context_data(self, **kwargs):
|
||||||
return super().get_queryset().select_related("cover")
|
if self.object.cover:
|
||||||
|
kwargs.setdefault("cover", self.object.cover.url)
|
||||||
# This should not exists: it allows mapping not published pages
|
if self.object.title:
|
||||||
# or it should be only used for trashed pages.
|
kwargs.setdefault("title", self.object.display_title)
|
||||||
def not_published_redirect(self, page):
|
return super().get_context_data(**kwargs)
|
||||||
"""When a page is not published, redirect to the returned url instead
|
|
||||||
of an HTTP 404 code."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
if getattr(self, "object", None):
|
if getattr(self, "object", None):
|
||||||
return self.object
|
return self.object
|
||||||
|
|
||||||
obj = super().get_object()
|
obj = super().get_object()
|
||||||
if not obj.is_published:
|
self.category = self.get_category(obj)
|
||||||
redirect_url = self.not_published_redirect(obj)
|
|
||||||
if redirect_url:
|
|
||||||
raise Redirect(redirect_url)
|
|
||||||
raise Http404("%s not found" % self.model._meta.verbose_name)
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_page(self):
|
def get_page(self):
|
||||||
|
@ -78,7 +107,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
|
||||||
|
|
||||||
|
@ -92,15 +120,21 @@ class PageListView(FiltersMixin, BasePageListView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset().select_related("category").order_by("-pub_date")
|
qs = super().get_queryset().select_related("category").order_by("-pub_date")
|
||||||
|
cat_ids = self.model.objects.published().values_list("category_id", flat=True)
|
||||||
|
self.categories = Category.objects.filter(id__in=cat_ids)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
@classmethod
|
||||||
kwargs["categories"] = (
|
def get_secondary_nav(cls):
|
||||||
self.model.objects.published()
|
cat_ids = cls.model.objects.published().values_list("category_id", flat=True)
|
||||||
.filter(category__isnull=False)
|
categories = Category.objects.filter(id__in=cat_ids)
|
||||||
.values_list("category__title", "category__id")
|
return tuple(
|
||||||
.distinct()
|
(category.title, reverse(cls.model.list_url_name, kwargs={"category_slug": category.slug}))
|
||||||
|
for category in categories
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
kwargs["categories"] = self.categories
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -109,7 +143,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"]
|
||||||
|
@ -121,6 +154,15 @@ class PageDetailView(BasePageDetailView):
|
||||||
if self.object.allow_comments and "comment_form" not in kwargs:
|
if self.object.allow_comments and "comment_form" not in kwargs:
|
||||||
kwargs["comment_form"] = CommentForm()
|
kwargs["comment_form"] = CommentForm()
|
||||||
kwargs["comments"] = Comment.objects.filter(page=self.object).order_by("-date")
|
kwargs["comments"] = Comment.objects.filter(page=self.object).order_by("-date")
|
||||||
|
|
||||||
|
if self.object.parent_subclass:
|
||||||
|
kwargs["parent"] = self.object.parent_subclass
|
||||||
|
|
||||||
|
if "related_objects" not in kwargs:
|
||||||
|
related = self.get_related_queryset()
|
||||||
|
if related:
|
||||||
|
related = related[: self.related_count]
|
||||||
|
kwargs["related_objects"] = related
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import random
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -11,9 +13,6 @@ class BaseProgramMixin:
|
||||||
def get_program(self):
|
def get_program(self):
|
||||||
return self.object
|
return self.object
|
||||||
|
|
||||||
def get_sidebar_url(self):
|
|
||||||
return reverse("program-page-list", kwargs={"parent_slug": self.program.slug})
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.program = self.get_program()
|
self.program = self.get_program()
|
||||||
kwargs["program"] = self.program
|
kwargs["program"] = self.program
|
||||||
|
@ -23,13 +22,37 @@ class BaseProgramMixin:
|
||||||
class ProgramDetailView(BaseProgramMixin, PageDetailView):
|
class ProgramDetailView(BaseProgramMixin, PageDetailView):
|
||||||
model = Program
|
model = Program
|
||||||
|
|
||||||
def get_sidebar_queryset(self):
|
def get_related_queryset(self):
|
||||||
return super().get_sidebar_queryset().filter(parent=self.program)
|
queryset = (
|
||||||
|
self.get_queryset()
|
||||||
|
.filter(category_id=self.object.category_id)
|
||||||
|
.exclude(pk=self.object.pk)
|
||||||
|
.published()
|
||||||
|
.order_by("-pub_date")[:50]
|
||||||
|
)
|
||||||
|
return random.sample(list(queryset), min(len(queryset), self.related_count))
|
||||||
|
|
||||||
|
def get_related_url(self):
|
||||||
|
return reverse("program-list") + f"?category__id={self.object.category_id}"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
episodes = Episode.objects.program(self.object).published().order_by("-pub_date")
|
||||||
|
podcasts = episodes.with_podcasts()
|
||||||
|
articles = Article.objects.parent(self.object).published().order_by("-pub_date")
|
||||||
|
return super().get_context_data(
|
||||||
|
articles=articles[: self.related_count],
|
||||||
|
episodes=episodes[: self.related_count],
|
||||||
|
podcasts=podcasts[: self.related_count],
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProgramListView(PageListView):
|
class ProgramListView(PageListView):
|
||||||
model = Program
|
model = Program
|
||||||
attach_to_value = StaticPage.ATTACH_TO_PROGRAMS
|
attach_to_value = StaticPage.Target.PROGRAMS
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().order_by("title")
|
||||||
|
|
||||||
|
|
||||||
# FIXME: not used
|
# FIXME: not used
|
||||||
|
@ -43,9 +66,6 @@ class ProgramPageDetailView(BaseProgramMixin, ParentMixin, PageDetailView):
|
||||||
self.parent = self.object.program
|
self.parent = self.object.program
|
||||||
return self.object.program
|
return self.object.program
|
||||||
|
|
||||||
def get_sidebar_queryset(self):
|
|
||||||
return super().get_sidebar_queryset().filter(parent=self.program)
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramPageListView(BaseProgramMixin, PageListView):
|
class ProgramPageListView(BaseProgramMixin, PageListView):
|
||||||
model = Page
|
model = Page
|
||||||
|
|
|
@ -91,9 +91,9 @@
|
||||||
|
|
||||||
<div class="column is-two-fifths">
|
<div class="column is-two-fifths">
|
||||||
<h6 class="subtitle is-6 is-marginless">Metadata</h6>
|
<h6 class="subtitle is-6 is-marginless">Metadata</h6>
|
||||||
<table class="table has-background-transparent">
|
<table class="table bg-transparent">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><th class="has-text-right has-text-nowrap">
|
<tr><th class="has-text-right ws-nowrap">
|
||||||
{% translate "Status" %}
|
{% translate "Status" %}
|
||||||
</th>
|
</th>
|
||||||
<td :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}">
|
<td :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}">
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="source.data.air_time">
|
<tr v-if="source.data.air_time">
|
||||||
<th class="has-text-right has-text-nowrap">
|
<th class="has-text-right ws-nowrap">
|
||||||
{% translate "Air time" %}
|
{% translate "Air time" %}
|
||||||
</th><td>
|
</th><td>
|
||||||
<span class="far fa-clock"></span>
|
<span class="far fa-clock"></span>
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
</time>
|
</time>
|
||||||
</td>
|
</td>
|
||||||
<tr v-if="source.remaining">
|
<tr v-if="source.remaining">
|
||||||
<th class="has-text-right has-text-nowrap">
|
<th class="has-text-right ws-nowrap">
|
||||||
{% translate "Time left" %}
|
{% translate "Time left" %}
|
||||||
</th><td>
|
</th><td>
|
||||||
<span class="far fa-hourglass"></span>
|
<span class="far fa-hourglass"></span>
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="source.data.uri">
|
<tr v-if="source.data.uri">
|
||||||
<th class="has-text-right has-text-nowrap">
|
<th class="has-text-right ws-nowrap">
|
||||||
{% translate "Data source" %}
|
{% translate "Data source" %}
|
||||||
</th><td>
|
</th><td>
|
||||||
<span class="far fa-play-circle"></span>
|
<span class="far fa-play-circle"></span>
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
"v-calendar": "^3.1.2",
|
||||||
"vue": "^3.2.13"
|
"vue": "^3.2.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -20,7 +22,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",
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import './assets/styles.scss'
|
|
||||||
import './assets/admin.scss'
|
import './assets/admin.scss'
|
||||||
import './index.js'
|
import './index.js'
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
|
import {Calendar, DatePicker} from 'v-calendar';
|
||||||
import components from './components'
|
import components from './components'
|
||||||
|
|
||||||
const App = {
|
const App = {
|
||||||
el: '#app',
|
el: '#app',
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
components: {...components},
|
components: {
|
||||||
|
...components,
|
||||||
|
...{
|
||||||
|
VCalendar: Calendar,
|
||||||
|
VDatepicker: DatePicker
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
player() { return window.aircox.player; },
|
player() { return window.aircox.player; },
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
import {createApp} from 'vue'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class used to handle Vue applications. It provides way to load
|
|
||||||
* remote application and update history.
|
|
||||||
*/
|
|
||||||
export default class Builder {
|
|
||||||
constructor(config={}) {
|
|
||||||
this.config = config
|
|
||||||
this.title = null
|
|
||||||
this.app = null
|
|
||||||
this.vm = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch app from remote and mount application.
|
|
||||||
*/
|
|
||||||
fetch(url, {el='#app', ...options}={}) {
|
|
||||||
return fetch(url, options).then(response => response.text())
|
|
||||||
.then(content => {
|
|
||||||
let doc = new DOMParser().parseFromString(content, 'text/html')
|
|
||||||
let app = doc.querySelector(el)
|
|
||||||
content = app ? app.innerHTML : content
|
|
||||||
return this.mount({content, title: doc.title, reset:true, url })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mount application, using `create_app` if required.
|
|
||||||
*
|
|
||||||
* @param {String} options.content: replace app container content with it
|
|
||||||
* @param {String} options.title: set DOM document title.
|
|
||||||
* @param {String} [options.el=this.config.el]: mount application on this element (querySelector argument)
|
|
||||||
* @param {Boolean} [reset=False]: if True, force application recreation.
|
|
||||||
* @return `app.mount`'s result.
|
|
||||||
*/
|
|
||||||
mount({content=null, title=null, el=null, reset=false, props=null}={}) {
|
|
||||||
try {
|
|
||||||
this.unmount()
|
|
||||||
|
|
||||||
let config = this.config
|
|
||||||
if(el === null)
|
|
||||||
el = config.el
|
|
||||||
if(reset || !this.app)
|
|
||||||
this.app = this.createApp({title,content,el,...config}, props)
|
|
||||||
|
|
||||||
this.vm = this.app.mount(el)
|
|
||||||
window.scroll(0, 0)
|
|
||||||
return this.vm
|
|
||||||
} catch(error) {
|
|
||||||
this.unmount()
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createApp({el, title=null, content=null, ...config}, props) {
|
|
||||||
const container = document.querySelector(el)
|
|
||||||
if(!container)
|
|
||||||
return
|
|
||||||
if(content)
|
|
||||||
container.innerHTML = content
|
|
||||||
if(title)
|
|
||||||
document.title = title
|
|
||||||
return createApp(config, props)
|
|
||||||
}
|
|
||||||
|
|
||||||
unmount() {
|
|
||||||
this.app && this.app.unmount()
|
|
||||||
this.app = null
|
|
||||||
this.vm = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable hot reload: catch page change in order to fetch them and
|
|
||||||
* load page without actually leaving current one.
|
|
||||||
*/
|
|
||||||
enableHotReload(node=null, historySave=true) {
|
|
||||||
if(historySave)
|
|
||||||
this.historySave(document.location, true)
|
|
||||||
node.addEventListener('click', event => this.pageChanged(event), true)
|
|
||||||
node.addEventListener('submit', event => this.pageChanged(event), true)
|
|
||||||
node.addEventListener('popstate', event => this.statePopped(event), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pageChanged(event) {
|
|
||||||
let submit = event.type == 'submit';
|
|
||||||
let target = submit || event.target.tagName == 'A'
|
|
||||||
? event.target : event.target.closest('a');
|
|
||||||
if(!target || target.hasAttribute('target'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
let url = submit ? target.getAttribute('action') || ''
|
|
||||||
: target.getAttribute('href');
|
|
||||||
let domain = window.location.protocol + '//' + window.location.hostname
|
|
||||||
let stay = (url === '' || url.startsWith('/') || url.startsWith('?') ||
|
|
||||||
url.startsWith(domain)) && url.indexOf('wp-admin') == -1
|
|
||||||
if(url===null || !stay) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = {};
|
|
||||||
if(submit) {
|
|
||||||
let formData = new FormData(event.target);
|
|
||||||
if(target.method == 'get')
|
|
||||||
url += '?' + (new URLSearchParams(formData)).toString();
|
|
||||||
else
|
|
||||||
options = {...options, method: target.method, body: formData}
|
|
||||||
}
|
|
||||||
this.fetch(url, options).then(() => this.historySave(url))
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
statePopped(event) {
|
|
||||||
const state = event.state
|
|
||||||
if(state && state.content)
|
|
||||||
// document.title = this.title;
|
|
||||||
this.historyLoad(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save application state into browser history
|
|
||||||
historySave(url,replace=false) {
|
|
||||||
const el = document.querySelector(this.config.el)
|
|
||||||
const state = {
|
|
||||||
content: el.innerHTML,
|
|
||||||
title: document.title,
|
|
||||||
}
|
|
||||||
|
|
||||||
if(replace)
|
|
||||||
history.replaceState(state, '', url)
|
|
||||||
else
|
|
||||||
history.pushState(state, '', url)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load application from browser history's state
|
|
||||||
historyLoad(state) {
|
|
||||||
return this.mount({ content: state.content, title: state.title })
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,77 @@
|
||||||
|
@use "./vars";
|
||||||
|
@use "./components";
|
||||||
|
|
||||||
|
@import "~bulma/sass/utilities/_all.sass";
|
||||||
|
@import "~bulma/sass/elements/button";
|
||||||
|
@import "~bulma/sass/components/navbar";
|
||||||
|
|
||||||
|
|
||||||
|
// enforce button usage inside custom application
|
||||||
|
#player, .ax {
|
||||||
|
@include components.button;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.admin {
|
.admin {
|
||||||
|
|
||||||
|
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
|
||||||
|
box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.navbar-item.is-active {
|
||||||
|
border-bottom: 1px grey solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
& + .container {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-dropdown {
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-split {
|
||||||
|
margin: 0.2em 0em;
|
||||||
|
margin-right: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
border-right: 1px vars.$grey-light solid;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin: 0em;
|
||||||
|
padding: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toolbar {
|
||||||
|
margin: 1em 0em;
|
||||||
|
background-color: transparent;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding-right: 2em;
|
||||||
|
margin-right: 1em;
|
||||||
|
border-right: 1px vars.$grey-light solid;
|
||||||
|
|
||||||
|
font-size: vars.$text-size;
|
||||||
|
font-weight: vars.$weight-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-dropdown {
|
||||||
|
max-height: 40rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
input {
|
||||||
|
z-index: 10000;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.navbar .navbar-brand {
|
.navbar .navbar-brand {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
88
assets/src/assets/common.scss
Normal file
88
assets/src/assets/common.scss
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
@use "./vars" as v;
|
||||||
|
@import "./vendor";
|
||||||
|
@import "./helpers";
|
||||||
|
|
||||||
|
//-- helpers/modifiers
|
||||||
|
//-- forms
|
||||||
|
input.half-field:not(:active):not(:hover) {
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(0,0,0,0);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-- general
|
||||||
|
:root {
|
||||||
|
--body-bg: #fff;
|
||||||
|
--text-color: black;
|
||||||
|
--text-color-light: #555;
|
||||||
|
--break-color: rgb(225, 225, 225);
|
||||||
|
|
||||||
|
--main-color: #EFCA08;
|
||||||
|
--main-color-light: #F4da51;
|
||||||
|
--main-color-dark: #F49F0A;
|
||||||
|
--secondary-color: #00A6A6;
|
||||||
|
--secondary-color-light: #4cc0c0;
|
||||||
|
--secondary-color-dark: #007ba8;
|
||||||
|
|
||||||
|
--disabled-color: #aaa;
|
||||||
|
--disabled-bg: #eee;
|
||||||
|
--link-fg: #00A6A6;
|
||||||
|
--link-hv-fg: var(--text-color);
|
||||||
|
|
||||||
|
--nav-primary-height: 3rem;
|
||||||
|
--nav-secondary-height: 2.5rem;
|
||||||
|
--nav-fg: var(--text-color);
|
||||||
|
--nav-bg: var(--main-color);
|
||||||
|
--nav-secondary-bg: var(--main-color-light);
|
||||||
|
--nav-hv-fg: var(--button-hv-fg);
|
||||||
|
--nav-hv-bg: var(--button-hv-bg);
|
||||||
|
--nav-active-fg: var(--button-active-fg);
|
||||||
|
--nav-active-bg: var(--button-active-bg);
|
||||||
|
--nav-fs: 1rem;
|
||||||
|
--nav-2-fs: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--body-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@mixin mobile-small {
|
||||||
|
.grid { @include grid-1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body.mobile {
|
||||||
|
@include mobile-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: v.$screen-smaller) {
|
||||||
|
@include mobile-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: v.$screen-normal) {
|
||||||
|
html { font-size: 18px !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: v.$screen-wider) {
|
||||||
|
html { font-size: 20px !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: v.$screen-wider) {
|
||||||
|
html { font-size: 24px !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
|
||||||
|
font-family: var(--heading-font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.container:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
703
assets/src/assets/components.scss
Normal file
703
assets/src/assets/components.scss
Normal file
|
@ -0,0 +1,703 @@
|
||||||
|
@use "vars" as v;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--title-1-sz: 1.6rem;
|
||||||
|
--title-2-sz: 1.4rem;
|
||||||
|
--title-3-sz: 1.2rem;
|
||||||
|
--subtitle-1-sz: 1.6rem;
|
||||||
|
--subtitle-2-sz: 1.4rem;
|
||||||
|
--subtitle-3-sz: 1.2rem;
|
||||||
|
|
||||||
|
--heading-font-family: default;
|
||||||
|
--heading-bg: var(--main-color);
|
||||||
|
--heading-fg: var(--text-color);
|
||||||
|
--heading-hg-fg: var(--text-color);
|
||||||
|
--heading-hg-bg: var(--secondary-color);
|
||||||
|
--heading-link-hv-fg: var(--link-fg);
|
||||||
|
|
||||||
|
--cover-w: 14rem;
|
||||||
|
--cover-h: 14rem;
|
||||||
|
--cover-small-w: 10rem;
|
||||||
|
--cover-small-h: 10rem;
|
||||||
|
--cover-tiny-w: 10rem;
|
||||||
|
--cover-tiny-h: 10rem;
|
||||||
|
|
||||||
|
--card-w: var(--cover-w);
|
||||||
|
|
||||||
|
|
||||||
|
--preview-bg: var(--body-bg);
|
||||||
|
--preview-title-sz: var(--title-3-sz);
|
||||||
|
--preview-subtitle-sz: var(--title-3-sz);
|
||||||
|
--preview-cover-size: 14rem;
|
||||||
|
--preview-cover-small-size: 10rem;
|
||||||
|
--preview-cover-tiny-size: 4rem;
|
||||||
|
--preview-wide-content-sz: #{v.$text-size-2};
|
||||||
|
--preview-heading-bg-color: var(--main-color);
|
||||||
|
--header-height: var(--cover-h);
|
||||||
|
|
||||||
|
--a-carousel-p: #{v.$text-size-medium};
|
||||||
|
--a-carousel-ml: calc(#{v.$mp-4} - 0.5rem);
|
||||||
|
--a-carousel-gap: #{v.$mp-4};
|
||||||
|
--a-carousel-nav-x: -#{v.$mp-3e};
|
||||||
|
--a-carousel-bg: none; // var(--secondary-color-light);
|
||||||
|
|
||||||
|
--a-progress-bg: transparent;
|
||||||
|
--a-progress-bar-bg: var(--secondary-color);
|
||||||
|
--a-progress-bar-color: var(--text-color);
|
||||||
|
--a-progress-bar-pd: #{v.$mp-2};
|
||||||
|
|
||||||
|
--a-playlist-header-bg: var(--secondary-color);
|
||||||
|
--a-playlist-header-fg: var(--text-color);
|
||||||
|
--a-playlist-title-sz: #{v.$text-size};
|
||||||
|
--a-playlist-title-pd: #{v.$mp-3};
|
||||||
|
--a-playlist-item-border: 1px var(--secondary-color) solid;
|
||||||
|
|
||||||
|
--a-sound-bg: var(--main-color);
|
||||||
|
--a-sound-hv-bg: var(--main-color);
|
||||||
|
--a-sound-hv-fg: var(--secondary-color);
|
||||||
|
--a-sound-playing-fg: var(--secondary-color-dark);
|
||||||
|
--a-sound-text-sz: #{v.$text-size};
|
||||||
|
|
||||||
|
--a-player-url-fg: var(--text-color);
|
||||||
|
--a-player-panel-bg: var(--main-color);
|
||||||
|
--a-player-bar-height: var(--nav-primary-height);
|
||||||
|
--a-player-bar-bg: var(--main-color);
|
||||||
|
--a-player-bar-title-alone-sz: #{v.$text-size-medium};
|
||||||
|
--a-player-bar-button-fg: var(--button-fg);
|
||||||
|
--a-player-bar-button-fg: var(--button-bg);
|
||||||
|
--a-player-bar-button-hv-fg: var(--button-hv-fg);
|
||||||
|
--a-player-bar-button-hv-bg: var(--button-hv-bg);
|
||||||
|
|
||||||
|
--button-fg: var(--text-color);
|
||||||
|
--button-bg: var(--main-color);
|
||||||
|
--button-sec-bg: var(--main-color-light);
|
||||||
|
--button-hv-fg: var(--text-color);
|
||||||
|
--button-hv-bg: var(--secondary-color-light);
|
||||||
|
--button-active-fg: var(--text-color);
|
||||||
|
--button-active-bg: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: v.$screen-wide) {
|
||||||
|
:root {
|
||||||
|
--cover-w: 10rem;
|
||||||
|
--cover-h: 10rem;
|
||||||
|
--cover-small-w: 6rem;
|
||||||
|
--cover-small-h: 6rem;
|
||||||
|
--cover-tiny-w: 4rem;
|
||||||
|
--cover-tiny-h: 4rem;
|
||||||
|
|
||||||
|
--section-content-sz: 1rem;
|
||||||
|
|
||||||
|
// --preview-title-sz: #{v.$text-size};
|
||||||
|
// --preview-subtitle-sz: #{v.$text-size-smaller};
|
||||||
|
// --preview-wide-content-sz: #{v.$text-size};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- headings
|
||||||
|
.title, .header.preview .title {
|
||||||
|
&.is-1 { font-size: var(--title-1-sz); }
|
||||||
|
&.is-2 { font-size: var(--title-2-sz); }
|
||||||
|
&.is-3 { font-size: var(--title-3-sz); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle, .header.preview .subtitle {
|
||||||
|
color: var(--text-color-light);
|
||||||
|
|
||||||
|
&.is-1 { font-size: var(--subtitle-1-sz); }
|
||||||
|
&.is-2 { font-size: var(--subtitle-2-sz); }
|
||||||
|
&.is-3 { font-size: var(--subtitle-3-sz); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.title + .subtitle {
|
||||||
|
padding-top: 0em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headings a, a.heading, a.subtitle {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&:not(:empty) {
|
||||||
|
// border-bottom: 1px var(--heading-bg) solid;
|
||||||
|
// color: var(--heading-fg);
|
||||||
|
padding: v.$mp-2;
|
||||||
|
margin-top: 0em !important;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
&.highlight, &.active,
|
||||||
|
.preview.active &,
|
||||||
|
{
|
||||||
|
// border-color: var(--heading-hg-bg);
|
||||||
|
color: var(--heading-hg-fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- button
|
||||||
|
@mixin button {
|
||||||
|
.button, a.button, button.button {
|
||||||
|
font-size: v.$text-size;
|
||||||
|
display: inline-block;
|
||||||
|
padding: v.$mp-2;
|
||||||
|
border: none; //1px var(--button-fg) solid;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
// font-size: v.$text-size-medium;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
color: var(--button-fg);
|
||||||
|
background-color: var(--button-bg);
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
background-color: var(--button-sec-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label, label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
&:not(:only-child) {
|
||||||
|
&:first-child { margin-right: v.$mp-3; }
|
||||||
|
&:last-child { margin-left: v.$mp-3 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--button-hv-fg);
|
||||||
|
background-color: var(--button-hv-bg);
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active:not(:hover) {
|
||||||
|
color: var(--button-active-fg);
|
||||||
|
background-color: var(--button-active-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([disabled]), &:not(.disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled], &.disabled {
|
||||||
|
background-color: var(--text-color-light);
|
||||||
|
color: var(--secondary-color);
|
||||||
|
border-color: var(--secondary-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger {
|
||||||
|
border-radius: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.button-group, .nav {
|
||||||
|
.button {
|
||||||
|
border-radius: 0px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-top: 0px;
|
||||||
|
border-bottom: 0px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&:not(:first-child) { border-left: 0px; }
|
||||||
|
&:last-child { border-right: 0px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- preview
|
||||||
|
.preview {
|
||||||
|
position: relative;
|
||||||
|
background-size: cover;
|
||||||
|
background-color: var(--preview-bg) !important;
|
||||||
|
|
||||||
|
&.preview-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: remove
|
||||||
|
&.columns, .headings.columns {
|
||||||
|
margin-left: 0em;
|
||||||
|
margin-right: 0em;
|
||||||
|
.column { padding: 0em; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.title, .title:not(:last-child) {
|
||||||
|
// second is bulma reset
|
||||||
|
font-weight: v.$weight-bold;
|
||||||
|
font-size: var(--preview-title-sz);
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
|
.subtitle {
|
||||||
|
font-weight: v.$weight-bolder;
|
||||||
|
font-size: var(--preview-subtitle-sz);
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
|
//.content, .actions {
|
||||||
|
// font-size: v.$text-size-bigger;
|
||||||
|
//}
|
||||||
|
|
||||||
|
.headings {
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
> * { margin: 0em; }
|
||||||
|
.column { padding: 0em; }
|
||||||
|
|
||||||
|
a { color: var(--text-color); }
|
||||||
|
a:hover { color: var(--heading-link-hv-fg) !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tiny {
|
||||||
|
.content {
|
||||||
|
font-size: v.$text-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.preview-cover {
|
||||||
|
background: var(--preview-bg);
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
height: var(--cover-h);
|
||||||
|
max-width: calc( var(--cover-w) * 1.5 );
|
||||||
|
min-width: var(--cover-w);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px #c4c4c4 solid;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: var(--cover-h);
|
||||||
|
max-width: calc( var(--cover-w) * 1.5 );
|
||||||
|
min-width: var(--cover-w);
|
||||||
|
}
|
||||||
|
img.hide { visibility: hidden; }
|
||||||
|
|
||||||
|
|
||||||
|
&.small, .preview.small & {
|
||||||
|
min-width: unset;
|
||||||
|
height: var(--preview-cover-small-size);
|
||||||
|
width: var(--preview-cover-small-size) !important;
|
||||||
|
min-width: var(--preview-cover-small-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tiny, .preview.tiny & {
|
||||||
|
min-width: unset;
|
||||||
|
height: var(--preview-cover-tiny-size);
|
||||||
|
width: var(--preview-cover-tiny-size) !important;
|
||||||
|
min-width: var(--preview-cover-tiny-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:not(.no-cover) {
|
||||||
|
min-height: var(--header-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-cover {
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headings {
|
||||||
|
padding-top: v.$mp-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headings, > .container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .container, {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- list
|
||||||
|
.list-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
// padding: v.$mp-3;
|
||||||
|
|
||||||
|
.headings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0em;
|
||||||
|
margin-bottom: v.$mp-2 !important;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
// background-color: var(--preview-heading-bg-color);
|
||||||
|
padding: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.title { flex-grow: 1; }
|
||||||
|
.subtitle {
|
||||||
|
font-size: var(--preview-title-sz);
|
||||||
|
// background-color: var(--preview-heading-bg-color);
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
&:not(:empty) { min-width: 9rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-content {
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: unset;
|
||||||
|
|
||||||
|
.list-item:not(.no-cover) & {
|
||||||
|
min-height: var(--preview-cover-small-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.wide) .media {
|
||||||
|
padding: v.$mp-3;
|
||||||
|
// border-radius: v.$mp-2;
|
||||||
|
border: 1px solid var(--break-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: v.$screen-very-small) {
|
||||||
|
.list-item .headings {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
display: inline;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: unset !important;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- wide
|
||||||
|
.list-item.wide {
|
||||||
|
& .preview-cover {
|
||||||
|
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .content {
|
||||||
|
font-size: var(--preview-wide-content-sz);
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- card
|
||||||
|
.preview-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: var(--card-w);
|
||||||
|
padding: 0rem !important;
|
||||||
|
margin-bottom: auto;
|
||||||
|
|
||||||
|
background-color: var(--preview-bg) !important;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
figure {
|
||||||
|
// box-shadow: 0em 0em 1.2em rgba(0, 0, 0, 0.4) !important;
|
||||||
|
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--heading-link-hv-fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.headings {
|
||||||
|
margin-top: v.$mp-2;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: v.$text-size-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
figure {
|
||||||
|
// box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
|
||||||
|
height: var(--cover-h);
|
||||||
|
width: var(--cover-w);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
position: absolute;
|
||||||
|
padding: v.$mp-2;
|
||||||
|
bottom: 0rem;
|
||||||
|
right: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- ---- Carousel
|
||||||
|
.a-carousel {
|
||||||
|
.a-carousel-viewport {
|
||||||
|
box-shadow: inset 0em 0em 20rem var(--a-carousel-bg);
|
||||||
|
// background-color: var(--a-carousel-bg);
|
||||||
|
padding: 0rem;
|
||||||
|
padding-top: var(--a-carousel-p);
|
||||||
|
margin-top: calc( 0rem - var(--a-carousel-p) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-container {
|
||||||
|
width: 100%;
|
||||||
|
gap: var(--a-carousel-gap);
|
||||||
|
transition: margin-left 1s;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-bullets-container {
|
||||||
|
// due to a-carousel margin-left
|
||||||
|
padding-left: var(--a-carousel-ml);
|
||||||
|
|
||||||
|
.bullet {
|
||||||
|
margin: v.$mp-1;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover { color: var(--link-fg); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- ---- progress bar
|
||||||
|
.a-progress {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0em;
|
||||||
|
padding: 0em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--a-progress-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-progress-bar-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
> time, .a-progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
padding: var(--a-progress-bar-pd);
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-progress-bar {
|
||||||
|
background-color: var(--a-progress-bar-bg);
|
||||||
|
color: var(--a-progress-bar-color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- ---- player
|
||||||
|
// ---- playlist
|
||||||
|
.playlist, .a-playlist {
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.title, .button {
|
||||||
|
background-color: var(--a-playlist-header-bg);
|
||||||
|
color: var(--a-playlist-header-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: var(--a-playlist-title-sz);
|
||||||
|
margin: 0;
|
||||||
|
padding: var(--a-playlist-title-pd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
border-bottom: var(--a-playlist-item-border);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- sound item
|
||||||
|
.a-sound-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
height: 3rem;
|
||||||
|
background-color: var(--a-sound-bg);
|
||||||
|
|
||||||
|
&.playing .label {
|
||||||
|
color: var(--a-sound-playing-fg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--a-sound-hv-bg);
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--a-sound-hv-fg) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label:hover::before, &.playing .label::before {
|
||||||
|
content: "\f04b";
|
||||||
|
font-family: "Font Awesome 6 Free";
|
||||||
|
margin-right: v.$mp-3e;
|
||||||
|
}
|
||||||
|
&.playing .label:hover::before {
|
||||||
|
content: '';
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.headings > * {
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
padding: 0em v.$mp-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin: 0em !important;
|
||||||
|
padding: v.$mp-3e;
|
||||||
|
font-size: var(--a-sound-text-sz);
|
||||||
|
font-family: var(--heading-font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 3em;
|
||||||
|
font-size: var(--a-sound-text-sz);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--a-sound-hv-fg) !important;
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- player
|
||||||
|
.player-container {
|
||||||
|
z-index: 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-player {
|
||||||
|
box-shadow: 0em -0.5em 0.5em rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
a { color: var(--a-player-url-fg); }
|
||||||
|
.button {
|
||||||
|
color: var(--text-black);
|
||||||
|
&:hover { color: var(--button-fg); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-player-panels {
|
||||||
|
background: var(--a-player-panel-bg);
|
||||||
|
height: 0%;
|
||||||
|
transition: height 1s;
|
||||||
|
}
|
||||||
|
.a-player-panels.is-open {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-player-panel {
|
||||||
|
padding-bottom: v.$mp-3;
|
||||||
|
max-height: 80%;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.a-sound-item:not(:hover) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-player-progress {
|
||||||
|
height: 0.4em;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
time { display: none; }
|
||||||
|
|
||||||
|
&:hover, .a-player-panels.is-open + & {
|
||||||
|
background: var(--a-player-bar-bg);
|
||||||
|
height: 2em;
|
||||||
|
time { display: unset; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-player-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
height: var(--a-player-bar-height);
|
||||||
|
|
||||||
|
border-top: 1px v.$grey-light solid;
|
||||||
|
background: var(--a-player-bar-bg);
|
||||||
|
|
||||||
|
> * { height: 100%; }
|
||||||
|
|
||||||
|
.cover { height: 100%; }
|
||||||
|
.title {
|
||||||
|
font-size: v.$text-size;
|
||||||
|
margin: 0em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
font-size: var(--a-player-bar-title-alone-sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: v.$text-size-medium;
|
||||||
|
height: 100%;
|
||||||
|
padding: v.$mp-2 !important;
|
||||||
|
min-width: calc(var(--a-player-bar-height) + v.$mp-2 * 2);
|
||||||
|
border-radius: 0px;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
background-color: var(--button-active-bg);
|
||||||
|
color: var(--button-active-fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-player-bar-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: vertical;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0 v.$mp-3;
|
||||||
|
border-right: 1px black solid;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
max-height: calc( var(--a-player-bar-height) - v.$mp-3 );
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
92
assets/src/assets/helpers.scss
Normal file
92
assets/src/assets/helpers.scss
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
@use "./vars" as v;
|
||||||
|
|
||||||
|
// ---- layout
|
||||||
|
.align-left { text-align: left; justify-content: left; }
|
||||||
|
.align-right { text-align: right; justify-content: right; }
|
||||||
|
|
||||||
|
.clear-left { clear: left !important }
|
||||||
|
.clear-right { clear: right !important }
|
||||||
|
.clear-both { clear: both !important }
|
||||||
|
|
||||||
|
.d-inline { display: inline !important; }
|
||||||
|
.d-block { display: block !important; }
|
||||||
|
.d-inline-block { display: inline-block !important; }
|
||||||
|
|
||||||
|
.p-relative { position: relative !important }
|
||||||
|
.p-absolute { position: absolute !important }
|
||||||
|
.p-fixed { position: fixed !important }
|
||||||
|
.p-sticky { position: sticky !important }
|
||||||
|
.p-static { position: static !important }
|
||||||
|
|
||||||
|
.ws-nowrap { white-space: nowrap; }
|
||||||
|
|
||||||
|
// ---- grid
|
||||||
|
@mixin grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-auto-flow: dense;
|
||||||
|
gap: v.$mp-4;
|
||||||
|
}
|
||||||
|
@mixin grid-1 { grid-template-columns: 1fr; }
|
||||||
|
@mixin grid-2 { grid-template-columns: 1fr 1fr; }
|
||||||
|
@mixin grid-3 { grid-template-columns: 1fr 1fr 1fr; }
|
||||||
|
|
||||||
|
.grid { @include grid; }
|
||||||
|
.grid-1 { @include grid; @include grid-1; }
|
||||||
|
.grid-2 { @include grid; @include grid-2; }
|
||||||
|
.grid-3 { @include grid; @include grid-3; }
|
||||||
|
|
||||||
|
// ---- flex
|
||||||
|
.flex-row { display: flex; flex-direction: row }
|
||||||
|
.flex-column { display: flex; flex-direction: column }
|
||||||
|
.flex-grow-0 { flex-grow: 0 !important; }
|
||||||
|
.flex-grow-1 { flex-grow: 1 !important; }
|
||||||
|
|
||||||
|
.float-right { float: right }
|
||||||
|
.float-left { float: left }
|
||||||
|
|
||||||
|
// ---- boxing
|
||||||
|
.is-fullwidth { width: 100%; }
|
||||||
|
.is-fullheight { height: 100%; }
|
||||||
|
.is-fixed-bottom {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.no-border { border: 0px !important; }
|
||||||
|
|
||||||
|
.overflow-hidden { overflow: hidden }
|
||||||
|
.overflow-hidden.is-fullwidth { max-width: 100%; }
|
||||||
|
|
||||||
|
.height-full { height: 100%; }
|
||||||
|
|
||||||
|
*[draggable="true"] {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- animations
|
||||||
|
@keyframes blink {
|
||||||
|
from { opacity: 1; }
|
||||||
|
to { opacity: 0.4; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.blink { animation: 1s ease-in-out 3s infinite alternate blink; }
|
||||||
|
.loading { animation: 1s ease-in-out 1s infinite alternate blink; }
|
||||||
|
|
||||||
|
|
||||||
|
// -- colors
|
||||||
|
.main-color { color: var(--main-color); }
|
||||||
|
.secondary-color { color: var(--secondary-color); }
|
||||||
|
|
||||||
|
.bg-transparent { background-color: transparent; }
|
||||||
|
|
||||||
|
.is-success {
|
||||||
|
background-color: v.$green !important;
|
||||||
|
border-color: v.$green-dark !important;
|
||||||
|
}
|
||||||
|
.is-danger {
|
||||||
|
background-color: v.$red !important;
|
||||||
|
border-color: v.$red-dark !important;
|
||||||
|
}
|
478
assets/src/assets/public.scss
Normal file
478
assets/src/assets/public.scss
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
@use "./vars" as v;
|
||||||
|
@use "./components";
|
||||||
|
|
||||||
|
@use "./vendor";
|
||||||
|
|
||||||
|
|
||||||
|
// ---- main theme & layout
|
||||||
|
.page {
|
||||||
|
padding-bottom: 5rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--link-fg);
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--link-hv-fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container {
|
||||||
|
margin-top: v.$mp-3;
|
||||||
|
margin-bottom: v.$mp-4;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
padding-bottom: calc(v.$mp-4 / 2);
|
||||||
|
border-bottom: 2px var(--break-color) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
font-size: var(--title-2-sz);
|
||||||
|
clear: both;
|
||||||
|
margin: v.$mp-3 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*[data-oembed-url] {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ---- components
|
||||||
|
.dropdown-item {
|
||||||
|
font-size: unset !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-weekday-1, .vc-weekday-7 {
|
||||||
|
color: var(--secondary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.schedules {
|
||||||
|
padding-top: 0;
|
||||||
|
margin-bottom: calc(0rem - v.$mp-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule {
|
||||||
|
display: inline-block;
|
||||||
|
margin: v.$mp-3;
|
||||||
|
margin-left: 0rem;
|
||||||
|
padding: v.$mp-2;
|
||||||
|
border-bottom: 1px var(--main-color) solid;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
padding: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
font-weight: v.$weight-bold;
|
||||||
|
margin-right: v.$mp-3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- buttons, forms
|
||||||
|
@include components.button;
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: v.$mp-3;
|
||||||
|
justify-content: right;
|
||||||
|
|
||||||
|
&.no-label label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, .action, a {
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 2rem;
|
||||||
|
padding: v.$mp-2;
|
||||||
|
|
||||||
|
.not-selected { opacity: 0.6; }
|
||||||
|
.icon { margin: 0em !important; }
|
||||||
|
label { margin-left: v.$mp-2; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.label, .textarea, .input, .select {
|
||||||
|
font-size: v.$text-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field.is-horizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: horizontal;
|
||||||
|
|
||||||
|
.label { min-width: 7rem }
|
||||||
|
.control {
|
||||||
|
flex: 1;
|
||||||
|
> * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: v.$screen-small) {
|
||||||
|
textarea {
|
||||||
|
height: calc( v.$text-size * 7 ) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-item.active, .table tr.is-selected {
|
||||||
|
color: var(--secondary-color);
|
||||||
|
background-color: var(--main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- headings
|
||||||
|
.title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
&.is-3 { margin-top: v.$mp-3; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- main navigation
|
||||||
|
.navs {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--nav-bg);
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.burger {
|
||||||
|
display: none;
|
||||||
|
background-color: var(--nav-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
padding: v.$mp-2;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
font-family: var(--heading-font-family);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--nav-fg) !important;
|
||||||
|
|
||||||
|
.icon:first-child, .icon + span {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--nav-hv-bg);
|
||||||
|
color: var(--nav-hv-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--nav-active-bg);
|
||||||
|
color: var(--nav-active-fg) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
font-size: v.$text-size;
|
||||||
|
min-width: 15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
height: var(--nav-primary-height);
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-brand {
|
||||||
|
display: inline-block;
|
||||||
|
padding: v.$mp-3;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
font-size: var(--nav-fs);
|
||||||
|
font-weight: v.$weight-bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
background-color: var(--nav-secondary-bg);
|
||||||
|
//position: absolute;
|
||||||
|
//width: 100%;
|
||||||
|
//box-shadow: 0em 0.5em 0.5em rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
justify-content: right;
|
||||||
|
//display: none;
|
||||||
|
|
||||||
|
.nav.primary:hover + &,
|
||||||
|
&:hover {
|
||||||
|
display: flex;
|
||||||
|
top: var(--nav-primary-height);
|
||||||
|
left: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
font-size: var(--nav-2-fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- breadcrumbs
|
||||||
|
.breadcrumbs {
|
||||||
|
text-align: right;
|
||||||
|
padding: v.$mp-3 0rem;
|
||||||
|
font-size: v.$text-size-smaller;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
&:empty { display: none; }
|
||||||
|
|
||||||
|
a + a {
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "/";
|
||||||
|
margin: 0 v.$mp-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: v.$screen-normal) {
|
||||||
|
.page {
|
||||||
|
margin-top: var(--nav-primary-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navs {
|
||||||
|
z-index: 100000;
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
.nav:first-child {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav + .nav {
|
||||||
|
flex-grow: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.burger {
|
||||||
|
display: unset;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--nav-secondary-bg);
|
||||||
|
left: 0;
|
||||||
|
top: 100%;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0em 0.5em 0.5em rgba(0,0,0,0.05);
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: block;
|
||||||
|
font-weight: v.$weight-normal;
|
||||||
|
font-size: var(--nav-fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu:not(.active) {
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nav li {
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
a, .button {
|
||||||
|
font-size: v.$text-size-medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.nav-urls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
margin-top: v.$mp-3;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
> a:only-child {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.urls {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: v.$mp-3;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
a:not(:last-child) {
|
||||||
|
margin-right: v.$mp-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
flex-grow: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
flex-grow: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- page header
|
||||||
|
.header {
|
||||||
|
&.preview-header {
|
||||||
|
//display: flex;
|
||||||
|
align-items: start;
|
||||||
|
gap: v.$mp-3;
|
||||||
|
min-height: unset;
|
||||||
|
padding-top: v.$mp-3 !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.headings {
|
||||||
|
width: unset;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-cover {
|
||||||
|
min-height: calc( var(--header-height) / 3 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.header-cover:not(:only-child) {
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: var(--body-bg);
|
||||||
|
margin: 0 0 v.$mp-4 v.$mp-4;
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
max-width: calc(var(--header-height) * 2);
|
||||||
|
height: var(--header-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.header-cover:only-child {
|
||||||
|
with: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: v.$screen-small) {
|
||||||
|
.container.header {
|
||||||
|
width: calc( 100% - v.$mp-2 );
|
||||||
|
|
||||||
|
.headings {
|
||||||
|
width: 100%;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cover {
|
||||||
|
float: none;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-height: calc(var(--cover-h) * 1);
|
||||||
|
max-width: calc(var(--cover-w) * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- ---- detail
|
||||||
|
.page-content {
|
||||||
|
margin-top: v.$mp-6;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: v.$mp-6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- ---- list
|
||||||
|
.list-item {
|
||||||
|
&.logs {
|
||||||
|
.track {
|
||||||
|
margin-right: v.$mp-3;
|
||||||
|
.icon {
|
||||||
|
margin-right: v.$mp-2;
|
||||||
|
color: var(--secondary-color-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3n):not(.wide) .media,
|
||||||
|
{
|
||||||
|
border-color: var(--main-color-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3n+1):not(.wide) .media,
|
||||||
|
{
|
||||||
|
border-color: var(--secondary-color-dark) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ---- responsive
|
||||||
|
@media screen and (max-width: v.$screen-normal) {
|
||||||
|
.page .container {
|
||||||
|
margin-left: v.$mp-4;
|
||||||
|
margin-right: v.$mp-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: v.$screen-small) {
|
||||||
|
.page .container {
|
||||||
|
margin-left: v.$mp-2;
|
||||||
|
margin-right: v.$mp-2;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,327 +0,0 @@
|
||||||
@charset "utf-8";
|
|
||||||
|
|
||||||
@import "~bulma/sass/utilities/_all.sass";
|
|
||||||
@import "~bulma/sass/components/dropdown.sass";
|
|
||||||
|
|
||||||
$body-background-color: $light;
|
|
||||||
$menu-item-hover-background-color: #dfdfdf;
|
|
||||||
$menu-item-active-background-color: #d2d2d2;
|
|
||||||
|
|
||||||
@import "~bulma";
|
|
||||||
|
|
||||||
//-- helpers/modifiers
|
|
||||||
.is-fullwidth { width: 100%; }
|
|
||||||
.is-fullheight { height: 100%; }
|
|
||||||
.is-fixed-bottom {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.is-borderless { border: none; }
|
|
||||||
|
|
||||||
.has-text-nowrap {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-background-transparent {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-opacity-light {
|
|
||||||
opacity: 0.7;
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.float-right { float: right }
|
|
||||||
.float-left { float: left }
|
|
||||||
.overflow-hidden { overflow: hidden }
|
|
||||||
.overflow-hidden.is-fullwidth { max-width: 100%; }
|
|
||||||
|
|
||||||
|
|
||||||
*[draggable="true"] {
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-- forms
|
|
||||||
input.half-field:not(:active):not(:hover) {
|
|
||||||
border: none;
|
|
||||||
background-color: rgba(0,0,0,0);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-- animations
|
|
||||||
@keyframes blink {
|
|
||||||
from { opacity: 1; }
|
|
||||||
to { opacity: 0.4; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.blink {
|
|
||||||
animation: 1s ease-in-out 3s infinite alternate blink;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-- navbar
|
|
||||||
.navbar + .container {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
|
|
||||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.navbar-item.is-active {
|
|
||||||
border-bottom: 1px grey solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar {
|
|
||||||
.navbar-dropdown {
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-split {
|
|
||||||
margin: 0.2em 0em;
|
|
||||||
margin-right: 1em;
|
|
||||||
padding-right: 1em;
|
|
||||||
border-right: 1px $grey-light solid;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
margin: 0em;
|
|
||||||
padding: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toolbar {
|
|
||||||
margin: 1em 0em;
|
|
||||||
background-color: transparent;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
padding-right: 2em;
|
|
||||||
margin-right: 1em;
|
|
||||||
border-right: 1px $grey-light solid;
|
|
||||||
|
|
||||||
font-size: $size-5;
|
|
||||||
color: $text-light;
|
|
||||||
font-weight: $weight-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-- 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 {
|
|
||||||
z-index: 10000;
|
|
||||||
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
|
|
||||||
|
|
||||||
.player-panels {
|
|
||||||
height: 0%;
|
|
||||||
transition: height 3s;
|
|
||||||
}
|
|
||||||
.player-panels.is-open {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-panel {
|
|
||||||
margin: 0.4em;
|
|
||||||
max-height: 80%;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
margin: 0em;
|
|
||||||
padding: 0em;
|
|
||||||
border-color: $info;
|
|
||||||
border-style: 'solid';
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-bar {
|
|
||||||
border-top: 1px $grey-light solid;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
height: 3.75em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .media-left:not(:last-child) {
|
|
||||||
margin-right: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .media-cover {
|
|
||||||
border-left: 1px black solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover {
|
|
||||||
font-size: 1.5rem !important;
|
|
||||||
height: 2.5em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .media-content {
|
|
||||||
padding-top: 0.4em;
|
|
||||||
padding-left: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
font-size: 1.5rem !important;
|
|
||||||
height: 100%;
|
|
||||||
padding: auto 0.2em !important;
|
|
||||||
min-width: 2.5em;
|
|
||||||
border-radius: 0px;
|
|
||||||
transition: background-color 1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 0em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//-- media
|
|
||||||
.media {
|
|
||||||
.subtitle {
|
|
||||||
margin-bottom: 0.4em;
|
|
||||||
}
|
|
||||||
.media-content .headline {
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-- general
|
|
||||||
body {
|
|
||||||
background-color: $body-background-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
section > .toolbar {
|
|
||||||
background-color: rgba(0,0,0,0.05);
|
|
||||||
padding: 1em;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
main {
|
|
||||||
.cover.is-small { width: 10em; }
|
|
||||||
.cover.is-tiny { height: 2em; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
aside {
|
|
||||||
& > section {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover.is-small { width: 10em; }
|
|
||||||
.cover.is-tiny { height: 2em; }
|
|
||||||
|
|
||||||
.media .subtitle {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sound-item {
|
|
||||||
.cover { height: 5em; }
|
|
||||||
.media-content a { padding: 0em; }
|
|
||||||
margin-bottom: 0.2em;
|
|
||||||
}
|
|
||||||
.sound-item .media-right .button {
|
|
||||||
margin-right: 0.2em;
|
|
||||||
min-width: 2.5em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.timetable {
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
}
|
|
52
assets/src/assets/vars.scss
Normal file
52
assets/src/assets/vars.scss
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
@charset "utf-8";
|
||||||
|
|
||||||
|
$black: #000;
|
||||||
|
$white: #fff;
|
||||||
|
$red: #e00;
|
||||||
|
$red-dark: #b00;
|
||||||
|
$green: #0e0;
|
||||||
|
$green-dark: #0b0;
|
||||||
|
$grey-light: #ddd;
|
||||||
|
|
||||||
|
$mp-1: 0.2rem;
|
||||||
|
$mp-1e: 0.2em;
|
||||||
|
$mp-2: 0.4rem;
|
||||||
|
$mp-2e: 0.4em;
|
||||||
|
$mp-3: 0.6rem;
|
||||||
|
$mp-3e: 0.6em;
|
||||||
|
$mp-4: 1.2rem;
|
||||||
|
$mp-4e: 1.2em;
|
||||||
|
$mp-5: 1.6rem;
|
||||||
|
$mp-5e: 1.6em;
|
||||||
|
$mp-6: 2rem;
|
||||||
|
$mp-6e: 2em;
|
||||||
|
$mp-7: 4rem;
|
||||||
|
$mp-7e: 4em;
|
||||||
|
|
||||||
|
$text-size-small: 0.6rem;
|
||||||
|
$text-size-smaller: 0.8rem;
|
||||||
|
$text-size: 1rem;
|
||||||
|
$text-size-2: 1.2rem;
|
||||||
|
$text-size-medium: 1.4rem;
|
||||||
|
$text-size-bigger: 1.6rem;
|
||||||
|
$text-size-big: 2rem;
|
||||||
|
|
||||||
|
$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;
|
||||||
|
$screen-small: 600px;
|
||||||
|
$screen-smaller: 900px;
|
||||||
|
$screen-normal: 1024px;
|
||||||
|
$screen-wider: 1280px;
|
||||||
|
$screen-wide: 1380px;
|
33
assets/src/assets/vendor.scss
Normal file
33
assets/src/assets/vendor.scss
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
@import 'v-calendar/style.css';
|
||||||
|
|
||||||
|
// ---- bulma
|
||||||
|
$body-color: #000;
|
||||||
|
$title-color: #000;
|
||||||
|
|
||||||
|
|
||||||
|
@import "~bulma/sass/utilities/_all.sass";
|
||||||
|
|
||||||
|
|
||||||
|
@import "~bulma/sass/base/_all";
|
||||||
|
@import "~bulma/sass/components/dropdown";
|
||||||
|
// @import "~bulma/sass/components/card";
|
||||||
|
@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";
|
242
assets/src/components/ACarousel.vue
Normal file
242
assets/src/components/ACarousel.vue
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
<template>
|
||||||
|
<section class="a-carousel">
|
||||||
|
<nav ref="viewport" class="a-carousel-viewport">
|
||||||
|
<section ref="container" :class="['a-carousel-container', containerClass]">
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</section>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<nav class="a-carousel-bullets-container">
|
||||||
|
<span class="left">
|
||||||
|
<span class="icon bullet" @click="prev()" v-if="showPrev">
|
||||||
|
<i :class="leftButtonIcon"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<template v-if="bullets.length > 1">
|
||||||
|
<span class="icon bullet" v-bind:key="bullet" v-for="bullet of bullets" @click="select(bullet)">
|
||||||
|
<i v-if="bullet == index" class="fa fa-circle"></i>
|
||||||
|
<i v-else class="far fa-circle"></i>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<span class="right">
|
||||||
|
<span class="icon bullet" @click="next()" v-if="showNext">
|
||||||
|
<i :class="rightButtonIcon"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<slot name="bullets-right" :v-bind="this"></slot>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.a-carousel {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-viewport {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-container > * {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-bullets-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-bullets-container .bullet {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-bullets-container .left {
|
||||||
|
min-width: 2rem;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-bullets-container .right {
|
||||||
|
min-width: 2rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-carousel-bullets-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import {ref} from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
class Offset {
|
||||||
|
constructor(el, min=null, max=null) {
|
||||||
|
this.el = el
|
||||||
|
this.rect = el.getBoundingClientRect();
|
||||||
|
({min, max} = this.minmax(min, max))
|
||||||
|
this.min = min
|
||||||
|
this.max = max
|
||||||
|
this.size = max-min
|
||||||
|
}
|
||||||
|
|
||||||
|
minmax(min=null, max=null) {
|
||||||
|
min = min === null ? this.rect.left : min
|
||||||
|
max = max === null ? this.rect.right : max
|
||||||
|
return {min, max}
|
||||||
|
}
|
||||||
|
|
||||||
|
relative(to) {
|
||||||
|
return new Offset(this.el, this.min-to.min, this.max-to.min)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Card extends Offset {
|
||||||
|
constructor(el, index) {
|
||||||
|
super(el)
|
||||||
|
this.index = index
|
||||||
|
}
|
||||||
|
|
||||||
|
visible(viewportOffset) {
|
||||||
|
return viewportOffset.min <= this.min && viewportOffset.max >= this.max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
viewport: ref(null),
|
||||||
|
container: ref(null),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
cards: [],
|
||||||
|
index: 0,
|
||||||
|
refresh_: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
cardSelector: {type: String, default: ''},
|
||||||
|
containerClass: {type: String, default: ''},
|
||||||
|
buttonClass: {type: String, default: 'button'},
|
||||||
|
leftButtonIcon: {type: String, default: "fas fa-chevron-left"},
|
||||||
|
rightButtonIcon: {type: String, default: "fas fa-chevron-right"},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
card() { return this.cards()[this.index] },
|
||||||
|
|
||||||
|
showPrev() {
|
||||||
|
return this.index > 0
|
||||||
|
},
|
||||||
|
|
||||||
|
showNext() {
|
||||||
|
if(!this.cards || this.cards.length <= 1)
|
||||||
|
return false
|
||||||
|
|
||||||
|
let last = this.bullets[this.bullets.length-1]
|
||||||
|
return this.index != last
|
||||||
|
},
|
||||||
|
|
||||||
|
bullets() {
|
||||||
|
if(!this.cards || !this.$refs.viewport)
|
||||||
|
return []
|
||||||
|
|
||||||
|
let contOff = new Offset(this.$refs.container)
|
||||||
|
let viewMax = new Offset(this.$refs.viewport).size
|
||||||
|
let bullets = []
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
let max = viewMax
|
||||||
|
bullets.push(i)
|
||||||
|
while(i < this.cards.length) {
|
||||||
|
// skip until next view
|
||||||
|
for(; i < this.cards.length; i++) {
|
||||||
|
let card = this.cards[i].relative(contOff)
|
||||||
|
if(card.max > max) {
|
||||||
|
max = card.min + viewMax
|
||||||
|
bullets.push(i)
|
||||||
|
i++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bullets
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
getCards() {
|
||||||
|
if(!this.$refs.container)
|
||||||
|
return []
|
||||||
|
let nodes = (!this.cardSelector) ?
|
||||||
|
[...this.$refs.container.children] :
|
||||||
|
[...this.$refs.container.querySelectorAll(this.cardSelector)]
|
||||||
|
return nodes.map((el, index) => new Card(el, index))
|
||||||
|
},
|
||||||
|
|
||||||
|
select(index, relative=false) {
|
||||||
|
if(relative)
|
||||||
|
index = this.index + index
|
||||||
|
|
||||||
|
index = Math.min(index, this.cards.length)
|
||||||
|
index = Math.max(index, 0)
|
||||||
|
let card = this.cards[index]
|
||||||
|
if(!card)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
card = new Card(card.el)
|
||||||
|
const cont = new Offset(this.$refs.container)
|
||||||
|
const rel = card.relative(cont)
|
||||||
|
this.$refs.container.style.marginLeft = `-${rel.min}px`
|
||||||
|
this.index = index;
|
||||||
|
return card.el
|
||||||
|
},
|
||||||
|
|
||||||
|
next() {
|
||||||
|
let n = this.bullets.indexOf(this.index)
|
||||||
|
let index = this.bullets[n+1]
|
||||||
|
this.select(index)
|
||||||
|
},
|
||||||
|
|
||||||
|
prev() {
|
||||||
|
let n = this.bullets.indexOf(this.index)
|
||||||
|
let index = this.bullets[n-1]
|
||||||
|
this.select(index)
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.cards = this.getCards()
|
||||||
|
this.select(this.index)
|
||||||
|
this.refresh_++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.observers = [
|
||||||
|
new MutationObserver(() => this.refresh()),
|
||||||
|
new ResizeObserver(() => this.refresh())
|
||||||
|
]
|
||||||
|
this.observers[0].observe(this.$refs.container, {"childList": true})
|
||||||
|
this.observers[1].observe(this.$refs.container)
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
unmounted() {
|
||||||
|
for(var observer of this.observers)
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
49
assets/src/components/ADropdown.vue
Normal file
49
assets/src/components/ADropdown.vue
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<component :is="tag" :class="[itemClass, active ? activeClass : '']">
|
||||||
|
<slot name="before-button" :toggle="toggle" :active="active"></slot>
|
||||||
|
<slot name="button" :toggle="toggle" :active="active">
|
||||||
|
<component :is="buttonTag" :class="buttonClass" @click="toggle()">
|
||||||
|
<span class="icon" v-if="labelIcon">
|
||||||
|
<i :class="labelIcon"></i>
|
||||||
|
</span>
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
<span class="icon">
|
||||||
|
<i v-if="!active" :class="buttonIcon"></i>
|
||||||
|
<i v-if="active" :class="buttonIconClose"></i>
|
||||||
|
</span>
|
||||||
|
</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: ""},
|
||||||
|
labelIcon: {type: String, default: ""},
|
||||||
|
buttonTag: {type: String, default: "button"},
|
||||||
|
activeClass: {type: String, default: "is-active"},
|
||||||
|
buttonClass: {type: String, default: "button"},
|
||||||
|
buttonIcon: { 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>
|
|
@ -1,7 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
|
||||||
<slot :page="page" :podcasts="podcasts"></slot>
|
<slot :page="page" :podcasts="podcasts"></slot>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user