Merge pull request #743 from ae-utbm/taiste

Taiste
This commit is contained in:
thomas girod 2024-07-28 21:37:38 +02:00 committed by GitHub
commit 0790ae2298
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
240 changed files with 9604 additions and 8993 deletions

View File

@ -8,11 +8,7 @@ updates:
- package-ecosystem: "pip" # See documentation for possible values - package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests directory: "/" # Location of package manifests
schedule: schedule:
interval: "daily" interval: "weekly"
# Raise pull requests for version updates
# to pip against the `develop` branch
target-branch: "taiste" target-branch: "taiste"
reviewers:
- "ae-utbm/developpers-v3"
commit-message: commit-message:
prefix: "[UPDATE] " prefix: "[UPDATE] "

4
.gitignore vendored
View File

@ -17,4 +17,6 @@ sith/settings_custom.py
sith/search_indexes/ sith/search_indexes/
.coverage .coverage
coverage_report/ coverage_report/
doc/_build
# compiled documentation
site/

View File

@ -1,10 +1,21 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.5.1 rev: v0.5.5
hooks: hooks:
- id: ruff # just check the code, and print the errors - id: ruff # just check the code, and print the errors
- id: ruff # actually fix the fixable errors, but print nothing - id: ruff # actually fix the fixable errors, but print nothing
args: ["--fix", "--silent"] args: ["--fix", "--silent"]
# Run the formatter. # Run the formatter.
- id: ruff-format - id: ruff-format
- repo: https://github.com/rtts/djhtml
rev: 3.0.6
hooks:
- id: djhtml
name: format templates
entry: djhtml --tabwidth 2
types: ["jinja"]
- id: djcss
name: format scss files
entry: djcss --tabwidth 2
types: ["scss"]

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Accounting type list{% endtrans %} {% trans %}Accounting type list{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Bank account: {% endtrans %}{{ object.name }} {% trans %}Bank account: {% endtrans %}{{ object.name }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Bank account list{% endtrans %} {% trans %}Bank account list{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Club account:{% endtrans %} {{ object.name }} {% trans %}Club account:{% endtrans %} {{ object.name }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Company list{% endtrans %} {% trans %}Company list{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -10,9 +10,9 @@
or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
%} %}
<p><a href="{{ url('accounting:co_new') }}">{% trans %}Create new company{% endtrans %}</a></p> <p><a href="{{ url('accounting:co_new') }}">{% trans %}Create new company{% endtrans %}</a></p>
{% endif %} {% endif %}
<br/> <br/>
<table> <table>
<thead> <thead>
<tr> <tr>
<td>{% trans %}Companies{% endtrans %}</td> <td>{% trans %}Companies{% endtrans %}</td>
@ -25,6 +25,6 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}General journal:{% endtrans %} {{ object.name }} {% trans %}General journal:{% endtrans %} {{ object.name }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -95,9 +95,9 @@
</td> </td>
<td><a href="{{ url('accounting:op_pdf', op_id=o.id) }}">{% trans %}Generate{% endtrans %}</a></td> <td><a href="{{ url('accounting:op_pdf', op_id=o.id) }}">{% trans %}Generate{% endtrans %}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,12 +1,12 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}General journal:{% endtrans %} {{ object.name }} {% trans %}General journal:{% endtrans %} {{ object.name }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="accounting"> <div id="accounting">
<h3>{% trans %}Accounting statement: {% endtrans %} {{ object.name }}</h3> <h3>{% trans %}Accounting statement: {% endtrans %} {{ object.name }}</h3>
<table> <table>
@ -29,5 +29,5 @@
<p><strong>{% trans %}Amount: {% endtrans %}</strong>{{ "%.2f" % object.amount }} €</p> <p><strong>{% trans %}Amount: {% endtrans %}</strong>{{ "%.2f" % object.amount }} €</p>
<p><strong>{% trans %}Effective amount: {% endtrans %}</strong>{{ "%.2f" %object.effective_amount }} €</p> <p><strong>{% trans %}Effective amount: {% endtrans %}</strong>{{ "%.2f" %object.effective_amount }} €</p>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,11 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}General journal:{% endtrans %} {{ object.name }} {% trans %}General journal:{% endtrans %} {{ object.name }}
{% endblock %} {% endblock %}
{% macro display_tables(dict) %} {% macro display_tables(dict) %}
<div id="accounting"> <div id="accounting">
<h6>{% trans %}Credit{% endtrans %}</h6> <h6>{% trans %}Credit{% endtrans %}</h6>
<table> <table>
<thead> <thead>
@ -43,9 +43,9 @@
</tbody> </tbody>
</table> </table>
{% trans %}Total: {% endtrans %}{{ "%.2f" % dict['DEBIT_sum'] }} {% trans %}Total: {% endtrans %}{{ "%.2f" % dict['DEBIT_sum'] }}
{% endmacro %} {% endmacro %}
{% block content %} {% block content %}
<h3>{% trans %}Statement by nature: {% endtrans %} {{ object.name }}</h3> <h3>{% trans %}Statement by nature: {% endtrans %} {{ object.name }}</h3>
{% for k,v in statement.items() %} {% for k,v in statement.items() %}
@ -53,5 +53,5 @@
{{ display_tables(v) }} {{ display_tables(v) }}
<hr> <hr>
{% endfor %} {% endfor %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,12 +1,12 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}General journal:{% endtrans %} {{ object.name }} {% trans %}General journal:{% endtrans %} {{ object.name }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="accounting"> <div id="accounting">
<h3>{% trans %}Statement by person: {% endtrans %} {{ object.name }}</h3> <h3>{% trans %}Statement by person: {% endtrans %} {{ object.name }}</h3>
<h4>{% trans %}Credit{% endtrans %}</h4> <h4>{% trans %}Credit{% endtrans %}</h4>
@ -64,5 +64,5 @@
</table> </table>
<p>Total : {{ "%.2f" % total_debit }}</p> <p>Total : {{ "%.2f" % total_debit }}</p>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Label list{% endtrans %} {% trans %}Label list{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -1,11 +1,11 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Edit operation{% endtrans %} {% trans %}Edit operation{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="accounting"> <div id="accounting">
<p> <p>
<a href="{{ url('accounting:bank_list') }}">{% trans %}Accounting{% endtrans %}</a> > <a href="{{ url('accounting:bank_list') }}">{% trans %}Accounting{% endtrans %}</a> >
<a href="{{ url('accounting:bank_details', b_account_id=object.club_account.bank_account.id) }}">{{object.club_account.bank_account }}</a> > <a href="{{ url('accounting:bank_details', b_account_id=object.club_account.bank_account.id) }}">{{object.club_account.bank_account }}</a> >
@ -54,9 +54,9 @@
{% endif %} {% endif %}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form> </form>
{% endblock %} {% endblock %}
{% block script %} {% block script %}
{{ super() }} {{ super() }}
<script> <script>
$( function() { $( function() {
@ -117,7 +117,7 @@
target_type.change(update_targets); target_type.change(update_targets);
} ); } );
</script> </script>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Refound account{% endtrans %} {% trans %}Refound account{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Simplified type list{% endtrans %} {% trans %}Simplified type list{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -264,33 +264,26 @@ class TestOperation(TestCase):
) )
self.assertContains(response, "Total : 5575.72", status_code=200) self.assertContains(response, "Total : 5575.72", status_code=200)
self.assertContains(response, "Total : 71.42") self.assertContains(response, "Total : 71.42")
self.assertContains( content = response.content.decode()
response, self.assertInHTML(
""" """<td><a href="/user/1/">S&#39; Kia</a></td><td>3.00</td>""", content
<td><a href="/user/1/">S&#39; Kia</a></td>
<td>3.00</td>""",
) )
self.assertContains( self.assertInHTML(
response, """<td><a href="/user/1/">S&#39; Kia</a></td><td>823.00</td>""", content
"""
<td><a href="/user/1/">S&#39; Kia</a></td>
<td>823.00</td>""",
) )
def test_accounting_statement(self): def test_accounting_statement(self):
response = self.client.get( response = self.client.get(
reverse("accounting:journal_accounting_statement", args=[self.journal.id]) reverse("accounting:journal_accounting_statement", args=[self.journal.id])
) )
self.assertContains( assert response.status_code == 200
response, self.assertInHTML(
""" """
<tr> <tr>
<td>443 - Crédit - Ce code n&#39;existe pas</td> <td>443 - Crédit - Ce code n&#39;existe pas</td>
<td>3.00</td> <td>3.00</td>
</tr>""", </tr>""",
status_code=200, response.content.decode(),
) )
self.assertContains( self.assertContains(
response, response,

View File

@ -29,7 +29,7 @@ from django.utils.translation import gettext_lazy as _
from club.models import Club, Mailing, MailingSubscription, Membership from club.models import Club, Mailing, MailingSubscription, Membership
from core.models import User from core.models import User
from core.views.forms import SelectDate, TzAwareDateTimeField from core.views.forms import SelectDate, SelectDateTime
from counter.models import Counter from counter.models import Counter
@ -149,8 +149,12 @@ class MailingForm(forms.Form):
class SellingsForm(forms.Form): class SellingsForm(forms.Form):
begin_date = TzAwareDateTimeField(label=_("Begin date"), required=False) begin_date = forms.DateTimeField(
end_date = TzAwareDateTimeField(label=_("End date"), required=False) label=_("Begin date"), widget=SelectDateTime, required=False
)
end_date = forms.DateTimeField(
label=_("End date"), widget=SelectDateTime, required=False
)
counters = forms.ModelMultipleChoiceField( counters = forms.ModelMultipleChoiceField(
Counter.objects.order_by("name").all(), label=_("Counter"), required=False Counter.objects.order_by("name").all(), label=_("Counter"), required=False

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Club list{% endtrans %} {% trans %}Club list{% endtrans %}
{% endblock %} {% endblock %}
{% macro display_club(club) -%} {% macro display_club(club) -%}

View File

@ -2,19 +2,19 @@
{% from 'core/macros.jinja' import user_profile_link, paginate %} {% from 'core/macros.jinja' import user_profile_link, paginate %}
{% block content %} {% block content %}
<h3>{% trans %}Sales{% endtrans %}</h3> <h3>{% trans %}Sales{% endtrans %}</h3>
<form id="form" action="?page=1" method="post"> <form id="form" action="?page=1" method="post">
{% csrf_token %} {% csrf_token %}
{{ form }} {{ form }}
<p><input type="submit" value="{% trans %}Show{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Show{% endtrans %}" /></p>
<p><input type="submit" value="{% trans %}Download as cvs{% endtrans %}" formaction="{{ url('club:sellings_csv', club_id=object.id) }}"/></p> <p><input type="submit" value="{% trans %}Download as cvs{% endtrans %}" formaction="{{ url('club:sellings_csv', club_id=object.id) }}"/></p>
</form> </form>
<p> <p>
{% trans %}Quantity: {% endtrans %}{{ total_quantity }} {% trans %}units{% endtrans %}<br/> {% trans %}Quantity: {% endtrans %}{{ total_quantity }} {% trans %}units{% endtrans %}<br/>
{% trans %}Total: {% endtrans %}{{ total }} €<br/> {% trans %}Total: {% endtrans %}{{ total }} €<br/>
{% trans %}Benefit: {% endtrans %}{{ benefit }} {% trans %}Benefit: {% endtrans %}{{ benefit }}
</p> </p>
<table> <table>
<thead> <thead>
<tr> <tr>
<td>{% trans %}Date{% endtrans %}</td> <td>{% trans %}Date{% endtrans %}</td>
@ -52,15 +52,15 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<script type="text/javascript"> <script type="text/javascript">
function formPagination(link){ function formPagination(link){
$("form").attr("action", link.href); $("form").attr("action", link.href);
link.href = "javascript:void(0)"; // block link action link.href = "javascript:void(0)"; // block link action
$("form").submit(); $("form").submit();
} }
</script> </script>
{{ paginate(paginated_result, paginator, "formPagination(this)") }} {{ paginate(paginated_result, paginator, "formPagination(this)") }}
{% endblock %} {% endblock %}

View File

@ -1,8 +1,8 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block content %} {% block content %}
<h3>{% trans %}Club tools{% endtrans %}</h3> <h3>{% trans %}Club tools{% endtrans %}</h3>
<div> <div>
<h4>{% trans %}Communication:{% endtrans %}</h4> <h4>{% trans %}Communication:{% endtrans %}</h4>
<ul> <ul>
<li> <a href="{{ url('com:news_new') }}?club={{ object.id }}">{% trans %}Create a news{% endtrans %}</a></li> <li> <a href="{{ url('com:news_new') }}?club={{ object.id }}">{% trans %}Create a news{% endtrans %}</a></li>
@ -40,7 +40,7 @@
{% if object.unix_name == settings.SITH_LAUNDERETTE_MANAGER['unix_name'] %} {% if object.unix_name == settings.SITH_LAUNDERETTE_MANAGER['unix_name'] %}
<li><a href="{{ url('launderette:launderette_list') }}">{% trans %}Manage launderettes{% endtrans %}</a></li> <li><a href="{{ url('launderette:launderette_list') }}">{% trans %}Manage launderettes{% endtrans %}</a></li>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,7 +2,7 @@
{% from 'core/macros.jinja' import select_all_checkbox %} {% from 'core/macros.jinja' import select_all_checkbox %}
{% block title %} {% block title %}
{% trans %}Mailing lists{% endtrans %} {% trans %}Mailing lists{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -2,7 +2,7 @@
{% from 'core/macros_pages.jinja' import page_edit_form %} {% from 'core/macros_pages.jinja' import page_edit_form %}
{% block content %} {% block content %}
{{ page_edit_form(page, form, url('club:club_edit_page', club_id=page.club.id), csrf_token) }} {{ page_edit_form(page, form, url('club:club_edit_page', club_id=page.club.id), csrf_token) }}
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Club stats{% endtrans %} {% trans %}Club stats{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Mailing lists administration{% endtrans %} {% trans %}Mailing lists administration{% endtrans %}
{% endblock %} {% endblock %}
{% macro display_mailings(list) %} {% macro display_mailings(list) %}

View File

@ -2,7 +2,7 @@
{% from 'core/macros.jinja' import user_profile_link %} {% from 'core/macros.jinja' import user_profile_link %}
{% block title %} {% block title %}
{% trans %}News admin{% endtrans %} {% trans %}News admin{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -2,17 +2,17 @@
{% from 'core/macros.jinja' import user_profile_link, facebook_share, tweet, link_news_logo, gen_news_metatags %} {% from 'core/macros.jinja' import user_profile_link, facebook_share, tweet, link_news_logo, gen_news_metatags %}
{% block title %} {% block title %}
{% trans %}News{% endtrans %} - {% trans %}News{% endtrans %} -
{{ object.title }} {{ object.title }}
{% endblock %} {% endblock %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
{{ gen_news_metatags(news) }} {{ gen_news_metatags(news) }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<p><a href="{{ url('com:news_list') }}">{% trans %}Back to news{% endtrans %}</a></p> <p><a href="{{ url('com:news_list') }}">{% trans %}Back to news{% endtrans %}</a></p>
<section id="news_details"> <section id="news_details">
<div class="club_logo"> <div class="club_logo">
<img src="{{ link_news_logo(news)}}" alt="{{ news.club }}" /> <img src="{{ link_news_logo(news)}}" alt="{{ news.club }}" />

View File

@ -2,16 +2,16 @@
{% from 'core/macros.jinja' import user_profile_link %} {% from 'core/macros.jinja' import user_profile_link %}
{% block title %} {% block title %}
{% if object %} {% if object %}
{% trans %}Edit news{% endtrans %} {% trans %}Edit news{% endtrans %}
{% else %} {% else %}
{% trans %}Create news{% endtrans %} {% trans %}Create news{% endtrans %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if 'preview' in request.POST.keys() %} {% if 'preview' in request.POST.keys() %}
<section class="news_event"> <section class="news_event">
<h4>{{ form.instance.title }}</h4> <h4>{{ form.instance.title }}</h4>
<p class="date"> <p class="date">
<span>{{ form.instance.dates.first().start_date|localtime|date(DATETIME_FORMAT) }} <span>{{ form.instance.dates.first().start_date|localtime|date(DATETIME_FORMAT) }}
@ -23,14 +23,14 @@
<div>{{ form.instance.summary|markdown }}</div> <div>{{ form.instance.summary|markdown }}</div>
<div>{{ form.instance.content|markdown }}</div> <div>{{ form.instance.content|markdown }}</div>
<p>{% trans %}Author: {% endtrans %} {{ user_profile_link(form.instance.author) }}</p> <p>{% trans %}Author: {% endtrans %} {{ user_profile_link(form.instance.author) }}</p>
</section> </section>
{% endif %} {% endif %}
{% if object %} {% if object %}
<h2>{% trans %}Edit news{% endtrans %}</h2> <h2>{% trans %}Edit news{% endtrans %}</h2>
{% else %} {% else %}
<h2>{% trans %}Create news{% endtrans %}</h2> <h2>{% trans %}Create news{% endtrans %}</h2>
{% endif %} {% endif %}
<form action="" method="post"> <form action="" method="post">
{% csrf_token %} {% csrf_token %}
{{ form.non_field_errors() }} {{ form.non_field_errors() }}
{{ form.author }} {{ form.author }}
@ -55,13 +55,13 @@
{% endif %} {% endif %}
<p><input type="submit" name="preview" value="{% trans %}Preview{% endtrans %}" /></p> <p><input type="submit" name="preview" value="{% trans %}Preview{% endtrans %}" /></p>
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form> </form>
{% endblock %} {% endblock %}
{% block script %} {% block script %}
{{ super() }} {{ super() }}
<script> <script>
$( function() { $( function() {
var type = $('input[name=type]'); var type = $('input[name=type]');
var dates = $('.date'); var dates = $('.date');
var until = $('.until'); var until = $('.until');
@ -81,7 +81,7 @@ $( function() {
update_targets(); update_targets();
type.change(update_targets); type.change(update_targets);
} ); } );
</script> </script>
{% endblock %} {% endblock %}

View File

@ -2,18 +2,18 @@
{% from 'core/macros.jinja' import tweet_quick, fb_quick %} {% from 'core/macros.jinja' import tweet_quick, fb_quick %}
{% block title %} {% block title %}
{% trans %}News{% endtrans %} {% trans %}News{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if user.is_com_admin %} {% if user.is_com_admin %}
<div id="news_admin"> <div id="news_admin">
<a class="button" href="{{ url('com:news_admin_list') }}">{% trans %}Administrate news{% endtrans %}</a> <a class="button" href="{{ url('com:news_admin_list') }}">{% trans %}Administrate news{% endtrans %}</a>
</div> </div>
<br> <br>
{% endif %} {% endif %}
<div id="news"> <div id="news">
<div id="left_column" class="news_column"> <div id="left_column" class="news_column">
{% for news in object_list.filter(type="NOTICE") %} {% for news in object_list.filter(type="NOTICE") %}
<section class="news_notice"> <section class="news_notice">
@ -77,15 +77,15 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<div class="news_empty"> <div class="news_empty">
<em>{% trans %}Nothing to come...{% endtrans %}</em> <em>{% trans %}Nothing to come...{% endtrans %}</em>
</div> </div>
{% endif %} {% endif %}
{% set coming_soon = object_list.filter(dates__start_date__gte=timezone.now()+timedelta(days=5), {% set coming_soon = object_list.filter(dates__start_date__gte=timezone.now()+timedelta(days=5),
type="EVENT").order_by('dates__start_date') %} type="EVENT").order_by('dates__start_date') %}
{% if coming_soon %} {% if coming_soon %}
<h3>{% trans %}Coming soon... don't miss!{% endtrans %}</h3> <h3>{% trans %}Coming soon... don't miss!{% endtrans %}</h3>
{% for news in coming_soon %} {% for news in coming_soon %}
<section class="news_coming_soon"> <section class="news_coming_soon">
@ -96,19 +96,19 @@
{{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span> {{ news.dates.first().end_date|localtime|time(DATETIME_FORMAT) }}</span>
</section> </section>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<h3>{% trans %}All coming events{% endtrans %}</h3> <h3>{% trans %}All coming events{% endtrans %}</h3>
<iframe <iframe
src="https://embed.styledcalendar.com/#2mF2is8CEXhr4ADcX6qN" src="https://embed.styledcalendar.com/#2mF2is8CEXhr4ADcX6qN"
title="Styled Calendar" title="Styled Calendar"
class="styled-calendar-container" class="styled-calendar-container"
style="width: 100%; border: none; height: 1060px" style="width: 100%; border: none; height: 1060px"
data-cy="calendar-embed-iframe"> data-cy="calendar-embed-iframe">
</iframe> </iframe>
</div> </div>
<div id="right_column" class="news_column"> <div id="right_column" class="news_column">
<div id="agenda"> <div id="agenda">
<div id="agenda_title">{% trans %}Agenda{% endtrans %}</div> <div id="agenda_title">{% trans %}Agenda{% endtrans %}</div>
<div id="agenda_content"> <div id="agenda_content">
@ -157,7 +157,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,11 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Poster{% endtrans %} {% trans %}Poster{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="poster_edit"> <div id="poster_edit">
<div id="title"> <div id="title">
<div id="links" class="left"> <div id="links" class="left">
@ -36,7 +36,7 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,17 +1,17 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block script %} {% block script %}
{{ super() }} {{ super() }}
<script src="{{ static('com/js/poster_list.js') }}"></script> <script src="{{ static('com/js/poster_list.js') }}"></script>
{% endblock %} {% endblock %}
{% block title %} {% block title %}
{% trans %}Poster{% endtrans %} {% trans %}Poster{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="poster_list"> <div id="poster_list">
<div id="title"> <div id="title">
<h3>{% trans %}Posters{% endtrans %}</h3> <h3>{% trans %}Posters{% endtrans %}</h3>
@ -60,7 +60,7 @@
<div id="view"><div id="placeholder"></div></div> <div id="view"><div id="placeholder"></div></div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,12 +1,12 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block script %} {% block script %}
{{ super() }} {{ super() }}
<script src="{{ static('com/js/poster_list.js') }}"></script> <script src="{{ static('com/js/poster_list.js') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="poster_list"> <div id="poster_list">
<div id="title"> <div id="title">
<div id="links" class="left"> <div id="links" class="left">
@ -35,5 +35,5 @@
<div id="view"><div id="placeholder"></div></div> <div id="view"><div id="placeholder"></div></div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,11 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Screen{% endtrans %} {% trans %}Screen{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="screen_edit"> <div id="screen_edit">
<div id="title"> <div id="title">
<div id="links" class="left"> <div id="links" class="left">
@ -27,7 +27,7 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,11 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Screens{% endtrans %} {% trans %}Screens{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="screen_list"> <div id="screen_list">
<div id="title"> <div id="title">
<h3>{% trans %}Screens{% endtrans %}</h3> <h3>{% trans %}Screens{% endtrans %}</h3>
@ -32,7 +32,7 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,10 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
<title>{% trans %}Slideshow{% endtrans %}</title> <title>{% trans %}Slideshow{% endtrans %}</title>
<link href="{{ scss('com/slideshow.scss') }}" rel="stylesheet" type="text/css" /> <link href="{{ scss('com/slideshow.scss') }}" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<div id="slideshow"> <div id="slideshow">
<div id="slides"> <div id="slides">
@ -26,5 +26,5 @@
</div> </div>
<script src="{{ static('core/js/jquery-3.6.2.min.js') }}"></script> <script src="{{ static('core/js/jquery-3.6.2.min.js') }}"></script>
<script src="{{ static('com/js/slideshow.js') }}"></script> <script src="{{ static('com/js/slideshow.js') }}"></script>
</body> </body>
</html> </html>

View File

@ -2,16 +2,16 @@
{% from 'core/macros.jinja' import user_profile_link %} {% from 'core/macros.jinja' import user_profile_link %}
{% block title %} {% block title %}
{% trans %}Weekmail{% endtrans %} {% trans %}Weekmail{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h3>{% trans %}Weekmail{% endtrans %} {{ object.id }}</h3> <h3>{% trans %}Weekmail{% endtrans %} {{ object.id }}</h3>
<p><a href="{{ url('com:weekmail_preview') }}">{% trans %}Preview{% endtrans %}</a></p> <p><a href="{{ url('com:weekmail_preview') }}">{% trans %}Preview{% endtrans %}</a></p>
<p><a href="{{ url('com:weekmail_preview') }}?send=true">{% trans %}Send{% endtrans %}</a></p> <p><a href="{{ url('com:weekmail_preview') }}?send=true">{% trans %}Send{% endtrans %}</a></p>
<p><a href="{{ url('com:weekmail_article') }}">{% trans %}New article{% endtrans %}</a></p> <p><a href="{{ url('com:weekmail_article') }}">{% trans %}New article{% endtrans %}</a></p>
<h4>{% trans %}Articles in no weekmail yet{% endtrans %}</h4> <h4>{% trans %}Articles in no weekmail yet{% endtrans %}</h4>
<table> <table>
<thead> <thead>
<tr> <tr>
<td>{% trans %}Author{% endtrans %}</td> <td>{% trans %}Author{% endtrans %}</td>
@ -38,9 +38,9 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<h4>{% trans %}Articles included the next weekmail{% endtrans %}</h4> <h4>{% trans %}Articles included the next weekmail{% endtrans %}</h4>
<table> <table>
<thead> <thead>
<tr> <tr>
<td>{% trans %}Author{% endtrans %}</td> <td>{% trans %}Author{% endtrans %}</td>
@ -67,12 +67,12 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<form action="" method="post" enctype="multipart/form-data"> <form action="" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form.as_p() }} {{ form.as_p() }}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -2,12 +2,12 @@
{% from 'core/macros.jinja' import user_profile_link %} {% from 'core/macros.jinja' import user_profile_link %}
{% block title %} {% block title %}
{{ weekmail.title }} {{ weekmail.title }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<a href="{{ url('com:weekmail') }}">{% trans %}Back{% endtrans %}</a> <a href="{{ url('com:weekmail') }}">{% trans %}Back{% endtrans %}</a>
{% if bad_recipients %} {% if bad_recipients %}
<p> <p>
<span class="important"> <span class="important">
{% trans %}The following recipients were refused by the SMTP:{% endtrans %} {% trans %}The following recipients were refused by the SMTP:{% endtrans %}
@ -23,7 +23,7 @@
{% csrf_token %} {% csrf_token %}
<button type="submit" name="send" value="clean">{% trans %}Clean subscribers{% endtrans %}</button> <button type="submit" name="send" value="clean">{% trans %}Clean subscribers{% endtrans %}</button>
</form> </form>
{% else %} {% else %}
{% if request.GET['send'] %} {% if request.GET['send'] %}
<p>{% trans %}Are you sure you want to send this weekmail?{% endtrans %}</p> <p>{% trans %}Are you sure you want to send this weekmail?{% endtrans %}</p>
{% if request.LANGUAGE_CODE != settings.LANGUAGE_CODE[:2] %} {% if request.LANGUAGE_CODE != settings.LANGUAGE_CODE[:2] %}
@ -34,9 +34,9 @@
<button type="submit" name="send" value="validate">{% trans %}Send{% endtrans %}</button> <button type="submit" name="send" value="validate">{% trans %}Send{% endtrans %}</button>
</form> </form>
{% endif %} {% endif %}
{% endif %} {% endif %}
<hr> <hr>
{{ weekmail_rendered|safe }} {{ weekmail_rendered|safe }}
{% endblock %} {% endblock %}

View File

@ -1,12 +1,12 @@
<style type="text/css" media="all"> <style type="text/css" media="all">
h1, h2, h3, h4, h5, h6, p { h1, h2, h3, h4, h5, h6, p {
padding: 5px ; padding: 5px ;
margin: 5px; margin: 5px;
} }
img { img {
margin: 5px ; margin: 5px ;
width: 95%; width: 95%;
} }
</style> </style>
<div style="background: #CBD1DD; padding: 0px 5%;"> <div style="background: #CBD1DD; padding: 0px 5%;">
<div style="background: #F9FAFB;"> <div style="background: #F9FAFB;">
@ -48,5 +48,5 @@ img {
<img src="{{ weekmail.get_footer() }}" <img src="{{ weekmail.get_footer() }}"
</div> </div>
</div> </div>

View File

@ -1,8 +1,8 @@
# {{ weekmail.title }} # {{ weekmail.title }}
{%- if weekmail.intro %} {%- if weekmail.intro %}
## {% trans %}Intro{% endtrans %} ## {% trans %}Intro{% endtrans %}
{{ weekmail.intro }} {{ weekmail.intro }}
{% endif %} {% endif %}
## {% trans %}Table of content{% endtrans %} ## {% trans %}Table of content{% endtrans %}
@ -11,22 +11,22 @@
{% endfor -%} {% endfor -%}
{% for a in weekmail.articles.all() %} {% for a in weekmail.articles.all() %}
## [{{ a.club }}] {{ a.title }} ## [{{ a.club }}] {{ a.title }}
{{ a.content }} {{ a.content }}
{% endfor -%} {% endfor -%}
{%- if weekmail.joke %} {%- if weekmail.joke %}
## {% trans %}Joke{% endtrans %} ## {% trans %}Joke{% endtrans %}
{{ weekmail.joke }} {{ weekmail.joke }}
{% endif -%} {% endif -%}
{%- if weekmail.protip %} {%- if weekmail.protip %}
## {% trans %}Pro tip{% endtrans %} ## {% trans %}Pro tip{% endtrans %}
{{ weekmail.protip }} {{ weekmail.protip }}
{% endif -%} {% endif -%}
{%- if weekmail.conclusion %} {%- if weekmail.conclusion %}
## {% trans %}Final word{% endtrans %} ## {% trans %}Final word{% endtrans %}
{{ weekmail.conclusion }} {{ weekmail.conclusion }}
{% endif -%} {% endif -%}

View File

@ -69,11 +69,11 @@ class TestCom(TestCase):
}, },
) )
r = self.client.get(reverse("core:index")) r = self.client.get(reverse("core:index"))
self.assertContains( assert r.status_code == 200
r, self.assertInHTML(
"""<div id="alert_box"> """<div id="alert_box"><div class="markdown"><h3>ALERTE!</h3>
<div class="markdown"><h3>ALERTE!</h3> <p><strong>Caaaataaaapuuuulte!!!!</strong></p>""",
<p><strong>Caaaataaaapuuuulte!!!!</strong></p>""", r.content.decode(),
) )
def test_info_msg(self): def test_info_msg(self):
@ -86,10 +86,12 @@ class TestCom(TestCase):
}, },
) )
r = self.client.get(reverse("core:index")) r = self.client.get(reverse("core:index"))
self.assertContains(
r, assert r.status_code == 200
"""<div id="info_box"> self.assertInHTML(
<div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""", """<div id="info_box"><div class="markdown">
<h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""",
r.content.decode(),
) )
def test_birthday_non_subscribed_user(self): def test_birthday_non_subscribed_user(self):

View File

@ -50,7 +50,7 @@ from core.views import (
QuickNotifMixin, QuickNotifMixin,
TabedViewMixin, TabedViewMixin,
) )
from core.views.forms import MarkdownInput, TzAwareDateTimeField from core.views.forms import MarkdownInput, SelectDateTime
# Sith object # Sith object
@ -72,12 +72,15 @@ class PosterForm(forms.ModelForm):
widgets = {"screens": forms.CheckboxSelectMultiple} widgets = {"screens": forms.CheckboxSelectMultiple}
help_texts = {"file": _("Format: 16:9 | Resolution: 1920x1080")} help_texts = {"file": _("Format: 16:9 | Resolution: 1920x1080")}
date_begin = TzAwareDateTimeField( date_begin = forms.DateTimeField(
label=_("Start date"), label=_("Start date"),
widget=SelectDateTime,
required=True, required=True,
initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S"), initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
) )
date_end = TzAwareDateTimeField(label=_("End date"), required=False) date_end = forms.DateTimeField(
label=_("End date"), widget=SelectDateTime, required=False
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None) self.user = kwargs.pop("user", None)
@ -191,9 +194,13 @@ class NewsForm(forms.ModelForm):
"content": MarkdownInput, "content": MarkdownInput,
} }
start_date = TzAwareDateTimeField(label=_("Start date"), required=False) start_date = forms.DateTimeField(
end_date = TzAwareDateTimeField(label=_("End date"), required=False) label=_("Start date"), widget=SelectDateTime, required=False
until = TzAwareDateTimeField(label=_("Until"), required=False) )
end_date = forms.DateTimeField(
label=_("End date"), widget=SelectDateTime, required=False
)
until = forms.DateTimeField(label=_("Until"), widget=SelectDateTime, required=False)
automoderation = forms.BooleanField(label=_("Automoderation"), required=False) automoderation = forms.BooleanField(label=_("Automoderation"), required=False)
@ -258,7 +265,7 @@ class NewsEditView(CanEditMixin, UpdateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
if form.is_valid() and "preview" not in request.POST.keys(): if form.is_valid() and "preview" not in request.POST:
return self.form_valid(form) return self.form_valid(form)
else: else:
return self.form_invalid(form) return self.form_invalid(form)

View File

@ -21,8 +21,6 @@
# #
# #
import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from core.models import SithFile from core.models import SithFile
@ -37,9 +35,6 @@ class Command(BaseCommand):
) )
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
files = SithFile.objects.filter(id__in=options["ids"]).all() files = SithFile.objects.filter(id__in=options["ids"]).all()
for f in files: for f in files:
f._check_fs() f._check_fs()

View File

@ -22,7 +22,7 @@
# #
# #
import os import sys
import sass import sass
from django.conf import settings from django.conf import settings
@ -34,44 +34,36 @@ class Command(BaseCommand):
help = "Compile scss files from static folder" help = "Compile scss files from static folder"
def compile(self, filename): def compile(self, filename: str):
args = {"filename": filename, "include_paths": settings.STATIC_ROOT} args = {
"filename": filename,
"include_paths": settings.STATIC_ROOT.name,
"output_style": "compressed",
}
if settings.SASS_PRECISION: if settings.SASS_PRECISION:
args["precision"] = settings.SASS_PRECISION args["precision"] = settings.SASS_PRECISION
return sass.compile(**args) return sass.compile(**args)
def is_compilable(self, file, ext_list):
path, ext = os.path.splitext(file)
return ext in ext_list
def exec_on_folder(self, folder, func):
to_exec = []
for file in os.listdir(folder):
file = os.path.join(folder, file)
if os.path.isdir(file):
self.exec_on_folder(file, func)
elif self.is_compilable(file, [".scss"]):
to_exec.append(file)
for file in to_exec:
func(file)
def compilescss(self, file):
print("compiling %s" % file)
with open(file.replace(".scss", ".css"), "w") as newfile:
newfile.write(self.compile(file))
def removescss(self, file):
print("removing %s" % file)
os.remove(file)
def handle(self, *args, **options): def handle(self, *args, **options):
if os.path.isdir(settings.STATIC_ROOT): if not settings.STATIC_ROOT.is_dir():
print("---- Compiling scss files ---") raise Exception(
self.exec_on_folder(settings.STATIC_ROOT, self.compilescss) "No static folder availaible, please use collectstatic before compiling scss"
print("---- Removing scss files ----") )
self.exec_on_folder(settings.STATIC_ROOT, self.removescss) to_exec = list(settings.STATIC_ROOT.rglob("*.scss"))
else: if len(to_exec) == 0:
print( self.stdout.write("Nothing to compile.")
"No static folder avalaible, please use collectstatic before compiling scss" sys.exit(0)
self.stdout.write("---- Compiling scss files ---")
for file in to_exec:
# remove existing css files that will be replaced
# keeping them while compiling the scss would break
# import statements resolution
css_file = file.with_suffix(".css")
if css_file.exists():
css_file.unlink()
compiled_files = {file: self.compile(str(file.resolve())) for file in to_exec}
for file, scss in compiled_files.items():
file.replace(file.with_suffix(".css")).write_text(scss)
self.stdout.write(
"Files compiled : \n" + "\n- ".join(str(f) for f in compiled_files)
) )

View File

@ -21,7 +21,6 @@
# #
# #
from pathlib import Path
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@ -33,7 +32,7 @@ class Command(BaseCommand):
help = "Output the fully rendered SYNTAX.md file" help = "Output the fully rendered SYNTAX.md file"
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = Path(settings.BASE_DIR) root_path = settings.BASE_DIR
with open(root_path / "core/fixtures/SYNTAX.md", "r") as md: with open(root_path / "core/fixtures/SYNTAX.md", "r") as md:
result = markdown(md.read()) result = markdown(md.read())
print(result, end="") print(result, end="")

View File

@ -0,0 +1,383 @@
import random
from datetime import date, timedelta
from decimal import Decimal
from typing import Iterator
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.core.management.base import BaseCommand
from django.db.models import Exists, F, Min, OuterRef, Subquery, Sum
from django.db.models.functions import Coalesce
from django.utils.timezone import make_aware, now
from faker import Faker
from club.models import Club, Membership
from core.models import RealGroup, User
from counter.models import (
Counter,
Customer,
Permanency,
Product,
ProductType,
Refilling,
Selling,
)
from pedagogy.models import UV
from subscription.models import Subscription
class Command(BaseCommand):
help = "Add more fixtures for a more complete development environment"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.faker = Faker("fr_FR")
def handle(self, *args, **options):
if not settings.DEBUG:
raise Exception("Never call this command in prod. Never.")
self.stdout.write("Creating users...")
users = [
User(
username=self.faker.user_name(),
first_name=self.faker.first_name(),
last_name=self.faker.last_name(),
date_of_birth=self.faker.date_of_birth(minimum_age=15, maximum_age=25),
email=self.faker.email(),
phone=self.faker.phone_number(),
address=self.faker.address(),
)
for _ in range(600)
]
# there may a duplicate or two
# Not a problem, we will just have 599 users instead of 600
User.objects.bulk_create(users, ignore_conflicts=True)
users = list(User.objects.order_by("-id")[: len(users)])
subscribers = random.sample(users, k=int(0.8 * len(users)))
self.stdout.write("Creating subscriptions...")
self.create_subscriptions(users)
self.stdout.write("Creating club memberships...")
users_qs = User.objects.filter(id__in=[s.id for s in subscribers])
subscribers_now = list(
users_qs.annotate(
filter=Exists(
Subscription.objects.filter(
member_id=OuterRef("pk"), subscription_end__gte=now()
)
)
)
)
old_subscribers = list(
users_qs.annotate(
filter=Exists(
Subscription.objects.filter(
member_id=OuterRef("pk"), subscription_end__lt=now()
)
)
)
)
self.make_club(
Club.objects.get(unix_name="ae"),
random.sample(subscribers_now, k=min(30, len(subscribers_now))),
random.sample(old_subscribers, k=min(60, len(old_subscribers))),
)
self.make_club(
Club.objects.get(unix_name="troll"),
random.sample(subscribers_now, k=min(20, len(subscribers_now))),
random.sample(old_subscribers, k=min(80, len(old_subscribers))),
)
self.stdout.write("Creating uvs...")
self.create_uvs()
self.stdout.write("Creating products...")
self.create_products()
self.stdout.write("Creating sales and refills...")
sellers = random.sample(list(User.objects.all()), 100)
self.create_sales(sellers)
self.stdout.write("Creating permanences...")
self.create_permanences(sellers)
self.stdout.write("Done")
def create_subscriptions(self, users: list[User]):
def prepare_subscription(user: User, start_date: date) -> Subscription:
payment_method = random.choice(settings.SITH_SUBSCRIPTION_PAYMENT_METHOD)[0]
duration = random.randint(1, 4)
sub = Subscription(member=user, payment_method=payment_method)
sub.subscription_start = sub.compute_start(d=start_date, duration=duration)
sub.subscription_end = sub.compute_end(duration)
return sub
subscriptions = []
customers = []
# first set of subscriptions
for i, user in enumerate(users):
sub = prepare_subscription(user, self.faker.past_date("-10y"))
subscriptions.append(sub)
customers.append(
Customer(
user=user,
account_id=f"{9900 + i}{self.faker.random_lowercase_letter()}",
)
)
while sub.subscription_end < now().date() and random.random() > 0.7:
# 70% chances to subscribe again
# (expect if it would make the subscription start after tomorrow)
sub = prepare_subscription(
user, self.faker.past_date(sub.subscription_end)
)
subscriptions.append(sub)
Subscription.objects.bulk_create(subscriptions)
Customer.objects.bulk_create(customers, ignore_conflicts=True)
def make_club(self, club: Club, members: list[User], old_members: list[User]):
def zip_roles(users: list[User]) -> Iterator[tuple[User, int]]:
roles = iter(sorted(settings.SITH_CLUB_ROLES.keys(), reverse=True))
user_idx = 0
while (role := next(roles)) > 2:
# one member for each major role
yield users[user_idx], role
user_idx += 1
for _ in range(int(0.3 * (len(users) - user_idx))):
# 30% of the remaining in the board
yield users[user_idx], 2
user_idx += 1
for remaining in users[user_idx + 1 :]:
# everything else is a simple member
yield remaining, 1
memberships = []
old_members = old_members.copy()
random.shuffle(old_members)
for old in old_members:
start = self.faker.date_between("-3y", "-1y")
memberships.append(
Membership(
start_date=start,
end_date=self.faker.past_date(start),
user=old,
role=random.choice(list(settings.SITH_CLUB_ROLES.keys())),
club=club,
)
)
for member, role in zip_roles(members):
start = self.faker.past_date("-1y")
memberships.append(
Membership(
start_date=start,
user=member,
role=role,
club=club,
)
)
Membership.objects.bulk_create(memberships)
def create_uvs(self):
root = User.objects.get(username="root")
categories = ["CS", "TM", "OM", "QC", "EC"]
branches = ["TC", "GMC", "GI", "EDIM", "E", "IMSI", "HUMA"]
languages = ["FR", "FR", "EN"]
semesters = ["AUTUMN", "SPRING", "AUTUMN_AND_SPRING"]
teachers = [self.faker.name() for _ in range(50)]
uvs = []
for _ in range(1000):
code = (
self.faker.random_uppercase_letter()
+ self.faker.random_uppercase_letter()
+ str(random.randint(10, 90))
)
uvs.append(
UV(
code=code,
author=root,
manager=random.choice(teachers),
title=self.faker.text(max_nb_chars=50),
department=random.choice(branches),
credit_type=random.choice(categories),
credits=6,
semester=random.choice(semesters),
language=random.choice(languages),
program=self.faker.paragraph(random.randint(3, 10)),
skills="\n* ".join(self.faker.sentences(random.randint(3, 10))),
key_concepts="\n* ".join(
self.faker.sentences(random.randint(3, 10))
),
hours_CM=random.randint(15, 40),
hours_TD=random.randint(15, 40),
hours_TP=random.randint(15, 40),
hours_THE=random.randint(15, 40),
hours_TE=random.randint(15, 40),
)
)
UV.objects.bulk_create(uvs, ignore_conflicts=True)
def create_products(self):
categories = []
for _ in range(10):
categories.append(ProductType(name=self.faker.text(max_nb_chars=30)))
ProductType.objects.bulk_create(categories)
categories = list(
ProductType.objects.filter(name__in=[c.name for c in categories])
)
ae = Club.objects.get(unix_name="ae")
other_clubs = random.sample(list(Club.objects.all()), k=3)
groups = list(
RealGroup.objects.filter(
name__in=["Subscribers", "Old subscribers", "Public"]
)
)
counters = list(
Counter.objects.filter(name__in=["Foyer", "MDE", "La Gommette", "Eboutic"])
)
# 2/3 of the products are owned by AE
clubs = [ae, ae, ae, ae, ae, ae, *other_clubs]
products = []
buying_groups = []
selling_places = []
for _ in range(200):
price = random.randint(0, 10) + random.choice([0, 0.25, 0.5, 0.75])
product = Product(
name=self.faker.text(max_nb_chars=30),
description=self.faker.text(max_nb_chars=120),
product_type=random.choice(categories),
code="".join(self.faker.random_letters(length=random.randint(4, 8))),
purchase_price=price,
selling_price=price,
special_selling_price=price - min(0.5, price),
club=random.choice(clubs),
limit_age=0 if random.random() > 0.2 else 18,
archived=bool(random.random() > 0.7),
)
products.append(product)
for group in random.sample(groups, k=random.randint(0, 3)):
# there will be products without buying groups
# but there are also such products in the real database
buying_groups.append(
Product.buying_groups.through(product=product, group=group)
)
for counter in random.sample(counters, random.randint(0, 4)):
selling_places.append(
Counter.products.through(counter=counter, product=product)
)
Product.objects.bulk_create(products)
Product.buying_groups.through.objects.bulk_create(buying_groups)
Counter.products.through.objects.bulk_create(selling_places)
@staticmethod
def _update_balances():
customers = Customer.objects.annotate(
money_in=Sum(F("refillings__amount"), default=0),
money_out=Coalesce(
Subquery(
Selling.objects.filter(customer=OuterRef("pk"))
.values("customer_id") # group by customer
.annotate(res=Sum(F("unit_price") * F("quantity"), default=0))
.values("res")
),
Decimal("0"),
),
).annotate(real_balance=F("money_in") - F("money_out"))
for c in customers:
c.amount = c.real_balance
Customer.objects.bulk_update(customers, fields=["amount"])
def create_sales(self, sellers: list[User]):
customers = list(
Customer.objects.annotate(
since=Subquery(
Subscription.objects.filter(member__customer=OuterRef("pk"))
.annotate(res=Min("subscription_start"))
.values("res")
)
)
)
products = list(Product.objects.all())
counters = list(
Counter.objects.filter(name__in=["Foyer", "MDE", "La Gommette"])
)
sales = []
reloads = []
for customer in customers:
# the longer the customer has existed, the higher the mean of nb_products
mu = 5 + (now().year - customer.since.year) * 2
nb_sales = max(0, int(random.normalvariate(mu=mu, sigma=mu * 5)))
favoured_products = random.sample(products, k=(random.randint(1, 5)))
favoured_counter = random.choice(counters)
this_customer_sales = []
for _ in range(nb_sales):
product = (
random.choice(favoured_products)
if random.random() > 0.7
else random.choice(products)
)
counter = (
favoured_counter
if random.random() > 0.7
else random.choice(counters)
)
this_customer_sales.append(
Selling(
product=product,
counter=counter,
club_id=product.club_id,
quantity=random.randint(1, 5),
unit_price=product.selling_price,
seller=random.choice(sellers),
customer=customer,
date=make_aware(
self.faker.date_time_between(customer.since, now().date())
),
)
)
total_expanse = sum(s.unit_price * s.quantity for s in this_customer_sales)
total_reloaded = 0
while total_reloaded < total_expanse:
amount = random.choice(list(range(5, 51, 5)))
total_reloaded += amount
reloads.append(
Refilling(
counter=random.choice(counters),
amount=amount,
operator=random.choice(sellers),
customer=customer,
date=make_aware(
self.faker.date_time_between(customer.since, now().date())
),
is_validated=True,
)
)
sales.extend(this_customer_sales)
Refilling.objects.bulk_create(reloads)
Selling.objects.bulk_create(sales)
self._update_balances()
def create_permanences(self, sellers: list[User]):
counters = list(
Counter.objects.filter(name__in=["Foyer", "MDE", "La Gommette"])
)
perms = []
for seller in sellers:
favoured_counter = random.choice(counters)
nb_perms = abs(int(random.normalvariate(mu=275, sigma=100)))
active_period_start = self.faker.past_date("-10y")
active_period_end = self.faker.date_between(
active_period_start,
min(now().date(), active_period_start + relativedelta(years=5)),
)
for _ in range(nb_perms):
counter = (
favoured_counter
if random.random() > 0.8
else random.choice(counters)
)
duration = self.faker.time_delta(timedelta(hours=1))
start = make_aware(
self.faker.date_time_between(active_period_start, active_period_end)
)
perms.append(
Permanency(
counter=counter, user=seller, start=start, end=start + duration
)
)
Permanency.objects.bulk_create(perms)

View File

@ -21,7 +21,6 @@
# #
# #
import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@ -37,9 +36,6 @@ class Command(BaseCommand):
) )
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
files = SithFile.objects.filter(id__in=options["ids"]).all() files = SithFile.objects.filter(id__in=options["ids"]).all()
for f in files: for f in files:
f._repair_fs() f._repair_fs()

View File

@ -13,28 +13,32 @@
# #
# #
import os from django.conf import settings
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
help = "Set up a new instance of the Sith AE" help = "Set up the development environment."
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname( if not settings.DEBUG:
os.path.dirname(os.path.dirname(os.path.dirname(__file__))) raise Exception("Never call this command in prod. Never.")
) data_dir = settings.BASE_DIR / "data"
try: settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
os.mkdir(os.path.join(root_path) + "/data") if not data_dir.is_dir():
print("Data dir created") data_dir.mkdir()
except Exception as e: db_path = settings.BASE_DIR / "db.sqlite3"
repr(e) if db_path.exists():
try: call_command("flush", "--noinput")
os.remove(os.path.join(root_path, "db.sqlite3")) self.stdout.write("Existing database reset")
print("db.sqlite3 deleted")
except Exception as e:
repr(e)
call_command("migrate") call_command("migrate")
self.stdout.write("Add the base fixtures.")
call_command("populate") call_command("populate")
self.stdout.write("Generate additional random fixtures")
call_command("populate_more")
self.stdout.write("Build the xapian index")
call_command("rebuild_index", "--noinput")
settings.EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
self.stdout.write("Setup complete!")

View File

@ -27,6 +27,7 @@ import importlib
import os import os
import unicodedata import unicodedata
from datetime import date, timedelta from datetime import date, timedelta
from pathlib import Path
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from django.conf import settings from django.conf import settings
@ -56,8 +57,6 @@ from django.utils.html import escape
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from core import utils
if TYPE_CHECKING: if TYPE_CHECKING:
from club.models import Club from club.models import Club
@ -377,7 +376,9 @@ class User(AbstractBaseUser):
USERNAME_FIELD = "username" USERNAME_FIELD = "username"
def promo_has_logo(self): def promo_has_logo(self):
return utils.file_exist("./core/static/core/img/promo_%02d.png" % self.promo) return Path(
settings.BASE_DIR / f"core/static/core/img/promo_{self.promo}.png"
).exists()
def has_module_perms(self, package_name): def has_module_perms(self, package_name):
return self.is_active return self.is_active

View File

@ -22,7 +22,6 @@
# #
# #
import os
from collections import OrderedDict from collections import OrderedDict
from django.conf import settings from django.conf import settings
@ -37,7 +36,7 @@ class ScssFinder(FileSystemFinder):
def __init__(self, apps=None, *args, **kwargs): def __init__(self, apps=None, *args, **kwargs):
location = settings.STATIC_ROOT location = settings.STATIC_ROOT
if not os.path.isdir(location): if not location.is_dir():
return return
self.locations = [("", location)] self.locations = [("", location)]
self.storages = OrderedDict() self.storages = OrderedDict()

View File

@ -21,61 +21,35 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
import functools
import os from pathlib import Path
from urllib.parse import urljoin
import sass import sass
from django.conf import settings from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.encoding import force_bytes, iri_to_uri from django_jinja.builtins.filters import static
from core.scss.storage import ScssFileStorage, find_file from core.scss.storage import ScssFileStorage, find_file
class ScssProcessor(object): @functools.cache
"""If DEBUG mode enabled : compile the scss file def _scss_storage():
Else : give the path of the corresponding css supposed to already be compiled return ScssFileStorage()
Don't forget to use compilestatics to compile scss for production.
"""
prefix = iri_to_uri(getattr(settings, "STATIC_URL", "/static/"))
storage = ScssFileStorage()
scss_extensions = [".scss"]
def __init__(self, path=None): def process_scss_path(path: Path):
self.path = path css_path = path.with_suffix(".css")
if settings.DEBUG:
def _convert_scss(self):
basename, ext = os.path.splitext(self.path)
css_filename = self.path.replace(".scss", ".css")
url = urljoin(self.prefix, css_filename)
if not settings.DEBUG:
return url
if ext not in self.scss_extensions:
return static(self.path)
# Compilation on the fly
compile_args = { compile_args = {
"filename": find_file(self.path), "filename": find_file(path),
"include_paths": settings.SASS_INCLUDE_FOLDERS, "include_paths": settings.SASS_INCLUDE_FOLDERS,
} }
if settings.SASS_PRECISION: if settings.SASS_PRECISION:
compile_args["precision"] = settings.SASS_PRECISION compile_args["precision"] = settings.SASS_PRECISION
content = sass.compile(**compile_args) content = sass.compile(**compile_args)
content = force_bytes(content) storage = _scss_storage()
if storage.exists(css_path):
if self.storage.exists(css_filename): storage.delete(css_path)
self.storage.delete(css_filename) storage.save(css_path, ContentFile(content))
self.storage.save(css_filename, ContentFile(content)) return static(css_path)
return url
def get_converted_scss(self):
if self.path:
return self._convert_scss()
else:
return ""

View File

@ -0,0 +1,30 @@
$first-color: hsl(220, 100%, 50%);
$second-color: hsl(48, 100%, 67%);
$primary-color: hsl(219.9, 53.7%, 50%);
$secondary-color: hsl(204, 64%, 44%);
$primary-color-text: hsl(0, 0%, 100%);
$secondary-color-text: hsla(0, 0%, 0%, 0.87);
$primary-light-color: hsl(219.8, 46.4%, 64.9%);
$primary-dark-color: hsl(203, 75%, 40%);
$secondary-light-color: hsl(40, 68%, 65%);
$secondary-dark-color: hsl(40, 68%, 35%);
$primary-neutral-color: hsl(219.6, 20.8%, 50%);
$primary-neutral-light-color: hsl(0, 0%, 94%);
$primary-neutral-dark-color: hsl(210, 29%, 29%);
$secondary-neutral-color: hsl(204, 64%, 44%);
$secondary-neutral-light-color: hsl(0, 0%, 91%);
$secondary-neutral-dark-color: hsl(40, 57.6%, 17%);
$white-color: hsl(219.6, 20.8%, 98%);
$black-color: hsl(0, 0%, 17%);
$faceblue: hsl(221, 44%, 41%);
$twitblue: hsl(206, 82%, 63%);
$shadow-color: rgb(223, 223, 223);
$background-button-color: hsl(0, 0%, 95%);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.JSZipUtils=e():"undefined"!=typeof global?global.JSZipUtils=e():"undefined"!=typeof self&&(self.JSZipUtils=e())}(function(){return function o(i,f,u){function s(n,e){if(!f[n]){if(!i[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(a)return a(n,!0);throw new Error("Cannot find module '"+n+"'")}var r=f[n]={exports:{}};i[n][0].call(r.exports,function(e){var t=i[n][1][e];return s(t||e)},r,r.exports,o,i,f,u)}return f[n].exports}for(var a="function"==typeof require&&require,e=0;e<u.length;e++)s(u[e]);return s}({1:[function(e,t,n){"use strict";var u={};function r(){try{return new window.XMLHttpRequest}catch(e){}}u._getBinaryFromXHR=function(e){return e.response||e.responseText};var s="undefined"!=typeof window&&window.ActiveXObject?function(){return r()||function(){try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(e){}}()}:r;u.getBinaryContent=function(t,n){var e,r,o,i;"function"==typeof(n=n||{})?(i=n,n={}):"function"==typeof n.callback&&(i=n.callback),i||"undefined"==typeof Promise?(r=function(e){i(null,e)},o=function(e){i(e,null)}):e=new Promise(function(e,t){r=e,o=t});try{var f=s();f.open("GET",t,!0),"responseType"in f&&(f.responseType="arraybuffer"),f.overrideMimeType&&f.overrideMimeType("text/plain; charset=x-user-defined"),f.onreadystatechange=function(e){if(4===f.readyState)if(200===f.status||0===f.status)try{r(u._getBinaryFromXHR(f))}catch(e){o(new Error(e))}else o(new Error("Ajax error for "+t+" : "+this.status+" "+this.statusText))},n.progress&&(f.onprogress=function(e){n.progress({path:t,originalEvent:e,percent:e.loaded/e.total*100,loaded:e.loaded,total:e.total})}),f.send()}catch(e){o(new Error(e),null)}return e},t.exports=u},{}]},{},[1])(1)});

13
core/static/core/js/jszip/jszip.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Jimmy Wärting
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.

View File

@ -0,0 +1 @@
export*from"./src/es6.js";

View File

@ -0,0 +1 @@
import FileSystemHandle from"./FileSystemHandle.js";import{errors}from"./util.js";const{GONE:GONE,MOD_ERR:MOD_ERR}=errors,kAdapter=Symbol("adapter");class FileSystemDirectoryHandle extends FileSystemHandle{[kAdapter];constructor(e){super(e),this[kAdapter]=e}async getDirectoryHandle(e,t={}){if(""===e)throw new TypeError("Name can't be an empty string.");if("."===e||".."===e||e.includes("/"))throw new TypeError("Name contains invalid characters.");t.create=!!t.create;const r=await this[kAdapter].getDirectoryHandle(e,t);return new FileSystemDirectoryHandle(r)}async*entries(){const{FileSystemFileHandle:e}=await import("./FileSystemFileHandle.js");for await(const[t,r]of this[kAdapter].entries())yield[r.name,"file"===r.kind?new e(r):new FileSystemDirectoryHandle(r)]}async*getEntries(){const{FileSystemFileHandle:e}=await import("./FileSystemFileHandle.js");console.warn("deprecated, use .entries() instead");for await(let t of this[kAdapter].entries())yield"file"===t.kind?new e(t):new FileSystemDirectoryHandle(t)}async getFileHandle(e,t={}){const{FileSystemFileHandle:r}=await import("./FileSystemFileHandle.js");if(""===e)throw new TypeError("Name can't be an empty string.");if("."===e||".."===e||e.includes("/"))throw new TypeError("Name contains invalid characters.");t.create=!!t.create;return new r(await this[kAdapter].getFileHandle(e,t))}async removeEntry(e,t={}){if(""===e)throw new TypeError("Name can't be an empty string.");if("."===e||".."===e||e.includes("/"))throw new TypeError("Name contains invalid characters.");return t.recursive=!!t.recursive,this[kAdapter].removeEntry(e,t)}async resolve(e){if(await e.isSameEntry(this))return[];const t=[{handle:this,path:[]}];for(;t.length;){let{handle:r,path:n}=t.pop();for await(const a of r.values()){if(await a.isSameEntry(e))return[...n,a.name];"directory"===a.kind&&t.push({handle:a,path:[...n,a.name]})}}return null}async*keys(){for await(const[e]of this[kAdapter].entries())yield e}async*values(){for await(const[e,t]of this)yield t}[Symbol.asyncIterator](){return this.entries()}}if(Object.defineProperty(FileSystemDirectoryHandle.prototype,Symbol.toStringTag,{value:"FileSystemDirectoryHandle",writable:!1,enumerable:!1,configurable:!0}),Object.defineProperties(FileSystemDirectoryHandle.prototype,{getDirectoryHandle:{enumerable:!0},entries:{enumerable:!0},getFileHandle:{enumerable:!0},removeEntry:{enumerable:!0}}),globalThis.FileSystemDirectoryHandle){const e=globalThis.FileSystemDirectoryHandle.prototype;async function ensureDoActuallyStillExist(e){const t=await navigator.storage.getDirectory();if(null===await t.resolve(e))throw new DOMException(...GONE)}e.resolve=async function(e){if(await e.isSameEntry(this))return[];const t=[{handle:this,path:[]}];for(;t.length;){let{handle:r,path:n}=t.pop();for await(const a of r.values()){if(await a.isSameEntry(e))return[...n,a.name];"directory"===a.kind&&t.push({handle:a,path:[...n,a.name]})}}return null};const t=e.entries;e.entries=async function*(){await ensureDoActuallyStillExist(this),yield*t.call(this)},e[Symbol.asyncIterator]=async function*(){yield*this.entries()};const r=e.removeEntry;e.removeEntry=async function(e,n={}){return r.call(this,e,n).catch((async e=>{if(e instanceof DOMException&&"UnknownError"===e.name&&!n.recursive){if(!(await t.call(this).next()).done)throw new DOMException(...MOD_ERR)}throw e}))}}export default FileSystemDirectoryHandle;export{FileSystemDirectoryHandle};

View File

@ -0,0 +1 @@
import FileSystemHandle from"./FileSystemHandle.js";import FileSystemWritableFileStream from"./FileSystemWritableFileStream.js";import{errors}from"./util.js";const{INVALID:INVALID,SYNTAX:SYNTAX,GONE:GONE}=errors,kAdapter=Symbol("adapter");class FileSystemFileHandle extends FileSystemHandle{[kAdapter];constructor(e){super(e),this[kAdapter]=e}async createWritable(e={}){return new FileSystemWritableFileStream(await this[kAdapter].createWritable(e))}async getFile(){return this[kAdapter].getFile()}}if(Object.defineProperty(FileSystemFileHandle.prototype,Symbol.toStringTag,{value:"FileSystemFileHandle",writable:!1,enumerable:!1,configurable:!0}),Object.defineProperties(FileSystemFileHandle.prototype,{createWritable:{enumerable:!0},getFile:{enumerable:!0}}),globalThis.FileSystemFileHandle&&!globalThis.FileSystemFileHandle.prototype.createWritable){const e=new WeakMap;let t;const a=()=>{let e,t;onmessage=async a=>{const i=a.ports[0],r=a.data;switch(r.type){case"open":const a=r.name;let i=await navigator.storage.getDirectory();for(const e of r.path)i=await i.getDirectoryHandle(e);e=await i.getFileHandle(a),t=await e.createSyncAccessHandle();break;case"write":t.write(r.data,{at:r.position}),t.flush();break;case"truncate":t.truncate(r.size);break;case"abort":case"close":t.close()}i.postMessage(0)}};globalThis.FileSystemFileHandle.prototype.createWritable=async function(i){if(!t){const e=`(${a.toString()})()`,i=new Blob([e],{type:"text/javascript"});t=URL.createObjectURL(i)}const r=new Worker(t,{type:"module"});let n=0;const s=new TextEncoder;let o=await this.getFile().then((e=>e.size));const l=e=>new Promise(((t,a)=>{const i=new MessageChannel;i.port1.onmessage=e=>{e.data instanceof Error?a(e.data):t(e.data),i.port1.close(),i.port2.close(),i.port1.onmessage=null},r.postMessage(e,[i.port2])})),c=await navigator.storage.getDirectory(),p=await e.get(this),y=await c.resolve(p);if(null===y)throw new DOMException(...GONE);let d;await l({type:"open",path:y,name:this.name}),!1===i?.keepExistingData&&(await l({type:"truncate",size:0}),o=0);return new FileSystemWritableFileStream({start:e=>{d=e},async write(e){if("write"===(e=e?.constructor===Object?{...e}:{type:"write",data:e,position:n}).type){if(!("data"in e))throw await l({type:"close"}),new DOMException(...SYNTAX("write requires a data argument"));if(e.position??=n,"string"==typeof e.data)e.data=s.encode(e.data);else if(e.data instanceof ArrayBuffer)e.data=new Uint8Array(e.data);else if(e.data instanceof Uint8Array||!ArrayBuffer.isView(e.data)){if(!(e.data instanceof Uint8Array)){const t=await new Response(e.data).arrayBuffer();e.data=new Uint8Array(t)}}else e.data=new Uint8Array(e.data.buffer,e.data.byteOffset,e.data.byteLength);Number.isInteger(e.position)&&e.position>=0&&(n=e.position),n+=e.data.byteLength,o+=e.data.byteLength}else{if("seek"===e.type){if(Number.isInteger(e.position)&&e.position>=0){if(o<e.position)throw new DOMException(...INVALID);return console.log("seeking",e),void(n=e.position)}throw await l({type:"close"}),new DOMException(...SYNTAX("seek requires a position argument"))}if("truncate"===e.type){if(!(Number.isInteger(e.size)&&e.size>=0))throw await l({type:"close"}),new DOMException(...SYNTAX("truncate requires a size argument"));o=e.size,n>o&&(n=o)}}await l(e)},async close(){await l({type:"close"}),r.terminate()},async abort(e){await l({type:"abort",reason:e}),r.terminate()}})};const i=FileSystemDirectoryHandle.prototype.getFileHandle;FileSystemDirectoryHandle.prototype.getFileHandle=async function(...t){const a=await i.call(this,...t);return e.set(a,this),a}}export default FileSystemFileHandle;export{FileSystemFileHandle};

View File

@ -0,0 +1 @@
const kAdapter=Symbol("adapter");class FileSystemHandle{[kAdapter];name;kind;constructor(e){this.kind=e.kind,this.name=e.name,this[kAdapter]=e}async queryPermission(e={}){const{mode:r="read"}=e,t=this[kAdapter];if(t.queryPermission)return t.queryPermission({mode:r});if("read"===r)return"granted";if("readwrite"===r)return t.writable?"granted":"denied";throw new TypeError(`Mode ${r} must be 'read' or 'readwrite'`)}async requestPermission({mode:e="read"}={}){const r=this[kAdapter];if(r.requestPermission)return r.requestPermission({mode:e});if("read"===e)return"granted";if("readwrite"===e)return r.writable?"granted":"denied";throw new TypeError(`Mode ${e} must be 'read' or 'readwrite'`)}async remove(e={}){await this[kAdapter].remove(e)}async isSameEntry(e){return this===e||!(!e||"object"!=typeof e||this.kind!==e.kind||!e[kAdapter])&&this[kAdapter].isSameEntry(e[kAdapter])}}Object.defineProperty(FileSystemHandle.prototype,Symbol.toStringTag,{value:"FileSystemHandle",writable:!1,enumerable:!1,configurable:!0}),globalThis.FileSystemHandle&&(globalThis.FileSystemHandle.prototype.queryPermission??=function(e){return"granted"});export default FileSystemHandle;export{FileSystemHandle};

View File

@ -0,0 +1 @@
import config from"./config.js";const{WritableStream:WritableStream}=config;class FileSystemWritableFileStream extends WritableStream{#e;constructor(e){super(e),this.#e=e,Object.setPrototypeOf(this,FileSystemWritableFileStream.prototype),this._closed=!1}async close(){this._closed=!0;const e=this.getWriter(),t=e.close();return e.releaseLock(),t}seek(e){return this.write({type:"seek",position:e})}truncate(e){return this.write({type:"truncate",size:e})}write(e){if(this._closed)return Promise.reject(new TypeError("Cannot write to a CLOSED writable stream"));const t=this.getWriter(),r=t.write(e);return t.releaseLock(),r}}Object.defineProperty(FileSystemWritableFileStream.prototype,Symbol.toStringTag,{value:"FileSystemWritableFileStream",writable:!1,enumerable:!1,configurable:!0}),Object.defineProperties(FileSystemWritableFileStream.prototype,{close:{enumerable:!0},seek:{enumerable:!0},truncate:{enumerable:!0},write:{enumerable:!0}}),!globalThis.FileSystemFileHandle||globalThis.FileSystemFileHandle.prototype.createWritable||globalThis.FileSystemWritableFileStream||(globalThis.FileSystemWritableFileStream=FileSystemWritableFileStream);export default FileSystemWritableFileStream;export{FileSystemWritableFileStream};

View File

@ -0,0 +1 @@
import{errors}from"../util.js";const{INVALID:INVALID,GONE:GONE,MISMATCH:MISMATCH,MOD_ERR:MOD_ERR,SYNTAX:SYNTAX,SECURITY:SECURITY,DISALLOWED:DISALLOWED}=errors;export class Sink{constructor(){}write(e){}close(){}}export class FileHandle{constructor(){this._path=""}async getFile(){return new File([],"")}async createWritable(){}async isSameEntry(e){return e._path===this._path}}export class FolderHandle{constructor(){this._path=""}async*entries(){yield}async isSameEntry(e){return e._path===this._path}async getDirectoryHandle(e,r){return new FolderHandle}async getFileHandle(e,r){return new FileHandle}async removeEntry(e,r){}}const fs=new FolderHandle("");export default()=>fs;

View File

@ -0,0 +1 @@
import{errors}from"../util.js";const{INVALID:INVALID,GONE:GONE,MISMATCH:MISMATCH,MOD_ERR:MOD_ERR,SYNTAX:SYNTAX}=errors,DIR={headers:{"content-type":"dir"}},FILE=()=>({headers:{"content-type":"file","last-modified":Date.now()}}),hasOwn=Object.prototype.hasOwnProperty;class Sink{constructor(e,t,i){this._cache=e,this.path=t,this.size=i.size,this.position=0,this.file=i}write(e,t){if("object"==typeof e)if("write"===e.type){if(Number.isInteger(e.position)&&e.position>=0&&(this.size<e.position&&(this.file=new Blob([this.file,new ArrayBuffer(e.position-this.size)])),this.position=e.position),!("data"in e))throw new DOMException(...SYNTAX("write requires a data argument"));e=e.data}else{if("seek"===e.type){if(Number.isInteger(e.position)&&e.position>=0){if(this.size<e.position)throw new DOMException(...INVALID);return void(this.position=e.position)}throw new DOMException(...SYNTAX("seek requires a position argument"))}if("truncate"===e.type){if(Number.isInteger(e.size)&&e.size>=0){let t=this.file;return t=e.size<this.size?t.slice(0,e.size):new File([t,new Uint8Array(e.size-this.size)],t.name),this.size=t.size,this.position>t.size&&(this.position=t.size),void(this.file=t)}throw new DOMException(...SYNTAX("truncate requires a size argument"))}}e=new Blob([e]);let i=this.file;const s=i.slice(0,this.position),n=i.slice(this.position+e.size);let a=this.position-s.size;a<0&&(a=0),i=new File([s,new Uint8Array(a),e,n],i.name),this.size=i.size,this.position+=e.size,this.file=i}async close(){const[e]=await this._cache.keys(this.path);if(!e)throw new DOMException(...GONE);return this._cache.put(this.path,new Response(this.file,FILE()))}}export class FileHandle{constructor(e,t){this._cache=t,this.path=e,this.kind="file",this.writable=!0,this.readable=!0}get name(){return this.path.split("/").pop()}async isSameEntry(e){return this.path===e.path}async getFile(){const e=await this._cache.match(this.path);if(!e)throw new DOMException(...GONE);const t=await e.blob();return new File([t],this.name,{lastModified:+e.headers.get("last-modified")})}async createWritable(e){const[t]=await this._cache.keys(this.path);if(!t)throw new DOMException(...GONE);return new Sink(this._cache,this.path,e.keepExistingData?await this.getFile():new File([],this.name))}}export class FolderHandle{constructor(e,t){this._dir=e,this.writable=!0,this.readable=!0,this._cache=t,this.kind="directory",this.name=e.split("/").pop()}async*entries(){for(const[e,t]of Object.entries(await this._tree))yield[e.split("/").pop(),t?new FileHandle(e,this._cache):new FolderHandle(e,this._cache)]}async isSameEntry(e){return this._dir===e._dir}async getDirectoryHandle(e,t){const i=this._dir.endsWith("/")?this._dir+e:`${this._dir}/${e}`,s=await this._tree;if(hasOwn.call(s,i)){if(s[i])throw new DOMException(...MISMATCH);return new FolderHandle(i,this._cache)}if(t.create)return s[i]=!1,await this._cache.put(i,new Response("{}",DIR)),await this._save(s),new FolderHandle(i,this._cache);throw new DOMException(...GONE)}get _tree(){return this._cache.match(this._dir).then((e=>e.json())).catch((e=>{throw new DOMException(...GONE)}))}_save(e){return this._cache.put(this._dir,new Response(JSON.stringify(e),DIR))}async getFileHandle(e,t){const i=this._dir.endsWith("/")?this._dir+e:`${this._dir}/${e}`,s=await this._tree;if(hasOwn.call(s,i)){if(!s[i])throw new DOMException(...MISMATCH);return new FileHandle(i,this._cache)}if(t.create){const e=await this._tree;return e[i]=!0,await this._cache.put(i,new Response("",FILE())),await this._save(e),new FileHandle(i,this._cache)}throw new DOMException(...GONE)}async removeEntry(e,t){const i=await this._tree,s=this._dir.endsWith("/")?this._dir+e:`${this._dir}/${e}`;if(!hasOwn.call(i,s))throw new DOMException(...GONE);if(t.recursive){const e=[...Object.entries(i)];for(;e.length;){const[t,i]=e.pop();if(i)await this._cache.delete(t);else{const i=await this._cache.match(t).then((e=>e.json()));e.push(...Object.entries(i))}}delete i[s]}else{const e=i[s];if(delete i[s],e)await this._cache.delete(s);else{const e=await this._cache.match(s).then((e=>e.json()));if(Object.keys(e).length)throw new DOMException(...MOD_ERR);await this._cache.delete(s)}}await this._save(i)}}export default async function(){const e=await caches.open("sandboxed-fs");return await e.match("/")||await e.put("/",new Response("{}",DIR)),new FolderHandle(location.origin+"/",e)}

View File

@ -0,0 +1 @@
import{join,basename}from"https://deno.land/std@0.108.0/path/mod.ts";import{errors}from"../util.js";const{INVALID:INVALID,GONE:GONE,MISMATCH:MISMATCH,MOD_ERR:MOD_ERR,SYNTAX:SYNTAX}=errors;async function fileFrom(t){const e=Deno.readFileSync(t),i=await Deno.stat(t);return new File([e],basename(t),{lastModified:Number(i.mtime)})}export class Sink{constructor(t,e){this.fileHandle=t,this.size=e,this.position=0}async abort(){await this.fileHandle.close()}async write(t){if("object"==typeof t)if("write"===t.type){if(Number.isInteger(t.position)&&t.position>=0&&(this.position=t.position),!("data"in t))throw await this.fileHandle.close(),new DOMException(...SYNTAX("write requires a data argument"));t=t.data}else{if("seek"===t.type){if(Number.isInteger(t.position)&&t.position>=0){if(this.size<t.position)throw new DOMException(...INVALID);return void(this.position=t.position)}throw await this.fileHandle.close(),new DOMException(...SYNTAX("seek requires a position argument"))}if("truncate"===t.type){if(Number.isInteger(t.size)&&t.size>=0)return await this.fileHandle.truncate(t.size),this.size=t.size,void(this.position>this.size&&(this.position=this.size));throw await this.fileHandle.close(),new DOMException(...SYNTAX("truncate requires a size argument"))}}if(t instanceof ArrayBuffer)t=new Uint8Array(t);else if("string"==typeof t)t=(new TextEncoder).encode(t);else if(t instanceof Blob){await this.fileHandle.seek(this.position,Deno.SeekMode.Start);for await(const e of t.stream()){const t=await this.fileHandle.write(e);this.position+=t,this.size+=t}return}await this.fileHandle.seek(this.position,Deno.SeekMode.Start);const e=await this.fileHandle.write(t);this.position+=e,this.size+=e}async close(){await this.fileHandle.close()}}export class FileHandle{#t;constructor(t,e){this.#t=t,this.name=e,this.kind="file"}async getFile(){return await Deno.stat(this.#t).catch((t=>{if("NotFound"===t.name)throw new DOMException(...GONE)})),fileFrom(this.#t)}async isSameEntry(t){return this.#t===this.#e.apply(t)}#e(){return this.#t}async createWritable(t){const e=await Deno.open(this.#t,{write:!0,truncate:!t.keepExistingData}).catch((t=>{if("NotFound"===t.name)throw new DOMException(...GONE);throw t})),{size:i}=await e.stat();return new Sink(e,i)}}export class FolderHandle{#t="";constructor(t,e=""){this.name=e,this.kind="directory",this.#t=join(t)}async isSameEntry(t){return this.#t===this.#e.apply(t)}#e(){return this.#t}async*entries(){const t=this.#t;try{for await(const e of Deno.readDir(t)){const{name:i}=e,n=join(t,i),o=await Deno.lstat(n);o.isFile?yield[i,new FileHandle(n,i)]:o.isDirectory&&(yield[i,new FolderHandle(n,i)])}}catch(t){throw"NotFound"===t.name?new DOMException(...GONE):t}}async getDirectoryHandle(t,e){const i=join(this.#t,t),n=await Deno.lstat(i).catch((t=>{if("NotFound"!==t.name)throw t})),o=n?.isDirectory;if(n&&o)return new FolderHandle(i,t);if(n&&!o)throw new DOMException(...MISMATCH);if(!e.create)throw new DOMException(...GONE);return await Deno.mkdir(i),new FolderHandle(i,t)}async getFileHandle(t,e){const i=join(this.#t,t),n=await Deno.lstat(i).catch((t=>{if("NotFound"!==t.name)throw t})),o=n?.isFile;if(n&&o)return new FileHandle(i,t);if(n&&!o)throw new DOMException(...MISMATCH);if(!e.create)throw new DOMException(...GONE);return(await Deno.open(i,{create:!0,write:!0})).close(),new FileHandle(i,t)}async queryPermission(){return"granted"}async removeEntry(t,e){const i=join(this.#t,t);(await Deno.lstat(i).catch((t=>{if("NotFound"===t.name)throw new DOMException(...GONE);throw t}))).isDirectory?e.recursive?await Deno.remove(i,{recursive:!0}).catch((t=>{if("ENOTEMPTY"===t.code)throw new DOMException(...MOD_ERR);throw t})):await Deno.remove(i).catch((()=>{throw new DOMException(...MOD_ERR)})):await Deno.remove(i)}}export default t=>new FolderHandle(join(Deno.cwd(),t));

View File

@ -0,0 +1 @@
import{errors}from"../util.js";import config from"../config.js";const{WritableStream:WritableStream,TransformStream:TransformStream,DOMException:DOMException,Blob:Blob}=config,{GONE:GONE}=errors,isOldSafari=/constructor/i.test(window.HTMLElement);export class FileHandle{constructor(e="unkown"){this.name=e,this.kind="file"}async getFile(){throw new DOMException(...GONE)}async isSameEntry(e){return this===e}async createWritable(e={}){const t=await(navigator.serviceWorker?.getRegistration()),r=document.createElement("a"),s=new TransformStream,a=s.writable;if(r.download=this.name,isOldSafari||!t){let e=[];s.readable.pipeTo(new WritableStream({write(t){e.push(new Blob([t]))},close(){const t=new Blob(e,{type:"application/octet-stream; charset=utf-8"});e=[],r.href=URL.createObjectURL(t),r.click(),setTimeout((()=>URL.revokeObjectURL(r.href)),1e4)}}))}else{const{writable:r,readablePort:a}=new RemoteWritableStream(WritableStream),o=encodeURIComponent(this.name).replace(/['()]/g,escape).replace(/\*/g,"%2A"),n={"content-disposition":"attachment; filename*=UTF-8''"+o,"content-type":"application/octet-stream; charset=utf-8",...e.size?{"content-length":e.size}:{}},i=setTimeout((()=>t.active.postMessage(0)),1e4);s.readable.pipeThrough(new TransformStream({transform(e,t){if(e instanceof Uint8Array)return t.enqueue(e);const r=new Response(e).body.getReader(),s=e=>r.read().then((e=>e.done?0:s(t.enqueue(e.value))));return s()}})).pipeTo(r).finally((()=>{clearInterval(i)})),t.active.postMessage({url:t.scope+o,headers:n,readablePort:a},[a]);const c=document.createElement("iframe");c.hidden=!0,c.src=t.scope+o,document.body.appendChild(c)}return a.getWriter()}}const WRITE=0,PULL=0,ERROR=1,ABORT=1,CLOSE=2;class MessagePortSink{constructor(e){e.onmessage=e=>this._onMessage(e.data),this._port=e,this._resetReady()}start(e){return this._controller=e,this._readyPromise}write(e){const t={type:0,chunk:e};return this._port.postMessage(t,[e.buffer]),this._resetReady(),this._readyPromise}close(){this._port.postMessage({type:2}),this._port.close()}abort(e){this._port.postMessage({type:1,reason:e}),this._port.close()}_onMessage(e){0===e.type&&this._resolveReady(),1===e.type&&this._onError(e.reason)}_onError(e){this._controller.error(e),this._rejectReady(e),this._port.close()}_resetReady(){this._readyPromise=new Promise(((e,t)=>{this._readyResolve=e,this._readyReject=t})),this._readyPending=!0}_resolveReady(){this._readyResolve(),this._readyPending=!1}_rejectReady(e){this._readyPending||this._resetReady(),this._readyPromise.catch((()=>{})),this._readyReject(e),this._readyPending=!1}}class RemoteWritableStream{constructor(e){const t=new MessageChannel;this.readablePort=t.port1,this.writable=new e(new MessagePortSink(t.port2))}}

View File

@ -0,0 +1 @@
import{errors}from"../util.js";const{INVALID:INVALID,GONE:GONE,MISMATCH:MISMATCH,MOD_ERR:MOD_ERR,SYNTAX:SYNTAX,ABORT:ABORT}=errors;function setupTxErrorHandler(e,t){e.onerror=()=>t(e.error),e.onabort=()=>t(e.error||new DOMException(...ABORT))}class Sink{constructor(e,t,i,s){this.db=e,this.id=t,this.size=i,this.position=0,this.file=s}write(e){if("object"==typeof e)if("write"===e.type){if(Number.isInteger(e.position)&&e.position>=0&&(this.size<e.position&&(this.file=new File([this.file,new ArrayBuffer(e.position-this.size)],this.file.name,this.file)),this.position=e.position),!("data"in e))throw new DOMException(...SYNTAX("write requires a data argument"));e=e.data}else{if("seek"===e.type){if(Number.isInteger(e.position)&&e.position>=0){if(this.size<e.position)throw new DOMException(...INVALID);return void(this.position=e.position)}throw new DOMException(...SYNTAX("seek requires a position argument"))}if("truncate"===e.type){if(Number.isInteger(e.size)&&e.size>=0){let t=this.file;return t=e.size<this.size?new File([t.slice(0,e.size)],t.name,t):new File([t,new Uint8Array(e.size-this.size)],t.name,t),this.size=t.size,this.position>t.size&&(this.position=t.size),void(this.file=t)}throw new DOMException(...SYNTAX("truncate requires a size argument"))}}e=new Blob([e]);let t=this.file;const i=t.slice(0,this.position),s=t.slice(this.position+e.size);let n=this.position-i.size;n<0&&(n=0),t=new File([i,new Uint8Array(n),e,s],t.name),this.size=t.size,this.position+=e.size,this.file=t}close(){return new Promise(((e,t)=>{const[i,s]=store(this.db);s.get(this.id).onsuccess=e=>{e.target.result?s.put(this.file,this.id):t(new DOMException(...GONE))},i.oncomplete=()=>e(),i.onerror=t,i.onabort=t}))}}class FileHandle{constructor(e,t,i){this._db=e,this._id=t,this.name=i,this.kind="file",this.readable=!0,this.writable=!0}async isSameEntry(e){return this._id===e._id}async getFile(){const e=await new Promise(((e,t)=>{const i=store(this._db)[1].get(this._id);i.onsuccess=t=>e(t.target.result),i.onerror=e=>t(e.target.error)}));if(!e)throw new DOMException(...GONE);return e}async createWritable(e){let t=await this.getFile();return t=e.keepExistingData?t:new File([],this.name),new Sink(this._db,this._id,t.size,t)}}function store(e){const t=e.transaction("entries","readwrite",{durability:"relaxed"});return[t,t.objectStore("entries")]}function rimraf(e,t,i=!0){const{source:s,result:n}=e.target;for(const[e,r]of Object.values(t||n))r?s.delete(e):i?(s.get(e).onsuccess=rimraf,s.delete(e)):s.get(e).onsuccess=t=>{0!==Object.keys(t.target.result).length?t.target.transaction.abort():s.delete(e)}}class FolderHandle{constructor(e,t,i){this._db=e,this._id=t,this.kind="directory",this.name=i,this.readable=!0,this.writable=!0}async*entries(){const e=store(this._db)[1].get(this._id);await new Promise(((t,i)=>{e.onsuccess=()=>t(),e.onerror=()=>i(e.error)}));const t=e.result;if(!t)throw new DOMException(...GONE);for(const[e,[i,s]]of Object.entries(t))yield[e,s?new FileHandle(this._db,i,e):new FolderHandle(this._db,i,e)]}isSameEntry(e){return this._id===e._id}getDirectoryHandle(e,t){return new Promise(((i,s)=>{const n=store(this._db)[1],r=n.get(this._id);r.onsuccess=()=>{const o=r.result,c=o[e];c?c[1]?s(new DOMException(...MISMATCH)):i(new FolderHandle(this._db,c[0],e)):t.create?n.add({}).onsuccess=t=>{const s=t.target.result;o[e]=[s,!1],n.put(o,this._id).onsuccess=()=>i(new FolderHandle(this._db,s,e))}:s(new DOMException(...GONE))}}))}getFileHandle(e,t){return new Promise(((i,s)=>{const n=store(this._db)[1],r=n.get(this._id);r.onsuccess=()=>{const o=r.result,c=o[e];if(c&&c[1]&&i(new FileHandle(this._db,c[0],e)),c&&!c[1]&&s(new DOMException(...MISMATCH)),c||t.create||s(new DOMException(...GONE)),!c&&t.create){const t=n.put(new File([],e));t.onsuccess=()=>{const s=t.result;o[e]=[s,!0];n.put(o,this._id).onsuccess=()=>{i(new FileHandle(this._db,s,e))}}}}}))}async removeEntry(e,t){return new Promise(((i,s)=>{const[n,r]=store(this._db),o=r.get(this._id);o.onsuccess=i=>{const n=o.result,c={_:n[e]};if(!c._)return s(new DOMException(...GONE));delete n[e],r.put(n,this._id),rimraf(i,c,!!t.recursive)},n.oncomplete=i,n.onerror=s,n.onabort=()=>{s(new DOMException(...MOD_ERR))}}))}}export default(e={persistent:!1})=>new Promise((e=>{const t=indexedDB.open("fileSystem");t.onupgradeneeded=()=>{const e=t.result;e.createObjectStore("entries",{autoIncrement:!0}).transaction.oncomplete=t=>{e.transaction("entries","readwrite").objectStore("entries").add({})}},t.onsuccess=()=>{e(new FolderHandle(t.result,1,""))}}));

View File

@ -0,0 +1 @@
import{errors}from"../util.js";const{GONE:GONE,MISMATCH:MISMATCH,SYNTAX:SYNTAX,DISALLOWED:DISALLOWED}=errors;export class FileHandle{constructor(e,t){this.name=e.name,this.kind="file",this._deleted=!1,this._root=t,this._entry=e,this.writable=!1,this.readable=!0}async getFile(){const e=await fetch(`https://cdn.jsdelivr.net/${this._root}/${this.name}`),t=await e.blob();return new File([t],this.name,{type:t.type,lastModified:this._entry.time})}async createWritable(){throw new DOMException(...DISALLOWED)}async isSameEntry(e){return this===e}}function toDic(e,t){const n={};for(const i of e)i.time=+new Date(i.time),"file"===i.type?n[i.name]=new FileHandle(i,t):n[i.name]=new FolderHandle(i.files,`${t}/${i.name}`,i.name);return n}export class FolderHandle{constructor(e,t,n=""){this.name=n,this.kind="directory",this._deleted=!1,this._entries=toDic(e,t),this.writable=!1,this.readable=!0}async*entries(){yield*Object.entries(this._entries)}async isSameEntry(e){return this===e}async getDirectoryHandle(e,t){if(this._deleted)throw new DOMException(...GONE);const n=this._entries[e];if(n){if(n instanceof FileHandle)throw new DOMException(...MISMATCH);return n}throw t.create?new DOMException(...DISALLOWED):new DOMException(...GONE)}async getFileHandle(e,t){const n=this._entries[e],i=n instanceof FileHandle;if(n&&i)return n;if(n&&!i)throw new DOMException(...MISMATCH);if(!n&&!t.create)throw new DOMException(...GONE);if(!n&&t.create)throw new DOMException(...DISALLOWED)}async removeEntry(e,t){throw new DOMException(...DISALLOWED)}}export default async e=>{const t=await fetch(`https://data.jsdelivr.com/v1/package/${e}`),{files:n}=await t.json();return new FolderHandle(n,e)};

View File

@ -0,0 +1 @@
import{errors}from"../util.js";import config from"../config.js";const{File:File,Blob:Blob,DOMException:DOMException}=config,{INVALID:INVALID,GONE:GONE,MISMATCH:MISMATCH,MOD_ERR:MOD_ERR,SYNTAX:SYNTAX,SECURITY:SECURITY,DISALLOWED:DISALLOWED}=errors;export class Sink{constructor(e,i){this.fileHandle=e,this.file=i,this.size=i.size,this.position=0}write(e){let i=this.file;if("object"==typeof e)if("write"===e.type){if(Number.isInteger(e.position)&&e.position>=0&&(this.position=e.position,this.size<e.position&&(this.file=new File([this.file,new ArrayBuffer(e.position-this.size)],this.file.name,this.file))),!("data"in e))throw new DOMException(...SYNTAX("write requires a data argument"));e=e.data}else{if("seek"===e.type){if(Number.isInteger(e.position)&&e.position>=0){if(this.size<e.position)throw new DOMException(...INVALID);return void(this.position=e.position)}throw new DOMException(...SYNTAX("seek requires a position argument"))}if("truncate"===e.type){if(Number.isInteger(e.size)&&e.size>=0)return i=e.size<this.size?new File([i.slice(0,e.size)],i.name,i):new File([i,new Uint8Array(e.size-this.size)],i.name),this.size=i.size,this.position>i.size&&(this.position=i.size),void(this.file=i);throw new DOMException(...SYNTAX("truncate requires a size argument"))}}e=new Blob([e]);let t=this.file;const s=t.slice(0,this.position),n=t.slice(this.position+e.size);let o=this.position-s.size;o<0&&(o=0),t=new File([s,new Uint8Array(o),e,n],t.name),this.size=t.size,this.position+=e.size,this.file=t}close(){if(this.fileHandle._deleted)throw new DOMException(...GONE);this.fileHandle._file=this.file,this.file=this.position=this.size=null,this.fileHandle.onclose&&this.fileHandle.onclose(this.fileHandle)}}export class FileHandle{constructor(e="",i=new File([],e),t=!0){this._file=i,this.name=e,this.kind="file",this._deleted=!1,this.writable=t,this.readable=!0}async getFile(){if(this._deleted)throw new DOMException(...GONE);return this._file}async createWritable(e){if(!this.writable)throw new DOMException(...DISALLOWED);if(this._deleted)throw new DOMException(...GONE);const i=e.keepExistingData?await this.getFile():new File([],this.name);return new Sink(this,i)}async isSameEntry(e){return this===e}async _destroy(){this._deleted=!0,this._file=null}}export class FolderHandle{constructor(e,i=!0){this.name=e,this.kind="directory",this._deleted=!1,this._entries={},this.writable=i,this.readable=!0}async*entries(){if(this._deleted)throw new DOMException(...GONE);yield*Object.entries(this._entries)}async isSameEntry(e){return this===e}async getDirectoryHandle(e,i){if(this._deleted)throw new DOMException(...GONE);const t=this._entries[e];if(t){if(t instanceof FileHandle)throw new DOMException(...MISMATCH);return t}if(i.create)return this._entries[e]=new FolderHandle(e);throw new DOMException(...GONE)}async getFileHandle(e,i){const t=this._entries[e],s=t instanceof FileHandle;if(t&&s)return t;if(t&&!s)throw new DOMException(...MISMATCH);if(!t&&!i.create)throw new DOMException(...GONE);return!t&&i.create?this._entries[e]=new FileHandle(e):void 0}async removeEntry(e,i){const t=this._entries[e];if(!t)throw new DOMException(...GONE);await t._destroy(i.recursive),delete this._entries[e]}async _destroy(e){for(let i of Object.values(this._entries)){if(!e)throw new DOMException(...MOD_ERR);await i._destroy(e)}this._entries={},this._deleted=!0}}const fs=new FolderHandle("");export default()=>fs;

View File

@ -0,0 +1 @@
import fs from"node:fs/promises";import{join}from"node:path";import{errors}from"../util.js";import config from"../config.js";const{DOMException:DOMException}=config,{INVALID:INVALID,GONE:GONE,MISMATCH:MISMATCH,MOD_ERR:MOD_ERR,SYNTAX:SYNTAX}=errors;function isBlob(t){return t&&"object"==typeof t&&"function"==typeof t.constructor&&("function"==typeof t.stream||"function"==typeof t.arrayBuffer)&&/^(Blob|File)$/.test(t[Symbol.toStringTag])}export class Sink{constructor(t,i){this._fileHandle=t,this._size=i,this._position=0}async abort(){await this._fileHandle.close()}async write(t){if("object"==typeof t)if("write"===t.type){if(Number.isInteger(t.position)&&t.position>=0&&(this._position=t.position),!("data"in t))throw await this._fileHandle.close(),new DOMException(...SYNTAX("write requires a data argument"));t=t.data}else{if("seek"===t.type){if(Number.isInteger(t.position)&&t.position>=0){if(this._size<t.position)throw new DOMException(...INVALID);return void(this._position=t.position)}throw await this._fileHandle.close(),new DOMException(...SYNTAX("seek requires a position argument"))}if("truncate"===t.type){if(Number.isInteger(t.size)&&t.size>=0)return await this._fileHandle.truncate(t.size),this._size=t.size,void(this._position>this._size&&(this._position=this._size));throw await this._fileHandle.close(),new DOMException(...SYNTAX("truncate requires a size argument"))}}if(t instanceof ArrayBuffer)t=new Uint8Array(t);else if("string"==typeof t)t=Buffer.from(t);else if(isBlob(t)){for await(const i of t.stream()){const t=await this._fileHandle.writev([i],this._position);this._position+=t.bytesWritten,this._size+=t.bytesWritten}return}const i=await this._fileHandle.writev([t],this._position);this._position+=i.bytesWritten,this._size+=i.bytesWritten}async close(){await this._fileHandle.close()}}export class FileHandle{constructor(t,i){this._path=t,this.name=i,this.kind="file"}async getFile(){await fs.stat(this._path).catch((t=>{if("ENOENT"===t.code)throw new DOMException(...GONE)}));const{fileFrom:t}=await import("fetch-blob/from.js");return t(this._path)}async isSameEntry(t){return this._path===this._getPath.apply(t)}_getPath(){return this._path}async createWritable(t){const i=await fs.open(this._path,t.keepExistingData?"r+":"w+").catch((t=>{if("ENOENT"===t.code)throw new DOMException(...GONE);throw t})),{size:e}=await i.stat();return new Sink(i,e)}}export class FolderHandle{_path="";constructor(t="",i=""){this.name=i,this.kind="directory",this._path=t}async isSameEntry(t){return this._path===t._path}async*entries(){const t=this._path,i=await fs.readdir(t).catch((t=>{if("ENOENT"===t.code)throw new DOMException(...GONE);throw t}));for(let e of i){const i=join(t,e),o=await fs.lstat(i);o.isFile()?yield[e,new FileHandle(i,e)]:o.isDirectory()&&(yield[e,new FolderHandle(i,e)])}}async getDirectoryHandle(t,i){const e=join(this._path,t),o=await fs.lstat(e).catch((t=>{if("ENOENT"!==t.code)throw t})),s=o?.isDirectory();if(o&&s)return new FolderHandle(e,t);if(o&&!s)throw new DOMException(...MISMATCH);if(!i.create)throw new DOMException(...GONE);return await fs.mkdir(e),new FolderHandle(e,t)}async getFileHandle(t,i){const e=join(this._path,t),o=await fs.lstat(e).catch((t=>{if("ENOENT"!==t.code)throw t})),s=o?.isFile();if(o&&s)return new FileHandle(e,t);if(o&&!s)throw new DOMException(...MISMATCH);if(!i.create)throw new DOMException(...GONE);return await(await fs.open(e,"w")).close(),new FileHandle(e,t)}async queryPermission(){return"granted"}async removeEntry(t,i){const e=join(this._path,t);(await fs.lstat(e).catch((t=>{if("ENOENT"===t.code)throw new DOMException(...GONE);throw t}))).isDirectory()?i.recursive?await fs.rm(e,{recursive:!0}).catch((t=>{if("ENOTEMPTY"===t.code)throw new DOMException(...MOD_ERR);throw t})):await fs.rmdir(e).catch((t=>{if("ENOTEMPTY"===t.code)throw new DOMException(...MOD_ERR);throw t})):await fs.unlink(e)}}export default t=>new FolderHandle(t);

View File

@ -0,0 +1 @@
import{errors}from"../util.js";const{DISALLOWED:DISALLOWED}=errors;class Sink{constructor(e,i){this.writer=e,this.fileEntry=i}async write(e){if("object"==typeof e)if("write"===e.type){if(Number.isInteger(e.position)&&e.position>=0&&(this.writer.seek(e.position),this.writer.position!==e.position&&(await new Promise(((i,t)=>{this.writer.onwriteend=i,this.writer.onerror=t,this.writer.truncate(e.position)})),this.writer.seek(e.position))),!("data"in e))throw new DOMException("Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. write requires a data argument","SyntaxError");e=e.data}else{if("seek"===e.type){if(Number.isInteger(e.position)&&e.position>=0){if(this.writer.seek(e.position),this.writer.position!==e.position)throw new DOMException("seeking position failed","InvalidStateError");return}throw new DOMException("Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. seek requires a position argument","SyntaxError")}if("truncate"===e.type)return new Promise((i=>{if(!(Number.isInteger(e.size)&&e.size>=0))throw new DOMException("Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. truncate requires a size argument","SyntaxError");this.writer.onwriteend=e=>i(),this.writer.truncate(e.size)}))}await new Promise(((i,t)=>{this.writer.onwriteend=i,this.writer.onerror=t,this.writer.write(new Blob([e]))}))}close(){return new Promise(this.fileEntry.file.bind(this.fileEntry))}}export class FileHandle{constructor(e,i=!0){this.file=e,this.kind="file",this.writable=i,this.readable=!0}get name(){return this.file.name}isSameEntry(e){return this.file.toURL()===e.file.toURL()}getFile(){return new Promise(this.file.file.bind(this.file))}createWritable(e){if(!this.writable)throw new DOMException(...DISALLOWED);return new Promise(((i,t)=>this.file.createWriter((t=>{!1===e.keepExistingData?(t.onwriteend=e=>i(new Sink(t,this.file)),t.truncate(0)):i(new Sink(t,this.file))}),t)))}}export class FolderHandle{constructor(e,i=!0){this.dir=e,this.writable=i,this.readable=!0,this.kind="directory",this.name=e.name}isSameEntry(e){return this.dir.fullPath===e.dir.fullPath}async*entries(){const e=this.dir.createReader(),i=await new Promise(e.readEntries.bind(e));for(const e of i)yield[e.name,e.isFile?new FileHandle(e,this.writable):new FolderHandle(e,this.writable)]}getDirectoryHandle(e,i){return new Promise(((t,r)=>{this.dir.getDirectory(e,i,(e=>{t(new FolderHandle(e))}),r)}))}getFileHandle(e,i){return new Promise(((t,r)=>this.dir.getFile(e,i,(e=>t(new FileHandle(e))),r)))}async removeEntry(e,i){const t=await this.getDirectoryHandle(e,{create:!1}).catch((i=>"TypeMismatchError"===i.name?this.getFileHandle(e,{create:!1}):i));if(t instanceof Error)throw t;return new Promise(((e,r)=>{t instanceof FolderHandle?i.recursive?t.dir.removeRecursively((()=>e()),r):t.dir.remove((()=>e()),r):t.file&&t.file.remove((()=>e()),r)}))}}export default(e={})=>new Promise(((i,t)=>window.webkitRequestFileSystem(e._persistent,0,(e=>i(new FolderHandle(e.root))),t)));

View File

@ -0,0 +1 @@
const config={ReadableStream:globalThis.ReadableStream,WritableStream:globalThis.WritableStream,TransformStream:globalThis.TransformStream,DOMException:globalThis.DOMException,Blob:globalThis.Blob,File:globalThis.File};export default config;

View File

@ -0,0 +1 @@
import showDirectoryPicker from"./showDirectoryPicker.js";import showOpenFilePicker from"./showOpenFilePicker.js";import showSaveFilePicker from"./showSaveFilePicker.js";import getOriginPrivateDirectory from"./getOriginPrivateDirectory.js";import FileSystemWritableFileStream from"./FileSystemWritableFileStream.js";import FileSystemDirectoryHandle from"./FileSystemDirectoryHandle.js";import FileSystemFileHandle from"./FileSystemFileHandle.js";import FileSystemHandle from"./FileSystemHandle.js";export{FileSystemDirectoryHandle,FileSystemFileHandle,FileSystemHandle,FileSystemWritableFileStream,getOriginPrivateDirectory,showDirectoryPicker,showOpenFilePicker,showSaveFilePicker};

View File

@ -0,0 +1 @@
async function getOriginPrivateDirectory(e,t={}){if(!e)return globalThis.navigator?.storage?.getDirectory()||globalThis.getOriginPrivateDirectory();const{FileSystemDirectoryHandle:i}=await import("./FileSystemDirectoryHandle.js"),r=await e;return new i(await(r.default?r.default(t):r(t)))}globalThis.DataTransferItem&&!DataTransferItem.prototype.getAsFileSystemHandle&&(DataTransferItem.prototype.getAsFileSystemHandle=async function(){const e=this.webkitGetAsEntry(),[{FileHandle:t,FolderHandle:i},{FileSystemDirectoryHandle:r},{FileSystemFileHandle:a}]=await Promise.all([import("./adapters/sandbox.js"),import("./FileSystemDirectoryHandle.js"),import("./FileSystemFileHandle.js")]);return e.isFile?new a(new t(e,!1)):new r(new i(e,!1))});export default getOriginPrivateDirectory;

View File

@ -0,0 +1 @@
const native=globalThis.showDirectoryPicker;async function showDirectoryPicker(e={}){if(native&&!e._preferPolyfill)return native(e);const t=document.createElement("input");t.type="file",t.webkitdirectory=!0,t.multiple=!0,t.style.position="fixed",t.style.top="-100000px",t.style.left="-100000px",document.body.appendChild(t);const i=import("./util.js");return await new Promise((e=>{t.addEventListener("change",e),t.click()})),i.then((e=>e.getDirHandlesFromInput(t)))}export default showDirectoryPicker;export{showDirectoryPicker};

View File

@ -0,0 +1 @@
const def={accepts:[]},native=globalThis.showOpenFilePicker;async function showOpenFilePicker(e={}){const t={...def,...e};if(native&&!e._preferPolyfill)return native(t);const i=document.createElement("input");i.type="file",i.multiple=t.multiple,i.accept=(t.accepts||[]).map((e=>[...(e.extensions||[]).map((e=>"."+e)),...e.mimeTypes||[]])).flat().join(","),Object.assign(i.style,{position:"fixed",top:"-100000px",left:"-100000px"}),document.body.appendChild(i);const n=import("./util.js");return await new Promise((e=>{i.addEventListener("change",e,{once:!0}),i.click()})),i.remove(),n.then((e=>e.getFileHandlesFromInput(i)))}export default showOpenFilePicker;export{showOpenFilePicker};

View File

@ -0,0 +1 @@
const native=globalThis.showSaveFilePicker;async function showSaveFilePicker(e={}){if(native&&!e._preferPolyfill)return native(e);e._name&&(console.warn("deprecated _name, spec now have `suggestedName`"),e.suggestedName=e._name);const{FileSystemFileHandle:a}=await import("./FileSystemFileHandle.js"),{FileHandle:i}=await import("./adapters/downloader.js");return new a(new i(e.suggestedName))}export default showSaveFilePicker;export{showSaveFilePicker};

View File

@ -0,0 +1 @@
export const errors={INVALID:["seeking position failed.","InvalidStateError"],GONE:["A requested file or directory could not be found at the time an operation was processed.","NotFoundError"],MISMATCH:["The path supplied exists, but was not an entry of requested type.","TypeMismatchError"],MOD_ERR:["The object can not be modified in this way.","InvalidModificationError"],SYNTAX:e=>[`Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. ${e}`,"SyntaxError"],SECURITY:["It was determined that certain files are unsafe for access within a Web application, or that too many calls are being made on file resources.","SecurityError"],DISALLOWED:["The request is not allowed by the user agent or the platform in the current context.","NotAllowedError"]};export const config={writable:globalThis.WritableStream};export async function fromDataTransfer(e){console.warn("deprecated fromDataTransfer - use `dt.items[0].getAsFileSystemHandle()` instead");const[t,r,a]=await Promise.all([import("./adapters/memory.js"),import("./adapters/sandbox.js"),import("./FileSystemDirectoryHandle.js")]),n=new t.FolderHandle("",!1);return n._entries=e.map((e=>e.isFile?new r.FileHandle(e,!1):new r.FolderHandle(e,!1))),new a.FileSystemDirectoryHandle(n)}export async function getDirHandlesFromInput(e){const{FolderHandle:t,FileHandle:r}=await import("./adapters/memory.js"),{FileSystemDirectoryHandle:a}=await import("./FileSystemDirectoryHandle.js"),n=Array.from(e.files),i=n[0].webkitRelativePath.split("/",1)[0],o=new t(i,!1);return n.forEach((e=>{const a=e.webkitRelativePath.split("/");a.shift();const n=a.pop();a.reduce(((e,r)=>(e._entries[r]||(e._entries[r]=new t(r,!1)),e._entries[r])),o)._entries[n]=new r(e.name,e,!1)})),new a(o)}export async function getFileHandlesFromInput(e){const{FileHandle:t}=await import("./adapters/memory.js"),{FileSystemFileHandle:r}=await import("./FileSystemFileHandle.js");return Array.from(e.files).map((e=>new r(new t(e.name,e,!1))))}

View File

@ -0,0 +1 @@
const WRITE=0,PULL=0,ERROR=1,ABORT=1,CLOSE=2,PING=3;class MessagePortSource{controller;constructor(e){this.port=e,this.port.onmessage=e=>this.onMessage(e.data)}start(e){this.controller=e}pull(){this.port.postMessage({type:0})}cancel(e){this.port.postMessage({type:1,reason:e.message}),this.port.close()}onMessage(e){0===e.type&&this.controller.enqueue(e.chunk),1===e.type&&(this.controller.error(e.reason),this.port.close()),2===e.type&&(this.controller.close(),this.port.close())}}self.addEventListener("install",(()=>{self.skipWaiting()})),self.addEventListener("activate",(e=>{e.waitUntil(self.clients.claim())}));const map=new Map;globalThis.addEventListener("message",(e=>{const t=e.data;t.url&&t.readablePort&&(t.rs=new ReadableStream(new MessagePortSource(e.data.readablePort),new CountQueuingStrategy({highWaterMark:4})),map.set(t.url,t))})),globalThis.addEventListener("fetch",(e=>{const t=e.request.url,s=map.get(t);if(!s)return null;map.delete(t),e.respondWith(new Response(s.rs,{headers:s.headers}))}));

View File

@ -1,5 +1,7 @@
@import "colors";
nav.navbar { nav.navbar {
background-color: hsl(203, 75%, 40%); background-color: $primary-dark-color;
margin: 1em; margin: 1em;
color: white; color: white;
border-radius: 0.6em; border-radius: 0.6em;

View File

@ -0,0 +1,37 @@
@import "colors";
.pagination {
text-align: center;
gap: 10px;
button {
background-color: $secondary-neutral-light-color;
min-width: 37px;
&:hover {
background-color: darken($secondary-neutral-light-color, 10%);
}
&:disabled {
color: #fff;
background-color: darken($secondary-neutral-light-color, 5%);
}
&.active,
&.active:hover {
background-color: $primary-neutral-color;
color: white;
border-radius: 50%;
}
&:first-of-type,
&:last-of-type {
// previous and next buttons
&:disabled {
// Hide the arrows when they are disabled, without
// changing the layout of the navigation
opacity: 0;
}
}
}
}

View File

@ -1,39 +1,9 @@
$first-color: hsl(220, 100%, 50%); @import "colors";
$second-color: hsl(48, 100%, 67%);
$primary-color: hsl(219.9, 53.7%, 50%);
$secondary-color: hsl(204, 64%, 44%);
$primary-color-text: hsl(0, 0%, 100%);
$secondary-color-text: hsla(0, 0%, 0%, 0.87);
$primary-light-color: hsl(219.8, 46.4%, 64.9%);
$primary-dark-color: hsl(203, 75%, 40%);
$secondary-light-color: hsl(40, 68%, 65%);
$secondary-dark-color: hsl(40, 68%, 35%);
$primary-neutral-color: hsl(219.6, 20.8%, 50%);
$primary-neutral-light-color: hsl(0, 0%, 94%);
$primary-neutral-dark-color: hsl(210, 29%, 29%);
$secondary-neutral-color: hsl(204, 64%, 44%);
$secondary-neutral-light-color: hsl(0, 0%, 91%);
$secondary-neutral-dark-color: hsl(40, 57.6%, 17%);
$white-color: hsl(219.6, 20.8%, 98%);
$black-color: hsl(0, 0%, 17%);
$faceblue: hsl(221, 44%, 41%);
$twitblue: hsl(206, 82%, 63%);
$shadow-color: rgb(223, 223, 223);
$background-button-color: hsl(0, 0%, 95%);
/*--------------------------MEDIA QUERY HELPERS------------------------*/ /*--------------------------MEDIA QUERY HELPERS------------------------*/
$small-devices: 576px; $small-devices: 576px;
$medium-devices: 768px; $medium-devices: 768px;
$large-devices: 992px; $large-devices: 992px;
$extra-large-devices: 1200px;
/*--------------------------------GENERAL------------------------------*/ /*--------------------------------GENERAL------------------------------*/
@ -43,17 +13,6 @@ body {
font-family: sans-serif; font-family: sans-serif;
} }
button:disabled,
button:disabled:hover {
color: #fff;
background-color: #6c757d;
}
button.active,
button.active:hover {
color: #fff;
background-color: $secondary-color;
}
a.button, a.button,
button, button,
@ -246,7 +205,7 @@ a:not(.button) {
#page { #page {
width: 90%; width: 90%;
margin: 20px auto 0; margin: 20px auto 0;
/*---------------------------------NAV---------------------------------*/ /*---------------------------------NAV---------------------------------*/
.btn { .btn {
font-size: 15px; font-size: 15px;
@ -278,7 +237,7 @@ a:not(.button) {
} }
} }
/*--------------------------------CONTENT------------------------------*/ /*--------------------------------CONTENT------------------------------*/
#quick_notif { #quick_notif {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
@ -357,7 +316,7 @@ a:not(.button) {
} }
} }
/*---------------------------------NEWS--------------------------------*/ /*---------------------------------NEWS--------------------------------*/
#news { #news {
display: flex; display: flex;
@ -385,11 +344,11 @@ a:not(.button) {
background: $second-color; background: $second-color;
box-shadow: $shadow-color 1px 1px 1px; box-shadow: $shadow-color 1px 1px 1px;
padding: 0.4em; padding: 0.4em;
margin: 0em 0em 0.5em 0em; margin: 0 0 0.5em 0;
text-transform: uppercase; text-transform: uppercase;
font-size: 1.1em; font-size: 1.1em;
&:not(:first-of-type) { &:not(:first-of-type) {
margin: 2em 0em 1em 0em; margin: 2em 0 1em 0;
} }
} }
} }
@ -400,7 +359,7 @@ a:not(.button) {
} }
} }
/* AGENDA/BIRTHDAYS */ /* AGENDA/BIRTHDAYS */
#agenda, #agenda,
#birthdays { #birthdays {
display: block; display: block;
@ -410,7 +369,7 @@ a:not(.button) {
margin-bottom: 1em; margin-bottom: 1em;
#agenda_title, #agenda_title,
#birthdays_title { #birthdays_title {
margin: 0em; margin: 0;
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
box-shadow: $shadow-color 1px 1px 1px; box-shadow: $shadow-color 1px 1px 1px;
padding: 0.5em; padding: 0.5em;
@ -444,7 +403,7 @@ a:not(.button) {
} }
} }
ul.birthdays_year { ul.birthdays_year {
margin: 0em; margin: 0;
list-style-type: none; list-style-type: none;
font-weight: bold; font-weight: bold;
> li { > li {
@ -454,7 +413,7 @@ a:not(.button) {
} }
} }
ul { ul {
margin: 0em; margin: 0;
margin-left: 1em; margin-left: 1em;
list-style-type: square; list-style-type: square;
list-style-position: inside; list-style-position: inside;
@ -463,9 +422,9 @@ a:not(.button) {
} }
} }
} }
/* END AGENDA/BIRTHDAYS */ /* END AGENDA/BIRTHDAYS */
/* EVENTS TODAY AND NEXT FEW DAYS */ /* EVENTS TODAY AND NEXT FEW DAYS */
.news_events_group { .news_events_group {
box-shadow: $shadow-color 1px 1px 1px; box-shadow: $shadow-color 1px 1px 1px;
margin-left: 1em; margin-left: 1em;
@ -516,14 +475,14 @@ a:not(.button) {
float: left; float: left;
min-width: 7em; min-width: 7em;
max-width: 9em; max-width: 9em;
margin: 0em; margin: 0;
margin-right: 1em; margin-right: 1em;
margin-top: 0.8em; margin-top: 0.8em;
img { img {
max-height: 6em; max-height: 6em;
max-width: 8em; max-width: 8em;
display: block; display: block;
margin: 0em auto; margin: 0 auto;
} }
} }
.news_date { .news_date {
@ -544,15 +503,15 @@ a:not(.button) {
} }
} }
} }
/* END EVENTS TODAY AND NEXT FEW DAYS */ /* END EVENTS TODAY AND NEXT FEW DAYS */
/* COMING SOON */ /* COMING SOON */
.news_coming_soon { .news_coming_soon {
display: list-item; display: list-item;
list-style-type: square; list-style-type: square;
list-style-position: inside; list-style-position: inside;
margin-left: 1em; margin-left: 1em;
padding-left: 0em; padding-left: 0;
a { a {
font-weight: bold; font-weight: bold;
text-transform: uppercase; text-transform: uppercase;
@ -561,35 +520,35 @@ a:not(.button) {
font-size: 0.9em; font-size: 0.9em;
} }
} }
/* END COMING SOON */ /* END COMING SOON */
/* NOTICES */ /* NOTICES */
.news_notice { .news_notice {
margin: 0em 0em 1em 1em; margin: 0 0 1em 1em;
padding: 0.4em; padding: 0.4em;
padding-left: 1em; padding-left: 1em;
background: $secondary-neutral-light-color; background: $secondary-neutral-light-color;
box-shadow: $shadow-color 0 0 2px; box-shadow: $shadow-color 0 0 2px;
border-radius: 18px 5px 18px 5px; border-radius: 18px 5px 18px 5px;
h4 { h4 {
margin: 0em; margin: 0;
} }
.news_content { .news_content {
margin-left: 1em; margin-left: 1em;
} }
} }
/* END NOTICES */ /* END NOTICES */
/* CALLS */ /* CALLS */
.news_call { .news_call {
margin: 0em 0em 1em 1em; margin: 0 0 1em 1em;
padding: 0.4em; padding: 0.4em;
padding-left: 1em; padding-left: 1em;
background: $secondary-neutral-light-color; background: $secondary-neutral-light-color;
border: 1px solid grey; border: 1px solid grey;
box-shadow: $shadow-color 1px 1px 1px; box-shadow: $shadow-color 1px 1px 1px;
h4 { h4 {
margin: 0em; margin: 0;
} }
.news_date { .news_date {
font-size: 0.9em; font-size: 0.9em;
@ -598,7 +557,7 @@ a:not(.button) {
margin-left: 1em; margin-left: 1em;
} }
} }
/* END CALLS */ /* END CALLS */
.news_empty { .news_empty {
margin-left: 1em; margin-left: 1em;
@ -631,12 +590,12 @@ a:not(.button) {
width: 19%; width: 19%;
float: left; float: left;
min-width: 15em; min-width: 15em;
margin: 0em; margin: 0;
img { img {
max-height: 15em; max-height: 15em;
max-width: 12em; max-width: 12em;
display: block; display: block;
margin: 0em auto; margin: 0 auto;
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
@ -646,7 +605,6 @@ a:not(.button) {
padding: 0.5em 1em; padding: 0.5em 1em;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
display: inline-block;
font-size: 1.2em; font-size: 1.2em;
border-radius: 2px; border-radius: 2px;
float: right; float: right;
@ -1198,8 +1156,8 @@ u,
color: $primary-neutral-dark-color; color: $primary-neutral-dark-color;
height: 100%; height: 100%;
width: 100%; width: 100%;
margin: 0em; margin: 0;
padding: 0em; padding: 0;
display: block; display: block;
} }
} }
@ -1393,7 +1351,7 @@ footer {
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
div { div {
margin: 0.6em 0em; margin: 0.6em 0;
color: $white-color; color: $white-color;
border-radius: 5px; border-radius: 5px;
display: flex; display: flex;
@ -1507,514 +1465,3 @@ a.ui-button:active,
} }
} }
} }
/* --------------------------------------pedagogy-----------------------------------*/
$pedagogy-blue: #1bb9ea;
$pedagogy-orange: #ea7900;
$pedagogy-hover-blue: #0e97ce;
$pedagogy-light-blue: #caf0ff;
$pedagogy-white-text: #f0f0f0;
.pedagogy {
#pagination {
text-align: center;
}
&.star-not-checked {
color: #f7f7f7;
margin-bottom: 0;
margin-top: 0;
}
&.star-checked {
color: $pedagogy-orange;
margin-bottom: 0;
margin-top: 0;
}
&.grade-without-star {
display: none;
}
@media screen and (max-width: $large-devices) {
&.star-not-checked {
margin-left: 5px;
margin-right: 5px;
}
&.star-checked {
margin-left: 5px;
margin-right: 5px;
}
}
@media screen and (max-width: $small-devices) {
&.grade-without-star {
display: block;
}
&.grade-with-star {
display: none;
}
}
#dynamic_view {
font-size: 1.1em;
overflow-wrap: break-word;
td {
text-align: center;
border: none;
}
}
#search_form {
.search-form-container {
display: grid;
grid-template-columns: auto auto;
grid-template-rows: auto auto auto;
grid-template-areas:
"action-bar action-bar"
"search-bar search-bar"
"radio-department radio-department"
"radio-credit-type radio-semester";
}
.action-bar {
grid-area: action-bar;
margin-bottom: 10px;
}
.search-bar {
grid-area: search-bar;
display: grid;
grid-template-columns: auto 200px;
grid-template-rows: auto;
grid-template-areas: "search-bar-input search-bar-button";
@media screen and (max-width: $medium-devices) {
grid-template-columns: auto auto;
grid-template-rows: auto;
grid-template-areas: "search-bar-input search-bar-button";
}
@media screen and (max-width: $small-devices) {
grid-template-columns: auto;
grid-template-rows: auto;
grid-template-areas: "search-bar-input";
.search-bar-button {
display: none;
}
}
.search-bar-input {
grid-area: search-bar-input;
background: $pedagogy-light-blue;
}
.search-bar-button {
grid-area: search-bar-button;
background: $pedagogy-orange;
color: white;
font-weight: bold;
margin-left: 20px;
}
}
.radio-department {
grid-area: radio-department;
}
.radio-credit-type {
grid-area: radio-credit-type;
}
.radio-semester {
grid-area: radio-semester;
}
.radio-guide input[type="radio"],
input[type="checkbox"] {
display: none;
}
.radio-guide {
margin-top: 10px;
color: white;
}
.radio-guide label {
display: inline-block;
background-color: $pedagogy-blue;
padding: 10px 20px;
font-family: Arial, sans-serif;
font-size: 16px;
border-radius: 4px;
}
.radio-guide input[type="radio"]:checked + label {
background-color: $pedagogy-orange;
}
.radio-guide input[type="checkbox"]:checked + label {
background-color: $pedagogy-orange;
}
.radio-guide label:hover {
background-color: $pedagogy-hover-blue;
}
}
#uv_detail {
color: #062f38;
.uv-quick-info-container {
display: grid;
grid-template-columns: 20% 20% 20% 20% auto;
grid-template-rows: auto auto;
grid-template-areas:
"hours-cm hours-td hours-tp hours-te hours-the"
"department credit-type semester . .";
}
.department {
grid-area: department;
}
.credit-type {
grid-area: credit-type;
}
.semester {
grid-area: semester;
}
.hours-cm {
grid-area: hours-cm;
}
.hours-td {
grid-area: hours-td;
}
.hours-tp {
grid-area: hours-tp;
}
.hours-te {
grid-area: hours-te;
}
.hours-the {
grid-area: hours-the;
}
#leave_comment_not_allowed {
p {
text-align: center;
color: red;
}
}
#leave_comment {
.leave-comment-grid-container {
display: grid;
grid-template-columns: 270px auto;
grid-template-rows: 100%;
grid-template-areas: "stars comment";
@media screen and (max-width: $large-devices) {
grid-template-columns: 100%;
grid-template-rows: auto auto;
grid-template-areas:
"stars"
"comment";
}
}
.ui-accordion-content {
background-color: $white-color;
border-color: $pedagogy-orange;
border-right: none;
}
.form-stars {
grid-area: stars;
}
.form-comment {
grid-area: comment;
}
.ui-accordion-header {
background-color: $pedagogy-orange;
color: $pedagogy-white-text;
clip-path: polygon(0 0%, 0 100%, 30% 100%, 33% 0);
@media screen and (max-width: $large-devices) {
clip-path: none;
}
}
.ui-accordion-header-icon {
color: $pedagogy-white-text;
margin-right: 10px;
}
.input-stars {
margin-top: 20px;
}
input[type="submit"] {
float: right;
}
}
.uv-details-container {
display: grid;
grid-template-columns: 150px 100px auto;
grid-template-rows: 156px 1fr;
grid-template-areas:
"grade grade-stars uv-infos"
". . uv-infos";
@media screen and (max-width: $large-devices) {
grid-template-columns: 50% 50%;
grid-template-rows: auto auto;
grid-template-areas:
"grade grade-stars"
"uv-infos uv-infos";
}
}
.grade {
grid-area: grade;
color: $pedagogy-white-text;
background-color: $pedagogy-blue;
padding-right: 10px;
> p {
text-align: right;
font-weight: bold;
}
}
.grade-stars {
grid-area: grade-stars;
color: $pedagogy-white-text;
background-color: $pedagogy-blue;
font-weight: bold;
}
.uv-infos {
grid-area: uv-infos;
padding-left: 10px;
}
.comment-container {
display: grid;
grid-template-columns: 300px auto;
grid-template-rows: auto auto auto;
grid-template-areas:
"grade-block comment"
"grade-block info"
"comment-end-bar comment-end-bar";
margin-bottom: 30px;
margin-top: 10px;
@media screen and (max-width: $large-devices) {
grid-template-columns: auto;
grid-template-rows: auto auto auto auto;
grid-template-areas:
"grade-block"
"comment"
"info"
"comment-end-bar";
}
.grade-block {
grid-area: grade-block;
width: 300px;
display: grid;
grid-template-columns: 150px 150px;
grid-template-rows: 156px auto;
grid-template-areas:
"grade-type grade-stars"
"grade-extension grade-extension";
grid-gap: 15px;
clip-path: polygon(0 0, 0 100%, 100% 100%, 100% 30px, 270px 0);
align-items: start;
background-color: $pedagogy-blue;
@media screen and (max-width: $large-devices) {
grid-template-columns: 50% auto;
grid-template-rows: auto;
grid-template-areas: "grade-type grade-stars";
width: auto;
clip-path: none;
align-content: space-evenly;
align-items: end;
}
.grade-extension {
grid-area: grade-extension;
background-color: $pedagogy-blue;
}
.grade-type {
grid-area: grade-type;
> p {
color: $pedagogy-white-text;
font-weight: bold;
text-align: right;
}
}
.grade-stars {
grid-area: grade-stars;
}
}
.comment {
grid-area: comment;
display: grid;
grid-template-columns: auto;
grid-template-rows: auto auto;
grid-template-areas:
"anchor"
"markdown";
@media screen and (max-width: $large-devices) {
border-left: solid;
border-right: solid;
border-color: $pedagogy-blue;
}
.anchor {
grid-area: anchor;
text-align: right;
margin-right: 15px;
}
.markdown {
grid-area: markdown;
min-height: 139px;
margin-top: 0;
margin-right: 0;
padding: 10px;
text-align: justify;
overflow: auto;
}
}
.info {
grid-area: info;
padding-bottom: 10px;
@media screen and (max-width: $large-devices) {
border-left: solid;
border-right: solid;
border-color: $pedagogy-blue;
}
.status-reported {
color: red;
float: left;
padding-left: 10px;
}
.actions {
float: right;
}
}
.comment-end-bar {
grid-area: comment-end-bar;
display: grid;
grid-template-columns: 33% auto auto;
grid-template-rows: 2.5em;
grid-template-areas: "author date report";
background-color: $pedagogy-blue;
margin-top: -1px;
@media screen and (max-width: $large-devices) {
grid-template-columns: auto;
grid-template-rows: auto auto auto;
grid-template-areas:
"report"
"date"
"author";
margin-top: 0;
text-align: center;
}
.author {
grid-area: author;
padding-top: 6px;
padding-left: 20px;
background-color: $pedagogy-orange;
clip-path: polygon(0 10px, 0 100%, 350px 200%, 300px 10px);
@media screen and (max-width: $large-devices) {
clip-path: none;
padding: 0;
padding-bottom: 7px;
}
a {
color: $pedagogy-white-text;
font-weight: bold;
}
a:hover {
color: $pedagogy-hover-blue;
}
}
.date {
grid-area: date;
color: $pedagogy-white-text;
@media screen and (max-width: $large-devices) {
padding-bottom: 7px;
}
}
.report {
grid-area: report;
justify-self: right;
padding-right: 30px;
padding-left: 30px;
a {
color: $pedagogy-white-text;
}
a:hover {
color: $pedagogy-hover-blue;
}
@media screen and (max-width: $large-devices) {
text-align: center;
justify-self: inherit;
padding-bottom: 7px;
background-color: $white-color;
border-left: solid;
border-right: solid;
border-color: $pedagogy-blue;
a {
color: $black-color;
}
}
}
}
}
}
}

View File

@ -199,12 +199,6 @@
> form { > form {
> p { > p {
box-sizing: border-box; box-sizing: border-box;
> input {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
} }
> .results_on_deck > div { > .results_on_deck > div {
@ -219,12 +213,15 @@
right: 0; right: 0;
} }
} }
input {
> input { min-width: 100%;
width: 100%;
max-width: 100%; max-width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
button {
font-weight: bold;
}
} }
} }
} }

View File

@ -32,7 +32,6 @@
width: 100%; width: 100%;
} }
// Django moment
> div.mini_profile_link { > div.mini_profile_link {
position: relative; position: relative;
@ -106,7 +105,6 @@
} }
} }
// Django moment
> a.mini_profile_link { > a.mini_profile_link {
display: none; display: none;
} }

View File

@ -2,9 +2,9 @@
{% block content %} {% block content %}
<h3>{% trans %}403, Forbidden{% endtrans %}</h3> <h3>{% trans %}403, Forbidden{% endtrans %}</h3>
{{ super() }} {{ super() }}
{% endblock %} {% endblock %}

View File

@ -2,9 +2,9 @@
{% block content %} {% block content %}
<div id="page"> <div id="page">
<h3>{% trans %}404, Not Found{% endtrans %}</h3> <h3>{% trans %}404, Not Found{% endtrans %}</h3>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<script <script
src="https://browser.sentry-cdn.com/7.11.1/bundle.min.js" src="https://browser.sentry-cdn.com/7.11.1/bundle.min.js"
integrity="sha384-qcYSo5+/E8hEkPmHFa79GRDsGT84SRhBJHRw3+dbQyh0UwueiFP1jCsRBClEREcs" integrity="sha384-qcYSo5+/E8hEkPmHFa79GRDsGT84SRhBJHRw3+dbQyh0UwueiFP1jCsRBClEREcs"
@ -23,5 +23,5 @@
{% endif %} {% endif %}
}) })
</script> </script>
{% endif %} {% endif %}
{% endblock content %} {% endblock content %}

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{{ static('core/img/favicon.ico') }}"> <link rel="shortcut icon" href="{{ static('core/img/favicon.ico') }}">
<link rel="stylesheet" href="{{ static('core/base.css') }}"> <link rel="stylesheet" href="{{ static('core/base.css') }}">
<link rel="stylesheet" href="{{ static('core/jquery.datetimepicker.min.css') }}">
<link rel="stylesheet" href="{{ static('ajax_select/css/ajax_select.css') }}"> <link rel="stylesheet" href="{{ static('ajax_select/css/ajax_select.css') }}">
<link rel="stylesheet" href="{{ scss('core/style.scss') }}"> <link rel="stylesheet" href="{{ scss('core/style.scss') }}">
<link rel="stylesheet" href="{{ scss('core/markdown.scss') }}"> <link rel="stylesheet" href="{{ scss('core/markdown.scss') }}">
@ -295,47 +294,27 @@
</code> </code>
</footer> </footer>
{% endif %} {% endif %}
<!--
{% block tests %}
{{ tests }}
{% endblock %}
-->
{% block script %} {% block script %}
<script src="{{ static('core/js/ui/jquery-ui.min.js') }}"></script> <script src="{{ static('core/js/ui/jquery-ui.min.js') }}"></script>
<script src="{{ static('core/js/ui/i18n/datepicker-fr.js') }}"></script>
<script src="{{ static('core/js/jquery.datetimepicker.full.min.js') }}"></script>
<script src="{{ static('ajax_select/js/ajax_select.js') }}"></script> <script src="{{ static('ajax_select/js/ajax_select.js') }}"></script>
<script src="{{ url('javascript-catalog') }}"></script> <script src="{{ url('javascript-catalog') }}"></script>
<script> <script>
function showMenu() { function showMenu() {
let navbar = document.getElementById("navbar-content"); let navbar = document.getElementById("navbar-content");
const current = navbar.style.getPropertyValue("display"); const current = navbar.style.getPropertyValue("display");
navbar.style.setProperty("display", current == "none" ? "block" : "none"); navbar.style.setProperty("display", current === "none" ? "block" : "none");
} }
</script>
<script> $(document).keydown(function (e) {
$('.select_date').datepicker({
changeMonth: true,
changeYear: true,
dayNamesShort: $.datepicker.regional[ "{{ request.LANGUAGE_CODE }}" ].dayNamesShort,
dayNames: $.datepicker.regional[ "{{ request.LANGUAGE_CODE }}" ].dayNames,
monthNamesShort: $.datepicker.regional[ "{{ request.LANGUAGE_CODE }}" ].monthNamesShort,
monthNames: $.datepicker.regional[ "{{ request.LANGUAGE_CODE }}" ].monthNames,
}).datepicker( $.datepicker.regional[ "{{ request.LANGUAGE_CODE }}"] );
$(document).keydown(function (e) {
if ($(e.target).is('input')) { return } if ($(e.target).is('input')) { return }
if ($(e.target).is('textarea')) { return } if ($(e.target).is('textarea')) { return }
if ($(e.target).is('select')) { return } if ($(e.target).is('select')) { return }
if (e.keyCode == 83) { if (e.keyCode === 83) {
$("#search").focus(); $("#search").focus();
return false; return false;
} }
}); });
jQuery.datetimepicker.setLocale('{{ request.LANGUAGE_CODE|lower }}');
$('.select_datetime').datetimepicker({
format: 'Y-m-d H:i:s',
});
</script> </script>
{% endblock %} {% endblock %}
</body> </body>

View File

@ -1,16 +1,16 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans name=form.instance.__class__._meta.verbose_name %}Create {{ name }}{% endtrans %} {% trans name=form.instance.__class__._meta.verbose_name %}Create {{ name }}{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h2>{% trans name=form.instance.__class__._meta.verbose_name %}Create {{ name }}{% endtrans %}</h2> <h2>{% trans name=form.instance.__class__._meta.verbose_name %}Create {{ name }}{% endtrans %}</h2>
<form action="" method="post" enctype="multipart/form-data"> <form action="" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form.as_p() }} {{ form.as_p() }}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Delete confirmation{% endtrans %} {% trans %}Delete confirmation{% endtrans %}
{% endblock %} {% endblock %}
{% block info_boxes %} {% block info_boxes %}
@ -11,14 +11,14 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h2>{% trans %}Delete confirmation{% endtrans %}</h2> <h2>{% trans %}Delete confirmation{% endtrans %}</h2>
<form action="" method="post">{% csrf_token %} <form action="" method="post">{% csrf_token %}
<p>{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}</p> <p>{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}</p>
<input type="submit" value="{% trans %}Confirm{% endtrans %}" /> <input type="submit" value="{% trans %}Confirm{% endtrans %}" />
</form> </form>
<form method="GET" action="javascript:history.back();"> <form method="GET" action="javascript:history.back();">
<input type="submit" name="cancel" value="{% trans %}Cancel{% endtrans %}" /> <input type="submit" name="cancel" value="{% trans %}Cancel{% endtrans %}" />
</form> </form>
{% endblock %} {% endblock %}

View File

@ -1,24 +1,24 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% if object %} {% if object %}
{% trans obj=object %}Edit {{ obj }}{% endtrans %} {% trans obj=object %}Edit {{ obj }}{% endtrans %}
{% else %} {% else %}
{% trans %}Save{% endtrans %} {% trans %}Save{% endtrans %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if object %} {% if object %}
<h2>{% trans obj=object %}Edit {{ obj }}{% endtrans %}</h2> <h2>{% trans obj=object %}Edit {{ obj }}{% endtrans %}</h2>
{% else %} {% else %}
<h2>{% trans %}Save{% endtrans %}</h2> <h2>{% trans %}Save{% endtrans %}</h2>
{% endif %} {% endif %}
<form action="" method="post" enctype="multipart/form-data"> <form action="" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form.as_p() }} {{ form.as_p() }}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -1,30 +1,30 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% if file %} {% if file %}
{{ file.get_display_name() }} {{ file.get_display_name() }}
{% elif file_list %} {% elif file_list %}
{% trans %}File list{% endtrans %} {% trans %}File list{% endtrans %}
{% elif new_file %} {% elif new_file %}
{% trans %}New file{% endtrans %} {% trans %}New file{% endtrans %}
{% else %} {% else %}
{% trans %}Not found{% endtrans %} {% trans %}Not found{% endtrans %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% macro print_file_name(file) %} {% macro print_file_name(file) %}
{% if file %} {% if file %}
{{ print_file_name(file.parent) }} > {{ print_file_name(file.parent) }} >
<a href="{{ url('core:file_detail', file_id=file.id, popup=popup) }}">{{ file.get_display_name() }}</a> <a href="{{ url('core:file_detail', file_id=file.id, popup=popup) }}">{{ file.get_display_name() }}</a>
{% else %} {% else %}
<a href="{{ url('core:file_list', popup) }}">{% trans %}Files{% endtrans %}</a> <a href="{{ url('core:file_list', popup) }}">{% trans %}Files{% endtrans %}</a>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% block content %} {% block content %}
{{ print_file_name(file) }} {{ print_file_name(file) }}
<div class="tool_bar"> <div class="tool_bar">
<div class="tools"> <div class="tools">
<div> <div>
{% set home = user.home %} {% set home = user.home %}
@ -42,21 +42,21 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
<hr> <hr>
{% if file %} {% if file %}
{% block file %} {% block file %}
{% endblock %} {% endblock %}
{% endif %} {% endif %}
{% block script %} {% block script %}
{{ super() }} {{ super() }}
{% if popup %} {% if popup %}
<script> <script>
parent.$(".choose_file_widget").css("height", "75%"); parent.$(".choose_file_widget").css("height", "75%");
</script> </script>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}

View File

@ -1,18 +1,18 @@
{% extends "core/file.jinja" %} {% extends "core/file.jinja" %}
{% block title %} {% block title %}
{% trans %}Delete confirmation{% endtrans %} {% trans %}Delete confirmation{% endtrans %}
{% endblock %} {% endblock %}
{% block file %} {% block file %}
<h2>{% trans %}Delete confirmation{% endtrans %}</h2> <h2>{% trans %}Delete confirmation{% endtrans %}</h2>
<form action="" method="post">{% csrf_token %} <form action="" method="post">{% csrf_token %}
<p>{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}</p> <p>{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}</p>
<input type="submit" value="{% trans %}Confirm{% endtrans %}" /> <input type="submit" value="{% trans %}Confirm{% endtrans %}" />
</form> </form>
<form method="GET" action="javascript:history.back();"> <form method="GET" action="javascript:history.back();">
<input type="submit" name="cancel" value="{% trans %}Cancel{% endtrans %}" /> <input type="submit" name="cancel" value="{% trans %}Cancel{% endtrans %}" />
</form> </form>
{% endblock %} {% endblock %}

View File

@ -2,24 +2,24 @@
{% block file %} {% block file %}
<h3> <h3>
{% if file.is_folder %} {% if file.is_folder %}
<i class="fa fa-folder fa-3x" aria-hidden="true"></i> <i class="fa fa-folder fa-3x" aria-hidden="true"></i>
{% else %} {% else %}
<i class="fa fa-file fa-3x" aria-hidden="true"></i> <i class="fa fa-file fa-3x" aria-hidden="true"></i>
{% endif %} {% endif %}
{{ file.get_display_name() }} {{ file.get_display_name() }}
</h3> </h3>
<p>{% trans %}Owner: {% endtrans %}{{ file.owner.get_display_name() }}</p> <p>{% trans %}Owner: {% endtrans %}{{ file.owner.get_display_name() }}</p>
{% if file.is_folder %} {% if file.is_folder %}
{% if user.can_edit(file) %} {% if user.can_edit(file) %}
<form action="" method="post" enctype="multipart/form-data"> <form action="" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form.as_p() }} {{ form.as_p() }}
<p><input type="submit" value="{% trans %}Add{% endtrans %}"></p> <p><input type="submit" value="{% trans %}Add{% endtrans %}"></p>
</form> </form>
{% endif %} {% endif %}
<form action="" method="post" enctype="multipart/form-data"> <form action="" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<p> <p>
<input name="delete" type="submit" value="{% trans %}Delete{% endtrans %}"> | <input name="delete" type="submit" value="{% trans %}Delete{% endtrans %}"> |
@ -48,32 +48,32 @@
<a href="{{ url('core:file_detail', file_id=f.id, popup=popup) }}">{{ f.get_display_name() }}</a></li> <a href="{{ url('core:file_detail', file_id=f.id, popup=popup) }}">{{ f.get_display_name() }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</form> </form>
{% else %} {% else %}
<p>{% trans %}Real name: {% endtrans %}{{ file.file.name.split('/')[-1] }}</p> <p>{% trans %}Real name: {% endtrans %}{{ file.file.name.split('/')[-1] }}</p>
<p>{% trans %}Date: {% endtrans %}{{ file.date|localtime|date(DATETIME_FORMAT) }} - <p>{% trans %}Date: {% endtrans %}{{ file.date|localtime|date(DATETIME_FORMAT) }} -
{{ file.date|localtime|time(DATETIME_FORMAT) }}</p> {{ file.date|localtime|time(DATETIME_FORMAT) }}</p>
<p>{% trans %}Type: {% endtrans %}{{ file.mime_type }}</p> <p>{% trans %}Type: {% endtrans %}{{ file.mime_type }}</p>
<p>{% trans %}Size: {% endtrans %}{{ file.size }} {% trans %}bytes{% endtrans %}</p> <p>{% trans %}Size: {% endtrans %}{{ file.size }} {% trans %}bytes{% endtrans %}</p>
<p><a href="{{ url('core:download', file_id=file.id) }}">{% trans %}Download{% endtrans %}</a></p> <p><a href="{{ url('core:download', file_id=file.id) }}">{% trans %}Download{% endtrans %}</a></p>
{% endif %} {% endif %}
{% if not file.home_of and not file.home_of_club and file.parent %} {% if not file.home_of and not file.home_of_club and file.parent %}
<p><a href="{{ url('core:file_delete', file_id=file.id, popup=popup) }}">{% trans %}Delete{% endtrans %}</a></p> <p><a href="{{ url('core:file_delete', file_id=file.id, popup=popup) }}">{% trans %}Delete{% endtrans %}</a></p>
{% endif %} {% endif %}
{% if user.is_com_admin %} {% if user.is_com_admin %}
<p><a href="{{ url('core:file_moderate', file_id=file.id) }}">{% trans %}Moderate{% endtrans %}</a></p> <p><a href="{{ url('core:file_moderate', file_id=file.id) }}">{% trans %}Moderate{% endtrans %}</a></p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block script %} {% block script %}
{{ super() }} {{ super() }}
<script> <script>
{% if popup and file.is_file %} {% if popup and file.is_file %}
parent.$("#file_id").replaceWith('<div id="file_id" value="{{ file.id }}">{{ file.name }}</div>'); parent.$("#file_id").replaceWith('<div id="file_id" value="{{ file.id }}">{{ file.name }}</div>');
parent.$(".ui-dialog-buttonpane button").button("option", "disabled", false); parent.$(".ui-dialog-buttonpane button").button("option", "disabled", false);
{% endif %} {% endif %}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,12 +1,12 @@
{% extends "core/file.jinja" %} {% extends "core/file.jinja" %}
{% block file %} {% block file %}
<h2>{% trans obj=object %}Edit {{ obj }}{% endtrans %}</h2> <h2>{% trans obj=object %}Edit {{ obj }}{% endtrans %}</h2>
<form action="" method="post"> <form action="" method="post">
{% csrf_token %} {% csrf_token %}
{{ form.as_p() }} {{ form.as_p() }}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -1,10 +1,10 @@
{% extends "core/file.jinja" %} {% extends "core/file.jinja" %}
{% block content %} {% block content %}
{{ super() }} {{ super() }}
{% if file_list %} {% if file_list %}
<h3>{% trans %}File list{% endtrans %}</h3> <h3>{% trans %}File list{% endtrans %}</h3>
<ul> <ul>
{% for f in file_list %} {% for f in file_list %}
<li style="list-style-type: none;"> <li style="list-style-type: none;">
{% if f.is_folder %} {% if f.is_folder %}
@ -14,10 +14,10 @@
{% endif %} {% endif %}
<a href="{{ url('core:file_detail', file_id=f.id, popup=popup) }}">{{ f.name }}</a></li> <a href="{{ url('core:file_detail', file_id=f.id, popup=popup) }}">{{ f.name }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% else %} {% else %}
<p>{% trans %}There is no file in this website.{% endtrans %}</p> <p>{% trans %}There is no file in this website.{% endtrans %}</p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -1,12 +1,12 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}File moderation{% endtrans %} {% trans %}File moderation{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h3>{% trans %}File moderation{% endtrans %}</h3> <h3>{% trans %}File moderation{% endtrans %}</h3>
<div> <div>
{% for f in files %} {% for f in files %}
<div style="margin: 2px; padding: 2px; border: solid 1px red; text-align: center"> <div style="margin: 2px; padding: 2px; border: solid 1px red; text-align: center">
{% if f.is_folder %} {% if f.is_folder %}
@ -24,5 +24,5 @@
<a href="{{ url('core:file_delete', file_id=f.id) }}?next={{ url('core:file_moderation') }}">{% trans %}Delete{% endtrans %}</a></p> <a href="{{ url('core:file_delete', file_id=f.id) }}?next={{ url('core:file_moderation') }}">{% trans %}Delete{% endtrans %}</a></p>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,13 +1,13 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block content %} {% block content %}
<p><a href="{{ url('core:group_list') }}">{% trans %}Back to list{% endtrans %}</a></p> <p><a href="{{ url('core:group_list') }}">{% trans %}Back to list{% endtrans %}</a></p>
<h2>{% trans %}Edit group{% endtrans %}</h2> <h2>{% trans %}Edit group{% endtrans %}</h2>
<form action="" method="post"> <form action="" method="post">
{% csrf_token %} {% csrf_token %}
{{ form.as_p() }} {{ form.as_p() }}
<p><input type="submit" value="{% trans %}Update{% endtrans %}" /></p> <p><input type="submit" value="{% trans %}Update{% endtrans %}" /></p>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -1,13 +1,13 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% block title %} {% block title %}
{% trans %}Group list{% endtrans %} {% trans %}Group list{% endtrans %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h3>{% trans %}Group list{% endtrans %}</h3> <h3>{% trans %}Group list{% endtrans %}</h3>
<p><a href="{{ url('core:group_new') }}">{% trans %}New group{% endtrans %}</a></p> <p><a href="{{ url('core:group_new') }}">{% trans %}New group{% endtrans %}</a></p>
<table> <table>
<thead> <thead>
<tr> <tr>
<td>{% trans %}ID{% endtrans %}</td> <td>{% trans %}ID{% endtrans %}</td>
@ -26,6 +26,6 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}

View File

@ -1,15 +1,15 @@
{% macro user_profile_link(user) -%} {% macro user_profile_link(user) -%}
<a href="{{ url("core:user_profile", user_id=user.id) }}">{{ user.get_display_name() }}</a> <a href="{{ url("core:user_profile", user_id=user.id) }}">{{ user.get_display_name() }}</a>
{%- endmacro %} {%- endmacro %}
{% macro user_profile_link_short_name(user) -%} {% macro user_profile_link_short_name(user) -%}
<a href="{{ url("core:user_profile", user_id=user.id) }}">{{ user.get_short_name() }}</a> <a href="{{ url("core:user_profile", user_id=user.id) }}">{{ user.get_short_name() }}</a>
{%- endmacro %} {%- endmacro %}
{% macro user_link_with_pict(user) -%} {% macro user_link_with_pict(user) -%}
<a href="{{ url("core:user_profile", user_id=user.id) }}" class="mini_profile_link" > <a href="{{ url("core:user_profile", user_id=user.id) }}" class="mini_profile_link" >
{{ user.get_mini_item()|safe }} {{ user.get_mini_item()|safe }}
</a> </a>
{%- endmacro %} {%- endmacro %}
{% macro link_news_logo(news) -%} {% macro link_news_logo(news) -%}
@ -21,34 +21,34 @@
{%- endmacro %} {%- endmacro %}
{% macro gen_news_metatags(news) -%} {% macro gen_news_metatags(news) -%}
<meta name="twitter:card" content="summary" /> <meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="{{ settings.SITH_TWITTER }}" /> <meta name="twitter:site" content="{{ settings.SITH_TWITTER }}" />
<meta name="twitter:creator" content= "{{ settings.SITH_TWITTER }}" /> <meta name="twitter:creator" content= "{{ settings.SITH_TWITTER }}" />
<meta property="og:url" content="{{ news.get_full_url() }}" /> <meta property="og:url" content="{{ news.get_full_url() }}" />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:title" content="{{ news.title }}" /> <meta property="og:title" content="{{ news.title }}" />
<meta property="og:description" content="{{ news.summary }}" /> <meta property="og:description" content="{{ news.summary }}" />
<meta property="og:image" content="{{ "https://%s%s" % (settings.SITH_URL, link_news_logo(news)) }}" /> <meta property="og:image" content="{{ "https://%s%s" % (settings.SITH_URL, link_news_logo(news)) }}" />
{%- endmacro %} {%- endmacro %}
{% macro facebook_share(news) -%} {% macro facebook_share(news) -%}
<a rel="nofollow" target="#" class="share_button facebook" href="https://www.facebook.com/sharer/sharer.php?u={{ news.get_full_url() }}">{% trans %}Share on Facebook{% endtrans %}</a> <a rel="nofollow" target="#" class="share_button facebook" href="https://www.facebook.com/sharer/sharer.php?u={{ news.get_full_url() }}">{% trans %}Share on Facebook{% endtrans %}</a>
{%- endmacro %} {%- endmacro %}
{% macro tweet(news) -%} {% macro tweet(news) -%}
<a rel="nofollow" target="#" class="share_button twitter" href="https://twitter.com/intent/tweet?text={{ news.get_full_url() }}">{% trans %}Tweet{% endtrans %}</a> <a rel="nofollow" target="#" class="share_button twitter" href="https://twitter.com/intent/tweet?text={{ news.get_full_url() }}">{% trans %}Tweet{% endtrans %}</a>
{%- endmacro %} {%- endmacro %}
{% macro fb_quick(news) -%} {% macro fb_quick(news) -%}
<a rel="nofollow" target="#" href="https://www.facebook.com/sharer/sharer.php?u={{ news.get_full_url() }}" class="fb fa fa-facebook-square fa-2x"></a> <a rel="nofollow" target="#" href="https://www.facebook.com/sharer/sharer.php?u={{ news.get_full_url() }}" class="fb fa fa-facebook-square fa-2x"></a>
{%- endmacro %} {%- endmacro %}
{% macro tweet_quick(news) -%} {% macro tweet_quick(news) -%}
<a rel="nofollow" target="#" href="https://twitter.com/intent/tweet?text={{ news.get_full_url() }}" class="twitter fa fa-twitter-square fa-2x"></a> <a rel="nofollow" target="#" href="https://twitter.com/intent/tweet?text={{ news.get_full_url() }}" class="twitter fa fa-twitter-square fa-2x"></a>
{%- endmacro %} {%- endmacro %}
{% macro user_mini_profile(user) %} {% macro user_mini_profile(user) %}
<div class="user_mini_profile"> <div class="user_mini_profile">
<div class="user_mini_profile_infos"> <div class="user_mini_profile_infos">
<div class="user_mini_profile_infos_text"> <div class="user_mini_profile_infos_text">
<div class="user_mini_profile_name">{{ user.get_full_name() }}</div> <div class="user_mini_profile_name">{{ user.get_full_name() }}</div>
@ -78,7 +78,7 @@
title="{% trans %}Profile{% endtrans %}" /> title="{% trans %}Profile{% endtrans %}" />
{% endif %} {% endif %}
</div> </div>
</div> </div>
{%- endmacro %} {%- endmacro %}
{% macro user_subscription(user) %} {% macro user_subscription(user) %}

Some files were not shown because too many files have changed in this diff Show More