Compare commits

..

8 Commits

Author SHA1 Message Date
Frédéric Péters b7943cd53f translation update
gitea/gadjo/pipeline/head There was a failure building this commit Details
2023-04-03 17:45:47 +02:00
Frédéric Péters a9bab7fa0b general: add reveal checkbox to password fields (#74652) 2023-04-03 17:45:47 +02:00
Lauréline Guérin 39dbdabcd3
widgets: add MultiSelectWidget (#75655)
gitea/gadjo/pipeline/head This commit looks good Details
from combo.apps.dataviz
2023-03-31 16:25:14 +02:00
Valentin Deniaud 2b824e5b5a misc: bump djhtml version (#75442)
gitea/gadjo/pipeline/head This commit looks good Details
2023-03-29 14:46:58 +02:00
Valentin Deniaud a955c9702a misc: bump black version (#75442) 2023-03-29 14:46:58 +02:00
Valentin Deniaud 42c8c46981 misc: change pyupgrade target version to 3.9 (#75442) 2023-03-29 14:46:58 +02:00
Corentin Sechet c6ad492272 js: configure javascript linter (#75132)
gitea/gadjo/pipeline/head This commit looks good Details
2023-03-28 11:08:01 +02:00
Corentin Sechet d65d2e0e2f misc: update .gitignore (#75216)
gitea/gadjo/pipeline/head This commit looks good Details
2023-03-07 18:00:09 +01:00
10 changed files with 313 additions and 12 deletions

131
.eslintrc.yml Normal file
View File

@ -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}]

7
.gitignore vendored
View File

@ -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

View File

@ -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
gadjo/forms/__init__.py Normal file
View File

46
gadjo/forms/widgets.py Normal file
View File

@ -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_

View File

@ -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;
}
}

View File

@ -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))

View File

@ -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 %}

View File

@ -1,10 +1,10 @@
{% load gadjo i18n %}
<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 %}>
{{ 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>

View File

@ -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>