mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-29 01:42:37 +02:00
Added support to split messages #133
This commit is contained in:
parent
37f2e38af3
commit
5e37e33716
6 changed files with 407 additions and 1 deletions
|
@ -239,7 +239,7 @@ class MemberForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class InviteForm(FlaskForm):
|
class InviteForm(FlaskForm):
|
||||||
emails = TextAreaField(_("People to notify"))
|
emails = StringField(_("People to notify"), render_kw={"class": "tag"})
|
||||||
submit = SubmitField(_("Send invites"))
|
submit = SubmitField(_("Send invites"))
|
||||||
|
|
||||||
def validate_emails(form, field):
|
def validate_emails(form, field):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@import "bootstrap.min.css";
|
@import "bootstrap.min.css";
|
||||||
|
@import "tagsinput.css";
|
||||||
@import "bootstrap-datepicker3.standalone.css";
|
@import "bootstrap-datepicker3.standalone.css";
|
||||||
@import "../fonts/fontfaces.css";
|
@import "../fonts/fontfaces.css";
|
||||||
|
|
||||||
|
|
16
ihatemoney/static/css/tagsinput.css
Normal file
16
ihatemoney/static/css/tagsinput.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.tagsinput,.tagsinput *{box-sizing:border-box}
|
||||||
|
.tagsinput{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;background:#fff;font-family:sans-serif;font-size:14px;line-height:20px;color:#556270;padding:5px 5px 0;border:1px solid #e6e6e6;border-radius:2px}
|
||||||
|
.tagsinput.focus{border-color:#ccc}
|
||||||
|
.tagsinput .tag{position:relative;background:#556270;display:block;max-width:100%;word-wrap:break-word;color:#fff;padding:5px 30px 5px 5px;border-radius:2px;margin:0 5px 5px 0}
|
||||||
|
.tagsinput .tag .tag-remove{position:absolute;background:0 0;display:block;width:30px;height:30px;top:0;right:0;cursor:pointer;text-decoration:none;text-align:center;color:#ff6b6b;line-height:30px;padding:0;border:0}
|
||||||
|
.tagsinput .tag .tag-remove:after,.tagsinput .tag .tag-remove:before{background:#ff6b6b;position:absolute;display:block;width:10px;height:2px;top:14px;left:10px;content:''}
|
||||||
|
.tagsinput .tag .tag-remove:before{-webkit-transform:rotateZ(45deg);transform:rotateZ(45deg)}
|
||||||
|
.tagsinput .tag .tag-remove:after{-webkit-transform:rotateZ(-45deg);transform:rotateZ(-45deg)}
|
||||||
|
.tagsinput div{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}
|
||||||
|
.tagsinput div input{background:0 0;display:block;width:100%;font-size:14px;line-height:20px;padding:5px;border:0;margin:0 5px 5px 0}
|
||||||
|
.tagsinput div input.error{color:#ff6b6b}
|
||||||
|
.tagsinput div input::-ms-clear{display:none}
|
||||||
|
.tagsinput div input::-webkit-input-placeholder{color:#ccc;opacity:1}
|
||||||
|
.tagsinput div input:-moz-placeholder{color:#ccc;opacity:1}
|
||||||
|
.tagsinput div input::-moz-placeholder{color:#ccc;opacity:1}
|
||||||
|
.tagsinput div input:-ms-input-placeholder{color:#ccc;opacity:1}
|
381
ihatemoney/static/js/tagsinput.js
Normal file
381
ihatemoney/static/js/tagsinput.js
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
//Credits https://bootsnipp.com/snippets/exqd3
|
||||||
|
/* jQuery Tags Input Revisited Plugin
|
||||||
|
*
|
||||||
|
* Copyright (c) Krzysztof Rusnarczyk
|
||||||
|
* Licensed under the MIT license */
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
var delimiter = [];
|
||||||
|
var inputSettings = [];
|
||||||
|
var callbacks = [];
|
||||||
|
|
||||||
|
$.fn.addTag = function(value, options) {
|
||||||
|
options = jQuery.extend({
|
||||||
|
focus: false,
|
||||||
|
callback: true
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
this.each(function() {
|
||||||
|
var id = $(this).attr('id');
|
||||||
|
|
||||||
|
var tagslist = $(this).val().split(_getDelimiter(delimiter[id]));
|
||||||
|
if (tagslist[0] === '') tagslist = [];
|
||||||
|
|
||||||
|
value = jQuery.trim(value);
|
||||||
|
|
||||||
|
if ((inputSettings[id].unique && $(this).tagExist(value)) || !_validateTag(value, inputSettings[id], tagslist, delimiter[id])) {
|
||||||
|
$('#' + id + '_tag').addClass('error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('<span>', {class: 'tag'}).append(
|
||||||
|
$('<span>', {class: 'tag-text'}).text(value),
|
||||||
|
$('<button>', {class: 'tag-remove'}).click(function() {
|
||||||
|
return $('#' + id).removeTag(encodeURI(value));
|
||||||
|
})
|
||||||
|
).insertBefore('#' + id + '_addTag');
|
||||||
|
|
||||||
|
tagslist.push(value);
|
||||||
|
|
||||||
|
$('#' + id + '_tag').val('');
|
||||||
|
if (options.focus) {
|
||||||
|
$('#' + id + '_tag').focus();
|
||||||
|
} else {
|
||||||
|
$('#' + id + '_tag').blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.tagsInput.updateTagsField(this, tagslist);
|
||||||
|
|
||||||
|
if (options.callback && callbacks[id] && callbacks[id]['onAddTag']) {
|
||||||
|
var f = callbacks[id]['onAddTag'];
|
||||||
|
f.call(this, this, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callbacks[id] && callbacks[id]['onChange']) {
|
||||||
|
var i = tagslist.length;
|
||||||
|
var f = callbacks[id]['onChange'];
|
||||||
|
f.call(this, this, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.removeTag = function(value) {
|
||||||
|
value = decodeURI(value);
|
||||||
|
|
||||||
|
this.each(function() {
|
||||||
|
var id = $(this).attr('id');
|
||||||
|
|
||||||
|
var old = $(this).val().split(_getDelimiter(delimiter[id]));
|
||||||
|
|
||||||
|
$('#' + id + '_tagsinput .tag').remove();
|
||||||
|
|
||||||
|
var str = '';
|
||||||
|
for (i = 0; i < old.length; ++i) {
|
||||||
|
if (old[i] != value) {
|
||||||
|
str = str + _getDelimiter(delimiter[id]) + old[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.tagsInput.importTags(this, str);
|
||||||
|
|
||||||
|
if (callbacks[id] && callbacks[id]['onRemoveTag']) {
|
||||||
|
var f = callbacks[id]['onRemoveTag'];
|
||||||
|
f.call(this, this, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tagExist = function(val) {
|
||||||
|
var id = $(this).attr('id');
|
||||||
|
var tagslist = $(this).val().split(_getDelimiter(delimiter[id]));
|
||||||
|
return (jQuery.inArray(val, tagslist) >= 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.importTags = function(str) {
|
||||||
|
var id = $(this).attr('id');
|
||||||
|
$('#' + id + '_tagsinput .tag').remove();
|
||||||
|
$.fn.tagsInput.importTags(this, str);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tagsInput = function(options) {
|
||||||
|
var settings = jQuery.extend({
|
||||||
|
interactive: true,
|
||||||
|
placeholder: '',
|
||||||
|
minChars: 0,
|
||||||
|
maxChars: null,
|
||||||
|
limit: null,
|
||||||
|
validationPattern: null,
|
||||||
|
width: 'auto',
|
||||||
|
height: 'auto',
|
||||||
|
autocomplete: null,
|
||||||
|
hide: true,
|
||||||
|
delimiter: ',',
|
||||||
|
unique: true,
|
||||||
|
removeWithBackspace: true
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
var uniqueIdCounter = 0;
|
||||||
|
|
||||||
|
this.each(function() {
|
||||||
|
if (typeof $(this).data('tagsinput-init') !== 'undefined') return;
|
||||||
|
|
||||||
|
$(this).data('tagsinput-init', true);
|
||||||
|
|
||||||
|
if (settings.hide) $(this).hide();
|
||||||
|
|
||||||
|
var id = $(this).attr('id');
|
||||||
|
if (!id || _getDelimiter(delimiter[$(this).attr('id')])) {
|
||||||
|
id = $(this).attr('id', 'tags' + new Date().getTime() + (++uniqueIdCounter)).attr('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = jQuery.extend({
|
||||||
|
pid: id,
|
||||||
|
real_input: '#' + id,
|
||||||
|
holder: '#' + id + '_tagsinput',
|
||||||
|
input_wrapper: '#' + id + '_addTag',
|
||||||
|
fake_input: '#' + id + '_tag'
|
||||||
|
}, settings);
|
||||||
|
|
||||||
|
delimiter[id] = data.delimiter;
|
||||||
|
inputSettings[id] = {
|
||||||
|
minChars: settings.minChars,
|
||||||
|
maxChars: settings.maxChars,
|
||||||
|
limit: settings.limit,
|
||||||
|
validationPattern: settings.validationPattern,
|
||||||
|
unique: settings.unique
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.onAddTag || settings.onRemoveTag || settings.onChange) {
|
||||||
|
callbacks[id] = [];
|
||||||
|
callbacks[id]['onAddTag'] = settings.onAddTag;
|
||||||
|
callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
|
||||||
|
callbacks[id]['onChange'] = settings.onChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
var markup = $('<div>', {id: id + '_tagsinput', class: 'tagsinput'}).append(
|
||||||
|
$('<div>', {id: id + '_addTag'}).append(
|
||||||
|
settings.interactive ? $('<input>', {id: id + '_tag', class: 'tag-input', value: '', placeholder: settings.placeholder}) : null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$(markup).insertAfter(this);
|
||||||
|
|
||||||
|
$(data.holder).css('width', settings.width);
|
||||||
|
$(data.holder).css('min-height', settings.height);
|
||||||
|
$(data.holder).css('height', settings.height);
|
||||||
|
|
||||||
|
if ($(data.real_input).val() !== '') {
|
||||||
|
$.fn.tagsInput.importTags($(data.real_input), $(data.real_input).val());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop here if interactive option is not chosen
|
||||||
|
if (!settings.interactive) return;
|
||||||
|
|
||||||
|
$(data.fake_input).val('');
|
||||||
|
$(data.fake_input).data('pasted', false);
|
||||||
|
|
||||||
|
$(data.fake_input).on('focus', data, function(event) {
|
||||||
|
$(data.holder).addClass('focus');
|
||||||
|
|
||||||
|
if ($(this).val() === '') {
|
||||||
|
$(this).removeClass('error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(data.fake_input).on('blur', data, function(event) {
|
||||||
|
$(data.holder).removeClass('focus');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (settings.autocomplete !== null && jQuery.ui.autocomplete !== undefined) {
|
||||||
|
$(data.fake_input).autocomplete(settings.autocomplete);
|
||||||
|
$(data.fake_input).on('autocompleteselect', data, function(event, ui) {
|
||||||
|
$(event.data.real_input).addTag(ui.item.value, {
|
||||||
|
focus: true,
|
||||||
|
unique: settings.unique
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$(data.fake_input).on('keypress', data, function(event) {
|
||||||
|
if (_checkDelimiter(event)) {
|
||||||
|
$(this).autocomplete("close");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$(data.fake_input).on('blur', data, function(event) {
|
||||||
|
$(event.data.real_input).addTag($(event.data.fake_input).val(), {
|
||||||
|
focus: true,
|
||||||
|
unique: settings.unique
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a user types a delimiter create a new tag
|
||||||
|
$(data.fake_input).on('keypress', data, function(event) {
|
||||||
|
if (_checkDelimiter(event)) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
$(event.data.real_input).addTag($(event.data.fake_input).val(), {
|
||||||
|
focus: true,
|
||||||
|
unique: settings.unique
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(data.fake_input).on('paste', function () {
|
||||||
|
$(this).data('pasted', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If a user pastes the text check if it shouldn't be splitted into tags
|
||||||
|
$(data.fake_input).on('input', data, function(event) {
|
||||||
|
if (!$(this).data('pasted')) return;
|
||||||
|
|
||||||
|
$(this).data('pasted', false);
|
||||||
|
|
||||||
|
var value = $(event.data.fake_input).val();
|
||||||
|
|
||||||
|
value = value.replace(/\n/g, '');
|
||||||
|
value = value.replace(/\s/g, '');
|
||||||
|
|
||||||
|
var tags = _splitIntoTags(event.data.delimiter, value);
|
||||||
|
|
||||||
|
if (tags.length > 1) {
|
||||||
|
for (var i = 0; i < tags.length; ++i) {
|
||||||
|
$(event.data.real_input).addTag(tags[i], {
|
||||||
|
focus: true,
|
||||||
|
unique: settings.unique
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deletes last tag on backspace
|
||||||
|
data.removeWithBackspace && $(data.fake_input).on('keydown', function(event) {
|
||||||
|
if (event.keyCode == 8 && $(this).val() === '') {
|
||||||
|
event.preventDefault();
|
||||||
|
var lastTag = $(this).closest('.tagsinput').find('.tag:last > span').text();
|
||||||
|
var id = $(this).attr('id').replace(/_tag$/, '');
|
||||||
|
$('#' + id).removeTag(encodeURI(lastTag));
|
||||||
|
$(this).trigger('focus');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Removes the error class when user changes the value of the fake input
|
||||||
|
$(data.fake_input).keydown(function(event) {
|
||||||
|
// enter, alt, shift, esc, ctrl and arrows keys are ignored
|
||||||
|
if (jQuery.inArray(event.keyCode, [13, 37, 38, 39, 40, 27, 16, 17, 18, 225]) === -1) {
|
||||||
|
$(this).removeClass('error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tagsInput.updateTagsField = function(obj, tagslist) {
|
||||||
|
var id = $(obj).attr('id');
|
||||||
|
$(obj).val(tagslist.join(_getDelimiter(delimiter[id])));
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tagsInput.importTags = function(obj, val) {
|
||||||
|
$(obj).val('');
|
||||||
|
|
||||||
|
var id = $(obj).attr('id');
|
||||||
|
var tags = _splitIntoTags(delimiter[id], val);
|
||||||
|
|
||||||
|
for (i = 0; i < tags.length; ++i) {
|
||||||
|
$(obj).addTag(tags[i], {
|
||||||
|
focus: false,
|
||||||
|
callback: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callbacks[id] && callbacks[id]['onChange']) {
|
||||||
|
var f = callbacks[id]['onChange'];
|
||||||
|
f.call(obj, obj, tags);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var _getDelimiter = function(delimiter) {
|
||||||
|
if (typeof delimiter === 'undefined') {
|
||||||
|
return delimiter;
|
||||||
|
} else if (typeof delimiter === 'string') {
|
||||||
|
return delimiter;
|
||||||
|
} else {
|
||||||
|
return delimiter[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var _validateTag = function(value, inputSettings, tagslist, delimiter) {
|
||||||
|
var result = true;
|
||||||
|
|
||||||
|
if (value === '') result = false;
|
||||||
|
if (value.length < inputSettings.minChars) result = false;
|
||||||
|
if (inputSettings.maxChars !== null && value.length > inputSettings.maxChars) result = false;
|
||||||
|
if (inputSettings.limit !== null && tagslist.length >= inputSettings.limit) result = false;
|
||||||
|
if (inputSettings.validationPattern !== null && !inputSettings.validationPattern.test(value)) result = false;
|
||||||
|
|
||||||
|
if (typeof delimiter === 'string') {
|
||||||
|
if (value.indexOf(delimiter) > -1) result = false;
|
||||||
|
} else {
|
||||||
|
$.each(delimiter, function(index, _delimiter) {
|
||||||
|
if (value.indexOf(_delimiter) > -1) result = false;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
var _checkDelimiter = function(event) {
|
||||||
|
var found = false;
|
||||||
|
|
||||||
|
if (event.which === 13) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof event.data.delimiter === 'string') {
|
||||||
|
if (event.which === event.data.delimiter.charCodeAt(0)) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$.each(event.data.delimiter, function(index, delimiter) {
|
||||||
|
if (event.which === delimiter.charCodeAt(0)) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
};
|
||||||
|
|
||||||
|
var _splitIntoTags = function(delimiter, value) {
|
||||||
|
if (value === '') return [];
|
||||||
|
|
||||||
|
if (typeof delimiter === 'string') {
|
||||||
|
return value.split(delimiter);
|
||||||
|
} else {
|
||||||
|
var tmpDelimiter = '∞';
|
||||||
|
var text = value;
|
||||||
|
|
||||||
|
$.each(delimiter, function(index, _delimiter) {
|
||||||
|
text = text.split(_delimiter).join(tmpDelimiter);
|
||||||
|
});
|
||||||
|
|
||||||
|
return text.split(tmpDelimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
})(jQuery);
|
|
@ -10,6 +10,7 @@
|
||||||
<script src="{{ url_for("static", filename="js/ihatemoney.js") }}"></script>
|
<script src="{{ url_for("static", filename="js/ihatemoney.js") }}"></script>
|
||||||
<script src="{{ url_for("static", filename="js/tether.min.js") }}"></script>
|
<script src="{{ url_for("static", filename="js/tether.min.js") }}"></script>
|
||||||
<script src="{{ url_for("static", filename="js/popper.min.js") }}"></script>
|
<script src="{{ url_for("static", filename="js/popper.min.js") }}"></script>
|
||||||
|
<script src="{{ url_for("static", filename="js/tagsinput.js") }}"></script>
|
||||||
<script src="{{ url_for("static", filename="js/bootstrap.min.js") }}"></script>
|
<script src="{{ url_for("static", filename="js/bootstrap.min.js") }}"></script>
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
<script type="text/javascript" charset="utf-8">
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
|
|
@ -42,5 +42,12 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
$('#emails').tagsInput({
|
||||||
|
'delimiter': [',',';','\t']
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue