aboutsummaryrefslogtreecommitdiff
path: root/ihatemoney
diff options
context:
space:
mode:
authorJosé Antonio de la Torre <jose.torre.heras@gmail.com>2019-10-15 22:09:13 +0200
committerAlexis Metaireau <alexis@notmyidea.org>2019-10-17 20:17:38 +0200
commit5e37e337167dbb7341f7299d17dd9baa92108cad (patch)
tree7e92144a29743adf71b5c7f62748ac8a9da30a8f /ihatemoney
parent37f2e38af39a0b9e5e83d9249bc579f43163e026 (diff)
downloadihatemoney-mirror-5e37e337167dbb7341f7299d17dd9baa92108cad.zip
ihatemoney-mirror-5e37e337167dbb7341f7299d17dd9baa92108cad.tar.gz
ihatemoney-mirror-5e37e337167dbb7341f7299d17dd9baa92108cad.tar.bz2
Added support to split messages #133
Diffstat (limited to 'ihatemoney')
-rw-r--r--ihatemoney/forms.py2
-rw-r--r--ihatemoney/static/css/main.css1
-rw-r--r--ihatemoney/static/css/tagsinput.css16
-rw-r--r--ihatemoney/static/js/tagsinput.js381
-rw-r--r--ihatemoney/templates/layout.html1
-rw-r--r--ihatemoney/templates/send_invites.html7
6 files changed, 407 insertions, 1 deletions
diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py
index c9b0547..7eaf633 100644
--- a/ihatemoney/forms.py
+++ b/ihatemoney/forms.py
@@ -239,7 +239,7 @@ class MemberForm(FlaskForm):
class InviteForm(FlaskForm):
- emails = TextAreaField(_("People to notify"))
+ emails = StringField(_("People to notify"), render_kw={"class": "tag"})
submit = SubmitField(_("Send invites"))
def validate_emails(form, field):
diff --git a/ihatemoney/static/css/main.css b/ihatemoney/static/css/main.css
index 52bd77b..6f7aabc 100644
--- a/ihatemoney/static/css/main.css
+++ b/ihatemoney/static/css/main.css
@@ -1,4 +1,5 @@
@import "bootstrap.min.css";
+@import "tagsinput.css";
@import "bootstrap-datepicker3.standalone.css";
@import "../fonts/fontfaces.css";
diff --git a/ihatemoney/static/css/tagsinput.css b/ihatemoney/static/css/tagsinput.css
new file mode 100644
index 0000000..cbb28cd
--- /dev/null
+++ b/ihatemoney/static/css/tagsinput.css
@@ -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}
diff --git a/ihatemoney/static/js/tagsinput.js b/ihatemoney/static/js/tagsinput.js
new file mode 100644
index 0000000..a5a4e84
--- /dev/null
+++ b/ihatemoney/static/js/tagsinput.js
@@ -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);
diff --git a/ihatemoney/templates/layout.html b/ihatemoney/templates/layout.html
index 89f65e7..7ff7296 100644
--- a/ihatemoney/templates/layout.html
+++ b/ihatemoney/templates/layout.html
@@ -10,6 +10,7 @@
<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/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>
{% block head %}{% endblock %}
<script type="text/javascript" charset="utf-8">
diff --git a/ihatemoney/templates/send_invites.html b/ihatemoney/templates/send_invites.html
index 87bdf0e..53492c8 100644
--- a/ihatemoney/templates/send_invites.html
+++ b/ihatemoney/templates/send_invites.html
@@ -42,5 +42,12 @@
</tbody>
</table>
+<script>
+$(function() {
+ $('#emails').tagsInput({
+ 'delimiter': [',',';','\t']
+ });
+})
+</script>
{% endblock %}