#132 | #121: backoffice / dev-1.0-121 #131

Merged
thomas merged 151 commits from dev-1.0-121 into develop-1.0 2024-04-28 20:02:14 +00:00
50 changed files with 1934 additions and 669 deletions
Showing only changes of commit 1661601caf - Show all commits

View File

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

View File

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

View File

@ -13,7 +13,9 @@ __all__ = ("Episode",)
class Episode(Page): class Episode(Page):
objects = ProgramChildQuerySet.as_manager() objects = ProgramChildQuerySet.as_manager()
detail_url_name = "episode-detail" detail_url_name = "episode-detail"
template_prefix = "episode"
item_template_name = "aircox/widgets/episode_item.html" item_template_name = "aircox/widgets/episode_item.html"
card_template_name = "aircox/widgets/episode_card.html"
@property @property
def program(self): def program(self):

View File

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

View File

@ -16,6 +16,7 @@ from model_utils.managers import InheritanceQuerySet
from .station import Station from .station import Station
__all__ = ( __all__ = (
"Renderable",
"Category", "Category",
"PageQuerySet", "PageQuerySet",
"Page", "Page",
@ -30,6 +31,17 @@ headline_re = re.compile(
) )
class Renderable:
template_prefix = "page"
template_name = "aircox/widgets/{prefix}.html"
def get_template_name(self, widget):
"""Return template name for the provided widget."""
return self.template_name.format(
prefix=self.template_prefix, widget=widget
)
class Category(models.Model): class Category(models.Model):
title = models.CharField(_("title"), max_length=64) title = models.CharField(_("title"), max_length=64)
slug = models.SlugField(_("slug"), max_length=64, db_index=True) slug = models.SlugField(_("slug"), max_length=64, db_index=True)
@ -68,7 +80,7 @@ class BasePageQuerySet(InheritanceQuerySet):
return self.filter(title__icontains=q) return self.filter(title__icontains=q)
class BasePage(models.Model): class BasePage(Renderable, models.Model):
"""Base class for publishable content.""" """Base class for publishable content."""
STATUS_DRAFT = 0x00 STATUS_DRAFT = 0x00
@ -112,7 +124,6 @@ class BasePage(models.Model):
objects = BasePageQuerySet.as_manager() objects = BasePageQuerySet.as_manager()
detail_url_name = None detail_url_name = None
item_template_name = "aircox/widgets/page_item.html"
class Meta: class Meta:
abstract = True abstract = True
@ -152,14 +163,14 @@ class BasePage(models.Model):
@property @property
def display_title(self): def display_title(self):
if self.is_published(): if self.is_published:
return self.title return self.title
return self.parent.display_title() return self.parent and self.parent.display_title or ""
@cached_property @cached_property
def headline(self): def display_headline(self):
if not self.content: if not self.content or not self.is_published:
return "" return self.parent and self.parent.display_headline or ""
content = bleach.clean(self.content, tags=[], strip=True) content = bleach.clean(self.content, tags=[], strip=True)
headline = headline_re.search(content) headline = headline_re.search(content)
return mark_safe(headline.groupdict()["headline"]) if headline else "" return mark_safe(headline.groupdict()["headline"]) if headline else ""

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -29,6 +29,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _mod
/***/ }), /***/ }),
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ADropdown.vue?vue&type=script&lang=js":
/*!*******************************************************************************************************************************************************************************************!*\
!*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ADropdown.vue?vue&type=script&lang=js ***!
\*******************************************************************************************************************************************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n data() {\n return {\n \"active\": this.open\n };\n },\n props: {\n itemClass: String,\n buttonClass: String,\n buttonIconOpen: {\n type: String,\n default: \"fa fa-angle-down\"\n },\n buttonIconClose: {\n type: String,\n default: \"fa fa-angle-up\"\n },\n contentClass: String,\n open: {\n type: Boolean,\n default: false\n },\n noButton: {\n type: Boolean,\n default: false\n }\n },\n methods: {\n toggle() {\n this.active = !this.active;\n }\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D");
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AEpisode.vue?vue&type=script&lang=js": /***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AEpisode.vue?vue&type=script&lang=js":
/*!******************************************************************************************************************************************************************************************!*\ /*!******************************************************************************************************************************************************************************************!*\
!*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AEpisode.vue?vue&type=script&lang=js ***! !*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AEpisode.vue?vue&type=script&lang=js ***!
@ -169,6 +179,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }), /***/ }),
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc":
/*!***********************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc ***!
\***********************************************************************************************************************************************************************************************************************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": function() { return /* binding */ render; }\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\nconst _hoisted_1 = {\n class: \"icon\"\n};\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", null, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.itemClass),\n onClick: _cache[1] || (_cache[1] = $event => $props.noButton && $options.toggle())\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"button\", {}, () => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['float-right', $props.buttonClass]),\n onClick: _cache[0] || (_cache[0] = $event => $options.toggle())\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", _hoisted_1, [!$data.active ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"i\", {\n key: 0,\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.buttonIconOpen)\n }, null, 2 /* CLASS */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), $data.active ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"i\", {\n key: 1,\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.buttonIconClose)\n }, null, 2 /* CLASS */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)])], 2 /* CLASS */)]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"item\")], 2 /* CLASS */), $data.active ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", {\n key: 0,\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.contentClass)\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\")], 2 /* CLASS */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/templateLoader.js??ruleSet%5B1%5D.rules%5B3%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D");
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AEpisode.vue?vue&type=template&id=2e4db98a": /***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AEpisode.vue?vue&type=template&id=2e4db98a":
/*!**********************************************************************************************************************************************************************************************************************************************************************!*\ /*!**********************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AEpisode.vue?vue&type=template&id=2e4db98a ***! !*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AEpisode.vue?vue&type=template&id=2e4db98a ***!
@ -295,7 +315,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
\********************/ \********************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"PlayerApp\": function() { return /* binding */ PlayerApp; }\n/* harmony export */ });\n/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./components */ \"./src/components/index.js\");\n\nconst App = {\n el: '#app',\n delimiters: ['[[', ']]'],\n components: {\n ..._components__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n computed: {\n player() {\n return window.aircox.player;\n }\n }\n};\nconst PlayerApp = {\n el: '#player',\n delimiters: ['[[', ']]'],\n components: {\n ..._components__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (App);\n\n//# sourceURL=webpack://aircox-assets/./src/app.js?"); eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"PlayerApp\": function() { return /* binding */ PlayerApp; }\n/* harmony export */ });\n/* harmony import */ var vue3_carousel_dist_carousel_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue3-carousel/dist/carousel.css */ \"./node_modules/vue3-carousel/dist/carousel.css\");\n/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./components */ \"./src/components/index.js\");\n/* harmony import */ var vue3_carousel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue3-carousel */ \"./node_modules/vue3-carousel/dist/carousel.es.js\");\n\n\n\nconst App = {\n el: '#app',\n delimiters: ['[[', ']]'],\n components: {\n ..._components__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n Slide: vue3_carousel__WEBPACK_IMPORTED_MODULE_2__.Slide,\n Carousel: vue3_carousel__WEBPACK_IMPORTED_MODULE_2__.Carousel,\n Pagination: vue3_carousel__WEBPACK_IMPORTED_MODULE_2__.Pagination,\n Navigation: vue3_carousel__WEBPACK_IMPORTED_MODULE_2__.Navigation\n },\n computed: {\n player() {\n return window.aircox.player;\n }\n },\n data() {\n return {\n carouselBreakpoints: {\n 400: {\n itemsToShow: 1\n },\n 600: {\n itemsToShow: 1.75\n },\n 800: {\n itemsToShow: 3\n },\n 1024: {\n itemsToShow: 4\n },\n 1280: {\n itemsToShow: 4\n },\n 1380: {\n itemsToShow: 5\n }\n }\n };\n }\n};\nconst PlayerApp = {\n el: '#player',\n delimiters: ['[[', ']]'],\n components: {\n ..._components__WEBPACK_IMPORTED_MODULE_1__[\"default\"]\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (App);\n\n//# sourceURL=webpack://aircox-assets/./src/app.js?");
/***/ }), /***/ }),
@ -305,7 +325,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
\***************************/ \***************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": function() { return /* binding */ Builder; }\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\n\n/**\n * Utility class used to handle Vue applications. It provides way to load\n * remote application and update history.\n */\nclass Builder {\n constructor(config = {}) {\n this.config = config;\n this.title = null;\n this.app = null;\n this.vm = null;\n }\n\n /**\n * Fetch app from remote and mount application.\n */\n fetch(url, {\n el = '#app',\n ...options\n } = {}) {\n return fetch(url, options).then(response => response.text()).then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.querySelector(el);\n content = app ? app.innerHTML : content;\n return this.mount({\n content,\n title: doc.title,\n reset: true,\n url\n });\n });\n }\n\n /**\n * Mount application, using `create_app` if required.\n *\n * @param {String} options.content: replace app container content with it\n * @param {String} options.title: set DOM document title.\n * @param {String} [options.el=this.config.el]: mount application on this element (querySelector argument)\n * @param {Boolean} [reset=False]: if True, force application recreation.\n * @return `app.mount`'s result.\n */\n mount({\n content = null,\n title = null,\n el = null,\n reset = false,\n props = null\n } = {}) {\n try {\n this.unmount();\n let config = this.config;\n if (el === null) el = config.el;\n if (reset || !this.app) this.app = this.createApp({\n title,\n content,\n el,\n ...config\n }, props);\n this.vm = this.app.mount(el);\n window.scroll(0, 0);\n return this.vm;\n } catch (error) {\n this.unmount();\n throw error;\n }\n }\n createApp({\n el,\n title = null,\n content = null,\n ...config\n }, props) {\n const container = document.querySelector(el);\n if (!container) return;\n if (content) container.innerHTML = content;\n if (title) document.title = title;\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.createApp)(config, props);\n }\n unmount() {\n this.app && this.app.unmount();\n this.app = null;\n this.vm = null;\n }\n\n /**\n * Enable hot reload: catch page change in order to fetch them and\n * load page without actually leaving current one.\n */\n enableHotReload(node = null, historySave = true) {\n if (historySave) this.historySave(document.location, true);\n node.addEventListener('click', event => this._onPageChange(event), true);\n node.addEventListener('submit', event => this._onPageChange(event), true);\n node.addEventListener('popstate', event => this._onPopState(event), true);\n }\n _onPageChange(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A' ? event.target : event.target.closest('a');\n if (!target || target.hasAttribute('target')) return;\n let url = submit ? target.getAttribute('action') || '' : target.getAttribute('href');\n if (url === null || !(url === '' || url.startsWith('/') || url.startsWith('?'))) return;\n let options = {};\n if (submit) {\n let formData = new FormData(event.target);\n if (target.method == 'get') url += '?' + new URLSearchParams(formData).toString();else options = {\n ...options,\n method: target.method,\n body: formData\n };\n }\n this.fetch(url, options).then(() => this.historySave(url));\n event.preventDefault();\n event.stopPropagation();\n }\n _onPopState(event) {\n if (event.state && event.state.content)\n // document.title = this.title;\n this.historyLoad(event.state);\n }\n\n /// Save application state into browser history\n historySave(url, replace = false) {\n const el = document.querySelector(this.config.el);\n const state = {\n content: el.innerHTML,\n title: document.title\n };\n if (replace) history.replaceState(state, '', url);else history.pushState(state, '', url);\n }\n\n /// Load application from browser history's state\n historyLoad(state) {\n return this.mount({\n content: state.content,\n title: state.title\n });\n }\n}\n\n//# sourceURL=webpack://aircox-assets/./src/appBuilder.js?"); eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": function() { return /* binding */ Builder; }\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\n\n/**\n * Utility class used to handle Vue applications. It provides way to load\n * remote application and update history.\n */\nclass Builder {\n constructor(config = {}) {\n this.config = config;\n this.title = null;\n this.app = null;\n this.vm = null;\n }\n\n /**\n * Fetch app from remote and mount application.\n */\n fetch(url, {\n el = '#app',\n ...options\n } = {}) {\n return fetch(url, options).then(response => response.text()).then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.querySelector(el);\n content = app ? app.innerHTML : content;\n return this.mount({\n content,\n title: doc.title,\n reset: true,\n url\n });\n });\n }\n\n /**\n * Mount application, using `create_app` if required.\n *\n * @param {String} options.content: replace app container content with it\n * @param {String} options.title: set DOM document title.\n * @param {String} [options.el=this.config.el]: mount application on this element (querySelector argument)\n * @param {Boolean} [reset=False]: if True, force application recreation.\n * @return `app.mount`'s result.\n */\n mount({\n content = null,\n title = null,\n el = null,\n reset = false,\n props = null\n } = {}) {\n try {\n this.unmount();\n let config = this.config;\n if (el === null) el = config.el;\n if (reset || !this.app) this.app = this.createApp({\n title,\n content,\n el,\n ...config\n }, props);\n this.vm = this.app.mount(el);\n window.scroll(0, 0);\n return this.vm;\n } catch (error) {\n this.unmount();\n throw error;\n }\n }\n createApp({\n el,\n title = null,\n content = null,\n ...config\n }, props) {\n const container = document.querySelector(el);\n if (!container) return;\n if (content) container.innerHTML = content;\n if (title) document.title = title;\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.createApp)(config, props);\n }\n unmount() {\n this.app && this.app.unmount();\n this.app = null;\n this.vm = null;\n }\n\n /**\n * Enable hot reload: catch page change in order to fetch them and\n * load page without actually leaving current one.\n */\n enableHotReload(node = null, historySave = true) {\n if (historySave) this.historySave(document.location, true);\n node.addEventListener('click', event => this.pageChanged(event), true);\n node.addEventListener('submit', event => this.pageChanged(event), true);\n node.addEventListener('popstate', event => this.statePopped(event), true);\n }\n pageChanged(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A' ? event.target : event.target.closest('a');\n if (!target || target.hasAttribute('target')) return;\n let url = submit ? target.getAttribute('action') || '' : target.getAttribute('href');\n let domain = window.location.protocol + '//' + window.location.hostname;\n let stay = (url === '' || url.startsWith('/') || url.startsWith('?') || url.startsWith(domain)) && url.indexOf('wp-admin') == -1;\n if (url === null || !stay) {\n return;\n }\n let options = {};\n if (submit) {\n let formData = new FormData(event.target);\n if (target.method == 'get') url += '?' + new URLSearchParams(formData).toString();else options = {\n ...options,\n method: target.method,\n body: formData\n };\n }\n this.fetch(url, options).then(() => this.historySave(url));\n event.preventDefault();\n event.stopPropagation();\n }\n statePopped(event) {\n const state = event.state;\n if (state && state.content)\n // document.title = this.title;\n this.historyLoad(state);\n }\n\n /// Save application state into browser history\n historySave(url, replace = false) {\n const el = document.querySelector(this.config.el);\n const state = {\n content: el.innerHTML,\n title: document.title\n };\n if (replace) history.replaceState(state, '', url);else history.pushState(state, '', url);\n }\n\n /// Load application from browser history's state\n historyLoad(state) {\n return this.mount({\n content: state.content,\n title: state.title\n });\n }\n}\n\n//# sourceURL=webpack://aircox-assets/./src/appBuilder.js?");
/***/ }), /***/ }),
@ -315,7 +335,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
\*********************************/ \*********************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"admin\": function() { return /* binding */ admin; },\n/* harmony export */ \"base\": function() { return /* binding */ base; }\n/* harmony export */ });\n/* harmony import */ var _AAutocomplete_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AAutocomplete.vue */ \"./src/components/AAutocomplete.vue\");\n/* harmony import */ var _AEpisode_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./AEpisode.vue */ \"./src/components/AEpisode.vue\");\n/* harmony import */ var _AList_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AList.vue */ \"./src/components/AList.vue\");\n/* harmony import */ var _APage_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./APage.vue */ \"./src/components/APage.vue\");\n/* harmony import */ var _APlayer_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./APlayer.vue */ \"./src/components/APlayer.vue\");\n/* harmony import */ var _APlaylist_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APlaylist.vue */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./APlaylistEditor.vue */ \"./src/components/APlaylistEditor.vue\");\n/* harmony import */ var _AProgress_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./AProgress.vue */ \"./src/components/AProgress.vue\");\n/* harmony import */ var _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./ASoundItem.vue */ \"./src/components/ASoundItem.vue\");\n/* harmony import */ var _AStatistics_vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./AStatistics.vue */ \"./src/components/AStatistics.vue\");\n/* harmony import */ var _AStreamer_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./AStreamer.vue */ \"./src/components/AStreamer.vue\");\n\n\n\n\n\n\n\n\n\n\n\n\n/**\n * Core components\n */\nconst base = {\n AAutocomplete: _AAutocomplete_vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"],\n AEpisode: _AEpisode_vue__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n AList: _AList_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n APage: _APage_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n APlayer: _APlayer_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n APlaylist: _APlaylist_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n AProgress: _AProgress_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"],\n ASoundItem: _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"]\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (base);\nconst admin = {\n ...base,\n AStatistics: _AStatistics_vue__WEBPACK_IMPORTED_MODULE_9__[\"default\"],\n AStreamer: _AStreamer_vue__WEBPACK_IMPORTED_MODULE_10__[\"default\"],\n APlaylistEditor: _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"]\n};\n\n//# sourceURL=webpack://aircox-assets/./src/components/index.js?"); eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"admin\": function() { return /* binding */ admin; },\n/* harmony export */ \"base\": function() { return /* binding */ base; }\n/* harmony export */ });\n/* harmony import */ var _AAutocomplete_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AAutocomplete.vue */ \"./src/components/AAutocomplete.vue\");\n/* harmony import */ var _ADropdown_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ADropdown.vue */ \"./src/components/ADropdown.vue\");\n/* harmony import */ var _AEpisode_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AEpisode.vue */ \"./src/components/AEpisode.vue\");\n/* harmony import */ var _AList_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AList.vue */ \"./src/components/AList.vue\");\n/* harmony import */ var _APage_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./APage.vue */ \"./src/components/APage.vue\");\n/* harmony import */ var _APlayer_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APlayer.vue */ \"./src/components/APlayer.vue\");\n/* harmony import */ var _APlaylist_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./APlaylist.vue */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./APlaylistEditor.vue */ \"./src/components/APlaylistEditor.vue\");\n/* harmony import */ var _AProgress_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./AProgress.vue */ \"./src/components/AProgress.vue\");\n/* harmony import */ var _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./ASoundItem.vue */ \"./src/components/ASoundItem.vue\");\n/* harmony import */ var _AStatistics_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./AStatistics.vue */ \"./src/components/AStatistics.vue\");\n/* harmony import */ var _AStreamer_vue__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./AStreamer.vue */ \"./src/components/AStreamer.vue\");\n\n\n\n\n\n\n\n\n\n\n\n\n\n/**\n * Core components\n */\nconst base = {\n AAutocomplete: _AAutocomplete_vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"],\n ADropdown: _ADropdown_vue__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n AEpisode: _AEpisode_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n AList: _AList_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n APage: _APage_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n APlayer: _APlayer_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n APlaylist: _APlaylist_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n AProgress: _AProgress_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"],\n ASoundItem: _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_9__[\"default\"]\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (base);\nconst admin = {\n ...base,\n AStatistics: _AStatistics_vue__WEBPACK_IMPORTED_MODULE_10__[\"default\"],\n AStreamer: _AStreamer_vue__WEBPACK_IMPORTED_MODULE_11__[\"default\"],\n APlaylistEditor: _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"]\n};\n\n//# sourceURL=webpack://aircox-assets/./src/components/index.js?");
/***/ }), /***/ }),
@ -419,6 +439,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _AAu
/***/ }), /***/ }),
/***/ "./src/components/ADropdown.vue":
/*!**************************************!*\
!*** ./src/components/ADropdown.vue ***!
\**************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ADropdown_vue_vue_type_template_id_a87ed2dc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ADropdown.vue?vue&type=template&id=a87ed2dc */ \"./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc\");\n/* harmony import */ var _ADropdown_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ADropdown.vue?vue&type=script&lang=js */ \"./src/components/ADropdown.vue?vue&type=script&lang=js\");\n/* harmony import */ var _media_data_code_projets_aircox_assets_node_modules_vue_loader_dist_exportHelper_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node_modules/vue-loader/dist/exportHelper.js */ \"./node_modules/vue-loader/dist/exportHelper.js\");\n\n\n\n\n;\nconst __exports__ = /*#__PURE__*/(0,_media_data_code_projets_aircox_assets_node_modules_vue_loader_dist_exportHelper_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(_ADropdown_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"], [['render',_ADropdown_vue_vue_type_template_id_a87ed2dc__WEBPACK_IMPORTED_MODULE_0__.render],['__file',\"src/components/ADropdown.vue\"]])\n/* hot reload */\nif (false) {}\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (__exports__);\n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.vue?");
/***/ }),
/***/ "./src/components/AEpisode.vue": /***/ "./src/components/AEpisode.vue":
/*!*************************************!*\ /*!*************************************!*\
!*** ./src/components/AEpisode.vue ***! !*** ./src/components/AEpisode.vue ***!
@ -559,6 +589,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }), /***/ }),
/***/ "./src/components/ADropdown.vue?vue&type=script&lang=js":
/*!**************************************************************!*\
!*** ./src/components/ADropdown.vue?vue&type=script&lang=js ***!
\**************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": function() { return /* reexport safe */ _node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_0_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_ADropdown_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]; }\n/* harmony export */ });\n/* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_0_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_ADropdown_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./ADropdown.vue?vue&type=script&lang=js */ \"./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ADropdown.vue?vue&type=script&lang=js\");\n \n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.vue?");
/***/ }),
/***/ "./src/components/AEpisode.vue?vue&type=script&lang=js": /***/ "./src/components/AEpisode.vue?vue&type=script&lang=js":
/*!*************************************************************!*\ /*!*************************************************************!*\
!*** ./src/components/AEpisode.vue?vue&type=script&lang=js ***! !*** ./src/components/AEpisode.vue?vue&type=script&lang=js ***!
@ -699,6 +739,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }), /***/ }),
/***/ "./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc":
/*!********************************************************************!*\
!*** ./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc ***!
\********************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": function() { return /* reexport safe */ _node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_0_node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_3_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_ADropdown_vue_vue_type_template_id_a87ed2dc__WEBPACK_IMPORTED_MODULE_0__.render; }\n/* harmony export */ });\n/* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_0_node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_3_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_ADropdown_vue_vue_type_template_id_a87ed2dc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!../../node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./ADropdown.vue?vue&type=template&id=a87ed2dc */ \"./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc\");\n\n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.vue?");
/***/ }),
/***/ "./src/components/AEpisode.vue?vue&type=template&id=2e4db98a": /***/ "./src/components/AEpisode.vue?vue&type=template&id=2e4db98a":
/*!*******************************************************************!*\ /*!*******************************************************************!*\
!*** ./src/components/AEpisode.vue?vue&type=template&id=2e4db98a ***! !*** ./src/components/AEpisode.vue?vue&type=template&id=2e4db98a ***!

File diff suppressed because one or more lines are too long

View File

@ -49,7 +49,7 @@ Usefull context:
</script> </script>
<div id="app"> <div id="app">
{% block top-nav-container %} {% block top-nav-container %}
<nav class="navbar has-shadow" role="navigation" aria-label="main navigation"> <nav class="navbar" role="navigation" aria-label="main navigation">
<div class="container"> <div class="container">
<div class="navbar-brand"> <div class="navbar-brand">
<a href="/" title="{% translate "Home" %}" class="navbar-item"> <a href="/" title="{% translate "Home" %}" class="navbar-item">
@ -87,81 +87,31 @@ Usefull context:
</nav> </nav>
{% endblock %} {% endblock %}
<div class="container"> {% block main-container %}
<div class="columns is-desktop"> <main class="page">
<main class="column page"> {% block main %}
<header class="header"> <header class="container header {% if cover %}has-cover{% endif %}">
{% block header %} {% block header %}
<h1 class="title is-1"> {% if object %}
{% block title %} {% include header_template_name|default:"./widgets/header.html" with title=object.title cover=object.cover.url %}
{% if page and page.title %} {% else %}
{{ page.title }} {% include header_template_name|default:"./widgets/header.html" %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
</h1>
<h3 class="subtitle is-3">
{% block subtitle %}{% endblock %}
</h3>
<div class="columns is-size-4">
{% block header_nav %}
<span class="column">
{% block header_crumbs %}
{% if parent %}
<a href="{{ parent.get_absolute_url }}">
{{ parent.title }}</a></li>
{% endif %}
{% endblock %}
</span>
{% endblock %}
</div>
{% endblock %}
</header> </header>
{% block main %} {% block content-container %}
<div class="container content">
{% block content %} {% block content %}
{% if page and page.content %} {% if page and page.content %}
<section class="page-content mb-2">{{ page.content|safe }}</section> <section class="page-content mb-2">{{ page.content|safe }}</section>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% endblock main %} </div>
{% endblock %}
{% endblock %}
</main> </main>
{% if has_sidebar %}
{% comment %}Translators: main sidebar {% endcomment %}
<aside class="column is-one-third-desktop">
{# FIXME: block cover into sidebar one #}
{% block cover %}
{% if page and page.cover %}
<img class="cover mb-4" src="{{ page.cover.url }}" class="cover"/>
{% endif %}
{% endblock %} {% endblock %}
{% 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>
{% endblock %}
{% endwith %}
</aside>
{% endif %}
</div>
</div>
<hr>
</div> </div>
{% block player-container %} {% block player-container %}
<div id="player">{% include "aircox/widgets/player.html" %}</div> <div id="player">{% include "aircox/widgets/player.html" %}</div>

View File

@ -22,7 +22,9 @@
{{ station.name }} {{ station.name }}
{% endblock %} {% endblock %}
{% block main %}{{ block.super }} {% block content-container %}
{{ block.super }}
<div class="container">
{% block before_list %}{% endblock %} {% block before_list %}{% endblock %}
@ -31,7 +33,7 @@
{% 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 %}
@ -83,4 +85,5 @@
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</div>
{% endblock %} {% endblock %}

View File

@ -12,18 +12,24 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %} {% block filters-item %}
<span class="control">
<input type="date">
</span>
{% endblock %}
{% block before_list %} {% block header %}
{% with "diffusion-list" as url_name %} {% with "./widgets/diffusion_list_header.html" as header_template_name %}
{% include "aircox/widgets/dates_menu.html" %} {{ block.super }}
{% endwith %} {% endwith %}
{% 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 %}

View File

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

View File

@ -1,65 +1,46 @@
{% extends "aircox/page_list.html" %} {% extends "aircox/page_list.html" %}
{% comment %}Home page{% endcomment %} {% comment %}Home page{% endcomment %}
{% load i18n %} {% load i18n aircox %}
{% block head_title %}{{ station.name }}{% endblock %} {% block head_title %}{{ station.name }}{% endblock %}
{% block title %}
{% if not page or not page.title %}{{ station.name }}
{% else %}{{ block.super }}
{% endif %}
{% endblock %}
{% block before_list %}{% endblock %} {% block content-container %}
{% block pages_list %}
{% if page and page.content %}<hr/>{% endif %}
{% if next_diffs %} {% if next_diffs %}
<div class="columns"> <section class="container card-grid">
{% with render_card=True %} {% for obj in next_diffs|slice:"1:4" %}
{% for object in next_diffs %} {% if object != diffusion %}
{% with is_primary=object.is_now %} {% page_widget "card" obj.episode diffusion=obj timetable=True %}
<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 %} {% endif %}
{% if object.episode.category %}
// {{ object.episode.category.title }}
{% endif %}
</h4>
{% include object.item_template_name %}
</div>
{% endwith %}
{% endfor %} {% endfor %}
{% endwith %}
</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 %}
{% if object_list %}
<section class="container">
<h2 class="title is-3 p-2">
{% with station.name as station %}
{% blocktrans %}
Today on {{ station }}
{% endblocktrans %}
{% endwith %}
</h2>
<div role="list">
{% for object in object_list %}
{% page_widget "item" object.episode diffusion=object timetable=True open=True %}
{% endfor %}
</div>
</section>
{% endif %}
{% endblock %} {% endblock %}
{% block pagination %}
<ul class="pagination-list">
<li>
<a href="{% url "page-list" %}" class="pagination-link" {% block pages_list %}{% endblock %}
aria-label="{% translate "Show all publication" %}">
{% translate "More publications..." %}
</a>
</li>
</ul>
{% endblock %}
{% block sidebar %} {% block sidebar %}

View File

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

View File

@ -33,7 +33,7 @@ Context:
{% block comments %} {% block comments %}
{% if comments or comment_form %} {% if comments or comment_form %}
<section class="mt-6"> <section class="container mt-6">
<h4 class="title is-4">{% translate "Comments" %}</h4> <h4 class="title is-4">{% translate "Comments" %}</h4>
{% for comment in comments %} {% for comment in comments %}

View File

@ -2,11 +2,48 @@
{% comment %}Display a list of Pages{% endcomment %} {% comment %}Display a list of Pages{% endcomment %}
{% load i18n aircox %} {% load i18n aircox %}
{% block before_list %} {% block header %}
{% if page and not object %}
{% with page as object %}
{{ block.super }} {{ block.super }}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block before_list %}
{% if view.has_filters and object_list %} {% if view.has_filters and object_list %}
<form method="GET" action="" class="media"> <form method="GET" action="" class="list-filters">
<a-dropdown :item-class="dropdown-trigger" :content-class="dropdown-menu">
<template #item>
{% block filters-item %}
<span class="icon">
<i class="fa fa-search"></i>
</span>
{% endblock %}
</template>
<template #default>
<div class="dropdown-menu">
{% block filters-content %}
<div class="dropdown-item">
<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>
{% endblock %}
</div>
</template>
</a-dropdown>
</form>
{% comment %}
<div class="media-content"> <div class="media-content">
{% block filters %} {% block filters %}
<div class="field is-horizontal"> <div class="field is-horizontal">
@ -48,15 +85,8 @@
{% endblock %} {% endblock %}
</div> </div>
<div class="media-right"> <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> </div>
{% endcomment %}
</form> </form>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,48 @@
{% load i18n %}
{% block outer %}
<article class="preview preview-card {% if wide %}wide{% endif %}{% if is_primary %}is-primary{% endif %}{% block card_class %}{% endblock %}"
style="background-image: url({{ object.cover.url }})">
{% if with_container %}
<div class="container">
{% endif %}
{% block inner %}
<header class="headings" >
{% block headings %}
<div>
<h2 class="heading title">{% block title %}{% endblock %}</h2>
</div>
<div>
<span class="heading subtitle">{% block subtitle %}{% endblock %}</span>
</div>
{% endblock %}
<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 btn-hg float-right" href="{{ object.get_absolute_url|escape }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
<label>{% translate "More infos" %}</label>
</a>
{% endblock %}
</div>
</header>
{% endblock %}
{% if with_container %}
</div>
{% endif %}
</article>
{% endblock %}

View File

@ -11,63 +11,48 @@ Context variables:
- is_thin (=False): if True, smaller cover and display less info - is_thin (=False): if True, smaller cover and display less info
{% endcomment %} {% endcomment %}
{% if render_card %} {% block outer %}
<article class="card {% if is_primary %}is-primary{% endif %}"> <article class="preview preview-item{% if is_primary %}is-primary{% endif %}{% block card_class %}{% endblock %}">
<header class="card-image"> {% block inner %}
<a href="{{ object.get_absolute_url }}"> <header class="headings"
<figure class="image is-4by3"> style="background-image: url({{ object.cover.url }})">
<img src="{% thumbnail object.cover|default:station.default_cover 480x480 %}"> {% block headings %}
</figure> <div>
</a> <span class="heading subtitle">{% block subtitle %}{% endblock %}</span>
</div>
{% endblock %}
</header> </header>
<div class="card-header">
<h4 class="title"> <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 btn-hg float-right" href="{{ object.get_absolute_url|escape }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
<label>{% translate "More infos" %}</label>
</a> </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 %}

View File

@ -0,0 +1,23 @@
{% load i18n %}
{% block outer %}
<article class="preview preview-card {% if not cover %}no-cover {% endif %}{% if is_primary %}is-primary{% endif %}{% block container_class %}{% endblock %}"
style="background-image: url({{ cover }})">
{% block inner %}
<header class="headings" >
{% block headings %}
<div>
<a href="{{ url|escape }}" class="heading title">{% block title %}{{ title|default:"" }}{% endblock %}</a>
</div>
<div>
<span class="heading subtitle">{% block subtitle %}{{ subtitle|default:"" }}{% endblock %}</span>
</div>
{% endblock %}
<div class="actions">
{% block actions %}{% endblock %}
</div>
</header>
{% endblock %}
</article>
{% endblock %}

View File

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

View File

@ -11,36 +11,10 @@ An empty date results to a title or a separator
{% endcomment %} {% endcomment %}
{% load i18n %} {% load i18n %}
<div class="media" role="menu"
aria-label="{% translate "pick a date" %}">
<div class="media-content">
<div class="tabs is-toggle">
<ul>
{% for day in dates %} {% for day in dates %}
<li class="{% if day == date %}is-active{% endif %}"> <div>
<a href="{% url url_name date=day %}"> <a class="heading {% if day == date %}highlight{% endif %}" href="{% url url_name date=day %}">
{{ day|date:"D. d" }} {{ day|date:"l d" }}
</a> </a>
</li> </div>
{% endfor %} {% endfor %}
</ul>
</div>
</div>
<div class="media-right">
<form action="{% url url_name %}" method="GET" class="navbar-body"
aria-label="{% translate "Jump to date" %}">
<div class="field has-addons">
<div class="control has-icons-left">
<span class="icon is-small is-left"><span class="far fa-calendar"></span></span>
<input type="{{ date_input|default:"date" }}" class="input date"
name="date" value="{{ date|date:"Y-m-d" }}">
</div>
<div class="control">
{% comment %}Translators: form button to select a date{% endcomment %}
<button class="button is-primary">{% translate "Go" %}</button>
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,10 @@
{% comment %}
Context:
- object: diffusion
- "episode_item"'s context (except object and diffusion)
{% endcomment %}
{% with object as diffusion %}
{% with object.episode as object %}
{% include "aircox/widgets/episode_card.html" %}
{% endwith %}
{% endwith %}

View File

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

View File

@ -0,0 +1,15 @@
{% extends "./header.html" %}
{% block outer %}
{% with date|date:"l d F Y" as subtitle %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block header-nav %}
<nav class="column has-text-right">
{{ block.super }}
{% include "./dates_menu.html" with url_name="diffusion-list" %}
</nav>
{% endblock %}

View File

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

View File

@ -0,0 +1,36 @@
{% extends "./basepage_card.html" %}
{% load humanize %}
{% block title %}
{% if not object.is_published and object.program.is_published %}
<a href="{{ object.program.get_absolute_url }}">
{{ object.program.title }}
</a>
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block class %}
{% if object.is_now %}is-active{% endif %}
{% endblock %}
{% block subtitle %}
{% if diffusion %}
{{ diffusion.start|naturalday }},
{{ diffusion.start|date:"g:i" }}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block content %}
{% if not object.content %}
{% with object.parent.content as content %}
{{ block.super }}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}

View File

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

View File

@ -0,0 +1,23 @@
{% extends "./card.html" %}
{% block container_class %}wide{% endblock %}
{% block headings %}
<div class="columns">
<div class="column">
{{ block.super }}
<section class="heading content">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</section>
</div>
{% block header-nav %}{% endblock %}
</div>
{% endblock %}

View File

@ -0,0 +1,38 @@
{% extends "./base_header.html" %}
{% load i18n humanize %}
{% block subtitle %}
{% if diffusion %}
{{ diffusion.start|naturalday }},
{{ diffusion.start|date:"g:i" }}
{% endif %}
{% endblock %}
{% block headings %}
{{ block.super }}
{% with diffusion.episode.content|default:diffusion.episode.program.content|striptags|safe as content %}
{% if content %}
<div class="heading-container content">
{{ content|truncatewords:64|linebreaks }}
</div>
{% endif %}
<div class="actions">
<button class="btn-outline-hg button">
<span class="icon">
<i class="fas fa-play"></i>
</span>
{% trans "Listen" %}
</button>
<a class="btn-outline-hg button" href="{{ diffusion.episode.get_absolute_url }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
{% trans "More informations" %}
</a>
</div>
{% endwith %}
{% endblock %}

View File

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

View File

@ -1,3 +1,4 @@
{% extends "./page.html" %}
{% load i18n aircox %} {% load i18n aircox %}
{% comment %} {% comment %}
List item for a log, either for a logged track or diffusion (as diffusion). List item for a log, either for a logged track or diffusion (as diffusion).
@ -11,12 +12,10 @@ for design review.
{% endcomment %} {% endcomment %}
{% block outer %}
{% if object|is_diffusion %} {% if object|is_diffusion %}
{% with object as diffusion %} {% include "./diffusion.html" with object=diffusion.episode %}
{% include "aircox/widgets/diffusion_item.html" %}
{% endwith %}
{% else %} {% else %}
{% with object.track as object %} {% include "aircox/widgets/track_item.html" with object=object.track log=object %}
{% include "aircox/widgets/track_item.html" %}
{% endwith %}
{% endif %} {% endif %}
{% endblock %}

View File

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

View File

@ -0,0 +1,15 @@
{% extends "./header.html" %}
{% block outer %}
{% with date|date:"l d F Y" as subtitle %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block header-nav %}
<nav class="column has-text-right">
{{ block.super }}
{% include "./dates_menu.html" with url_name="log-list" %}
</nav>
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,42 @@
{% load i18n %}
{% block outer %}
<article class="preview preview-card columns wide{% if is_primary %}is-primary{% endif %}{% block card_class %}{% endblock %}">
{% block inner %}
<header class="headings column preview-cover"
style="background-image: url({{ object.cover.url }})">
{% block headings %}
<div>
<a href="{{ url|escape }}" class="heading title">{% block title %}{% endblock %}</a>
</div>
<div>
<span class="heading subtitle">{% block subtitle %}{% endblock %}</span>
</div>
{% endblock %}
</header>
<div class="height-full column">
<section class="content headings-container">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|striptags|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</section>
<div class="actions">
{% block actions %}
<a class="button btn-hg float-right" href="{{ object.get_absolute_url|escape }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
<label>{% translate "More infos" %}</label>
</a>
{% endblock %}
</div>
</div>
{% endblock %}
</article>
{% endblock %}

View File

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

View File

@ -8,6 +8,7 @@ __all__ = ("BaseView", "BaseAPIView")
class BaseView(TemplateResponseMixin, ContextMixin): class BaseView(TemplateResponseMixin, ContextMixin):
header_template_name = "aircox/widgets/header.html"
has_sidebar = True has_sidebar = True
"""Show side navigation.""" """Show side navigation."""
has_filters = False has_filters = False
@ -38,6 +39,7 @@ class BaseView(TemplateResponseMixin, ContextMixin):
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("has_filters", self.has_filters)
kwargs.setdefault("header_template_name", self.header_template_name)
has_sidebar = kwargs.setdefault("has_sidebar", self.has_sidebar) has_sidebar = kwargs.setdefault("has_sidebar", self.has_sidebar)
if has_sidebar and "sidebar_object_list" not in kwargs: if has_sidebar and "sidebar_object_list" not in kwargs:

View File

@ -27,14 +27,15 @@ class HomeView(BaseView, ListView):
def get_next_diffs(self): def get_next_diffs(self):
now = tz.now() now = tz.now()
current_diff = Diffusion.objects.on_air().now(now).first() query = Diffusion.objects.on_air().select_related("episode")
next_diffs = Diffusion.objects.on_air().after(now) current_diff = query.now(now).first()
next_diffs = query.after(now)
if current_diff: if current_diff:
diffs = [current_diff] + list( diffs = [current_diff] + list(
next_diffs.exclude(pk=current_diff.pk)[:2] next_diffs.exclude(pk=current_diff.pk)[:9]
) )
else: else:
diffs = next_diffs[:3] diffs = next_diffs[:10]
return diffs return diffs
def get_last_publications(self): def get_last_publications(self):
@ -52,8 +53,16 @@ class HomeView(BaseView, ListView):
return items return items
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) next_diffs = self.get_next_diffs()
context["logs"] = self.get_logs(context["object_list"]) current_diff = next_diffs and next_diffs[0]
context["next_diffs"] = self.get_next_diffs()
context["last_publications"] = self.get_last_publications()[:5] kwargs.update(
return context {
"object": current_diff.episode,
"diffusion": current_diff,
"logs": self.get_logs(self.object_list),
"next_diffs": next_diffs,
"last_publications": self.get_last_publications()[:5],
}
)
return super().get_context_data(**kwargs)

View File

@ -12,7 +12,8 @@
"@fortawesome/fontawesome-free": "^6.0.0", "@fortawesome/fontawesome-free": "^6.0.0",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"vue": "^3.2.13" "vue": "^3.2.13",
"vue3-carousel": "^0.3.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
@ -20,7 +21,7 @@
"@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"bulma": "^0.9.3", "bulma": "^0.9.4",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3", "eslint-plugin-vue": "^8.0.3",
"sass": "^1.49.9", "sass": "^1.49.9",

View File

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

View File

@ -1,5 +1,50 @@
@charset "utf-8"; @charset "utf-8";
$font-special: "bagnard";
$font-special-url: url("assets/Bagnard.otf");
$black: #000;
$white: #fff;
$mp-1: 0.2em;
$mp-2: 0.4em;
$mp-3: 0.8em;
$mp-4: 1.2em;
$mp-5: 1.6em;
$mp-6: 2em;
$mp-7: 4em;
$text-size-small: 0.6em;
$text-size-smaller: 0.8em;
$text-size: 1em;
$text-size-medium: 1.2em;
$text-size-bigger: 1.6em;
$text-size-big: 2em;
$h1-size: 40px;
$h2-size: 32px;
$h3-size: 28px;
$h4-size: 24px;
$h5-size: 20px;
$h6-size: 14px;
$weight-light: 100;
$weight-lighter: 300;
$weight-normal: 400;
$weight-bolder: 500;
$weight-bold: 700;
$screen-very-small: 400px;
//TODO: switch small & smaller
$screen-small: 600px;
$screen-smaller: 800px;
$screen-normal: 1024px;
$screen-wider: 1280px;
$screen-wide: 1380px;
@import "~bulma/sass/utilities/_all.sass"; @import "~bulma/sass/utilities/_all.sass";
@import "~bulma/sass/components/dropdown.sass"; @import "~bulma/sass/components/dropdown.sass";
@ -325,3 +370,324 @@ aside {
width: 100%; width: 100%;
border: none; border: none;
} }
// -- layout
:root {
--text-color: black;
--highlight-color: rgba(255, 255, 0, 1);
--highlight-color-alpha: rgba(255, 255, 0, 0.6);
--highlight-color-2: rgb(0, 0, 254);
--highlight-color-2-alpha: rgb(0, 0, 254, 0.6);
--heading-height: 30em;
--heading-title-bg-color: rgba(255, 255, 0, 1);
--heading-bg-color: var(--highlight-color);
--heading-bg-highlight-color: var(--highlight-color-2);
--preview-media-height: 10em;
--preview-media-cover-size: 10em;
--preview-cover-size: 24em;
--preview-cover-small-size: 14em;
}
.page a {
background-color: var(--highlight-color-alpha);
color: var(--text-color);
}
// ---- helpers
.d-inline { display: inline; }
.d-block { display: block; }
.d-inline-block { display: inline-block; }
.p-relative { position: relative }
.p-absolute { position: absolute }
.p-fixed { position: fixed }
.p-sticky { position: sticky }
.p-static { position: static }
.height-full { height: 100%; }
.flex-push-right { margin-left: auto; }
.is-clickable { cursor: pointer; }
// ---- components
.btn-hg, .btn-outline-hg {
border: 0.1em var(--highlight-color) solid;
background-color: var(--highlight-color-alpha) !important;
border-radius: 0.2em;
}
.button {
&.action {
border-color: var(--highlight-color-2-alpha);
justify-content: center;
padding: $mp-2 !important;
min-width: 2em;
.icon { margin: 0em !important; }
label {
margin-left: $mp-2;
}
}
}
.list-filters {
text-align: right;
}
.content {
font-size: $text-size-medium;
}
// ---- items
.preview {
position: relative;
background-size: cover;
&.preview-card {
&:not(.wide) {
max-width: 30em;
}
}
&.preview-item {
width: 100%;
}
&.columns, .headings.columns {
margin: 0em;
.column { padding: 0em; }
}
.title {
font-weight: $weight-bold;
font-size: $text-size-bigger;
}
.subtitle {
font-weight: $weight-bolder;
font-size: $text-size-bigger;
}
.content, .actions {
font-size: $text-size-medium;
}
.headings {
background-size: cover;
* { margin: 0em; padding: 0em; }
a {
color: var(--text-color);
}
}
}
.heading, .headings-container > * {
display: inline-block;
&:not(:empty) {
background-color: var(--heading-bg-color);
padding: $mp-1;
margin-top: 0em !important;
vertical-align: top;
&.highlight {
background-color: var(--heading-bg-highlight-color);
color: var(--highlight-color);
}
}
&.title {
background-color: var(--heading-title-bg-color);
}
}
.actions {
&.no-label label {
display: none;
}
}
.list-item {
width: 100%;
&:not(:last-child) {
margin-bottom: calc($mp-4 / 2);
}
&:not(:first-child) {
margin-top: calc($mp-4 / 2);
}
.headings {
padding-top: 0em;
margin-bottom: $mp-3 !important;
background-color: var(--heading-bg-color);
}
.media-content {
height: var(--preview-cover-small-size);
display: flex;
flex-direction: column;
.content { flex-grow: 1; }
.actions {
flex-grow: 0;
text-align: right;
}
}
}
// ---- cards
.preview-cover {
background-size: cover;
height: var(--preview-cover-size);
width: var(--preview-cover-size);
&.small {
min-width: unset;
height: var(--preview-cover-small-size);
width: var(--preview-cover-small-size) !important;
}
}
.preview-card {
height: var(--preview-cover-size);
min-width: var(--preview-cover-size);
.card-grid & {
min-width: unset;
}
.headings {
width: 100%;
min-height: 100%;
padding-top: $mp-3;
& > div:not(:last-child) {
margin-bottom: $mp-3;
}
nav a {
font-size: 1em;
}
}
.heading {
margin-bottom: $mp-3;
}
.actions {
position: absolute;
bottom: $mp-3;
right: $mp-3;
label {
display: none;
}
}
}
.header {
background-size: cover;
.preview {
height: var(--heading-height);
&.no-cover {
height: unset;
}
.headings, > .container {
width: 100%;
}
> .container, {
height: 100%;
}
}
.preview-card {
max-width: unset;
}
.title {
font-size: $h1-size;
}
.subtitle {
font-size: $h2-size;
}
.content {
display: inline !important;
font-size: $text-size-medium;
font-weight: $weight-bolder;
text-align: right;
p {
margin-bottom: $mp-3
}
}
}
// -- program grid
.preview-card {
header {
.info {
float: right;
}
}
&.is-active {
border-bottom: 1px var(--highlight-color) solid;
}
}
.card-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: $mp-4;
}
@media screen and (max-width: $screen-wide) {
.preview:not(.list-item) {
height: 20em !important;
}
.card-grid .preview-card {
height: 20em;
}
}
@media screen and (max-width: $screen-normal) {
.card-grid {
grid-template-columns: 1fr 1fr;
.preview-card:nth-child(3) {
display: none;
}
}
}

View File

@ -0,0 +1,23 @@
<template>
<carousel :items-to-show="1.5">
<slot></slot>
<template #addons>
<navigation />
<pagination />
</template>
</carousel>
</template>
<script>
// If you are using PurgeCSS, make sure to whitelist the carousel CSS classes
import 'vue3-carousel/dist/carousel.css'
import { Carousel, Pagination, Navigation } from 'vue3-carousel'
export default {
components: {
Carousel,
Pagination,
Navigation,
},
}
</script>

View File

@ -0,0 +1,43 @@
<template>
<div>
<div :class="itemClass" @click="noButton && toggle()">
<slot name="button">
<span :class="['float-right', buttonClass]" @click="toggle()">
<span class="icon">
<i v-if="!active" :class="buttonIconOpen"></i>
<i v-if="active" :class="buttonIconClose"></i>
</span>
</span>
</slot>
<slot name="item"></slot>
</div>
<div :class="contentClass" v-if="active">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
"active": this.open,
}
},
props: {
itemClass: String,
buttonClass: String,
buttonIconOpen: { type: String, default:"fa fa-angle-down"},
buttonIconClose: { type: String, default:"fa fa-angle-up"},
contentClass: String,
open: {type: Boolean, default: false},
noButton: {type: Boolean, default: false},
},
methods: {
toggle() {
this.active = !this.active
}
},
}
</script>

View File

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