diff --git a/cms/management/commands/programs_to_cms.py b/cms/management/commands/programs_to_cms.py
index b71bc2c..ddeac21 100644
--- a/cms/management/commands/programs_to_cms.py
+++ b/cms/management/commands/programs_to_cms.py
@@ -62,7 +62,7 @@ class Command (BaseCommand):
start__gt = tz.now().date() - tz.timedelta(days = 20),
page__isnull = True,
initial__isnull = True
- )
+ ).exclude(type = Diffusion.Type.unconfirmed)
for diffusion in qs:
if not diffusion.program.page.count():
if not hasattr(diffusion.program, '__logged_diff_error'):
diff --git a/cms/models.py b/cms/models.py
index 36c639a..67baf23 100644
--- a/cms/models.py
+++ b/cms/models.py
@@ -31,6 +31,7 @@ import aircox.programs.models as programs
import aircox.controllers.models as controllers
import aircox.cms.settings as settings
+from aircox.cms.utils import image_url
from aircox.cms.sections import *
@@ -163,6 +164,15 @@ class Comment(models.Model):
_('comment'),
)
+
+ def __str__(self):
+ # Translators: text shown in the comments list (in admin)
+ return _('{date}, {author}: {content}...').format(
+ author = self.author,
+ date = self.date.strftime('%d %A %Y, %H:%M'),
+ content = self.content[:128]
+ )
+
def make_safe(self):
self.author = bleach.clean(self.author, tags=[])
if self.email:
@@ -263,6 +273,15 @@ class Publication(Page):
index.FilterField('show_in_menus'),
]
+
+ @property
+ def icon(self):
+ return image_url(self.cover, 'fill-64x64')
+
+ @property
+ def small_icon(self):
+ return image_url(self.cover, 'fill-32x32')
+
@property
def recents(self):
return self.get_children().type(Publication).not_in_menu().live() \
@@ -390,10 +409,8 @@ class Track(programs.Track,Orderable):
diffusion = ParentalKey('DiffusionPage',
related_name='tracks')
panels = [
- FieldRowPanel([
- FieldPanel('artist'),
- FieldPanel('title'),
- ]),
+ FieldPanel('artist'),
+ FieldPanel('title'),
FieldPanel('tags'),
FieldPanel('info'),
]
@@ -428,13 +445,15 @@ class DiffusionPage(Publication):
verbose_name = _('Diffusion')
verbose_name_plural = _('Diffusions')
- content_panels = [
- FieldPanel('diffusion'),
- FieldPanel('publish_archive'),
- ] + Publication.content_panels + [
+ content_panels = Publication.content_panels + [
InlinePanel('tracks', label=_('Tracks')),
]
+ promote_panels = [
+ # FieldPanel('diffusion'),
+ FieldPanel('publish_archive'),
+ ] + Publication.promote_panels
+
@classmethod
def from_diffusion(cl, diff, model = None, **kwargs):
model = model or cl
@@ -519,8 +538,6 @@ class DiffusionPage(Publication):
podcast.public = publish
podcast.save()
-
-
super().save(*args, **kwargs)
diff --git a/cms/sections.py b/cms/sections.py
index 5a239a9..8c3b270 100644
--- a/cms/sections.py
+++ b/cms/sections.py
@@ -221,19 +221,18 @@ class ListBase(models.Model):
else:
qs = qs.descendant_of(related)
- date = self.related.date if hasattr('date', related) else \
+ date = self.related.date if hasattr(related, 'date') else \
self.related.first_published_at
if self.date_filter == self.DateFilter.before_related:
qs = qs.filter(date__lt = date)
elif self.date_filter == self.DateFilter.after_related:
qs = qs.filter(date__gte = date)
# date
- else:
- date = tz.now()
- if self.date_filter == self.DateFilter.previous:
- qs = qs.filter(date__lt = date)
- elif self.date_filter == self.DateFilter.next:
- qs = qs.filter(date__gte = date)
+ date = tz.now()
+ if self.date_filter == self.DateFilter.previous:
+ qs = qs.filter(date__lt = date)
+ elif self.date_filter == self.DateFilter.next:
+ qs = qs.filter(date__gte = date)
# sort
if self.asc:
@@ -332,9 +331,7 @@ class ListBase(models.Model):
search = request.GET.get('search')
if search:
kwargs['terms'] = search
- print(search, qs)
qs = qs.search(search)
- print(qs.count())
set('list_selector', kwargs)
@@ -342,7 +339,7 @@ class ListBase(models.Model):
if qs:
paginator = Paginator(qs, 30)
try:
- qs = paginator.page('page')
+ qs = paginator.page(request.GET.get('page') or 1)
except PageNotAnInteger:
qs = paginator.page(1)
except EmptyPage:
@@ -803,7 +800,7 @@ class SectionList(ListBase, SectionRelativeItem):
'list. If empty, does not print an address'),
)
- panels = SectionItem.panels + [
+ panels = SectionRelativeItem.panels + [
MultiFieldPanel([
FieldPanel('focus_available'),
FieldPanel('count'),
@@ -815,6 +812,9 @@ class SectionList(ListBase, SectionRelativeItem):
from aircox.cms.models import Publication
context = super().get_context(request, page)
+ if self.is_related:
+ self.related = page
+
qs = self.get_queryset()
qs = qs.live()
if self.focus_available:
diff --git a/cms/static/cms/css/layout.css b/cms/static/cms/css/layout.css
index 78c7d02..394fd44 100644
--- a/cms/static/cms/css/layout.css
+++ b/cms/static/cms/css/layout.css
@@ -24,6 +24,9 @@ ul {
float: left;
}
+.small {
+ font-size: 0.8em;
+}
.icon {
max-width: 2em;
@@ -67,9 +70,11 @@ nav.menu {
padding: 0.2em;
height: 2.5em;
margin-bottom: 1em;
+ background-color: white;
+ box-shadow: 0em 0em 0.2em black;
}
.menu.top * {
- vertical-align: middle;
+ vertical-align: bottom;
}
.menu.top > section {
@@ -81,6 +86,10 @@ nav.menu {
margin: 0.2em 1em;
}
+.page_left, .page_right {
+ max-width: 16em;
+}
+
.page_left > section,
.page_right > section {
margin-bottom: 1em;
@@ -121,6 +130,12 @@ ul.list {
}
+.list nav {
+ text-align: center;
+ font-size: 0.9em;
+}
+
+
/** content: date list **/
.date_list nav {
text-align:center;
@@ -188,8 +203,6 @@ ul.list {
font-size: 0.8em;
}
-
-
.comments ul {
margin-top: 2.5em;
}
@@ -208,19 +221,23 @@ ul.list {
float: right;
}
+
/** content: player **/
.player {
- width: 20em;
}
- .player:not([seekable]) > .controls {
+ .player:not([seekable]) > .controls > .progress {
display: none;
}
-.player .controls > * {
- margin: 0em 0.2em;
+.player .controls {
+ margin-top: 1em;
+ text-align: right;
}
+ .player .controls > * {
+ margin: 0em 0.2em;
+ }
.player .controls .single {
display: none;
@@ -245,14 +262,9 @@ ul.list {
border-right: 2px #818181 solid;
}
-.player .on_air a:not([href]), .on_air a[href=""] {
- display: none;
-}
-
.player .playlist .item {
margin: 0em;
padding: 0.2em 0.4em;
- height: 1em;
cursor: pointer;
}
@@ -266,25 +278,20 @@ ul.list {
.player .playlist .item .actions {
display: none;
+ font-size: 0.9em;
}
.player .playlist .item:hover .actions {
- display: inline-block;
+ display: inline;
}
- .player .playlist .item .info {
- float: right;
- width: 2em;
- display: inline-block;
- }
-
- .player .item:not([selected]) .button {
- display: none;
- }
.player .item[selected] {
- height: auto;
- font-size: 1.1em;
+ border-left: 1px #007EDF solid;
+ font-size: 1.0em;
+}
+
+.player .item:not([selected]) {
}
.player .button {
@@ -300,36 +307,33 @@ ul.list {
max-height: 2.0em;
}
- .player:not([state]) .button > img:not(.play),
- .player[state="paused"] .button > img:not(.play),
- .player[state="playing"] .button > img:not(.pause),
- .player[state="loading"] .button > img:not(.loading)
+
+ .player:not([state]) .item[selected] .button > img:not(.play),
+ .player[state="paused"] .item[selected] .button > img:not(.play),
+ .player[state="playing"] .item[selected] .button > img:not(.pause),
+ .player[state="loading"] .item[selected] .button > img:not(.loading)
{
display: none;
}
- .player[state="loading"] .box .button > img.loading {
- animation-duration: 2s;
- animation-iteration-count: infinite;
- animation-name: rotate;
- animation-timing-function: linear;
+ .player .item:not([selected]) .button > img.play {
+ display: block;
+ }
+ .player .item:not([selected]) .button > img:not(.play) {
+ display: none;
}
- @keyframes rotate {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
- }
+.player .list_item.live:hover .actions {
+ display: none;
+}
/** content: page **/
main .body ~ section:not(.comments) {
width: calc(50% - 1em);
- float: left;
+ vertical-align: top;
+ display: inline-block;
}
diff --git a/cms/static/cms/css/theme.css b/cms/static/cms/css/theme.css
index 3476e63..eb8c8f2 100644
--- a/cms/static/cms/css/theme.css
+++ b/cms/static/cms/css/theme.css
@@ -50,6 +50,7 @@ time {
.info {
font-size: 0.9em;
+ padding: 0.1em;
color: #007EDF;
}
@@ -81,14 +82,69 @@ main {
box-shadow: 0em 0em 0.2em black;
}
- main h1 {
- margin: 0em;
+ main h1:not(.detail_title) {
margin: 0em 0em 0.4em 0em;
}
- main .content img.cover {
+
+ main h1.detail_title {
+ margin: 0em;
+ padding: 0.2em;
+ position: relative;
+ left: -0.7em;
+ width: 80%;
+ background-color: rgba(255,255,255,0.8);
+ }
+
+ main img.detail_cover {
width: calc(100% + 2em);
+ margin-top: -3.3em;
margin-left: -1em;
}
+
+/** player **/
+.player[state='playing'] .item[selected] .button > img {
+ animation-duration: 4s;
+ animation-iteration-count: infinite;
+ animation-name: blink;
+}
+
+
+@keyframes blink {
+ from {
+ opacity: 1.0;
+ }
+
+ 50% {
+ opacity: 0.3;
+ }
+
+ to {
+ opacity: 1.0;
+ }
+}
+
+
+.player[state="loading"] .item[selected] .button > img.loading {
+ animation-duration: 2s;
+ animation-iteration-count: infinite;
+ animation-name: rotate;
+ animation-timing-function: linear;
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+
+
+
+
diff --git a/cms/static/cms/js/player.js b/cms/static/cms/js/player.js
index 6e89b68..eae96cd 100644
--- a/cms/static/cms/js/player.js
+++ b/cms/static/cms/js/player.js
@@ -18,11 +18,13 @@ function duration_str(seconds) {
}
-function Sound(title, detail, duration, streams) {
+function Sound(title, detail, duration, streams, cover, on_air) {
this.title = title;
this.detail = detail;
this.duration = duration;
this.streams = streams.splice ? streams.sort() : [streams];
+ this.cover = cover;
+ this.on_air = on_air;
}
Sound.prototype = {
@@ -30,25 +32,60 @@ Sound.prototype = {
detail: '',
streams: undefined,
duration: undefined,
- on_air_url: undefined,
+ cover: undefined,
+ on_air: false,
item: undefined,
get seekable() {
return this.duration != undefined;
},
+
+ make_item: function(playlist, base_item) {
+ if(this.item)
+ return;
+
+ var item = base_item.cloneNode(true);
+ item.removeAttribute('style');
+
+ item.querySelector('.title').innerHTML = this.title;
+ if(this.seekable)
+ item.querySelector('.duration').innerHTML =
+ duration_str(this.duration);
+ if(this.detail)
+ item.querySelector('.detail').href = this.detail;
+ if(playlist.player.show_cover && this.cover)
+ item.querySelector('img.play').src = this.cover;
+
+ item.sound = this;
+ this.item = item;
+
+ // events
+ var self = this;
+ item.querySelector('.action.remove').addEventListener(
+ 'click', function(event) { playlist.remove(self); }, false
+ );
+
+ item.addEventListener('click', function(event) {
+ if(event.target.className.indexOf('action') != -1)
+ return;
+ playlist.select(self, true)
+ }, false);
+ },
}
-function PlayerPlaylist(player) {
+function Playlist(player) {
this.player = player;
this.playlist = player.player.querySelector('.playlist');
this.item_ = player.player.querySelector('.playlist .item');
this.sounds = []
}
-PlayerPlaylist.prototype = {
+Playlist.prototype = {
+ on_air: undefined,
sounds: undefined,
+ sound: undefined,
/// Find a sound by its streams, and return it if found
find: function(streams) {
@@ -71,30 +108,12 @@ PlayerPlaylist.prototype = {
if(sound_)
return sound_;
- var item = this.item_.cloneNode(true);
- item.removeAttribute('style');
+ if(sound.on_air)
+ this.on_air = sound;
- item.querySelector('.title').innerHTML = sound.title;
- if(sound.seekable)
- item.querySelector('.duration').innerHTML =
- duration_str(sound.duration);
- if(sound.detail)
- item.querySelector('.detail').href = sound.detail;
+ sound.make_item(this, this.item_);
+ (container || this.playlist).appendChild(sound.item);
- item.sound = sound;
- sound.item = item;
-
- var self = this;
- item.querySelector('.action.remove').addEventListener(
- 'click', function(event) { self.remove(sound); }, false
- );
- item.addEventListener('click', function(event) {
- if(event.target.className.indexOf('action') != -1)
- return;
- self.player.select(sound, true)
- }, false);
-
- (container || this.playlist).appendChild(item);
this.sounds.push(sound);
this.save();
return sound;
@@ -106,8 +125,61 @@ PlayerPlaylist.prototype = {
this.sounds.splice(index,1);
this.playlist.removeChild(sound.item);
this.save();
+
+ this.player.stop()
+ this.next(false);
},
+ select: function(sound, play = true) {
+ this.player.playlist = this;
+ if(this.sound == sound) {
+ if(play)
+ this.player.play();
+ return;
+ }
+
+ if(this.sound)
+ this.unselect(this.sound);
+ this.sound = sound;
+
+ // audio
+ this.player.load_sound(this.sound);
+
+ // attributes
+ var container = this.player.player;
+ sound.item.setAttribute('selected', 'true');
+
+ if(!sound.on_air)
+ sound.item.querySelector('.content').insertBefore(
+ this.player.progress.item,
+ sound.item.querySelector('.content .duration')
+ )
+
+ if(sound.seekable)
+ container.setAttribute('seekable', 'true');
+ else
+ container.removeAttribute('seekable');
+
+ // play
+ if(play)
+ this.player.play();
+ },
+
+ unselect: function(sound) {
+ sound.item.removeAttribute('selected');
+ },
+
+ next: function(play = true) {
+ var index = this.sounds.indexOf(this.sound);
+ if(index < 0)
+ return;
+
+ index++;
+ if(index < this.sounds.length)
+ this.select(this.sounds[index]);
+ },
+
+ // storage
save: function() {
var list = [];
for(var i in this.sounds) {
@@ -124,7 +196,7 @@ PlayerPlaylist.prototype = {
for(var i in list) {
var sound = list[i];
sound = new Sound(sound.title, sound.detail, sound.duration,
- sound.streams)
+ sound.streams, sound.cover, sound.on_air)
this.add(sound, container)
}
this.playlist.appendChild(container);
@@ -132,53 +204,67 @@ PlayerPlaylist.prototype = {
}
-function Player(id) {
- this.store = new Store('player');
+function Player(id, on_air_url, show_cover) {
+ this.id = id;
+ this.on_air_url = on_air_url;
+ this.show_cover = show_cover;
+
+ this.store = new Store('player_' + id);
// html sounds
this.player = document.getElementById(id);
this.audio = this.player.querySelector('audio');
this.on_air = this.player.querySelector('.on_air');
+ this.progress = {
+ item: this.player.querySelector('.controls .progress'),
+ bar: this.player.querySelector('.controls .progress progress'),
+ duration: this.player.querySelector('.controls .progress .duration')
+ }
+ console.log(this.progress)
+
this.controls = {
- duration: this.player.querySelector('.controls .duration'),
- progress: this.player.querySelector('progress'),
single: this.player.querySelector('input.single'),
}
- this.playlist = new PlayerPlaylist(this);
+ this.playlist = new Playlist(this);
this.playlist.load();
this.init_events();
this.load();
+
+ this.update_on_air();
}
Player.prototype = {
/// current item being played
sound: undefined,
+ on_air_url: undefined,
init_events: function() {
var self = this;
function time_from_progress(event) {
- bounding = self.controls.progress.getBoundingClientRect()
+ bounding = self.progress.bar.getBoundingClientRect()
offset = (event.clientX - bounding.left);
return offset * self.audio.duration / bounding.width;
}
function update_info() {
- var controls = self.controls;
+ var progress = self.progress;
+ var pos = self.audio.currentTime;
+
// progress
if(!self.sound || !self.sound.seekable ||
- self.audio.duration == Infinity) {
- controls.duration.innerHTML = '';
- controls.progress.value = 0;
+ !pos || self.audio.duration == Infinity)
+ {
+ progress.duration.innerHTML = '';
+ progress.bar.value = 0;
return;
}
- var pos = self.audio.currentTime;
- controls.progress.value = pos;
- controls.progress.max = self.audio.duration;
- controls.duration.innerHTML = duration_str(pos);
+ progress.bar.value = pos;
+ progress.bar.max = self.audio.duration;
+ progress.duration.innerHTML = duration_str(pos);
}
// audio
@@ -207,7 +293,7 @@ Player.prototype = {
}, false);
// progress
- progress = this.controls.progress;
+ progress = this.progress.bar;
progress.addEventListener('click', function(event) {
player.audio.currentTime = time_from_progress(event);
}, false);
@@ -215,43 +301,55 @@ Player.prototype = {
progress.addEventListener('mouseout', update_info, false);
progress.addEventListener('mousemove', function(event) {
- if(self.audio.duration == Infinity)
+ if(self.audio.duration == Infinity || isNaN(self.audio.duration))
return;
var pos = time_from_progress(event);
- self.controls.duration.innerHTML = duration_str(pos);
+ self.progress.duration.innerHTML = duration_str(pos);
}, false);
},
- update_on_air: function(url) {
- if(!url) {
- // TODO HERE
- }
+ update_on_air: function() {
+ if(!this.on_air_url)
+ return;
var self = this;
+ window.setTimeout(function() {
+ self.update_on_air();
+ }, 60*1000);
+
+ if(!this.playlist.on_air)
+ return;
+
var req = new XMLHttpRequest();
- req.open('GET', url, true);
+ req.open('GET', this.on_air_url, true);
req.onreadystatechange = function() {
- if(req.readyState != 4 || (req.status != 200 && req.status != 0))
+ if(req.readyState != 4 || (req.status != 200 &&
+ req.status != 0))
return;
var data = JSON.parse(req.responseText)
- if(data.type == 'track') {
- self.on_air.querySelector('.info').innerHTML = '♫';
- self.on_air.querySelector('.title') =
- (data.artist || '') + ' — ' + (data.title);
- self.on_air.querySelector('.url').removeAttribute('href');
- }
- else {
- self.on_air.querySelector('.info').innerHTML = '';
- self.on_air.querySelector('.title').innerHTML = data.title;
- self.on_air.querySelector('.url').setAttribute('href', data.url);
- }
+ if(data.type == 'track')
+ data = {
+ title: '♫' + (data.artist ? data.artist + ' — ' : '') +
+ data.title,
+ url: ''
+ }
+ else
+ data = {
+ title: data.title,
+ info: '',
+ url: data.url
+ }
- if(timeout)
- window.setTimeout(function() {
- self.update_on_air(url);
- }, 60*1000);
+ var on_air = self.playlist.on_air;
+ on_air = on_air.item.querySelector('.content');
+
+ if(data.url)
+ on_air.innerHTML =
+ '' + data.title + '';
+ else
+ on_air.innerHTML = data.title;
};
req.send();
},
@@ -263,78 +361,44 @@ Player.prototype = {
this.audio.pause();
},
- unselect: function(sound) {
- sound.item.removeAttribute('selected');
- },
-
__mime_type: function(path) {
ext = path.substr(path.lastIndexOf('.')+1);
return 'audio/' + ext;
},
- select: function(sound, play = true) {
- if(this.sound == sound) {
- if(play)
- this.play();
- return;
- }
+ load_sound: function(sound) {
+ var audio = this.audio;
+ audio.pause();
- if(this.sound)
- this.unselect(this.sound);
-
- this.audio.pause();
-
- // streams as
- var sources = this.audio.querySelectorAll('source');
- for(var i = 0; i < sources.length; i++) {
- this.audio.removeChild(sources[i]);
- }
+ var sources = audio.querySelectorAll('source');
+ for(var i = 0; i < sources.length; i++)
+ audio.removeChild(sources[i]);
streams = sound.streams;
for(var i = 0; i < streams.length; i++) {
var source = document.createElement('source');
source.src = streams[i];
source.type = this.__mime_type(source.src);
- this.audio.appendChild(source);
+ audio.appendChild(source);
}
- this.audio.load();
-
- // attributes
- this.sound = sound;
- sound.item.setAttribute('selected', 'true');
-
- if(sound.seekable)
- this.player.setAttribute('seekable', 'true');
- else
- this.player.removeAttribute('seekable');
-
- // play
- if(play)
- this.play();
- },
-
- next: function() {
- var index = this.playlist.sounds.indexOf(this.sound);
- if(index < 0)
- return;
-
- index++;
- if(index < this.playlist.sounds.length)
- this.select(this.playlist.sounds[index], true);
+ audio.load();
},
save: function() {
+ // TODO: move stored sound into playlist
this.store.set('player', {
single: this.controls.single.checked,
- sound: this.sound && this.sound.streams,
+ sound: this.playlist.sound && this.playlist.sound.streams,
});
},
load: function() {
var data = this.store.get('player');
+ if(!data)
+ return;
this.controls.single.checked = data.single;
if(data.sound)
- this.sound = this.playlist.find(data.sound);
+ this.playlist.sound = this.playlist.find(data.sound);
},
}
diff --git a/cms/templates/cms/base_site.html b/cms/templates/cms/base_site.html
index 664524d..3d7fa5a 100644
--- a/cms/templates/cms/base_site.html
+++ b/cms/templates/cms/base_site.html
@@ -67,8 +67,13 @@
+ {% block footer %}
+ {% endblock %}