decidim-notify 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +160 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/config/decidim_notify_manifest.css +2 -0
  6. data/app/assets/config/decidim_notify_manifest.js +3 -0
  7. data/app/assets/images/decidim/notify/icon.svg +1 -0
  8. data/app/assets/javascripts/cable.js +13 -0
  9. data/app/assets/javascripts/channels/decidim/notify/conversations.js +90 -0
  10. data/app/assets/javascripts/decidim/notify/conversations.js.es6 +70 -0
  11. data/app/assets/javascripts/decidim/notify/select2.js.es6 +100 -0
  12. data/app/assets/stylesheets/decidim/notify/_hexagon.scss +126 -0
  13. data/app/assets/stylesheets/decidim/notify/admin.scss +25 -0
  14. data/app/assets/stylesheets/decidim/notify/notify.scss +220 -0
  15. data/app/cells/decidim/notify/chapter/show.erb +35 -0
  16. data/app/cells/decidim/notify/chapter_cell.rb +43 -0
  17. data/app/cells/decidim/notify/note/show.erb +20 -0
  18. data/app/cells/decidim/notify/note_cell.rb +38 -0
  19. data/app/cells/decidim/notify/participant/show.erb +9 -0
  20. data/app/cells/decidim/notify/participant_cell.rb +26 -0
  21. data/app/channels/decidim/notify/chapters_channel.rb +17 -0
  22. data/app/channels/decidim/notify/connection.rb +17 -0
  23. data/app/channels/decidim/notify/notes_channel.rb +17 -0
  24. data/app/channels/decidim/notify/participants_channel.rb +17 -0
  25. data/app/commands/decidim/notify/admin/create_chapter.rb +51 -0
  26. data/app/commands/decidim/notify/admin/destroy_chapter.rb +30 -0
  27. data/app/commands/decidim/notify/admin/update_chapter.rb +52 -0
  28. data/app/commands/decidim/notify/admin/update_config.rb +71 -0
  29. data/app/commands/decidim/notify/create_note.rb +52 -0
  30. data/app/commands/decidim/notify/delete_chapter.rb +28 -0
  31. data/app/commands/decidim/notify/delete_note.rb +28 -0
  32. data/app/commands/decidim/notify/update_chapter.rb +35 -0
  33. data/app/commands/decidim/notify/update_note.rb +51 -0
  34. data/app/controllers/concerns/decidim/notify/broadcasts.rb +56 -0
  35. data/app/controllers/concerns/decidim/notify/needs_ajax_rescue.rb +25 -0
  36. data/app/controllers/decidim/notify/admin/application_controller.rb +18 -0
  37. data/app/controllers/decidim/notify/admin/chapters_controller.rb +90 -0
  38. data/app/controllers/decidim/notify/admin/conversations_controller.rb +61 -0
  39. data/app/controllers/decidim/notify/application_controller.rb +16 -0
  40. data/app/controllers/decidim/notify/chapters_controller.rb +41 -0
  41. data/app/controllers/decidim/notify/conversations_controller.rb +105 -0
  42. data/app/forms/decidim/notify/admin/chapter_form.rb +14 -0
  43. data/app/forms/decidim/notify/admin/notify_config_form.rb +14 -0
  44. data/app/forms/decidim/notify/chapter_form.rb +13 -0
  45. data/app/forms/decidim/notify/note_form.rb +16 -0
  46. data/app/helpers/decidim/notify/application_helper.rb +10 -0
  47. data/app/models/concerns/decidim/notify/belongs_to_notify_component.rb +31 -0
  48. data/app/models/decidim/notify/application_record.rb +10 -0
  49. data/app/models/decidim/notify/author.rb +29 -0
  50. data/app/models/decidim/notify/chapter.rb +22 -0
  51. data/app/models/decidim/notify/note.rb +23 -0
  52. data/app/permissions/decidim/notify/admin/permissions.rb +23 -0
  53. data/app/permissions/decidim/notify/permissions.rb +45 -0
  54. data/app/views/decidim/notify/admin/chapters/_form.html.erb +20 -0
  55. data/app/views/decidim/notify/admin/chapters/edit.html.erb +7 -0
  56. data/app/views/decidim/notify/admin/chapters/index.html.erb +52 -0
  57. data/app/views/decidim/notify/admin/chapters/new.html.erb +7 -0
  58. data/app/views/decidim/notify/admin/conversations/_form.html.erb +27 -0
  59. data/app/views/decidim/notify/admin/conversations/index.html.erb +27 -0
  60. data/app/views/decidim/notify/conversations/_chapter.html.erb +1 -0
  61. data/app/views/decidim/notify/conversations/_form.html.erb +9 -0
  62. data/app/views/decidim/notify/conversations/_note.html.erb +1 -0
  63. data/app/views/decidim/notify/conversations/_participant.html.erb +1 -0
  64. data/app/views/decidim/notify/conversations/_script.js.erb +7 -0
  65. data/app/views/decidim/notify/conversations/index.html.erb +48 -0
  66. data/app/views/decidim/notify/conversations/private.html.erb +11 -0
  67. data/config/i18n-tasks.yml +35 -0
  68. data/config/locales/ca.yml +110 -0
  69. data/config/locales/cs.yml +110 -0
  70. data/config/locales/en.yml +110 -0
  71. data/config/locales/es.yml +110 -0
  72. data/db/migrate/20200504071404_create_notify_notes.rb +11 -0
  73. data/db/migrate/20200505061547_create_notify_authors.rb +17 -0
  74. data/db/migrate/20200505061640_add_notify_notes_references.rb +9 -0
  75. data/db/migrate/20200505195918_add_notify_author_admin.rb +7 -0
  76. data/db/migrate/20200507103034_change_notify_notes_authors.rb +15 -0
  77. data/db/migrate/20200514144040_add_notify_note_chapters.rb +15 -0
  78. data/lib/decidim/notify.rb +57 -0
  79. data/lib/decidim/notify/admin.rb +10 -0
  80. data/lib/decidim/notify/admin_engine.rb +25 -0
  81. data/lib/decidim/notify/component.rb +135 -0
  82. data/lib/decidim/notify/engine.rb +39 -0
  83. data/lib/decidim/notify/seeds/avatar1.png +0 -0
  84. data/lib/decidim/notify/seeds/avatar2.png +0 -0
  85. data/lib/decidim/notify/seeds/avatar3.png +0 -0
  86. data/lib/decidim/notify/seeds/avatar4.png +0 -0
  87. data/lib/decidim/notify/seeds/avatar5.png +0 -0
  88. data/lib/decidim/notify/test/factories.rb +48 -0
  89. data/lib/decidim/notify/test/shared_examples/component_examples.rb +23 -0
  90. data/lib/decidim/notify/version.rb +9 -0
  91. data/vendor/assets/javascripts/select2.js +6147 -0
  92. data/vendor/assets/stylesheets/select2-foundation-theme.css +249 -0
  93. data/vendor/assets/stylesheets/select2.css +515 -0
  94. metadata +177 -0
@@ -0,0 +1,160 @@
1
+ # Decidim::Notify
2
+
3
+ ![[CI] Build Status](https://github.com/Platoniq/decidim-module-notify/workflows/%5BCI%5D%20Test%20Notify/badge.svg)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/a00b6c950199d3530dc9/maintainability)](https://codeclimate.com/github/Platoniq/decidim-module-notify/maintainability)
5
+ [![Codecov](https://codecov.io/gh/Platoniq/decidim-module-notify/branch/master/graph/badge.svg)](https://codecov.io/gh/Platoniq/decidim-module-notify)
6
+
7
+
8
+ A note-taker feature focused on conversations. This module provides a component for any participatory space in Decidim.
9
+
10
+ ## Usage
11
+
12
+ Notify will be available as a Component for a Participatory
13
+ Space.
14
+
15
+ ![Notify component](examples/notify.png)
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem "decidim-notify", "~> 0.3.0"
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ ```bash
28
+ bundle
29
+ bundle exec rails decidim_notify:install:migrations
30
+ bundle exec rails db:migrate
31
+ ```
32
+
33
+ Depending on your Decidim version, choose the corresponding Plugin version to ensure compatibility:
34
+
35
+ | Notify version | Compatible Decidim versions |
36
+ |---|---|
37
+ | 0.3.x | 0.21.x - 0.23.x |
38
+
39
+ ### Configuration
40
+
41
+ Notify uses [ActionCable](https://guides.rubyonrails.org/action_cable_overview.html) to display real time notes.
42
+
43
+ By default uses [PostgreSQL NOTIFY](https://www.postgresql.org/docs/9.0/sql-notify.html) as a backend for ActionCable. However, it is recommended to use Redis because PostgreSQL is limited to a 8000 characters to send messages.
44
+
45
+ To configure Redis use a custom initializer in you application, for instance `config/initializers/notify_config.rb`:
46
+
47
+ ```ruby
48
+ Decidim::Notify.configure do |config|
49
+ config.cable_adapter = "redis"
50
+ config.cable_url = "redis://localhost:6379/1"
51
+ end
52
+ ```
53
+
54
+ ## Contributing
55
+
56
+ See [Decidim](https://github.com/decidim/decidim).
57
+
58
+
59
+ ### Developing
60
+
61
+ To start contributing to this project, first:
62
+
63
+ - Install the basic dependencies (such as Ruby and PostgreSQL)
64
+ - Clone this repository
65
+
66
+ Decidim's main repository also provides a Docker configuration file if you
67
+ prefer to use Docker instead of installing the dependencies locally on your
68
+ machine.
69
+
70
+ You can create the development app by running the following commands after
71
+ cloning this project:
72
+
73
+ ```bash
74
+ bundle
75
+ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rake development_app
76
+ ```
77
+
78
+ Note that the database user has to have rights to create and drop a database in
79
+ order to create the dummy test app database.
80
+
81
+ Then to test how the module works in Decidim, start the development server:
82
+
83
+ ```bash
84
+ cd development_app
85
+ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rails s
86
+ ```
87
+
88
+ In case you are using [rbenv](https://github.com/rbenv/rbenv) and have the
89
+ [rbenv-vars](https://github.com/rbenv/rbenv-vars) plugin installed for it, you
90
+ can add the environment variables to the root directory of the project in a file
91
+ named `.rbenv-vars`. If these are defined for the environment, you can omit
92
+ defining these in the commands shown above.
93
+
94
+ #### Code Styling
95
+
96
+ Please follow the code styling defined by the different linters that ensure we
97
+ are all talking with the same language collaborating on the same project. This
98
+ project is set to follow the same rules that Decidim itself follows.
99
+
100
+ [Rubocop](https://rubocop.readthedocs.io/) linter is used for the Ruby language.
101
+
102
+ You can run the code styling checks by running the following commands from the
103
+ console:
104
+
105
+ ```
106
+ bundle exec rubocop
107
+ ```
108
+
109
+ To ease up following the style guide, you should install the plugin to your
110
+ favorite editor, such as:
111
+
112
+ - Atom - [linter-rubocop](https://atom.io/packages/linter-rubocop)
113
+ - Sublime Text - [Sublime RuboCop](https://github.com/pderichs/sublime_rubocop)
114
+ - Visual Studio Code - [Rubocop for Visual Studio Code](https://github.com/misogi/vscode-ruby-rubocop)
115
+
116
+ ### Testing
117
+
118
+ To run the tests run the following in the gem development path:
119
+
120
+ ```bash
121
+ bundle
122
+ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rake test_app
123
+ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rspec
124
+ ```
125
+
126
+ Note that the database user has to have rights to create and drop a database in
127
+ order to create the dummy test app database.
128
+
129
+ In case you are using [rbenv](https://github.com/rbenv/rbenv) and have the
130
+ [rbenv-vars](https://github.com/rbenv/rbenv-vars) plugin installed for it, you
131
+ can add these environment variables to the root directory of the project in a
132
+ file named `.rbenv-vars`. In this case, you can omit defining these in the
133
+ commands shown above.
134
+
135
+ ### Test code coverage
136
+
137
+ If you want to generate the code coverage report for the tests, you can use
138
+ the `SIMPLECOV=1` environment variable in the rspec command as follows:
139
+
140
+ ```bash
141
+ SIMPLECOV=1 bundle exec rspec
142
+ ```
143
+
144
+ This will generate a folder named `coverage` in the project root which contains
145
+ the code coverage report.
146
+
147
+ ### Localization
148
+
149
+ If you would like to see this module in your own language, you can help with its
150
+ translation at Crowdin:
151
+
152
+ https://crowdin.com/translate/decidim-notify
153
+
154
+ ## License
155
+
156
+ This engine is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.
157
+
158
+ ## Credits
159
+
160
+ This plugin has been developed by ![Platoniq](examples/platoniq-logo.png)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/dev/common_rake"
4
+
5
+ def install_module(path)
6
+ Dir.chdir(path) do
7
+ system("bundle exec rake decidim_notify:install:migrations")
8
+ system("bundle exec rake db:migrate")
9
+ end
10
+ end
11
+
12
+ def seed_db(path)
13
+ Dir.chdir(path) do
14
+ system("bundle exec rake db:seed")
15
+ end
16
+ end
17
+
18
+ desc "Generates a dummy app for testing"
19
+ task test_app: "decidim:generate_external_test_app" do
20
+ ENV["RAILS_ENV"] = "test"
21
+ install_module("spec/decidim_dummy_app")
22
+ end
23
+
24
+ desc "Generates a development app."
25
+ task :development_app do
26
+ Bundler.with_original_env do
27
+ generate_decidim_app(
28
+ "development_app",
29
+ "--app_name",
30
+ "#{base_app_name}_development_app",
31
+ "--path",
32
+ "..",
33
+ "--recreate_db",
34
+ "--demo"
35
+ )
36
+ end
37
+
38
+ install_module("development_app")
39
+ seed_db("development_app")
40
+ end
@@ -0,0 +1,2 @@
1
+ /*= link decidim/notify/notify */
2
+ /*= link decidim/notify/admin */
@@ -0,0 +1,3 @@
1
+ //= link channels/decidim/notify/conversations.js
2
+ //= link decidim/notify/select2.js
3
+ //= link decidim/notify/conversations.js
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 35"><path d="M17.5 35A17.5 17.5 0 1 1 35 17.5 17.52 17.52 0 0 1 17.5 35zm0-33.06A15.56 15.56 0 1 0 33.06 17.5 15.57 15.57 0 0 0 17.5 1.94zm9.5 13.7H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zm0 3.68H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zM22.26 23H8a1 1 0 0 1 0-1.94h14.26a1 1 0 0 1 0 1.94z"/></svg>
@@ -0,0 +1,13 @@
1
+ // Action Cable provides the framework to deal with WebSockets in Rails.
2
+ // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3
+ //
4
+ //= require action_cable
5
+ //= require_self
6
+ //= require_tree ./channels
7
+
8
+ (function() {
9
+ this.App || (this.App = {});
10
+
11
+ App.cable = ActionCable.createConsumer();
12
+
13
+ }).call(this);
@@ -0,0 +1,90 @@
1
+ // = require cable
2
+
3
+ jQuery.fn.reverse = [].reverse;
4
+
5
+ var updateEmptyStatus = function(selector) {
6
+ $(selector).each(function(){
7
+ if($(this).children().length == 0){
8
+ $(this).addClass("empty");
9
+ } else {
10
+ $(this).removeClass("empty");
11
+ }
12
+ });
13
+ };
14
+
15
+ App.notifyNotesChannel = App.cable.subscriptions.create({ channel: "Decidim::Notify::NotesChannel", id: window.Notify && window.Notify.id }, {
16
+ received: function(data) {
17
+ // console.log("note received",data);
18
+
19
+ if(data.create) $(`#notify-chapter-notes-${data.chapterId||"unclassified"}`).prepend(data.create);
20
+ if(data.update) {
21
+ var $note = $(`#notify-note-${data.id}`);
22
+ var $old = $note.closest(".notify-chapter-notes");
23
+ var $new = $(`#notify-chapter-notes-${data.chapterId||"unclassified"}`);
24
+ if($old[0] != $new[0]) {
25
+ // TODO: put it in the right place by time of creation
26
+ $note.detach().prependTo($new);
27
+ }
28
+ $note.replaceWith(data.update);
29
+ }
30
+ if(data.destroy) $(`#notify-note-${data.destroy}`).remove();
31
+
32
+ updateEmptyStatus(".notify-chapter-notes");
33
+ }
34
+ });
35
+
36
+
37
+ App.notifyParticipantsChannel = App.cable.subscriptions.create({ channel: "Decidim::Notify::ParticipantsChannel", id: window.Notify && window.Notify.id }, {
38
+ received: function(data) {
39
+ // console.log("participants received",data);
40
+
41
+ $("#notify-note_takers").html(data.noteTakers);
42
+ $("#notify-participants").html(data.participants);
43
+ }
44
+ });
45
+
46
+ App.notifyChaptersChannel = App.cable.subscriptions.create({ channel: "Decidim::Notify::ChaptersChannel", id: window.Notify && window.Notify.id }, {
47
+ received: function(data) {
48
+ // console.log("chapter received",data);
49
+ var newOption;
50
+ if(data.create) {
51
+ $("#notify-chapters").prepend(data.create);
52
+ $(document).foundation();
53
+ if (!$(`#note_chapter [value="${data.title}"]`).length) {
54
+ newOption = new Option(data.title, data.title, true, true);
55
+ $("#note_chapter").append(newOption).trigger("change");
56
+ }
57
+ }
58
+
59
+ if(data.update) {
60
+ var $chapter = $(`#notify-chapter-${data.id} .chapter-title`);
61
+ if($chapter.length) {
62
+ var old = $chapter.text();
63
+ $chapter.text(data.update);
64
+ if(data.active) {
65
+ $(".notify-chapter h3").removeClass("active");
66
+ $(`.toggle-chapter-active .switch-input:not(#chapter_active-${data.id})`).prop("checked", false);
67
+ $chapter.closest("h3").addClass("active");
68
+ }
69
+ var activate = $('#note_body').val()=="" && data.active;
70
+ newOption = new Option(data.update, data.update, activate, activate);
71
+ $(`#note_chapter [value="${old}"]`).remove();
72
+ $('#note_chapter').append(newOption).trigger('change');
73
+ } else {
74
+ console.error("Chapter not found", data);
75
+ }
76
+ }
77
+
78
+ if(data.destroy) {
79
+ // Move notes to the unclassified
80
+ var $unclassified = $("#notify-chapter-notes-unclassified");
81
+ $(`#notify-chapter-notes-${data.destroy} .notify-note`).reverse().each(function() {
82
+ $(this).detach().prependTo($unclassified);
83
+ });
84
+
85
+ $(`#notify-chapter-${data.destroy}`).remove();
86
+ }
87
+
88
+ updateEmptyStatus(".notify-chapter-notes");
89
+ }
90
+ });
@@ -0,0 +1,70 @@
1
+ //= require decidim/notify/select2
2
+
3
+ $(() => {
4
+ const $info = $(".form-conversations-submit .info");
5
+ const $form = $('form#new_note');
6
+ const originalAction = $form.attr("action");
7
+
8
+ const resetForm = () => {
9
+ $("#note_code").html("");
10
+ $("#note_body").val("");
11
+ $form.find('[name="_method"]').val("post");
12
+ $form.attr("action", originalAction);
13
+ };
14
+
15
+ // reset button
16
+ $form.find('[type="reset"]').on('click', resetForm);
17
+
18
+ // edit button in notes
19
+ $('#notify-chapters').on('click', 'a.edit', (e) => {
20
+ e.preventDefault();
21
+ const $a = $(e.currentTarget);
22
+ const code = $a.closest('.notify-note').data('author-code');
23
+ const chapter = $a.closest('.notify-note').data('chapter');
24
+ const name = $a.closest('.notify-note').find('.note-name').text();
25
+ const body = $a.closest('.notify-note').find('.note-body').text();
26
+
27
+ $form.find('[name="_method"]').val("patch");
28
+ $form.attr("action", $(e.currentTarget).attr('href'));
29
+ $("#note_body").val(body);
30
+ $("#note_chapter").val(chapter);
31
+ $('#note_chapter').trigger('change');
32
+
33
+ if(code) {
34
+ $("#note_code").append(`<option value="${code}" selected>${name}</option>`);
35
+ $("#note_body").select();
36
+ } else {
37
+ $("#note_code").select2("open");
38
+ }
39
+
40
+ $("html, body").animate({ scrollTop: $('#new_note').offset().top }, 400);
41
+ // location = "#new_note";
42
+
43
+ // console.log("edit",$(e.currentTarget).attr('href'),code,body)
44
+ });
45
+
46
+ // Keypress CTRL-Enter sends form
47
+ $("#note_body").keypress(function(e){
48
+ if(e.ctrlKey && (e.which === 10 || e.which === 13 )) {
49
+ Rails.fire($form[0], 'submit');
50
+ }
51
+ });
52
+
53
+ const showInfo = (text, speed) => {
54
+ $info.stop(true,true).html(text).show();
55
+ setTimeout(() => {
56
+ $info.fadeOut("slow");
57
+ }, speed);
58
+ return $info;
59
+ };
60
+
61
+ // Rails AJAX events
62
+ document.body.addEventListener('ajax:error', (responseText) => {
63
+ showInfo(responseText.detail[0].message || responseText.detail[0], 5000).removeClass('text-success').addClass('text-alert');
64
+ });
65
+ document.body.addEventListener('ajax:success', () => {
66
+ showInfo("✔", 1000).removeClass('text-alert').addClass('text-success');
67
+ resetForm();
68
+ $('.dropdown-pane').foundation('close');
69
+ });
70
+ });
@@ -0,0 +1,100 @@
1
+ // = require select2
2
+
3
+ $(() => {
4
+ $('select.multiusers-select').each(function() {
5
+ const url = $(this).attr("data-url");
6
+ $(this).select2({
7
+ ajax: {
8
+ url: url,
9
+ delay: 100,
10
+ dataType: "json",
11
+ processResults: (data) => {
12
+ return {
13
+ results: data
14
+ }
15
+ }
16
+ },
17
+ escapeMarkup: (markup) => markup,
18
+ templateSelection: (item) => `${item.text}`,
19
+ minimumInputLength: 1,
20
+ theme: "foundation"
21
+ });
22
+ });
23
+
24
+ $('select.user-select').each(function() {
25
+ const url = $(this).attr("data-url");
26
+ const placeholder = $(this).attr("placeholder");
27
+
28
+ $(this).select2({
29
+ ajax: {
30
+ url: url,
31
+ delay: 100,
32
+ dataType: "json",
33
+ processResults: (data) => {
34
+ return {
35
+ results: data
36
+ }
37
+ }
38
+ },
39
+ minimumInputLength: 1,
40
+ placeholder: placeholder,
41
+ theme: "foundation",
42
+ allowClear: true,
43
+ selectOnClose: true,
44
+ escapeMarkup: (markup) => markup,
45
+ templateSelection: (item) => `<b>${item.id}</b> - ${item.text}`,
46
+ templateResult: function(item) {
47
+ return `<div class="select2-result-repository">
48
+ <div class="select2-result-repository__avatar" style="background-image:url(${item.avatar})">
49
+ <div class="hex1"></div><div class="hex2"></div>
50
+ </div>
51
+ <div class="select2-result-repository__meta">
52
+ <b>${item.id}</b> - ${item.text}
53
+ </div>
54
+ </div>`;
55
+ }
56
+ });
57
+
58
+ $(this).on('select2:close', () => $('#note_body').select());
59
+ $(this).on("select2:clear", function () {
60
+ $(this).on("select2:opening.cancelOpen", function (evt) {
61
+ evt.preventDefault();
62
+
63
+ $(this).off("select2:opening.cancelOpen");
64
+ });
65
+ });
66
+ });
67
+
68
+ $('select.chapter-select').each(function() {
69
+ const placeholder = $(this).attr("placeholder");
70
+
71
+ $(this).select2({
72
+ selectOnClose: true,
73
+ tags: true,
74
+ allowClear: true,
75
+ theme: "foundation",
76
+ placeholder: placeholder,
77
+ createTag: function (params) {
78
+ var term = $.trim(params.term);
79
+
80
+ if (term === '') {
81
+ return null;
82
+ }
83
+ var n = {
84
+ id: term,
85
+ text: term
86
+ }
87
+ return n;
88
+ }
89
+ });
90
+
91
+ $(this).on('select2:close', () => $('#note_body').select());
92
+ $(this).on("select2:clear", function () {
93
+ $(this).on("select2:opening.cancelOpen", function (evt) {
94
+ evt.preventDefault();
95
+
96
+ $(this).off("select2:opening.cancelOpen");
97
+ });
98
+ });
99
+ });
100
+ });