Commit 5efa07b9 authored by gijs's avatar gijs

Merge branch 'features-alex' of gitlab.constantvzw.org:osp/work.w

parents d14da9c7 9abe67fb
......@@ -51,6 +51,11 @@ INSTALLED_APPS = [
'playground',
'ckeditor',
# Django filer
'easy_thumbnails',
'filer',
'mptt',
]
MIDDLEWARE = [
......@@ -143,7 +148,7 @@ REST_FRAMEWORK = {
# Django Compressor setup
COMPRESS_PRECOMPILERS = (
('text/css', './node_modules/.bin/postcss {infile} --use postcss-cssnext --use postcss-import'),
('text/css', './node_modules/.bin/postcss {infile} --use postcss-cssnext --use postcss-import --use postcss-selector-matches'),
)
COMPRESS_ENABLED = False
......@@ -159,11 +164,11 @@ ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = "/compte"
CKEDITOR_CONFIGS = {
'default': {
'extraPlugins': ','.join([
'codesnippet',
'codesnippet', 'detail',
]),
'toolbar': 'Custom',
'toolbar_Custom': [
['Format'],
['Format', 'Detail'],
# ['Pull-out', 'Sidebar', 'Footer'],
['Bold', 'Italic', '-', 'Subscript', 'Superscript'],
['CodeSnippet'],
......
......@@ -4,19 +4,57 @@
<head>
<title>{{ flatpage.title }}</title>
<link rel="stylesheet" href="{% static 'playground/vendors/reset.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'playground/css/styles.css' %}">
{% compress css %}
<link rel="stylesheet" type="text/css" href="{% static 'playground/css/styles.css' %}">
{% endcompress %}
</head>
<body class="help__content">
<body class="help__content content">
{% load flatpages %}
{% get_flatpages as flatpages %}
<ul>
{% for page in flatpages|dictsort:"title" %}
<li><a href="/pages/{{ page.url }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
<!-- <ul> -->
<!-- {% for page in flatpages|dictsort:"title" %} -->
<!-- <li><a href="/pages/{{ page.url }}">{{ page.title }}</a></li> -->
<!-- {% endfor %} -->
<!-- </ul> -->
{{ flatpage.content }}
<script src="{% static 'playground/vendors/jquery.min.js' %}"></script>
<script charset="utf-8">
$('h3').each(function() {
$(this)
.click(function() { $(this).toggleClass('help__section--closed'); })
.addClass('help__section help__section--closed')
.nextUntil("h3, h2, h1")
.wrapAll('<div class="help__inner"></div>');
});
// Taken from <https://gist.github.com/mathewbyrne/1280286>
function slugify(text) {
return text
.toString()
.toLowerCase()
.normalize('NFD').replace(/[\u0300-\u036f]/g, "") // strip accents
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
}
var $toc = $('<ol class="help__toc"></ol>')
$("h2").each(function() {
var txt = $(this).text();
var id = slugify(txt);
$(this).attr('id', id);
var $li = $('<li></li>');
var $a = $('<a></a>').attr('href', '#' + id).text(txt);
$li.append($a);
$toc.append($li);
});
$toc.insertBefore($("h2").first());
</script>
</body>
</html>
......@@ -29,6 +29,7 @@ router = routers.DefaultRouter()
router.register(r'attachments', views.AttachmentViewSet)
router.register(r'scores', views.ScoreViewSet)
router.register(r'users', views.UserViewSet)
router.register(r'pages', views.FlatPageViewSet)
urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + [
......@@ -41,6 +42,8 @@ urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + [
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^filer/', include('filer.urls')),
url(r'pages/', include('django.contrib.flatpages.urls')),
url(r'^', include('playground.urls')),
......
......@@ -6,9 +6,18 @@ from taggit_serializer.serializers import (TagListSerializerField,
TaggitSerializer)
from guardian.shortcuts import assign_perm, get_users_with_perms, get_user_perms, remove_perm
from django.contrib.auth.models import User
from django.contrib.flatpages.models import FlatPage
class FlatPageSerializer(serializers.HyperlinkedModelSerializer):
# id = serializers.ReadOnlyField()
class Meta:
model = FlatPage
fields = ['url', 'title', 'content']
class UserSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField()
......@@ -43,6 +52,7 @@ class PermissionSerializer(serializers.Serializer):
list_serializer_class = PermissionListSerializer
class ScoreSerializer(TaggitSerializer, serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField()
tags = TagListSerializerField()
......@@ -52,9 +62,10 @@ class ScoreSerializer(TaggitSerializer, serializers.HyperlinkedModelSerializer):
model = Score
fields = '__all__'
def create(self, request, *args, **kwargs):
instance = super().create(request, *args, **kwargs)
assign_perm("view_score", request.user, instance)
def create(self, validated_data):
validated_data.pop('permissions', None)
instance = Score.objects.create(**validated_data)
assign_perm("view_score", self.context['request'].user, instance)
return instance
def update(self, instance, validated_data):
......
Copyright (c) 2018 Ayhan Akilli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# Details Widget
This widget handles details elements (`<details>` and `<summary>`). No configuration necessary.
## Example
You can see this plugin in action @ https://akilli.github.io/rte/ck4/
'use strict';
(function (CKEDITOR) {
CKEDITOR.plugins.setLang('detail', 'de', {
title: 'Details'
});
})(CKEDITOR);
'use strict';
(function (CKEDITOR) {
CKEDITOR.plugins.setLang('detail', 'en', {
title: 'Details'
});
})(CKEDITOR);
'use strict';
(function (CKEDITOR) {
CKEDITOR.dtd['$editable']['summary'] = 1;
CKEDITOR.plugins.add('detail', {
requires: 'widget',
icons: 'detail',
hidpi: true,
lang: 'de,en',
init: function (editor) {
editor.widgets.add('detail', {
button: editor.lang.detail.title,
template: '<details><summary>Summary</summary><div class="details-content"></div></details>',
editables: {
summary: {
selector: 'summary',
allowedContent: {}
},
content: {
selector: '.details-content'
}
},
allowedContent: 'details summary',
requiredContent: 'details; summary',
upcast: function (el) {
if (el.name !== 'details') {
return false;
}
var summary = el.getFirst('summary');
var content = new CKEDITOR.htmlParser.element('div', {'class': 'details-content'});
if (!!summary && summary.children.length > 0 && summary.children[0].type === CKEDITOR.NODE_ELEMENT) {
summary.setHtml(summary.children[0].getHtml());
} else if (!!summary && summary.children.length > 0 && summary.children[0].type === CKEDITOR.NODE_TEXT) {
summary.setHtml(summary.children[0].value);
} else if (!!summary) {
summary.setHtml('Summary');
} else {
summary = new CKEDITOR.htmlParser.element('summary');
summary.setHtml('Summary');
el.add(summary, 0);
}
el.add(content, 1);
if (el.children.length > 2) {
content.children = el.children.slice(2);
el.children = el.children.slice(0, 2);
}
return true;
},
downcast: function (el) {
el.attributes = [];
el.children = el.children.slice(0, 1);
el.setHtml(el.getHtml() + this.editables.content.getData());
},
init: function () {
var summary = this.element.getChild(0);
summary.on('keyup', function (ev) {
if (ev.data['$'].keyCode === 32) {
ev.data['$'].preventDefault();
editor.insertText(' ');
}
});
}
});
}
});
})(CKEDITOR);
......@@ -150,8 +150,7 @@ textarea {
button,
input {
font-family: 'GothicA1', sans-serif;
/* font-size: 19px; */
/* font-size: 90%; */
font-size: inherit;
line-height: 21px;
font-weight: 500;
}
......@@ -243,6 +242,7 @@ section.hidden { display: none; }
.modal__title { margin-bottom: 1em; }
/* TODO: Bemize */
.modal label { display: none; }
.modal input[type="email"],
......@@ -292,15 +292,13 @@ section.hidden { display: none; }
/* Main menu (sidebar)
========================================================================== */
.main-header .content {
/* width: 300px; */
padding: 30px;
}
.main-header { width: 300px; }
.main-header.is-collapsed { margin-left: -300px; }
.logo { margin: 21px 0 42px 0; }
.main-header__logo {
margin-top: 21px;
margin-bottom: 42px;
}
.logo__name {
font-size: 3em;
......@@ -1223,7 +1221,7 @@ li .icon--tag { color: white } */
color: rgb(202, 027, 027);
}
#create { margin-bottom: 1em;}
/* #create { margin-top: 1em;} */
.create-form { display: none; }
#create:hover .create-form { display: flex; }
......@@ -1258,6 +1256,8 @@ li .icon--tag { color: white } */
border-bottom: 2px solid;
padding-bottom: 6px;
/* margin-bottom: 18px; */
flex: 1 1 100px;
width: 100px;
}
.create-form input[type="text"]::placeholder { /* Most modern browsers support this now. */
......@@ -1273,28 +1273,87 @@ li .icon--tag { color: white } */
Help
========================================================================== */
.page__help { width: 500px; }
.page__help.is-collapsed { margin-right: -500px; }
.help__frame {
overflow: auto;
width: 500px;
width: 100%;
height: 100%;
transition: all .2s;
}
.help { width: 500px; }
.help.is-collapsed { margin-right: -500px; }
.help__content {
color: #173a42;
background-color: white;
padding: 30px;
font-size: 85%;
line-height: 1.35;
}
.help__content h2 { margin-top: 28pt; text-align: center; margin-bottom: 14pt; }
.help__content h2 {
/* position: sticky; */
/* background-color: white; */
/* top: 0; */
letter-spacing: 1px;
text-transform: uppercase;
}
.help__content h2,
.help__content h3 {
font-weight: bold;
padding-top: 28pt;
padding-bottom: 14pt;
}
.help__content p + p { margin-top: 14pt; }
.help__content h2:first-of-type { padding-top: 0; }
.help__content :matches(p, ul, ol, pre) + :matches(p, ul, ol, pre) { margin-top: 14pt; }
.help__content :matches(ul, ol) { margin-left: 2em; text-indent: -.75em; }
.help__content li:before { content: "– "; }
.help__content code { color: darkgrey; margin-left: 1.25em; font-size: 75%}
.help__section::before { content: "▼ "; }
.help__section--closed::before { content: "▶ "; }
.help__section--closed + .help__inner { display: none; }
.help__content img,
.help__content pre {
overflow: hidden;
max-width: 100%;
}
.help__toc {
/* position: sticky; */
/* top: 0; */
/* background-color: white; */
border-bottom: 2px solid;
margin-left: 84pt!important;
padding-bottom: 14pt;
/* padding-top: 14pt; */
margin-bottom: 28pt;
text-indent: 0!important;
font-weight: bold;
/* background-color: #173a42; */
/* color: white; */
/* padding: 14pt; */
}
.help__toc li {
display: inline-block;
margin-right: 1em;
}
.help__toc li::before {
content: "";
}
.help__toc li + li::before {
/* content: " | "; */
}
......@@ -1357,7 +1416,6 @@ li .icon--tag { color: white } */
.panel__toggle {
box-sizing: border-box;
line-height: 30px;
/* outline: 1px solid white; */
padding-right: 30px;
position: absolute;
text-align: right;
......@@ -1418,6 +1476,31 @@ li .icon--tag { color: white } */
}
.btn {
font-size: 85% !important;
color: var(--text-color);
border: none;
padding: .3em 1em .2em 1em;
background: transparent;
margin-top: .15em;
font-family: 'GothicA1', sans-serif;
font-size: inherit;
line-height: 21px;
font-weight: 500;
}
.btn:hover {
background-color: var(--background-color-button);
}
.search__form {
display: flex;
}
.search__textfield { width: 30px; flex: 1 1 30px;
}
.search__submit {
}
......@@ -11,6 +11,8 @@ window.W = window.W || {};
var baseView = this.getOption('application').getView();
var view = new W.HomeView();
baseView.showChildView('main', view);
baseView.getChildView('help').triggerMethod("hide");
},
......@@ -18,6 +20,7 @@ window.W = window.W || {};
var baseView = this.getOption('application').getView();
var view = new W.AboutView();
baseView.showChildView('main', view);
baseView.getChildView('help').triggerMethod("hide");
},
......@@ -25,6 +28,8 @@ window.W = window.W || {};
var baseView = this.getOption('application').getView();
var view = new W.UserListView();
baseView.showChildView('main', view);
baseView.getChildView('help').triggerMethod("hide");
},
......@@ -32,6 +37,8 @@ window.W = window.W || {};
var baseView = this.getOption('application').getView();
var view = new W.UserDetailView();
baseView.showChildView('main', view);
baseView.getChildView('help').triggerMethod("hide");
},
......@@ -39,6 +46,8 @@ window.W = window.W || {};
var baseView = this.getOption('application').getView();
var view = new W.ScoreListView();
baseView.showChildView('main', view);
baseView.getChildView('help').triggerMethod("hide");
},
......
......@@ -9,7 +9,7 @@ window.W = window.W || {};
W.ScoreRouter = Marionette.AppRouter.extend({
appRoutes: {
'': 'home',
'apropos(/)': 'about',
'la-notation-w(/)': 'about',
'partitions(/)': 'scoreList',
'partitions/:id(/)': 'scoreDetail',
'users(/)': 'userList',
......
......@@ -6,6 +6,26 @@ window.W.utils = window.W.utils || {};
(function (undefined) {
'use strict';
W.utils.animatedScroll = function (el, distance, duration) {
var start = Date.now(),
step = Math.max(distance / (duration / 60), 3),
tick = function () {
if (Date.now() - start > duration) {
// Animation is taking longer than requested
// scroll the remaining distance at onces
el.scrollBy(0, distance);
} else if (distance > 0) {
el.scrollBy(0, step);
distance -= step;
window.requestAnimationFrame(tick)
}
};
window.requestAnimationFrame(tick);
};
W.utils.getUserLanguage = function () {
var lang = W.utils.getCookie("lang");
......
......@@ -5,24 +5,6 @@ window.W = window.W || {};
(function (undefined) {
'use strict';
function animatedScroll (el, distance, duration) {
var start = Date.now(),
step = Math.max(distance / (duration / 60), 3),
tick = function () {
if (Date.now() - start > duration) {
// Animation is taking longer than requested
// scroll the remaining distance at onces
el.scrollBy(0, distance);
} else if (distance > 0) {
el.scrollBy(0, step);
distance -= step;
window.requestAnimationFrame(tick)
}
}
window.requestAnimationFrame(tick);
}
var MyDatetimeFormatter = _.extend({}, Backgrid.CellFormatter.prototype, {
fromRaw: function (rawValue, model) {
moment.locale('fr');
......@@ -2015,12 +1997,14 @@ window.W = window.W || {};
template: '#search-template',
triggers: {
'click .search': 'search',
'submit form': 'search'
},
onSearch: function (view, event) {
this.collection.queryParams.search = this.$el.find('.search-input').val();
this.collection.fetch();
return false;
},
});
......@@ -2481,7 +2465,8 @@ window.W = window.W || {};
template: '#home-template',
regions: {
latest_updated: '#latest_updated'
latest_updated: '#latest_updated',
user_scores: '#user_scores'
},
attributes: {
......@@ -2494,9 +2479,9 @@ window.W = window.W || {};
document.title = 'Notation W';
var scoreCollection = new W.ScoreCollection([], {
url: '/test',
// url: '/test',
state: {
// pageSize: 10,
pageSize: 5,
// firstPage: 1,
// currentPage: 1,
sortKey: "updated_at",
......@@ -2516,6 +2501,31 @@ window.W = window.W || {};
that.showChildView('latest_updated', myListView);
}
});
var scoreCollection2 = new W.ScoreCollection([], {
// All the `state` and `queryParams` key value pairs are merged with
// the defaults too.
state: {
pageSize: 5,
firstPage: 1,
currentPage: 1,
sortKey: "updated_at",
order: -1
}
});
scoreCollection2.queryParams.shared_with=true;
var myListView2 = new Backgrid.Grid({
columns: columns,
collection: scoreCollection2
});
myListView2.collection.fetch({
success: function () {
that.showChildView('user_scores', myListView2);
}
});
}
});
......@@ -2524,7 +2534,7 @@ window.W = window.W || {};
template: '#help-template',
attributes: {
class: 'help is-collapsed panel panel--right'
class: 'page__help help is-collapsed panel panel--right'
},
ui: {
......@@ -2549,10 +2559,10 @@ window.W = window.W || {};
ui: {
'toggle': '.js-toggle',
'login': '.btn-login',
'logout': '.btn-logout',
'register': '.btn-register',
'lang': '.btn-lang',
'login': '.js-login',
'logout': '.js-logout',
'register': '.js-register',
'lang': '.js-lang',
},
triggers: {
......
......@@ -11,8 +11,9 @@
<link rel="stylesheet" href="{% static 'playground/vendors/backgrid.css' %}">
<link rel="stylesheet" href="{% static 'playground/vendors/backgrid-paginator.min.css' %}">
<link rel="stylesheet" href="{% static 'playground/vendors/backbone.autocomplete.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'playground/css/styles.css' %}">
{% compress css %}
<link rel="stylesheet" type="text/css" href="{% static 'playground/css/styles.css' %}">
{% endcompress %}
<script id="about-template" type="text/template">
......
<p>La notation W est un système de notation de l’action performée. La notation W permet aussi bien de transcrire une performance qui a déjà eu lieu (dans le cas d’une passation de rôle, par exemple, ou de la constitution d’un répertoire) que d’élaborer la partition générale d’un spectacle à venir. Dans tous les cas, elle se veut un instrument de partage et de clarification des enjeux d’un travail collectif, matérialisés et objectivés par la partition.</p>
{% load flatpages %}
{% get_flatpages '/la-notation-w/' as about_pages %}
<p>La notation W repose sur une succession d’énoncés centrés sur un verbe à l’infinitif, à la manière des instructions ou <em>tasks</em> de chorégraphes et d’artistes tels que Simone Forti, Yvonne Rainer ou Sol LeWitt. Ces énoncés, qui utilisent le langage ordinaire pour désigner l’action en train de se faire (et non son but), W les appelle des axes. Combinés entre eux selon des règles précises, les axes forment la base de la notation W.</p>
<p>La notation W permet d’envisager de manière immédiate l’ensemble des dimensions d’une action performée :</p>
<ul>
<li>sur un plan chronologique, la succession raisonnée des actions performées;</li>
<li>sur un plan analytique, l’explicitation de chaque axe, c’est­à­dire sa subdivision en sous­axes qui en précisent le sens et le contenu, eux­mêmes divisés en sous­sous­axes etc., jusqu’au degré de précision jugé nécessaire par le notateur;</li>
<li>sur un plan paradigmatique, les actions simultanées et/ou alternatives.</li>
</ul>
<p>Une partition W est un système ouvert, qui peut tout à fait contenir des morceaux d’autres systèmes notationnels (la partition d’une fugue de Bach, une chorégraphie de Merce Cunningham générée par LifeForms, le texte de <em>Richard III</em>, un plan de scène). Il ne s’agit donc pas de se substituer à un système de notation existant (la danse ou la musique, par exemple, disposent de systèmes notationnels complets et rigoureux) mais de créer une structure multidimensionnelle permettant d’intégrer leurs partitions (et d’autres : texte, conduites lumière, son enregistré...) dans un ensemble plus vaste et articulé.</p>
{% for page in about_pages %}
{{ page.content|safe }}
{% endfor %}
<section>
<h1><%- t('Créer une nouvelle partition') %></h1>
<a href="#"><%- t('Créer une nouvelle partition') %></a>
<form class="create-form" action="" method="post">
<input type="text" name="title" placeholder="<%- t('Titre') %>" required>
<input type="submit" name="submit" value="<%- t('Créer') %>">
</form>
</section>