Compare commits
8 Commits
d1fc279963
...
b7943cd53f
Author | SHA1 | Date |
---|---|---|
Frédéric Péters | b7943cd53f | |
Frédéric Péters | a9bab7fa0b | |
Lauréline Guérin | 39dbdabcd3 | |
Valentin Deniaud | 2b824e5b5a | |
Valentin Deniaud | a955c9702a | |
Valentin Deniaud | 42c8c46981 | |
Corentin Sechet | c6ad492272 | |
Corentin Sechet | d65d2e0e2f |
|
@ -0,0 +1,131 @@
|
|||
env:
|
||||
browser: true
|
||||
es2017: true
|
||||
extends: eslint:recommended
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
ignorePatterns:
|
||||
- "gadjo/static/js/gadjo.js"
|
||||
rules:
|
||||
# Follow Standard JS guidelines : https://standardjs.com/rules.html, except rules
|
||||
# annotated with a 'custom' comment
|
||||
|
||||
# Linting
|
||||
array-callback-return: error
|
||||
constructor-super: error
|
||||
eqeqeq: [error, always, {null: ignore}]
|
||||
handle-callback-err: error
|
||||
no-array-constructor: error
|
||||
no-caller: error
|
||||
no-class-assign: error
|
||||
no-cond-assign: error
|
||||
no-const-assign: error
|
||||
no-control-regex: error
|
||||
no-debugger: error
|
||||
no-delete-var: error
|
||||
no-dupe-args: error
|
||||
no-dupe-class-members: error
|
||||
no-dupe-keys: error
|
||||
no-duplicate-case: error
|
||||
no-duplicate-imports: error
|
||||
no-empty-character-class: error
|
||||
no-empty-pattern: error
|
||||
no-eval: error
|
||||
no-ex-assign: error
|
||||
no-extend-native: error
|
||||
no-extra-boolean-cast: error
|
||||
no-fallthrough: error
|
||||
no-func-assign: error
|
||||
no-global-assign: error
|
||||
no-implied-eval: error
|
||||
no-inner-declarations: error
|
||||
no-invalid-regexp: error
|
||||
no-iterator: error
|
||||
no-labels: error
|
||||
no-new-func: error
|
||||
no-new-object: error
|
||||
no-new-require: error
|
||||
no-new-symbol: error
|
||||
no-new-wrappers: error
|
||||
no-new: error
|
||||
no-obj-calls: error
|
||||
no-octal-escape: error
|
||||
no-octal: error
|
||||
no-proto: error
|
||||
no-redeclare: error
|
||||
no-regex-spaces: error
|
||||
no-return-assign: error
|
||||
no-self-assign: error
|
||||
no-self-compare: error
|
||||
no-sequences: error
|
||||
no-shadow-restricted-names: error
|
||||
no-sparse-arrays: error
|
||||
no-template-curly-in-string: error
|
||||
no-this-before-super: error
|
||||
no-throw-literal: error
|
||||
no-undef: error
|
||||
no-unexpected-multiline: error
|
||||
no-unmodified-loop-condition: error
|
||||
no-unneeded-ternary: error
|
||||
no-unreachable: error
|
||||
no-unsafe-finally: error
|
||||
no-unsafe-negation: error
|
||||
no-unused-vars: error
|
||||
no-use-before-define: [error, {functions: false, variables: false, classes: false}]
|
||||
no-useless-call: error
|
||||
no-useless-computed-key: error
|
||||
no-useless-constructor: error
|
||||
no-useless-escape: error
|
||||
no-var: error
|
||||
no-with: error
|
||||
use-isnan: error
|
||||
valid-typeof: error
|
||||
|
||||
# Style / Formatting
|
||||
accessor-pairs: error
|
||||
block-spacing: error
|
||||
brace-style: [error, 1tbs, {allowSingleLine: true}]
|
||||
camelcase: error
|
||||
comma-dangle: [error, always-multiline] # custom : Adding a dangling comma make patches shorter
|
||||
comma-spacing: error
|
||||
comma-style: error
|
||||
curly: [error, multi-line]
|
||||
dot-location: [error, property]
|
||||
eol-last: [error, always]
|
||||
func-call-spacing: error
|
||||
indent: [error, 2]
|
||||
key-spacing: error
|
||||
keyword-spacing: error
|
||||
max-len: [error, {code: 110}] # custom: configured like this on python projects
|
||||
new-cap: [error, { newIsCap: true, capIsNew: false}]
|
||||
new-parens: error
|
||||
no-extra-parens: [error, functions]
|
||||
no-floating-decimal: error
|
||||
no-irregular-whitespace: error
|
||||
no-lone-blocks: error
|
||||
no-mixed-spaces-and-tabs: error
|
||||
no-multi-spaces: error
|
||||
no-multi-str: error
|
||||
no-multiple-empty-lines: error
|
||||
no-tabs: error
|
||||
no-trailing-spaces: error
|
||||
no-undef-init: error
|
||||
no-useless-rename: error
|
||||
no-whitespace-before-property: error
|
||||
object-property-newline: [error, { allowMultiplePropertiesPerLine: true }]
|
||||
one-var: [error, never]
|
||||
operator-linebreak: [error, before]
|
||||
padded-blocks: [error, never]
|
||||
quotes: [error, single]
|
||||
rest-spread-spacing: error
|
||||
semi-spacing: error
|
||||
semi: [error, never]
|
||||
space-before-function-paren: error
|
||||
space-in-parens: error
|
||||
space-infix-ops: error
|
||||
space-unary-ops: error
|
||||
spaced-comment: error
|
||||
template-curly-spacing: error
|
||||
wrap-iife: [error, any]
|
||||
yield-star-spacing: [error, {after: true, before: true}]
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
*.pyc
|
||||
*.swp
|
||||
.eggs
|
||||
/dist
|
||||
/gadjo.egg-info
|
||||
/gadjo/locale/fr/LC_MESSAGES/django.mo
|
||||
/gadjo/static/css/gadjo.css
|
||||
/gadjo/static/css/gadjo.multiselectwidget.css
|
||||
/gadjo/static/css/icons
|
||||
node_modules
|
||||
MANIFEST
|
||||
build
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: ['--target-version', 'py37', '--skip-string-normalization', '--line-length', '110']
|
||||
|
@ -12,12 +12,12 @@ repos:
|
|||
- id: isort
|
||||
args: ['--profile', 'black', '--line-length', '110']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.1.0
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ['--keep-percent-format', '--py37-plus']
|
||||
args: ['--keep-percent-format', '--py39-plus']
|
||||
- repo: https://github.com/rtts/djhtml
|
||||
rev: 'v1.5.2'
|
||||
rev: '3.0.6'
|
||||
hooks:
|
||||
- id: djhtml
|
||||
args: ['--tabwidth', '2']
|
||||
|
@ -25,3 +25,10 @@ repos:
|
|||
rev: v0.1
|
||||
hooks:
|
||||
- id: pre-commit-debian
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v8.36.0
|
||||
hooks:
|
||||
- id: eslint
|
||||
files: \.m?js$
|
||||
types: [file]
|
||||
args: [--fix]
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import django
|
||||
from django import forms
|
||||
|
||||
|
||||
class MultiSelectWidget(forms.MultiWidget):
|
||||
template_name = 'gadjo/widgets/multiselectwidget.html'
|
||||
|
||||
class Media:
|
||||
js = ('js/gadjo.multiselectwidget.js',)
|
||||
css = {'all': ('css/gadjo.multiselectwidget.css',)}
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
self.attrs = attrs
|
||||
widgets = [forms.Select(attrs=attrs)]
|
||||
super().__init__(widgets, attrs)
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
|
||||
self.widgets = []
|
||||
for _ in range(max(len(value), 1)):
|
||||
self.widgets.append(forms.Select(attrs=self.attrs, choices=self.choices))
|
||||
|
||||
# all subwidgets must have the same name
|
||||
if django.VERSION >= (3, 1):
|
||||
self.widgets_names = [''] * len(self.widgets)
|
||||
return super().get_context(name, value, attrs)
|
||||
else:
|
||||
context = super().get_context(name, value, attrs)
|
||||
subwidgets = context['widget']['subwidgets']
|
||||
for widget in subwidgets:
|
||||
widget['name'] = widget['name'].rsplit('_', 1)[0]
|
||||
return context
|
||||
|
||||
def decompress(self, value):
|
||||
return value or []
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
values = [x for x in data.getlist(name) if x]
|
||||
|
||||
# remove duplicates while keeping order
|
||||
return list(dict.fromkeys(values))
|
||||
|
||||
def id_for_label(self, id_):
|
||||
return id_
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gadjo 0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-08-21 06:03+0000\n"
|
||||
"PO-Revision-Date: 2020-08-21 08:03+0200\n"
|
||||
"POT-Creation-Date: 2023-02-25 18:07+0100\n"
|
||||
"PO-Revision-Date: 2023-02-25 18:08+0100\n"
|
||||
"Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n"
|
||||
"Language: French\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -25,36 +25,26 @@ msgstr "Il y a eu un problème à la validation du formulaire."
|
|||
msgid "(Hidden field %(name)s) %(error)s"
|
||||
msgstr "(champ caché %(name)s) %(error)s"
|
||||
|
||||
#: templates/gadjo/root.html:36
|
||||
#: templates/gadjo/password-widget.html:8
|
||||
msgid "Display password"
|
||||
msgstr "Afficher le mot de passe"
|
||||
|
||||
#: templates/gadjo/password-widget.html:9
|
||||
msgid "Display"
|
||||
msgstr "Afficher"
|
||||
|
||||
#: templates/gadjo/root.html:39
|
||||
msgid "Logout"
|
||||
msgstr "Déconnexion"
|
||||
|
||||
#: templates/gadjo/root.html:54
|
||||
msgid ""
|
||||
"\n"
|
||||
" <p><strong>Do you know your web browser is obsolete?</strong> We "
|
||||
"recommend\n"
|
||||
" you to <a href=\"http://windows.microsoft.com/en-us/internet-explorer/"
|
||||
"download-ie\">update\n"
|
||||
" your web browser</a> or to <a href=\"http://browsehappy.com/\">use\n"
|
||||
" different web browsers</a> as some features may not work.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" <p><strong>Savez-vous que votre navigateur est obsolète ?</strong> Nous "
|
||||
"vous recommandons de <a href=\"http://windows.microsoft.com/en-us/internet-"
|
||||
"explorer/download-ie\">mettre à jour votre navigateur</a> ou d'<a href="
|
||||
"\"http://browsehappy.com/\">utiliser un navigateur différent</a>.\n"
|
||||
" "
|
||||
|
||||
#: templates/gadjo/root.html:70
|
||||
#: templates/gadjo/root.html:60
|
||||
msgid "Homepage"
|
||||
msgstr "Accueil"
|
||||
|
||||
#: templates/gadjo/widget.html:11
|
||||
#: templates/gadjo/widget.html:12
|
||||
msgid "This field is required."
|
||||
msgstr "Ce champ est obligatoire."
|
||||
|
||||
#: templates/gadjo/widget.html:13
|
||||
#: templates/gadjo/widget.html:14
|
||||
msgid "(optional)"
|
||||
msgstr "(optionnel)"
|
||||
|
|
|
@ -703,3 +703,23 @@ div.godo--editor {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gadjo-password-field {
|
||||
position: relative;
|
||||
.title label {
|
||||
padding-right: 6em;
|
||||
}
|
||||
}
|
||||
|
||||
.password-visibility-checkbox {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
input + label {
|
||||
margin: 0;
|
||||
}
|
||||
input {
|
||||
margin: 0 0.25em 0 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
.gadjo-multi-select-widget {
|
||||
&--field {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
&--select-button-container {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
&--field {
|
||||
select {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
&:first-of-type .gadjo-multi-select-widget--button-remove {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--button-add::before {
|
||||
content: "\f067"; /* plus */
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
|
||||
&--button-remove::before {
|
||||
content: "\f068"; /* minus */
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
const multiSelectWidget = (function () {
|
||||
const addRow = function () {
|
||||
const widget = this.closest('.gadjo-multi-select-widget')
|
||||
event.preventDefault()
|
||||
|
||||
/* get last row node */
|
||||
const rows = widget.querySelectorAll('.gadjo-multi-select-widget--field')
|
||||
const lastRow = rows[rows.length - 1]
|
||||
|
||||
/* clone the row */
|
||||
const newRow = lastRow.cloneNode(true)
|
||||
|
||||
/* set new label and ids */
|
||||
const rowLabel = widget.dataset.rowLabel
|
||||
const newLabel = rowLabel + ' ' + rows.length
|
||||
newRow.querySelector('label').textContent = newLabel
|
||||
|
||||
const rowId = widget.dataset.rowId
|
||||
const newId = rowId + '_' + rows.length
|
||||
newRow.querySelector('label').setAttribute('for', newId)
|
||||
newRow.querySelector('select').setAttribute('id', newId)
|
||||
|
||||
/* add new row after the last row */
|
||||
lastRow.parentNode.insertBefore(newRow, lastRow.nextSibling)
|
||||
|
||||
const removeButton = newRow.querySelector('.gadjo-multi-select-widget--button-remove')
|
||||
removeButton.addEventListener('click', removeRow)
|
||||
}
|
||||
|
||||
const removeRow = function (event) {
|
||||
event.preventDefault()
|
||||
const field = this.closest('.content')
|
||||
let row = this.closest('.gadjo-multi-select-widget--field')
|
||||
row.remove()
|
||||
field.dispatchEvent(new Event('change'))
|
||||
}
|
||||
|
||||
const init = function (container) {
|
||||
const widgets = container.querySelectorAll('.gadjo-multi-select-widget')
|
||||
if (!widgets.length) return
|
||||
|
||||
widgets.forEach(function (widget) {
|
||||
const deletBtn = widget.querySelector('.gadjo-multi-select-widget--button-remove')
|
||||
const addBtn = widget.querySelector('.gadjo-multi-select-widget--button-add')
|
||||
|
||||
addBtn.removeEventListener('click', addRow)
|
||||
addBtn.addEventListener('click', addRow)
|
||||
deletBtn.removeEventListener('click', removeRow)
|
||||
deletBtn.addEventListener('click', removeRow)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
init,
|
||||
}
|
||||
})()
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => multiSelectWidget.init(document))
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "gadjo/widget.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block widget-css-classes %}{{ block.super }} gadjo-password-field{% endblock %}
|
||||
|
||||
{% block widget-bottom %}
|
||||
<div class="password-visibility-checkbox">
|
||||
<input id="password-visibility-checkbox-{{ field.id_for_label }}" type="checkbox" aria-label="{% trans "Display password" %}">
|
||||
<label for="password-visibility-checkbox-{{ field.id_for_label }}">{% trans "Display" %}</label>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
const checkbox = document.getElementById('password-visibility-checkbox-{{ field.id_for_label }}');
|
||||
const password_input = document.querySelector('#{{field.id_for_label}}_p input[type=password]');
|
||||
checkbox.addEventListener('change', function(e) {
|
||||
if (this.checked) {
|
||||
password_input.type = 'text';
|
||||
} else {
|
||||
password_input.type = 'password';
|
||||
}
|
||||
});
|
||||
checkbox.checked = false; // force to be hidden on load
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -36,7 +36,7 @@
|
|||
<li class="ui-avatar">{{ user.get_full_name|slice:":1" }}</li>
|
||||
<li class="ui-name">{% block user-name %}{{ user.get_full_name }}{% endblock %}</li>
|
||||
<li class="ui-logout"><a href="{% block logout-url %}index.html{% endblock %}"
|
||||
title="{% trans "Logout" %}"><span class="sr-only">{% trans "Logout" %}</span></a></li>
|
||||
title="{% trans "Logout" %}"><span class="sr-only">{% trans "Logout" %}</span></a></li>
|
||||
{% endif %}
|
||||
{% block help-link %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{% load gadjo i18n %}
|
||||
<div class="widget
|
||||
{{ field.css_classes }}
|
||||
django-{{ field|field_class_name }}
|
||||
{% if field.errors %}widget-with-error{% endif %}
|
||||
{% if field.field.required %}widget-required{% else %}widget-optional{% endif %}"
|
||||
{% if field.id_for_label %}id="{{field.id_for_label}}_p"{% endif %}>
|
||||
<div class="{% block widget-css-classes %}widget
|
||||
{{ field.css_classes }}
|
||||
django-{{ field|field_class_name }}
|
||||
{% if field.errors %}widget-with-error{% endif %}
|
||||
{% if field.field.required %}widget-required{% else %}widget-optional{% endif %}{% endblock %}"
|
||||
{% if field.id_for_label %}id="{{field.id_for_label}}_p"{% endif %}>
|
||||
{% block widget-title %}
|
||||
<div class="title" {% if field.id_for_label %}id="{{ field.id_for_label }}_title"{% endif %}>
|
||||
{{ field.label_tag }}
|
||||
|
@ -17,8 +17,8 @@
|
|||
{% endblock %}
|
||||
{% block widget-content %}
|
||||
<div class="content"
|
||||
{% if field.id_for_label %}aria-labelledby="{{ field.id_for_label }}_title"{% endif %}
|
||||
{% block widget-attrs %}{% endblock %}>
|
||||
{% if field.id_for_label %}aria-labelledby="{{ field.id_for_label }}_title"{% endif %}
|
||||
{% block widget-attrs %}{% endblock %}>
|
||||
{% block widget-error %}
|
||||
{% if field.errors %}
|
||||
<div class="error"><p>
|
||||
|
@ -36,6 +36,7 @@
|
|||
<div class="hint">{{ field.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block widget-bottom %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{% load i18n %}
|
||||
|
||||
<div class="gadjo-multi-select-widget" data-row-id="{{ widget.name }}" data-row-label="{% trans "Value" %}">
|
||||
<div class="gadjo-multi-select-widget--fields" role="group" aria-labelledby="{{ widget.name }}_title">
|
||||
{% for widget in widget.subwidgets %}
|
||||
<div class="gadjo-multi-select-widget--field">
|
||||
<label for="{{ widget.name }}_{{ forloop.counter }}" class="sr-only">{% trans "Value" %} {{ forloop.counter }}</label>
|
||||
<div class="gadjo-multi-select-widget--select-button-container">
|
||||
{% include widget.template_name %}
|
||||
<button type="button" name="{{ widget.name }}$remove_element" class="gadjo-multi-select-widget--button-remove" title="{% trans "Remove" %}" aria-label="{% trans "Remove value" %} {{ forloop.counter }}"></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<button type="button" name="{{ widget.name }}$add_element" class="gadjo-multi-select-widget--button-add" title="{% trans "Add" %}" aria-label="{% trans "Add" %}"></button>
|
||||
</div>
|
Loading…
Reference in New Issue