diff --git a/aircox/models/episode.py b/aircox/models/episode.py
index a329bfc..dfc1450 100644
--- a/aircox/models/episode.py
+++ b/aircox/models/episode.py
@@ -18,6 +18,7 @@ class EpisodeQuerySet(ProgramChildQuerySet):
class Episode(Page):
objects = EpisodeQuerySet.as_manager()
detail_url_name = "episode-detail"
+ list_url_name = "episode-list"
template_prefix = "episode"
@property
diff --git a/aircox/models/page.py b/aircox/models/page.py
index f59fbd8..062c1ae 100644
--- a/aircox/models/page.py
+++ b/aircox/models/page.py
@@ -139,7 +139,9 @@ class BasePage(Renderable, models.Model):
super().save(*args, **kwargs)
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
def is_draft(self):
@@ -216,6 +218,8 @@ class Page(BasePage):
)
objects = PageQuerySet.as_manager()
+ detail_url_name = ""
+ list_url_name = "page-list"
@cached_property
def parent_subclass(self):
@@ -223,6 +227,15 @@ class Page(BasePage):
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:
verbose_name = _("Publication")
verbose_name_plural = _("Publications")
@@ -243,46 +256,32 @@ class StaticPage(BasePage):
detail_url_name = "static-page-detail"
- ATTACH_TO_HOME = 0x00
- ATTACH_TO_DIFFUSIONS = 0x01
- ATTACH_TO_LOGS = 0x02
- ATTACH_TO_PROGRAMS = 0x03
- ATTACH_TO_EPISODES = 0x04
- ATTACH_TO_ARTICLES = 0x05
- ATTACH_TO_PAGES = 0x06
- ATTACH_TO_PODCASTS = 0x07
+ class Target(models.TextChoices):
+ HOME = "", _("Home Page")
+ TIMETABLE = "timetable-list", _("Timetable")
+ PROGRAMS = "program-list", _("Programs list")
+ 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_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")),
- (ATTACH_TO_PAGES, _("Publications list")),
- (ATTACH_TO_PODCASTS, _("Podcasts 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_PAGES: "page-list",
- }
-
- attach_to = models.SmallIntegerField(
+ attach_to = models.CharField(
_("attach to"),
- choices=ATTACH_TO_CHOICES,
+ choices=Target.choices,
+ max_length=32,
blank=True,
null=True,
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):
if self.attach_to:
- return reverse(self.VIEWS[self.attach_to])
+ return reverse(self.attach_to)
return super().get_absolute_url()
@@ -320,7 +319,7 @@ class NavItem(models.Model):
station = models.ForeignKey(Station, models.CASCADE, verbose_name=_("station"))
menu = models.SlugField(_("menu"), max_length=24)
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)
page = models.ForeignKey(
StaticPage,
@@ -339,14 +338,21 @@ class NavItem(models.Model):
def get_url(self):
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=""):
url = self.get_url()
+ text = self.get_text()
if active_class and request.path.startswith(url):
css_class += " " + active_class
if not url:
- return self.text
+ return text
elif not css_class:
- return format_html('{}', url, self.text)
+ return format_html('{}', url, text)
else:
- return format_html('{}', url, css_class, self.text)
+ return format_html('{}', url, css_class, text)
diff --git a/aircox/models/program.py b/aircox/models/program.py
index 7a4fd16..c5c1d76 100644
--- a/aircox/models/program.py
+++ b/aircox/models/program.py
@@ -61,6 +61,7 @@ class Program(Page):
objects = ProgramQuerySet.as_manager()
detail_url_name = "program-detail"
+ list_url_name = "program-list"
@property
def path(self):
diff --git a/aircox/static/aircox/css/admin.css b/aircox/static/aircox/css/admin.css
index 1f4b425..dc72036 100644
--- a/aircox/static/aircox/css/admin.css
+++ b/aircox/static/aircox/css/admin.css
@@ -33,9 +33,9 @@
--a-player-panel-bg: var(--highlight-color);
--a-player-bar-bg: var(--highlight-color);
--a-player-bar-title-alone-sz: 1.6rem;
- --button-fg: var(--text-color);
+ --button-fg: var(--highlight-color-2);
--button-bg: var(--highlight-color);
- --button-hg-fg: var(--highlight-color-2);
+ --button-hg-fg: var(--text-color);
--button-hg-bg: var(--highlight-color);
--button-active-fg: var(--highlight-color);
--button-active-bg: var(--highlight-color-2);
@@ -234,6 +234,14 @@
height: var(--preview-cover-size);
width: var(--preview-cover-size);
}
+.preview-card.small {
+ height: var(--preview-cover-small-size);
+ width: var(--preview-cover-small-size);
+}
+.preview-card.tiny {
+ height: var(--preview-cover-tiny-size);
+ width: var(--preview-cover-tiny-size);
+}
.preview-card:not(.header) {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
}
@@ -259,57 +267,6 @@ preview-header:not(.no-cover) .card-headings .heading, preview-header:not(.no-co
margin-bottom: 0.6rem;
}
-.header.preview-header {
- align-items: start;
- gap: 0.6rem;
- min-height: unset;
- padding-top: 0.6rem !important;
-}
-.header .headings {
- width: unset;
- flex-grow: 1;
- padding-top: 0 !important;
- display: flex;
- flex-direction: column;
-}
-.header.has-cover {
- min-height: calc(var(--header-height) / 2);
-}
-.header .title {
- font-size: 40px;
-}
-.header .subtitle {
- font-size: 32px;
-}
-
-.header-cover:not(:only-child) {
- float: right;
- height: var(--header-height);
- max-width: calc(var(--header-height) * 2);
- margin: 0 0 1.2rem 1.2rem;
-}
-
-.header-cover:only-child {
- with: 100%;
-}
-
-@media screen and (max-width: 600px) {
- .container.header {
- width: 100%;
- }
- .container.header .headings {
- width: 100%;
- clear: both;
- }
- .container.header .header-cover {
- float: none;
- width: 100%;
- max-width: unset;
- height: unset;
- margin-left: 0rem;
- margin-right: 0rem;
- }
-}
.card-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
@@ -430,6 +387,12 @@ preview-header:not(.no-cover) .card-headings .heading, preview-header:not(.no-co
.a-player a {
color: var(--a-player-url-fg);
}
+.a-player .button {
+ color: var(--text-black);
+}
+.a-player .button:hover {
+ color: var(--button-fg);
+}
.a-player-panels {
background: var(--a-player-panel-bg);
@@ -2719,12 +2682,12 @@ a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-i
#player .button, #player a.button, #player button.button, #player .nav-urls a, .ax .button, .ax a.button, .ax button.button, .ax .nav-urls a {
display: inline-block;
- padding: 0.6em;
- border-radius: 4px;
+ padding: 0.4em;
border: 1px var(--highlight-color-2-alpha) solid;
justify-content: center;
text-align: center;
font-size: 1.4rem;
+ color: var(--button-fg);
background-color: var(--button-bg);
}
#player .button .icon, #player a.button .icon, #player button.button .icon, #player .nav-urls a .icon, .ax .button .icon, .ax a.button .icon, .ax button.button .icon, .ax .nav-urls a .icon {
diff --git a/aircox/static/aircox/css/public.css b/aircox/static/aircox/css/public.css
index 3cb24be..d68702e 100644
--- a/aircox/static/aircox/css/public.css
+++ b/aircox/static/aircox/css/public.css
@@ -33,9 +33,9 @@
--a-player-panel-bg: var(--highlight-color);
--a-player-bar-bg: var(--highlight-color);
--a-player-bar-title-alone-sz: 1.6rem;
- --button-fg: var(--text-color);
+ --button-fg: var(--highlight-color-2);
--button-bg: var(--highlight-color);
- --button-hg-fg: var(--highlight-color-2);
+ --button-hg-fg: var(--text-color);
--button-hg-bg: var(--highlight-color);
--button-active-fg: var(--highlight-color);
--button-active-bg: var(--highlight-color-2);
@@ -234,6 +234,14 @@
height: var(--preview-cover-size);
width: var(--preview-cover-size);
}
+.preview-card.small {
+ height: var(--preview-cover-small-size);
+ width: var(--preview-cover-small-size);
+}
+.preview-card.tiny {
+ height: var(--preview-cover-tiny-size);
+ width: var(--preview-cover-tiny-size);
+}
.preview-card:not(.header) {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
}
@@ -259,57 +267,6 @@ preview-header:not(.no-cover) .card-headings .heading, preview-header:not(.no-co
margin-bottom: 0.6rem;
}
-.header.preview-header {
- align-items: start;
- gap: 0.6rem;
- min-height: unset;
- padding-top: 0.6rem !important;
-}
-.header .headings {
- width: unset;
- flex-grow: 1;
- padding-top: 0 !important;
- display: flex;
- flex-direction: column;
-}
-.header.has-cover {
- min-height: calc(var(--header-height) / 2);
-}
-.header .title {
- font-size: 40px;
-}
-.header .subtitle {
- font-size: 32px;
-}
-
-.header-cover:not(:only-child) {
- float: right;
- height: var(--header-height);
- max-width: calc(var(--header-height) * 2);
- margin: 0 0 1.2rem 1.2rem;
-}
-
-.header-cover:only-child {
- with: 100%;
-}
-
-@media screen and (max-width: 600px) {
- .container.header {
- width: 100%;
- }
- .container.header .headings {
- width: 100%;
- clear: both;
- }
- .container.header .header-cover {
- float: none;
- width: 100%;
- max-width: unset;
- height: unset;
- margin-left: 0rem;
- margin-right: 0rem;
- }
-}
.card-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
@@ -430,6 +387,12 @@ preview-header:not(.no-cover) .card-headings .heading, preview-header:not(.no-co
.a-player a {
color: var(--a-player-url-fg);
}
+.a-player .button {
+ color: var(--text-black);
+}
+.a-player .button:hover {
+ color: var(--button-fg);
+}
.a-player-panels {
background: var(--a-player-panel-bg);
@@ -7381,6 +7344,9 @@ a.tag:hover {
text-decoration: none;
padding: 0.4rem;
}
+.page a:hover {
+ color: var(--text-color);
+}
.page section.container {
padding-top: 2rem;
}
@@ -7419,12 +7385,12 @@ a.tag:hover {
.button, a.button, button.button, .nav-urls a {
display: inline-block;
- padding: 0.6em;
- border-radius: 4px;
+ padding: 0.4em;
border: 1px var(--highlight-color-2-alpha) solid;
justify-content: center;
text-align: center;
font-size: 1.4rem;
+ color: var(--button-fg);
background-color: var(--button-bg);
}
.button .icon, a.button .icon, button.button .icon, .nav-urls a .icon {
@@ -7487,7 +7453,6 @@ a.tag:hover {
display: none;
}
.actions button, .actions .action {
- background-color: var(--highlight-color);
justify-content: center;
min-width: 2rem;
}
@@ -7500,9 +7465,6 @@ a.tag:hover {
.actions button label, .actions .action label {
margin-left: 0.4rem;
}
-.actions button:hover, .actions button .selected, .actions .action:hover, .actions .action .selected {
- color: var(--highlight-color-2) !important;
-}
.label, .textarea, .input, .select {
font-size: 1.4rem;
@@ -7520,6 +7482,10 @@ a.tag:hover {
margin-top: 0.6rem;
}
+.navs {
+ position: relative;
+}
+
.nav {
display: flex;
background-color: var(--highlight-color);
@@ -7547,7 +7513,7 @@ a.tag:hover {
vertical-align: top;
display: inline-block;
}
-.nav .nav-item.active {
+.nav .nav-item.active, .nav .nav-item:hover {
background-color: var(--highlight-color-2);
color: var(--highlight-color);
}
@@ -7581,12 +7547,32 @@ a.tag:hover {
white-space: nowrap;
}
.nav.secondary {
+ position: absolute;
+ width: 100%;
+ z-index: 100;
+ box-shadow: 0em 0.5em 0.5em rgba(0, 0, 0, 0.05);
justify-content: right;
+ display: none;
+}
+.nav-item:hover + .nav.secondary, .nav.secondary:hover {
+ display: flex;
+ top: var(--nav-primary-height);
+ left: 0rem;
}
.nav.secondary .nav-item {
font-size: 1rem;
}
+.breadcrumbs {
+ text-align: right;
+ height: 2.2rem;
+ margin-bottom: 0.4rem;
+}
+.breadcrumbs a + a:before {
+ content: "/";
+ margin: 0 0.4rem;
+}
+
@media screen and (max-width: 1024px) {
.page {
margin-top: var(--nav-primary-height);
@@ -7685,7 +7671,7 @@ nav li a, nav li .button {
flex-direction: column;
}
.header.has-cover {
- min-height: calc(var(--header-height) / 2);
+ min-height: calc(var(--header-height) / 3);
}
.header .title {
font-size: 40px;
diff --git a/aircox/static/aircox/js/chunk-common.js b/aircox/static/aircox/js/chunk-common.js
index d3e6547..4f8dc18 100644
--- a/aircox/static/aircox/js/chunk-common.js
+++ b/aircox/static/aircox/js/chunk-common.js
@@ -375,7 +375,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
\**********************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _vueLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./vueLoader */ \"./src/vueLoader.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./model */ \"./src/model.js\");\n/* harmony import */ var _assets_common_scss__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./assets/common.scss */ \"./src/assets/common.scss\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n//-- aircox\n\n\n\n\n\nwindow.aircox = {\n // main application\n loader: null,\n get app() {\n return this.loader.app;\n },\n // player application\n playerLoader: null,\n get playerApp() {\n return this.playerLoader && this.playerLoader.app;\n },\n get player() {\n return this.playerLoader.vm && this.playerLoader.vm.$refs.player;\n },\n Set: _model__WEBPACK_IMPORTED_MODULE_4__.Set,\n Sound: _sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n /**\n * Initialize main application and player.\n */\n init(props = null, {\n hotReload = false,\n el = null,\n config = null,\n playerConfig = null,\n initApp = true,\n initPlayer = true,\n loader = null,\n playerLoader = null\n } = {}) {\n if (initPlayer) {\n playerConfig = playerConfig || _app__WEBPACK_IMPORTED_MODULE_1__.PlayerApp;\n playerLoader = playerLoader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"](playerConfig);\n playerLoader.enable(false);\n this.playerLoader = playerLoader;\n }\n if (initApp) {\n config = config || window.App || _app__WEBPACK_IMPORTED_MODULE_1__[\"default\"];\n config.el = el || config.el;\n loader = loader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"]({\n el,\n props,\n ...config\n });\n loader.enable(hotReload);\n this.loader = loader;\n }\n },\n /**\n * Filter navbar dropdown menu items\n */\n filter_menu(event) {\n var filter = new RegExp(event.target.value, 'gi');\n var container = event.target.closest('.navbar-dropdown');\n if (event.target.value) for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = item.innerHTML.search(filter) == -1 ? 'none' : null;else for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = null;\n },\n pickDate(url, date) {\n url = `${url}?date=${date.id}`;\n this.builder.fetch(url);\n }\n};\n\n//# sourceURL=webpack://aircox-assets/./src/index.js?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _vueLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./vueLoader */ \"./src/vueLoader.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./model */ \"./src/model.js\");\n/* harmony import */ var _assets_common_scss__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./assets/common.scss */ \"./src/assets/common.scss\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n//-- aircox\n\n\n\n\n\nwindow.aircox = {\n // main application\n loader: null,\n get app() {\n return this.loader.app;\n },\n // player application\n playerLoader: null,\n get playerApp() {\n return this.playerLoader && this.playerLoader.app;\n },\n get player() {\n return this.playerLoader.vm && this.playerLoader.vm.$refs.player;\n },\n Set: _model__WEBPACK_IMPORTED_MODULE_4__.Set,\n Sound: _sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n /**\n * Initialize main application and player.\n */\n init(props = null, {\n hotReload = false,\n el = null,\n config = null,\n playerConfig = null,\n initApp = true,\n initPlayer = true,\n loader = null,\n playerLoader = null\n } = {}) {\n if (initPlayer) {\n playerConfig = playerConfig || _app__WEBPACK_IMPORTED_MODULE_1__.PlayerApp;\n playerLoader = playerLoader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"](playerConfig);\n playerLoader.enable(false);\n this.playerLoader = playerLoader;\n }\n if (initApp) {\n config = config || window.App || _app__WEBPACK_IMPORTED_MODULE_1__[\"default\"];\n config.el = el || config.el;\n loader = loader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"]({\n el,\n props,\n ...config\n });\n loader.enable(hotReload);\n this.loader = loader;\n }\n },\n /**\n * Filter navbar dropdown menu items\n */\n filter_menu(event) {\n var filter = new RegExp(event.target.value, 'gi');\n var container = event.target.closest('.navbar-dropdown');\n if (event.target.value) for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = item.innerHTML.search(filter) == -1 ? 'none' : null;else for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = null;\n },\n pickDate(url, date) {\n url = `${url}?date=${date.id}`;\n this.loader.pageLoad.load(url);\n }\n};\n\n//# sourceURL=webpack://aircox-assets/./src/index.js?");
/***/ }),
diff --git a/aircox/templates/admin/index.html b/aircox/templates/admin/index.html
index ca8d0d3..72447b5 100644
--- a/aircox/templates/admin/index.html
+++ b/aircox/templates/admin/index.html
@@ -14,7 +14,7 @@
{% if diffusions %}
{% for obj in diffusions %}
- {% page_widget "card" obj.episode diffusion=obj timetable=True admin=True tag_class="small" %}
+ {% page_widget "card" obj.episode diffusion=obj timetable=True admin=True tag_class="" %}
{% endfor %}
{% else %}
diff --git a/aircox/templates/aircox/base.html b/aircox/templates/aircox/base.html
index ade4a3b..b2d6f91 100644
--- a/aircox/templates/aircox/base.html
+++ b/aircox/templates/aircox/base.html
@@ -61,6 +61,22 @@ Usefull context:
aria-label="{% translate "Main menu" %}">
{% endblock %}
@@ -79,6 +96,14 @@ Usefull context:
{% block secondary-nav %}{% endblock %}
+ {% spaceless %}
+ {% block breadcrumbs-container %}
+
+ {% block breadcrumbs %}{% endblock %}
+
+ {% endblock %}
+ {% endspaceless %}
+
{% block main-container %}
{% block main %}
@@ -99,20 +124,6 @@ Usefull context:
{% block subtitle %}
{% if subtitle %}
{{ subtitle }}
- {% elif parent and parent.is_published %}
-
-
-
-
- {{ parent.title }}
-
- {% elif page and page.category %}
-
-
-
-
- {{ page.category.title }}
-
{% endif %}
{% endblock %}
diff --git a/aircox/templates/aircox/home.html b/aircox/templates/aircox/home.html
index 2360fae..0330e80 100644
--- a/aircox/templates/aircox/home.html
+++ b/aircox/templates/aircox/home.html
@@ -3,6 +3,8 @@
{% block head_title %}{{ station.name }}{% endblock %}
+{% block breadcrumbs-container %}{% endblock %}
+
{% block content-container %}
{{ block.super }}
@@ -40,7 +42,7 @@
{% endfor %}