responsive menus
This commit is contained in:
		@ -42,6 +42,7 @@ class Schedule(Rerun):
 | 
			
		||||
        second_and_fourth = 0b001010, _("2nd and 4th {day} of the month")
 | 
			
		||||
        every = 0b011111, _("{day}")
 | 
			
		||||
        one_on_two = 0b100000, _("one {day} on two")
 | 
			
		||||
        # every_weekday = 0b10000000 _("from Monday to Friday")
 | 
			
		||||
 | 
			
		||||
    date = models.DateField(
 | 
			
		||||
        _("date"),
 | 
			
		||||
@ -71,6 +72,10 @@ class Schedule(Rerun):
 | 
			
		||||
        verbose_name = _("Schedule")
 | 
			
		||||
        verbose_name_plural = _("Schedules")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self._initial = kwargs
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "{} - {}, {}".format(
 | 
			
		||||
            self.program.title,
 | 
			
		||||
@ -110,16 +115,28 @@ class Schedule(Rerun):
 | 
			
		||||
        date = tz.datetime.combine(date, self.time)
 | 
			
		||||
        return date.replace(tzinfo=self.tz)
 | 
			
		||||
 | 
			
		||||
    def dates_of_month(self, date):
 | 
			
		||||
        """Return normalized diffusion dates of provided date's month."""
 | 
			
		||||
        if self.frequency == Schedule.Frequency.ponctual:
 | 
			
		||||
    def dates_of_month(self, date, frequency=None, sched_date=None):
 | 
			
		||||
        """Return normalized diffusion dates of provided date's month.
 | 
			
		||||
 | 
			
		||||
        :param Date date: date of the month to get dates from;
 | 
			
		||||
        :param Schedule.Frequency frequency: frequency (defaults to ``self.frequency``)
 | 
			
		||||
        :param Date sched_date: schedule start date (defaults to ``self.date``)
 | 
			
		||||
        :return list of diffusion dates
 | 
			
		||||
        """
 | 
			
		||||
        if frequency is None:
 | 
			
		||||
            frequency = self.frequency
 | 
			
		||||
 | 
			
		||||
        if sched_date is None:
 | 
			
		||||
            sched_date = self.date
 | 
			
		||||
 | 
			
		||||
        if frequency == Schedule.Frequency.ponctual:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        sched_wday, freq = self.date.weekday(), self.frequency
 | 
			
		||||
        sched_wday = sched_date.weekday()
 | 
			
		||||
        date = date.replace(day=1)
 | 
			
		||||
 | 
			
		||||
        # last of the month
 | 
			
		||||
        if freq == Schedule.Frequency.last:
 | 
			
		||||
        if frequency == Schedule.Frequency.last:
 | 
			
		||||
            date = date.replace(day=calendar.monthrange(date.year, date.month)[1])
 | 
			
		||||
            date_wday = date.weekday()
 | 
			
		||||
 | 
			
		||||
@ -134,33 +151,42 @@ class Schedule(Rerun):
 | 
			
		||||
        date_wday, month = date.weekday(), date.month
 | 
			
		||||
        date += tz.timedelta(days=(7 if date_wday > sched_wday else 0) - date_wday + sched_wday)
 | 
			
		||||
 | 
			
		||||
        if freq == Schedule.Frequency.one_on_two:
 | 
			
		||||
        if frequency == Schedule.Frequency.one_on_two:
 | 
			
		||||
            # - adjust date with modulo 14 (= 2 weeks in days)
 | 
			
		||||
            # - there are max 3 "weeks on two" per month
 | 
			
		||||
            if (date - self.date).days % 14:
 | 
			
		||||
            if (date - sched_date).days % 14:
 | 
			
		||||
                date += tz.timedelta(days=7)
 | 
			
		||||
            dates = (date + tz.timedelta(days=14 * i) for i in range(0, 3))
 | 
			
		||||
        else:
 | 
			
		||||
            dates = (date + tz.timedelta(days=7 * week) for week in range(0, 5) if freq & (0b1 << week))
 | 
			
		||||
            dates = (date + tz.timedelta(days=7 * week) for week in range(0, 5) if frequency & (0b1 << week))
 | 
			
		||||
 | 
			
		||||
        return [self.normalize(date) for date in dates if date.month == month]
 | 
			
		||||
 | 
			
		||||
    def diffusions_of_month(self, date):
 | 
			
		||||
    def diffusions_of_month(self, date, frequency=None, sched_date=None):
 | 
			
		||||
        """Get episodes and diffusions for month of provided date, including
 | 
			
		||||
        reruns.
 | 
			
		||||
 | 
			
		||||
        :param Date date: date of the month to get diffusions from;
 | 
			
		||||
        :param Schedule.Frequency frequency: frequency (defaults to ``self.frequency``)
 | 
			
		||||
        :param Date sched_date: schedule start date (defaults to ``self.date``)
 | 
			
		||||
        :returns: tuple([Episode], [Diffusion])
 | 
			
		||||
        """
 | 
			
		||||
        from .diffusion import Diffusion
 | 
			
		||||
        from .episode import Episode
 | 
			
		||||
 | 
			
		||||
        if self.initial is not None or self.frequency == Schedule.Frequency.ponctual:
 | 
			
		||||
        if frequency is None:
 | 
			
		||||
            frequency = self.frequency
 | 
			
		||||
 | 
			
		||||
        if sched_date is None:
 | 
			
		||||
            sched_date = self.date
 | 
			
		||||
 | 
			
		||||
        if self.initial is not None or frequency == Schedule.Frequency.ponctual:
 | 
			
		||||
            return [], []
 | 
			
		||||
 | 
			
		||||
        # dates for self and reruns as (date, initial)
 | 
			
		||||
        reruns = [(rerun, rerun.date - self.date) for rerun in self.rerun_set.all()]
 | 
			
		||||
        reruns = [(rerun, rerun.date - sched_date) for rerun in self.rerun_set.all()]
 | 
			
		||||
 | 
			
		||||
        dates = {date: None for date in self.dates_of_month(date)}
 | 
			
		||||
        dates = {date: None for date in self.dates_of_month(date, frequency, sched_date)}
 | 
			
		||||
        dates.update(
 | 
			
		||||
            (rerun.normalize(date.date() + delta), date) for date in list(dates.keys()) for rerun, delta in reruns
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -7970,82 +7970,12 @@ a.tag:hover {
 | 
			
		||||
  font-size: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-fullwidth {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-fullheight {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-fixed-bottom {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  margin-bottom: 0px;
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-borderless {
 | 
			
		||||
  border: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.has-text-nowrap {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.has-background-transparent {
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-opacity-light {
 | 
			
		||||
  opacity: 0.7;
 | 
			
		||||
}
 | 
			
		||||
.is-opacity-light:hover {
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.float-right {
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.float-left {
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.overflow-hidden {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.overflow-hidden.is-fullwidth {
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
*[draggable=true] {
 | 
			
		||||
  cursor: move;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input.half-field:not(:active):not(:hover) {
 | 
			
		||||
  border: none;
 | 
			
		||||
  background-color: rgba(0, 0, 0, 0);
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes blink {
 | 
			
		||||
  from {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
  }
 | 
			
		||||
  to {
 | 
			
		||||
    opacity: 0.4;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.blink {
 | 
			
		||||
  animation: 1s ease-in-out 2s infinite alternate blink;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading {
 | 
			
		||||
  animation: 1s ease-in-out 3s infinite alternate blink;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
 | 
			
		||||
  box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.1);
 | 
			
		||||
}
 | 
			
		||||
@ -8095,6 +8025,8 @@ a.navbar-item.is-active {
 | 
			
		||||
  --highlight-color-2: rgb(0, 0, 254);
 | 
			
		||||
  --highlight-color-2-alpha: rgb(0, 0, 254, 0.7);
 | 
			
		||||
  --highlight-color-2-grey: rgba(50, 200, 200, 1);
 | 
			
		||||
  --nav-primary-height: 4rem;
 | 
			
		||||
  --nav-secondary-height: 3rem;
 | 
			
		||||
  --header-height: 30rem;
 | 
			
		||||
  --heading-height: 30rem;
 | 
			
		||||
  --heading-title-bg-color: rgba(255, 255, 0, 1);
 | 
			
		||||
@ -8149,36 +8081,24 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  margin-top: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.d-inline {
 | 
			
		||||
  display: inline;
 | 
			
		||||
*[draggable=true] {
 | 
			
		||||
  cursor: move;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.d-block {
 | 
			
		||||
  display: block;
 | 
			
		||||
@keyframes blink {
 | 
			
		||||
  from {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
  }
 | 
			
		||||
  to {
 | 
			
		||||
    opacity: 0.4;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.blink {
 | 
			
		||||
  animation: 1s ease-in-out 3s infinite alternate blink;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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;
 | 
			
		||||
.loading {
 | 
			
		||||
  animation: 1s ease-in-out 1s infinite alternate blink;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-left {
 | 
			
		||||
@ -8191,8 +8111,16 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  justify-content: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.height-full {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
.d-inline {
 | 
			
		||||
  display: inline !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.d-block {
 | 
			
		||||
  display: block !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.d-inline-block {
 | 
			
		||||
  display: inline-block !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flex-push-right {
 | 
			
		||||
@ -8203,6 +8131,69 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  flex-grow: 0 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.float-right {
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.float-left {
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-fullwidth {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-fullheight {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-fixed-bottom {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  margin-bottom: 0px;
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-borderless {
 | 
			
		||||
  border: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.overflow-hidden {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.overflow-hidden.is-fullwidth {
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-relative {
 | 
			
		||||
  position: relative !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-absolute {
 | 
			
		||||
  position: absolute !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-fixed {
 | 
			
		||||
  position: fixed !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-sticky {
 | 
			
		||||
  position: sticky !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-static {
 | 
			
		||||
  position: static !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.height-full {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ws-nowrap {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.no-border {
 | 
			
		||||
  border: 0px !important;
 | 
			
		||||
}
 | 
			
		||||
@ -8215,6 +8206,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  color: var(--highlight-color-2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bg-transparent {
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-success {
 | 
			
		||||
  background-color: #0e0 !important;
 | 
			
		||||
  border-color: #0b0 !important;
 | 
			
		||||
@ -8233,6 +8228,16 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  color: var(--highlight-color-2) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.schedules {
 | 
			
		||||
  margin-top: -0.6rem !important;
 | 
			
		||||
  padding-top: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.schedule .day {
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  margin-right: 0.6rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button, a.button, button.button, .nav-urls a {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  padding: 0.6em;
 | 
			
		||||
@ -8246,10 +8251,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
.button .icon, a.button .icon, button.button .icon, .nav-urls a .icon {
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
.button .icon:first-child, a.button .icon:first-child, button.button .icon:first-child, .nav-urls a .icon:first-child {
 | 
			
		||||
.button .icon:not(:only-child):first-child, a.button .icon:not(:only-child):first-child, button.button .icon:not(:only-child):first-child, .nav-urls a .icon:not(:only-child):first-child {
 | 
			
		||||
  margin-right: 0.6rem;
 | 
			
		||||
}
 | 
			
		||||
.button .icon:last-child, a.button .icon:last-child, button.button .icon:last-child, .nav-urls a .icon:last-child {
 | 
			
		||||
.button .icon:not(:only-child):last-child, a.button .icon:not(:only-child):last-child, button.button .icon:not(:only-child):last-child, .nav-urls a .icon:not(:only-child):last-child {
 | 
			
		||||
  margin-left: 0.6rem;
 | 
			
		||||
}
 | 
			
		||||
.button:hover, a.button:hover, button.button:hover, .nav-urls a:hover {
 | 
			
		||||
@ -8279,16 +8284,17 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  border-radius: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-group .button {
 | 
			
		||||
.button-group .button, .nav .button {
 | 
			
		||||
  border-radius: 0px;
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  border-top: 0px;
 | 
			
		||||
  border-bottom: 0px;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
.button-group .button:not(:first-child) {
 | 
			
		||||
.button-group .button:not(:first-child), .nav .button:not(:first-child) {
 | 
			
		||||
  border-left: 0px;
 | 
			
		||||
}
 | 
			
		||||
.button-group .button:last-child {
 | 
			
		||||
.button-group .button:last-child, .nav .button:last-child {
 | 
			
		||||
  border-right: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8319,6 +8325,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  color: var(--highlight-color-2) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.label, .textarea, .input, .select {
 | 
			
		||||
  font-size: 1.4rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.title {
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
}
 | 
			
		||||
@ -8350,6 +8360,9 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
.nav:empty {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
.nav .burger {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
.nav .nav-item {
 | 
			
		||||
  padding: 0.6rem;
 | 
			
		||||
  flex-grow: 1;
 | 
			
		||||
@ -8358,25 +8371,38 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  font-family: var(--heading-font-family);
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
}
 | 
			
		||||
.nav .nav-item a, .nav .nav-item .button {
 | 
			
		||||
  display: block;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.nav .nav-item.active {
 | 
			
		||||
  background-color: var(--highlight-color-2);
 | 
			
		||||
  color: var(--highlight-color);
 | 
			
		||||
}
 | 
			
		||||
.nav.primary .nav-brand {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  margin-right: 0.6rem;
 | 
			
		||||
  padding: 0.6rem;
 | 
			
		||||
}
 | 
			
		||||
.nav.primary .nav-brand img {
 | 
			
		||||
  width: 12rem !important;
 | 
			
		||||
}
 | 
			
		||||
.nav.primary .nav-menu {
 | 
			
		||||
.nav .nav-menu {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-grow: 1;
 | 
			
		||||
  background-color: var(--highlight-color);
 | 
			
		||||
}
 | 
			
		||||
.nav.primary {
 | 
			
		||||
  height: var(--nav-primary-height);
 | 
			
		||||
}
 | 
			
		||||
.nav.primary .nav-menu {
 | 
			
		||||
  flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
.nav.primary .nav-brand {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  padding: 0.6rem;
 | 
			
		||||
  flex-grow: 0;
 | 
			
		||||
  flex-shrink: 1;
 | 
			
		||||
}
 | 
			
		||||
.nav.primary .nav-brand img {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
.nav.primary .nav-item {
 | 
			
		||||
  font-size: 1.2rem;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
.nav.secondary {
 | 
			
		||||
  justify-content: right;
 | 
			
		||||
@ -8385,6 +8411,52 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 1024px) {
 | 
			
		||||
  .page {
 | 
			
		||||
    margin-top: var(--nav-primary-height);
 | 
			
		||||
  }
 | 
			
		||||
  .navs {
 | 
			
		||||
    z-index: 100000;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    top: 0;
 | 
			
		||||
  }
 | 
			
		||||
  .navs .nav:first-child {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
  }
 | 
			
		||||
  .navs .nav + .nav {
 | 
			
		||||
    flew-grow: 0 !important;
 | 
			
		||||
  }
 | 
			
		||||
  .nav {
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
  }
 | 
			
		||||
  .nav .burger {
 | 
			
		||||
    display: unset;
 | 
			
		||||
    margin-left: auto;
 | 
			
		||||
  }
 | 
			
		||||
  .nav .nav-menu {
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    top: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    box-shadow: 0em 0.5em 0.5em rgba(0, 0, 0, 0.05);
 | 
			
		||||
  }
 | 
			
		||||
  .nav .nav-menu .nav-item {
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-size: 1.4rem;
 | 
			
		||||
    font-weight: 400;
 | 
			
		||||
  }
 | 
			
		||||
  .nav .nav-menu .nav-item:hover {
 | 
			
		||||
    background-color: var(--highlight-color-2-alpha);
 | 
			
		||||
    color: var(--highlight-color);
 | 
			
		||||
  }
 | 
			
		||||
  .nav .nav-menu:not(.active) {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
nav li {
 | 
			
		||||
  list-style: none;
 | 
			
		||||
}
 | 
			
		||||
@ -8648,6 +8720,10 @@ preview-header:not(.no-cover) .preview-card-headings .heading {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@media screen and (max-width: 1024px) {
 | 
			
		||||
  .container.header {
 | 
			
		||||
    margin-right: 0 !important;
 | 
			
		||||
    margin-left: 0 !important;
 | 
			
		||||
  }
 | 
			
		||||
  .page .container {
 | 
			
		||||
    margin-left: 1.2rem;
 | 
			
		||||
    margin-right: 1.2rem;
 | 
			
		||||
@ -8778,7 +8854,7 @@ preview-header:not(.no-cover) .preview-card-headings .heading {
 | 
			
		||||
  height: 3.75em !important;
 | 
			
		||||
  border-top: 1px hsl(0deg, 0%, 71%) solid;
 | 
			
		||||
  background: var(--player-bar-bg);
 | 
			
		||||
  box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
 | 
			
		||||
  box-shadow: 0em -0.5em 0.5em rgba(0, 0, 0, 0.05);
 | 
			
		||||
}
 | 
			
		||||
.a-player-bar > * {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
@ -169,6 +169,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _sou
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ASwitch.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/ASwitch.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  props: {\n    initialActive: {\n      type: Boolean,\n      default: null\n    },\n    el: {\n      type: String,\n      default: \"\"\n    },\n    label: {\n      type: String,\n      default: \"\"\n    },\n    icon: {\n      type: String,\n      default: \"fa fa-bars\"\n    },\n    ariaLabel: {\n      type: String,\n      default: \"\"\n    },\n    ariaDescription: {\n      type: String,\n      default: \"\"\n    },\n    activeClass: {\n      type: String,\n      default: \"active\"\n    },\n    /// switch toggle of all items of this group.\n    group: {\n      type: String,\n      default: \"\"\n    }\n  },\n  data() {\n    return {\n      active: this.initialActive\n    };\n  },\n  computed: {\n    groupClass() {\n      return this.group && \"a-switch-\" + this.group || '';\n    },\n    buttonClass() {\n      return [this.active && 'active' || '', this.groupClass];\n    }\n  },\n  methods: {\n    toggle() {\n      this.set(!this.active);\n    },\n    set(active) {\n      const el = document.querySelector(this.el);\n      if (active) el.classList.add(this.activeClass);else el.classList.remove(this.activeClass);\n      this.active = active;\n      if (active) this.resetGroup();\n    },\n    resetGroup() {\n      const els = document.querySelectorAll(\".\" + this.groupClass);\n      for (var el of els) if (el != this.$el) el.__vnode.ctx.ctx.set(false);\n    }\n  },\n  mounted() {\n    if (this.initialActive !== null) this.set(this.initialActive);\n  }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.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/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AActionButton.vue?vue&type=template&id=3f443389":
 | 
			
		||||
/*!***************************************************************************************************************************************************************************************************************************************************************************!*\
 | 
			
		||||
  !*** ./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/AActionButton.vue?vue&type=template&id=3f443389 ***!
 | 
			
		||||
@ -329,6 +339,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/ASwitch.vue?vue&type=template&id=62a3c675":
 | 
			
		||||
/*!*********************************************************************************************************************************************************************************************************************************************************************!*\
 | 
			
		||||
  !*** ./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/ASwitch.vue?vue&type=template&id=62a3c675 ***!
 | 
			
		||||
  \*********************************************************************************************************************************************************************************************************************************************************************/
 | 
			
		||||
/***/ (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 = [\"title\", \"aria-label\", \"aria-description\"];\nconst _hoisted_2 = {\n  class: \"icon\"\n};\nconst _hoisted_3 = {\n  key: 0\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)(\"button\", {\n    title: $props.ariaLabel,\n    \"aria-label\": $props.ariaLabel || $props.label,\n    \"aria-description\": $props.ariaDescription,\n    onClick: _cache[0] || (_cache[0] = (...args) => $options.toggle && $options.toggle(...args)),\n    class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($options.buttonClass)\n  }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\", {\n    active: $data.active\n  }, () => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n    class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.icon)\n  }, null, 2 /* CLASS */)]), $props.label ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"label\", _hoisted_3, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.label), 1 /* TEXT */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)])], 10 /* CLASS, PROPS */, _hoisted_1);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.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");
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/app.js":
 | 
			
		||||
/*!********************!*\
 | 
			
		||||
  !*** ./src/app.js ***!
 | 
			
		||||
@ -345,7 +365,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 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 _ACarousel_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ACarousel.vue */ \"./src/components/ACarousel.vue\");\n/* harmony import */ var _ADropdown_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ADropdown.vue */ \"./src/components/ADropdown.vue\");\n/* harmony import */ var _AEpisode_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AEpisode.vue */ \"./src/components/AEpisode.vue\");\n/* harmony import */ var _AList_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AList.vue */ \"./src/components/AList.vue\");\n/* harmony import */ var _APage_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APage.vue */ \"./src/components/APage.vue\");\n/* harmony import */ var _APlayer_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./APlayer.vue */ \"./src/components/APlayer.vue\");\n/* harmony import */ var _APlaylist_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./APlaylist.vue */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./APlaylistEditor.vue */ \"./src/components/APlaylistEditor.vue\");\n/* harmony import */ var _AProgress_vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./AProgress.vue */ \"./src/components/AProgress.vue\");\n/* harmony import */ var _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./ASoundItem.vue */ \"./src/components/ASoundItem.vue\");\n/* harmony import */ var _AStatistics_vue__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./AStatistics.vue */ \"./src/components/AStatistics.vue\");\n/* harmony import */ var _AStreamer_vue__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./AStreamer.vue */ \"./src/components/AStreamer.vue\");\n\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  ACarousel: _ACarousel_vue__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n  ADropdown: _ADropdown_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n  AEpisode: _AEpisode_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n  AList: _AList_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n  APage: _APage_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n  APlayer: _APlayer_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n  APlaylist: _APlaylist_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"],\n  AProgress: _AProgress_vue__WEBPACK_IMPORTED_MODULE_9__[\"default\"],\n  ASoundItem: _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_10__[\"default\"]\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (base);\nconst admin = {\n  ...base,\n  AStatistics: _AStatistics_vue__WEBPACK_IMPORTED_MODULE_11__[\"default\"],\n  AStreamer: _AStreamer_vue__WEBPACK_IMPORTED_MODULE_12__[\"default\"],\n  APlaylistEditor: _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_8__[\"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 _ACarousel_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ACarousel.vue */ \"./src/components/ACarousel.vue\");\n/* harmony import */ var _ADropdown_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ADropdown.vue */ \"./src/components/ADropdown.vue\");\n/* harmony import */ var _AEpisode_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AEpisode.vue */ \"./src/components/AEpisode.vue\");\n/* harmony import */ var _AList_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AList.vue */ \"./src/components/AList.vue\");\n/* harmony import */ var _APage_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APage.vue */ \"./src/components/APage.vue\");\n/* harmony import */ var _APlayer_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./APlayer.vue */ \"./src/components/APlayer.vue\");\n/* harmony import */ var _APlaylist_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./APlaylist.vue */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./APlaylistEditor.vue */ \"./src/components/APlaylistEditor.vue\");\n/* harmony import */ var _AProgress_vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./AProgress.vue */ \"./src/components/AProgress.vue\");\n/* harmony import */ var _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./ASoundItem.vue */ \"./src/components/ASoundItem.vue\");\n/* harmony import */ var _ASwitch_vue__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./ASwitch.vue */ \"./src/components/ASwitch.vue\");\n/* harmony import */ var _AStatistics_vue__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./AStatistics.vue */ \"./src/components/AStatistics.vue\");\n/* harmony import */ var _AStreamer_vue__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./AStreamer.vue */ \"./src/components/AStreamer.vue\");\n\n\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  ACarousel: _ACarousel_vue__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n  ADropdown: _ADropdown_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n  AEpisode: _AEpisode_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n  AList: _AList_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n  APage: _APage_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n  APlayer: _APlayer_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n  APlaylist: _APlaylist_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"],\n  AProgress: _AProgress_vue__WEBPACK_IMPORTED_MODULE_9__[\"default\"],\n  ASoundItem: _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_10__[\"default\"],\n  ASwitch: _ASwitch_vue__WEBPACK_IMPORTED_MODULE_11__[\"default\"]\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (base);\nconst admin = {\n  ...base,\n  AStatistics: _AStatistics_vue__WEBPACK_IMPORTED_MODULE_12__[\"default\"],\n  AStreamer: _AStreamer_vue__WEBPACK_IMPORTED_MODULE_13__[\"default\"],\n  APlaylistEditor: _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"]\n};\n\n//# sourceURL=webpack://aircox-assets/./src/components/index.js?");
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
@ -619,6 +639,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ASt
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/components/ASwitch.vue":
 | 
			
		||||
/*!************************************!*\
 | 
			
		||||
  !*** ./src/components/ASwitch.vue ***!
 | 
			
		||||
  \************************************/
 | 
			
		||||
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
 | 
			
		||||
 | 
			
		||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ASwitch_vue_vue_type_template_id_62a3c675__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ASwitch.vue?vue&type=template&id=62a3c675 */ \"./src/components/ASwitch.vue?vue&type=template&id=62a3c675\");\n/* harmony import */ var _ASwitch_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ASwitch.vue?vue&type=script&lang=js */ \"./src/components/ASwitch.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\"])(_ASwitch_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"], [['render',_ASwitch_vue_vue_type_template_id_62a3c675__WEBPACK_IMPORTED_MODULE_0__.render],['__file',\"src/components/ASwitch.vue\"]])\n/* hot reload */\nif (false) {}\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (__exports__);\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.vue?");
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/components/AActionButton.vue?vue&type=script&lang=js":
 | 
			
		||||
/*!******************************************************************!*\
 | 
			
		||||
  !*** ./src/components/AActionButton.vue?vue&type=script&lang=js ***!
 | 
			
		||||
@ -779,6 +809,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/components/ASwitch.vue?vue&type=script&lang=js":
 | 
			
		||||
/*!************************************************************!*\
 | 
			
		||||
  !*** ./src/components/ASwitch.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_ASwitch_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_ASwitch_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]!./ASwitch.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/ASwitch.vue?vue&type=script&lang=js\");\n \n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.vue?");
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/components/AActionButton.vue?vue&type=template&id=3f443389":
 | 
			
		||||
/*!************************************************************************!*\
 | 
			
		||||
  !*** ./src/components/AActionButton.vue?vue&type=template&id=3f443389 ***!
 | 
			
		||||
@ -939,6 +979,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/components/ASwitch.vue?vue&type=template&id=62a3c675":
 | 
			
		||||
/*!******************************************************************!*\
 | 
			
		||||
  !*** ./src/components/ASwitch.vue?vue&type=template&id=62a3c675 ***!
 | 
			
		||||
  \******************************************************************/
 | 
			
		||||
/***/ (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_ASwitch_vue_vue_type_template_id_62a3c675__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_ASwitch_vue_vue_type_template_id_62a3c675__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]!./ASwitch.vue?vue&type=template&id=62a3c675 */ \"./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/ASwitch.vue?vue&type=template&id=62a3c675\");\n\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.vue?");
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/components/ACarousel.vue?vue&type=style&index=0&id=b79f173e&scoped=true&lang=css":
 | 
			
		||||
/*!**********************************************************************************************!*\
 | 
			
		||||
  !*** ./src/components/ACarousel.vue?vue&type=style&index=0&id=b79f173e&scoped=true&lang=css ***!
 | 
			
		||||
 | 
			
		||||
@ -48,13 +48,17 @@ Usefull context:
 | 
			
		||||
            })
 | 
			
		||||
        </script>
 | 
			
		||||
        <div id="app">
 | 
			
		||||
            <div class="container">
 | 
			
		||||
            <div class="container navs">
 | 
			
		||||
                {% block nav %}
 | 
			
		||||
                <nav class="nav primary" role="navigation" aria-label="main navigation">
 | 
			
		||||
                    {% block nav-primary %}
 | 
			
		||||
                    <a class="nav-brand" href="{% url "home" %}">
 | 
			
		||||
                        <img src="{{ station.logo.url }}">
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <a-switch class="button burger"
 | 
			
		||||
                        el=".nav.primary .nav-menu" group="nav"
 | 
			
		||||
                        aria-label="{% translate "Main menu" %}">
 | 
			
		||||
                    </a-switch>
 | 
			
		||||
                    <div class="nav-menu">
 | 
			
		||||
                        {% block nav-primary-menu %}
 | 
			
		||||
                        {% nav_items "top" css_class="nav-item" active_class="active" as items %}
 | 
			
		||||
 | 
			
		||||
@ -49,41 +49,17 @@
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% if last_podcasts %}
 | 
			
		||||
{% if podcasts %}
 | 
			
		||||
<section class="container">
 | 
			
		||||
    <h2 class="title is-3 p-2">{% translate "Last podcasts" %}</h2>
 | 
			
		||||
 | 
			
		||||
    <a-carousel>
 | 
			
		||||
        {% for object in last_podcasts %}
 | 
			
		||||
        {% page_widget "card" object open=True %}
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </a-carousel>
 | 
			
		||||
 | 
			
		||||
    <nav class="nav-urls">
 | 
			
		||||
        <a href="{% url "podcast-list" %}"
 | 
			
		||||
            aria-label="{% translate "Show all podcasts" %}">
 | 
			
		||||
            {% translate "All podcasts" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </nav>
 | 
			
		||||
    {% include "./widgets/carousel.html" with objects=podcasts url_name="podcast-list" url_label=_("All podcasts") %}
 | 
			
		||||
</section>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
{% if last_publications %}
 | 
			
		||||
{% if publications %}
 | 
			
		||||
<section class="container">
 | 
			
		||||
    <h2 class="title is-3 p-2">{% translate "Last publications" %}</h2>
 | 
			
		||||
 | 
			
		||||
    <div role="list">
 | 
			
		||||
        {% for object in last_publications %}
 | 
			
		||||
        {% page_widget "item" object open=True %}
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <nav class="nav-urls">
 | 
			
		||||
        <a href="{% url "page-list" %}"
 | 
			
		||||
            aria-label="{% translate "Show all publications" %}">
 | 
			
		||||
            {% translate "All publications" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </nav>
 | 
			
		||||
    {% include "./widgets/carousel.html" with objects=publications url_name="page-list" url_label=_("All publications") %}
 | 
			
		||||
</section>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
@ -3,12 +3,20 @@
 | 
			
		||||
{% load i18n aircox %}
 | 
			
		||||
 | 
			
		||||
{% block secondary-nav %}
 | 
			
		||||
{% if view.categories %}
 | 
			
		||||
<nav class="nav secondary">
 | 
			
		||||
    {% for id, title in view.categories.items %}
 | 
			
		||||
    <div class="nav-menu nav-categories">
 | 
			
		||||
        {% for id, title in view.categories.items %}
 | 
			
		||||
        <a class="nav-item{% if category_id == id or parent and parent.category_id == id %} active{% endif %}"
 | 
			
		||||
            href="?category__id={{ id }}">{{ title }}</a>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </div>
 | 
			
		||||
    <a-switch class="button burger"
 | 
			
		||||
        el=".nav-categories" group="nav" icon="fas fa-tags"
 | 
			
		||||
        aria-label="{% translate "Categories" %}">
 | 
			
		||||
    </a-switch>
 | 
			
		||||
</nav>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block header %}
 | 
			
		||||
 | 
			
		||||
@ -6,21 +6,42 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block content-container %}
 | 
			
		||||
{% with schedules=program.schedule_set.all %}
 | 
			
		||||
{% if schedules %}
 | 
			
		||||
<section class="container schedules">
 | 
			
		||||
    {% for schedule in schedules %}
 | 
			
		||||
    <div class="schedule">
 | 
			
		||||
        <div class="heading">
 | 
			
		||||
            <span class="day">{{ schedule.get_frequency_display }}</span>
 | 
			
		||||
            {% with schedule.start|date:"H:i" as start %}
 | 
			
		||||
            {% with schedule.end|date:"H:i" as end %}
 | 
			
		||||
            <time datetime="{{ start }}">{{ start }}</time>
 | 
			
		||||
            —
 | 
			
		||||
            <time datetime="{{ end }}">{{ end }}</time>
 | 
			
		||||
            {% endwith %}
 | 
			
		||||
            {% endwith %}
 | 
			
		||||
            <small>
 | 
			
		||||
                {% if schedule.initial %}
 | 
			
		||||
                {% with schedule.initial.date as date %}
 | 
			
		||||
                <span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
 | 
			
		||||
                    ({% translate "Rerun" %})
 | 
			
		||||
                </span>
 | 
			
		||||
                {% endwith %}
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </small>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
</section>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
 | 
			
		||||
{% if episodes %}
 | 
			
		||||
<section class="container">
 | 
			
		||||
    <h3 class="title is-3">{% translate "Last Episodes" %}</h3>
 | 
			
		||||
    <a-carousel section-class="card-grid">
 | 
			
		||||
        {% for object in episodes %}
 | 
			
		||||
        {% page_widget "card" object %}
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </a-carousel>
 | 
			
		||||
 | 
			
		||||
    <nav class="nav-urls">
 | 
			
		||||
        <a href="{% url "episode-list" parent_slug=program.slug %}">
 | 
			
		||||
            {% translate "See more" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </nav>
 | 
			
		||||
    {% include "./widgets/carousel.html" with objects=episodes url_name="episode-list" url_parent=object url_label=_("All episodes") %}
 | 
			
		||||
</section>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
@ -28,47 +49,8 @@
 | 
			
		||||
{% if articles %}
 | 
			
		||||
<section class="container">
 | 
			
		||||
    <h3 class="title is-3">{% translate "Last Articles" %}</h3>
 | 
			
		||||
 | 
			
		||||
    <a-carousel section-class="card-grid">
 | 
			
		||||
        {% for object in articles %}
 | 
			
		||||
        {% page_widget "card" object %}
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </a-carousel>
 | 
			
		||||
 | 
			
		||||
    <nav class="nav-urls">
 | 
			
		||||
        <a href="{% url "article-list" parent_slug=program.slug %}"
 | 
			
		||||
            aria-label="{% translate "Show all program's articles" %}">
 | 
			
		||||
            {% translate "See more" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </nav>
 | 
			
		||||
    {% include "./widgets/carousel.html" with objects=articles url_name="article-list" url_parent=object url_label=_("All articles") %}
 | 
			
		||||
</section>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block sidebar %}
 | 
			
		||||
<section>
 | 
			
		||||
    <h4 class="title is-4">{% translate "Diffusions" %}</h4>
 | 
			
		||||
    {% for schedule in program.schedule_set.all %}
 | 
			
		||||
    {{ schedule.get_frequency_display }}
 | 
			
		||||
    {% with schedule.start|date:"H:i" as start %}
 | 
			
		||||
    {% with schedule.end|date:"H:i" as end %}
 | 
			
		||||
    <time datetime="{{ start }}">{{ start }}</time>
 | 
			
		||||
    —
 | 
			
		||||
    <time datetime="{{ end }}">{{ end }}</time>
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
    <small>
 | 
			
		||||
        {% if schedule.initial %}
 | 
			
		||||
        {% with schedule.initial.date as date %}
 | 
			
		||||
        <span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
 | 
			
		||||
            ({% translate "Rerun" %})
 | 
			
		||||
        </span>
 | 
			
		||||
        {% endwith %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </small>
 | 
			
		||||
    <br>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
</section>
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,26 @@
 | 
			
		||||
{% load aircox %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
Context:
 | 
			
		||||
- objects: list of objects to display
 | 
			
		||||
- url_name: url name to show the full list
 | 
			
		||||
- url_parent: parent page for the full list
 | 
			
		||||
- url_label: label of url button
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
 | 
			
		||||
<div class="card-grid">
 | 
			
		||||
{% for object in object_list %}
 | 
			
		||||
{% page_widget "card" object.episode diffusion=object %}
 | 
			
		||||
{% endfor %}
 | 
			
		||||
</div>
 | 
			
		||||
<a-carousel section-class="card-grid">
 | 
			
		||||
    {% for object in objects %}
 | 
			
		||||
    {% page_widget "card" object %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
</a-carousel>
 | 
			
		||||
 | 
			
		||||
{% if url_name %}
 | 
			
		||||
<nav class="nav-urls">
 | 
			
		||||
    {% if url_parent %}
 | 
			
		||||
    <a href="{% url url_name parent_slug=program.slug %}">
 | 
			
		||||
    {% else %}
 | 
			
		||||
    <a href="{% url url_name %}">
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {{ url_label|default:_("Show all") }}
 | 
			
		||||
    </a>
 | 
			
		||||
</nav>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
@ -11,26 +11,33 @@ An empty date results to a title or a separator
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% for day in dates %}
 | 
			
		||||
<a href="{% url url_name date=day %}" class="nav-item {% if day == date %}active{% endif %}">
 | 
			
		||||
    {{ day|date:"l d" }}
 | 
			
		||||
</a>
 | 
			
		||||
{% endfor %}
 | 
			
		||||
<a-switch class="button burger"
 | 
			
		||||
    el=".nav-dates" icon="far fa-calendar" group="nav"
 | 
			
		||||
    aria-label="{% translate "Dates" %}">
 | 
			
		||||
</a-switch>
 | 
			
		||||
 | 
			
		||||
<a-dropdown class="nav-item align-right flex-grow-0 dropdown is-right"
 | 
			
		||||
        content-class="dropdown-menu"
 | 
			
		||||
        button-tag="span" button-class="dropdown-trigger"
 | 
			
		||||
        button-icon-open="fa-solid fa-plus" button-icon-close="fa-solid fa-minus">
 | 
			
		||||
    <template #default>
 | 
			
		||||
        <div class="dropdown-content">
 | 
			
		||||
            <div class="dropdown-item">
 | 
			
		||||
                <h4>{% translate "Pick a date" %}</h4>
 | 
			
		||||
                <v-calendar mode="date" borderless
 | 
			
		||||
                    :initial-page="{month: {{date.month}}, year: {{date.year}}}"
 | 
			
		||||
                    @dayclick="(event) => window.aircox.pickDate({% url url_name %}, event)"
 | 
			
		||||
                    color="yellow"
 | 
			
		||||
                    />
 | 
			
		||||
<div class="nav-menu nav-dates">
 | 
			
		||||
    {% for day in dates %}
 | 
			
		||||
    <a href="{% url url_name date=day %}" class="nav-item {% if day == date %}active{% endif %}">
 | 
			
		||||
        {{ day|date:"l d" }}
 | 
			
		||||
    </a>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
 | 
			
		||||
    <a-dropdown class="nav-item align-right flex-grow-0 dropdown is-right"
 | 
			
		||||
            content-class="dropdown-menu"
 | 
			
		||||
            button-tag="span" button-class="dropdown-trigger"
 | 
			
		||||
            button-icon-open="fa-solid fa-plus" button-icon-close="fa-solid fa-minus">
 | 
			
		||||
        <template #default>
 | 
			
		||||
            <div class="dropdown-content">
 | 
			
		||||
                <div class="dropdown-item">
 | 
			
		||||
                    <h4>{% translate "Pick a date" %}</h4>
 | 
			
		||||
                    <v-calendar mode="date" borderless
 | 
			
		||||
                        :initial-page="{month: {{date.month}}, year: {{date.year}}}"
 | 
			
		||||
                        @dayclick="(event) => window.aircox.pickDate({% url url_name %}, event)"
 | 
			
		||||
                        color="yellow"
 | 
			
		||||
                        />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </template>
 | 
			
		||||
</a-dropdown>
 | 
			
		||||
        </template>
 | 
			
		||||
    </a-dropdown>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from aircox import models
 | 
			
		||||
from aircox.test import Interface
 | 
			
		||||
@ -37,26 +36,13 @@ class TestBaseView:
 | 
			
		||||
    def test_station(self, base_view, station):
 | 
			
		||||
        assert base_view.station == station
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_get_sidebar_queryset(self, base_view, pages, published_pages):
 | 
			
		||||
        query = base_view.get_sidebar_queryset().values_list("id", flat=True)
 | 
			
		||||
        page_ids = {r.id for r in published_pages}
 | 
			
		||||
        assert set(query) == page_ids
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_get_sidebar_url(self, base_view):
 | 
			
		||||
        assert base_view.get_sidebar_url() == reverse("page-list")
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_get_context_data(self, base_view, station, published_pages):
 | 
			
		||||
        base_view.has_sidebar = True
 | 
			
		||||
        base_view.get_sidebar_queryset = lambda: published_pages
 | 
			
		||||
        context = base_view.get_context_data()
 | 
			
		||||
        assert context == {
 | 
			
		||||
            "view": base_view,
 | 
			
		||||
            "station": station,
 | 
			
		||||
            "page": None,  # get_page() returns None
 | 
			
		||||
            "has_sidebar": base_view.has_sidebar,
 | 
			
		||||
            "audio_streams": station.streams,
 | 
			
		||||
            "model": base_view.model,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -57,6 +57,7 @@ urls = [
 | 
			
		||||
        name="episode-detail",
 | 
			
		||||
    ),
 | 
			
		||||
    path(_("podcasts/"), views.PodcastListView.as_view(), name="podcast-list"),
 | 
			
		||||
    path(_("podcasts/<slug:parent_slug>"), views.PodcastListView.as_view(), name="podcast-list"),
 | 
			
		||||
    path(_("week/"), views.DiffusionListView.as_view(), name="diffusion-list"),
 | 
			
		||||
    path(
 | 
			
		||||
        _("week/<date:date>/"),
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ class HomeView(AttachedToMixin, BaseView, ListView):
 | 
			
		||||
            diffs = next_diffs[: self.related_carousel_count]
 | 
			
		||||
        return diffs
 | 
			
		||||
 | 
			
		||||
    def get_last_publications(self):
 | 
			
		||||
    def get_publications(self):
 | 
			
		||||
        # note: with postgres db, possible to use distinct()
 | 
			
		||||
        qs = self.publications_queryset.all()
 | 
			
		||||
        parents = set()
 | 
			
		||||
@ -56,7 +56,7 @@ class HomeView(AttachedToMixin, BaseView, ListView):
 | 
			
		||||
                break
 | 
			
		||||
        return items
 | 
			
		||||
 | 
			
		||||
    def get_last_podcasts(self):
 | 
			
		||||
    def get_podcasts(self):
 | 
			
		||||
        return self.podcasts_queryset.all()[: self.related_carousel_count]
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
@ -69,8 +69,8 @@ class HomeView(AttachedToMixin, BaseView, ListView):
 | 
			
		||||
                "diffusion": current_diff,
 | 
			
		||||
                "logs": self.get_logs(self.object_list),
 | 
			
		||||
                "next_diffs": next_diffs,
 | 
			
		||||
                "last_publications": self.get_last_publications(),
 | 
			
		||||
                "last_podcasts": self.get_last_podcasts(),
 | 
			
		||||
                "publications": self.get_publications(),
 | 
			
		||||
                "podcasts": self.get_podcasts(),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
@ -39,9 +39,15 @@ class ProgramDetailView(BaseProgramMixin, PageDetailView):
 | 
			
		||||
        return reverse("program-list") + f"?category__id={self.object.category_id}"
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        episodes = Episode.objects.program(self.object).published().order_by("-pub_date")[: self.related_count]
 | 
			
		||||
        articles = Article.objects.parent(self.object).published().order_by("-pub_date")[: self.related_count]
 | 
			
		||||
        return super().get_context_data(articles=articles, episodes=episodes, **kwargs)
 | 
			
		||||
        episodes = Episode.objects.program(self.object).published().order_by("-pub_date")
 | 
			
		||||
        podcasts = episodes.with_podcasts()
 | 
			
		||||
        articles = Article.objects.parent(self.object).published().order_by("-pub_date")
 | 
			
		||||
        return super().get_context_data(
 | 
			
		||||
            articles=articles[: self.related_count],
 | 
			
		||||
            episodes=episodes[: self.related_count],
 | 
			
		||||
            podcasts=podcasts[: self.related_count],
 | 
			
		||||
            **kwargs,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProgramListView(PageListView):
 | 
			
		||||
 | 
			
		||||
@ -91,9 +91,9 @@
 | 
			
		||||
 | 
			
		||||
    <div class="column is-two-fifths">
 | 
			
		||||
        <h6 class="subtitle is-6 is-marginless">Metadata</h6>
 | 
			
		||||
        <table class="table has-background-transparent">
 | 
			
		||||
        <table class="table bg-transparent">
 | 
			
		||||
            <tbody>
 | 
			
		||||
                <tr><th class="has-text-right has-text-nowrap">
 | 
			
		||||
                <tr><th class="has-text-right ws-nowrap">
 | 
			
		||||
                    {% translate "Status" %}
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <td :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}">
 | 
			
		||||
@ -103,7 +103,7 @@
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr v-if="source.data.air_time">
 | 
			
		||||
                    <th class="has-text-right has-text-nowrap">
 | 
			
		||||
                    <th class="has-text-right ws-nowrap">
 | 
			
		||||
                        {% translate "Air time" %}
 | 
			
		||||
                    </th><td>
 | 
			
		||||
                        <span class="far fa-clock"></span>
 | 
			
		||||
@ -113,7 +113,7 @@
 | 
			
		||||
                        </time>
 | 
			
		||||
                    </td>
 | 
			
		||||
                <tr v-if="source.remaining">
 | 
			
		||||
                    <th class="has-text-right has-text-nowrap">
 | 
			
		||||
                    <th class="has-text-right ws-nowrap">
 | 
			
		||||
                        {% translate "Time left" %}
 | 
			
		||||
                    </th><td>
 | 
			
		||||
                        <span class="far fa-hourglass"></span>
 | 
			
		||||
@ -121,7 +121,7 @@
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr v-if="source.data.uri">
 | 
			
		||||
                    <th class="has-text-right has-text-nowrap">
 | 
			
		||||
                    <th class="has-text-right ws-nowrap">
 | 
			
		||||
                        {% translate "Data source" %}
 | 
			
		||||
                    </th><td>
 | 
			
		||||
                        <span class="far fa-play-circle"></span>
 | 
			
		||||
 | 
			
		||||
@ -59,41 +59,6 @@ $screen-wide: 1380px;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- helpers/modifiers
 | 
			
		||||
.is-fullwidth { width: 100%; }
 | 
			
		||||
.is-fullheight { height: 100%; }
 | 
			
		||||
.is-fixed-bottom {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    margin-bottom: 0px;
 | 
			
		||||
    border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
.is-borderless { border: none; }
 | 
			
		||||
 | 
			
		||||
.has-text-nowrap {
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.has-background-transparent {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-opacity-light {
 | 
			
		||||
    opacity: 0.7;
 | 
			
		||||
    &:hover {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.float-right { float: right }
 | 
			
		||||
.float-left { float: left }
 | 
			
		||||
.overflow-hidden { overflow: hidden }
 | 
			
		||||
.overflow-hidden.is-fullwidth { max-width: 100%; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
*[draggable="true"] {
 | 
			
		||||
    cursor: move;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//-- forms
 | 
			
		||||
input.half-field:not(:active):not(:hover) {
 | 
			
		||||
    border: none;
 | 
			
		||||
@ -102,21 +67,6 @@ input.half-field:not(:active):not(:hover) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- animations
 | 
			
		||||
@keyframes blink {
 | 
			
		||||
    from { opacity: 1; }
 | 
			
		||||
    to { opacity: 0.4; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.blink {
 | 
			
		||||
    animation: 1s ease-in-out 2s infinite alternate blink;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading {
 | 
			
		||||
    animation: 1s ease-in-out 3s infinite alternate blink;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- navbar
 | 
			
		||||
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
 | 
			
		||||
    box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
 | 
			
		||||
@ -178,6 +128,9 @@ a.navbar-item.is-active {
 | 
			
		||||
    --highlight-color-2-alpha: rgb(0, 0, 254, 0.7);
 | 
			
		||||
    --highlight-color-2-grey: rgba(50, 200, 200, 1);
 | 
			
		||||
 | 
			
		||||
    --nav-primary-height: 4rem;
 | 
			
		||||
    --nav-secondary-height: 3rem;
 | 
			
		||||
 | 
			
		||||
    --header-height: 30rem;
 | 
			
		||||
 | 
			
		||||
    --heading-height: 30rem;
 | 
			
		||||
@ -245,30 +198,65 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ---- helpers
 | 
			
		||||
*[draggable="true"] {
 | 
			
		||||
    cursor: move;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- animations
 | 
			
		||||
@keyframes blink {
 | 
			
		||||
    from { opacity: 1; }
 | 
			
		||||
    to { opacity: 0.4; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.blink { animation: 1s ease-in-out 3s infinite alternate blink; }
 | 
			
		||||
.loading { animation: 1s ease-in-out 1s infinite alternate blink; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// -- layout
 | 
			
		||||
.d-inline { display: inline; }
 | 
			
		||||
.d-block { display: block; }
 | 
			
		||||
.d-inline-block { display: inline-block; }
 | 
			
		||||
 | 
			
		||||
.p-relative { position: relative }
 | 
			
		||||
.p-absolute { position: absolute }
 | 
			
		||||
.p-fixed { position: fixed }
 | 
			
		||||
.p-sticky { position: sticky }
 | 
			
		||||
.p-static { position: static }
 | 
			
		||||
 | 
			
		||||
.align-left { text-align: left; justify-content: left; }
 | 
			
		||||
.align-right { text-align: right; justify-content: right; }
 | 
			
		||||
 | 
			
		||||
.height-full { height: 100%; }
 | 
			
		||||
.d-inline { display: inline !important; }
 | 
			
		||||
.d-block { display: block !important; }
 | 
			
		||||
.d-inline-block { display: inline-block !important; }
 | 
			
		||||
 | 
			
		||||
.flex-push-right { margin-left: auto; }
 | 
			
		||||
.flex-grow-0 { flex-grow: 0 !important; }
 | 
			
		||||
 | 
			
		||||
.float-right { float: right }
 | 
			
		||||
.float-left { float: left }
 | 
			
		||||
 | 
			
		||||
.is-fullwidth { width: 100%; }
 | 
			
		||||
.is-fullheight { height: 100%; }
 | 
			
		||||
.is-fixed-bottom {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    margin-bottom: 0px;
 | 
			
		||||
    border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
.is-borderless { border: none; }
 | 
			
		||||
 | 
			
		||||
.overflow-hidden { overflow: hidden }
 | 
			
		||||
.overflow-hidden.is-fullwidth { max-width: 100%; }
 | 
			
		||||
 | 
			
		||||
.p-relative { position: relative !important }
 | 
			
		||||
.p-absolute { position: absolute !important }
 | 
			
		||||
.p-fixed { position: fixed !important }
 | 
			
		||||
.p-sticky { position: sticky !important }
 | 
			
		||||
.p-static { position: static !important }
 | 
			
		||||
 | 
			
		||||
.height-full { height: 100%; }
 | 
			
		||||
 | 
			
		||||
.ws-nowrap { white-space: nowrap; }
 | 
			
		||||
.no-border { border: 0px !important; }
 | 
			
		||||
 | 
			
		||||
// -- colors
 | 
			
		||||
.highlight-color { color: var(--highlight-color); }
 | 
			
		||||
.highlight-color-2 { color: var(--highlight-color-2); }
 | 
			
		||||
 | 
			
		||||
.bg-transparent { background-color: transparent; }
 | 
			
		||||
 | 
			
		||||
.is-success {
 | 
			
		||||
    background-color: $green !important;
 | 
			
		||||
    border-color: $green-dark !important;
 | 
			
		||||
@ -288,6 +276,19 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.schedules {
 | 
			
		||||
    margin-top: calc(0rem - $mp-3) !important;
 | 
			
		||||
    padding-top: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.schedule {
 | 
			
		||||
    .day {
 | 
			
		||||
        font-weight: $weight-bold;
 | 
			
		||||
        margin-right: $mp-3;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// -- buttons, forms
 | 
			
		||||
.button, a.button, button.button, .nav-urls a {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
@ -302,8 +303,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
 | 
			
		||||
    .icon {
 | 
			
		||||
        vertical-align: middle;
 | 
			
		||||
        &:first-child { margin-right: $mp-3; }
 | 
			
		||||
        &:last-child { margin-left: $mp-3 }
 | 
			
		||||
        &:not(:only-child) {
 | 
			
		||||
            &:first-child { margin-right: $mp-3; }
 | 
			
		||||
            &:last-child { margin-left: $mp-3 }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -341,8 +344,9 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.button-group {
 | 
			
		||||
.button-group, .nav {
 | 
			
		||||
    .button {
 | 
			
		||||
        border-radius: 0px;
 | 
			
		||||
        background-color: transparent;
 | 
			
		||||
        border-top: 0px;
 | 
			
		||||
        border-bottom: 0px;
 | 
			
		||||
@ -384,6 +388,9 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.label, .textarea, .input, .select {
 | 
			
		||||
    font-size: $text-size-medium;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -- headings
 | 
			
		||||
.title {
 | 
			
		||||
@ -424,6 +431,8 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .burger { display: none; }
 | 
			
		||||
 | 
			
		||||
    .nav-item {
 | 
			
		||||
        padding: $mp-3;
 | 
			
		||||
        flex-grow: 1;
 | 
			
		||||
@ -433,31 +442,45 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
        font-family: var(--heading-font-family);
 | 
			
		||||
        text-transform: uppercase;
 | 
			
		||||
 | 
			
		||||
        a, .button {
 | 
			
		||||
            display: block;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.active {
 | 
			
		||||
            background-color: var(--highlight-color-2);
 | 
			
		||||
            color: var(--highlight-color);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.primary {
 | 
			
		||||
        .nav-brand {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            margin-right: $mp-3;
 | 
			
		||||
            padding: $mp-3;
 | 
			
		||||
    .nav-menu {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-grow: 1;
 | 
			
		||||
        background-color: var(--highlight-color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
            img {
 | 
			
		||||
                width: 12rem !important;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    &.primary {
 | 
			
		||||
        height: var(--nav-primary-height);
 | 
			
		||||
 | 
			
		||||
        .nav-menu {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            flex-grow: 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .nav-brand {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            padding: $mp-3;
 | 
			
		||||
            flex-grow: 0;
 | 
			
		||||
            flex-shrink: 1;
 | 
			
		||||
 | 
			
		||||
            img {
 | 
			
		||||
                height: 100%;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .nav-item {
 | 
			
		||||
            font-size: $text-size-2;
 | 
			
		||||
            font-weight: $weight-bold;
 | 
			
		||||
            white-space: nowrap;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -470,6 +493,64 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: $screen-normal) {
 | 
			
		||||
    .page {
 | 
			
		||||
        margin-top: var(--nav-primary-height);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .navs {
 | 
			
		||||
        z-index: 100000;
 | 
			
		||||
        position: fixed;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        top: 0;
 | 
			
		||||
 | 
			
		||||
        .nav:first-child {
 | 
			
		||||
            flex-grow: 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .nav + .nav {
 | 
			
		||||
            flew-grow: 0 !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .nav {
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
        .burger {
 | 
			
		||||
            display: unset;
 | 
			
		||||
            margin-left: auto;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .nav-menu {
 | 
			
		||||
            display: block;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            top: 100%;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            box-shadow: 0em 0.5em 0.5em rgba(0,0,0,0.05);
 | 
			
		||||
 | 
			
		||||
            .nav-item {
 | 
			
		||||
                display: block;
 | 
			
		||||
                font-size: $text-size-medium;
 | 
			
		||||
                font-weight: $weight-normal;
 | 
			
		||||
 | 
			
		||||
                &:hover {
 | 
			
		||||
                    background-color: var(--highlight-color-2-alpha);
 | 
			
		||||
                    color: var(--highlight-color);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .nav-menu:not(.active) {
 | 
			
		||||
            display: none !important
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
nav li {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
 | 
			
		||||
@ -792,6 +873,11 @@ nav li {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: $screen-normal) {
 | 
			
		||||
    .container.header {
 | 
			
		||||
        margin-right: 0 !important;
 | 
			
		||||
        margin-left: 0 !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .page .container {
 | 
			
		||||
        margin-left: $mp-4;
 | 
			
		||||
        margin-right: $mp-4;
 | 
			
		||||
@ -953,7 +1039,7 @@ nav li {
 | 
			
		||||
 | 
			
		||||
    border-top: 1px $grey-light solid;
 | 
			
		||||
    background: var(--player-bar-bg);
 | 
			
		||||
    box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
 | 
			
		||||
    box-shadow: 0em -0.5em 0.5em rgba(0, 0, 0, 0.05);
 | 
			
		||||
 | 
			
		||||
    > * { height: 100%; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										75
									
								
								assets/src/components/ASwitch.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								assets/src/components/ASwitch.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <button :title="ariaLabel"
 | 
			
		||||
            :aria-label="ariaLabel || label" :aria-description="ariaDescription"
 | 
			
		||||
            @click="toggle" :class="buttonClass">
 | 
			
		||||
        <slot name="default" :active="active">
 | 
			
		||||
            <span class="icon">
 | 
			
		||||
                <i :class="icon"></i>
 | 
			
		||||
            </span>
 | 
			
		||||
            <label v-if="label">{{ label }}</label>
 | 
			
		||||
        </slot>
 | 
			
		||||
    </button>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        initialActive: {type: Boolean, default: null},
 | 
			
		||||
        el: {type: String, default: ""},
 | 
			
		||||
        label: {type: String, default: ""},
 | 
			
		||||
        icon: {type: String, default: "fa fa-bars"},
 | 
			
		||||
        ariaLabel: {type: String, default: ""},
 | 
			
		||||
        ariaDescription: {type: String, default: ""},
 | 
			
		||||
        activeClass: {type: String, default:"active"},
 | 
			
		||||
        /// switch toggle of all items of this group.
 | 
			
		||||
        group: {type: String, default: ""},
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            active: this.initialActive,
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        groupClass() {
 | 
			
		||||
            return this.group && "a-switch-" + this.group || ''
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        buttonClass() {
 | 
			
		||||
            return [
 | 
			
		||||
                this.active && 'active' || '',
 | 
			
		||||
                this.groupClass
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        toggle() {
 | 
			
		||||
            this.set(!this.active)
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        set(active) {
 | 
			
		||||
            const el = document.querySelector(this.el)
 | 
			
		||||
            if(active)
 | 
			
		||||
                el.classList.add(this.activeClass)
 | 
			
		||||
            else
 | 
			
		||||
                el.classList.remove(this.activeClass)
 | 
			
		||||
            this.active = active
 | 
			
		||||
            if(active)
 | 
			
		||||
                this.resetGroup()
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        resetGroup() {
 | 
			
		||||
            const els = document.querySelectorAll("." + this.groupClass)
 | 
			
		||||
            for(var el of els)
 | 
			
		||||
                if(el != this.$el)
 | 
			
		||||
                    el.__vnode.ctx.ctx.set(false)
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mounted() {
 | 
			
		||||
        if(this.initialActive !== null)
 | 
			
		||||
            this.set(this.initialActive)
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@ -9,6 +9,7 @@ import APlaylist from './APlaylist.vue'
 | 
			
		||||
import APlaylistEditor from './APlaylistEditor.vue'
 | 
			
		||||
import AProgress from './AProgress.vue'
 | 
			
		||||
import ASoundItem from './ASoundItem.vue'
 | 
			
		||||
import ASwitch from './ASwitch.vue'
 | 
			
		||||
import AStatistics from './AStatistics.vue'
 | 
			
		||||
import AStreamer from './AStreamer.vue'
 | 
			
		||||
 | 
			
		||||
@ -17,7 +18,7 @@ import AStreamer from './AStreamer.vue'
 | 
			
		||||
 */
 | 
			
		||||
export const base = {
 | 
			
		||||
    AAutocomplete, ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
 | 
			
		||||
    AProgress, ASoundItem,
 | 
			
		||||
    AProgress, ASoundItem, ASwitch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default base
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user