super_settings 1.0.2 → 2.0.0

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +110 -16
  4. data/VERSION +1 -1
  5. data/app/helpers/super_settings/settings_helper.rb +13 -3
  6. data/app/views/layouts/super_settings/settings.html.erb +1 -1
  7. data/config/routes.rb +1 -1
  8. data/db/migrate/20210414004553_create_super_settings.rb +1 -7
  9. data/lib/super_settings/application/api.js +4 -1
  10. data/lib/super_settings/application/helper.rb +56 -17
  11. data/lib/super_settings/application/images/arrow-down-short.svg +3 -0
  12. data/lib/super_settings/application/images/arrow-up-short.svg +3 -0
  13. data/lib/super_settings/application/images/info-circle.svg +4 -0
  14. data/lib/super_settings/application/images/pencil-square.svg +4 -0
  15. data/lib/super_settings/application/images/plus.svg +3 -1
  16. data/lib/super_settings/application/images/trash3.svg +3 -0
  17. data/lib/super_settings/application/images/x-circle.svg +4 -0
  18. data/lib/super_settings/application/index.html.erb +54 -37
  19. data/lib/super_settings/application/layout.html.erb +5 -2
  20. data/lib/super_settings/application/layout_styles.css +7 -151
  21. data/lib/super_settings/application/layout_vars.css.erb +21 -0
  22. data/lib/super_settings/application/scripts.js +100 -21
  23. data/lib/super_settings/application/style_vars.css.erb +62 -0
  24. data/lib/super_settings/application/styles.css +183 -14
  25. data/lib/super_settings/application.rb +18 -11
  26. data/lib/super_settings/attributes.rb +1 -8
  27. data/lib/super_settings/configuration.rb +9 -0
  28. data/lib/super_settings/controller_actions.rb +2 -2
  29. data/lib/super_settings/engine.rb +1 -1
  30. data/lib/super_settings/history_item.rb +1 -1
  31. data/lib/super_settings/http_client.rb +165 -0
  32. data/lib/super_settings/rack_application.rb +3 -3
  33. data/lib/super_settings/rest_api.rb +5 -4
  34. data/lib/super_settings/setting.rb +13 -2
  35. data/lib/super_settings/storage/active_record_storage.rb +7 -0
  36. data/lib/super_settings/storage/history_attributes.rb +31 -0
  37. data/lib/super_settings/storage/http_storage.rb +60 -184
  38. data/lib/super_settings/storage/json_storage.rb +201 -0
  39. data/lib/super_settings/storage/mongodb_storage.rb +238 -0
  40. data/lib/super_settings/storage/redis_storage.rb +49 -111
  41. data/lib/super_settings/storage/s3_storage.rb +165 -0
  42. data/lib/super_settings/storage/storage_attributes.rb +64 -0
  43. data/lib/super_settings/storage/test_storage.rb +3 -5
  44. data/lib/super_settings/storage/transaction.rb +67 -0
  45. data/lib/super_settings/storage.rb +13 -6
  46. data/lib/super_settings/time_precision.rb +36 -0
  47. data/lib/super_settings.rb +11 -0
  48. data/super_settings.gemspec +4 -2
  49. metadata +22 -9
  50. data/lib/super_settings/application/images/edit.svg +0 -1
  51. data/lib/super_settings/application/images/info.svg +0 -1
  52. data/lib/super_settings/application/images/slash.svg +0 -1
  53. data/lib/super_settings/application/images/trash.svg +0 -1
@@ -1,33 +1,46 @@
1
1
  <%= style_tag %>
2
2
 
3
- <main>
4
- <form class="form-inline" style="display:block;" onsubmit="return false">
3
+ <main class="super-settings" data-api-base-url="<%= html_escape(api_base_url) %>">
4
+ <form class="super-settings-form-inline" style="display:block;" onsubmit="return false">
5
5
  <div class="super-settings-sticky-top">
6
6
  <span class="js-settings-count" style="display:inline-block; margin-right:1rem;"></span>
7
7
 
8
- <label for="filter" class="super-settings-sr-only">Filter</label>
9
- <input type="text" name="filter" value="" placeholder="Filter Keys" size="20" class="form-control" title="Filter Keys" id="filter" style="margin-right:1rem;">
8
+ <label for="super-settings-filter" class="super-settings-sr-only">Filter</label>
9
+ <input type="text" name="filter" value="" placeholder="Filter Keys" size="20" class="super-settings-form-control" title="Filter Keys" id="super-settings-filter" style="margin-right:1rem;">
10
10
 
11
- <button type="button" class="btn btn-default" id="add-setting"><%= icon_image(:plus) %> Add Setting</button>
11
+ <button type="button" class="super-settings-btn super-settings-btn-default" id="super-settings-add-setting"><%= icon_image(:plus, style: {"vertical-align": "text-top"}) %> Add Setting</button>
12
12
 
13
- <button type="button" class="btn btn-default" id="discard-changes" disabled>
13
+ <button type="button" class="super-settings-btn super-settings-btn-default" id="super-settings-discard-changes" disabled>
14
14
  Discard Changes
15
15
  </button>
16
16
 
17
- <button type="button" class="btn btn-primary" id="save-settings" disabled>
17
+ <button type="button" class="super-settings-btn super-settings-btn-primary" id="super-settings-save-settings" disabled>
18
18
  Save <span class="count"></span> Changes
19
19
  </button>
20
20
 
21
21
  <strong class="js-flash" style="display:none; margin-left:3rem;"></strong>
22
22
  </div>
23
23
 
24
- <table class="table table-striped" id="settings-table">
24
+ <table class="super-settings-table super-settings-table-striped" id="settings-table">
25
25
  <thead>
26
26
  <tr>
27
- <th scope="col" class="super-settings-key">Key</th>
27
+ <th scope="col" class="super-settings-key">
28
+ Key
29
+ <button class="super-settings-sort-control super-settings-btn-no-chrome" data-field="key" data-order="asc" data-selected="true" title="Sort by key">
30
+ <%= icon_image("arrow-down-short", data: {order: :asc}, style: {display: "inline-block"}) %>
31
+ <%= icon_image("arrow-up-short", data: {order: :desc}, style: {display: "none"}) %>
32
+ </button>
33
+ </th>
28
34
  <th scope="col" class="super-settings-value">Value</th>
29
35
  <th scope="col" class="super-settings-value-type">Type</th>
30
36
  <th scope="col" class="super-settings-description">Description</th>
37
+ <th scope="col" data-field="updated_at">
38
+ Modified
39
+ <button class="super-settings-sort-control super-settings-btn-no-chrome" data-field="updated_at" data-order="asc" title="Sort by modified time">
40
+ <%= icon_image("arrow-down-short", data: {order: :asc}, style: {display: "inline-block"}) %>
41
+ <%= icon_image("arrow-up-short", data: {order: :desc}, style: {display: "none"}) %>
42
+ </button>
43
+ </th>
31
44
  <th scope="col" class="super-settings-controls"><span class="super-settings-sr-only">Controls</span></th>
32
45
  </tr>
33
46
  </thead>
@@ -35,15 +48,15 @@
35
48
  </tbody>
36
49
  </table>
37
50
  </form>
38
- </main>
39
51
 
40
- <div id="modal" class="super-settings-modal js-close-modal" aria-hidden="true" aria-role="dialog">
41
- <div class="super-settings-modal-dialog">
42
- <button type="button" title="Close Dialog" class="super-settings-modal-close js-close-modal">&times;</button>
43
- <div class="super-settings-modal-content">
52
+ <div id="super-settings-modal" class="super-settings-modal js-close-modal" aria-hidden="true" aria-role="dialog">
53
+ <div class="super-settings-modal-dialog">
54
+ <button type="button" title="Close Dialog" class="super-settings-modal-close super-settings-btn-no-chrome js-close-modal">&times;</button>
55
+ <div class="super-settings-modal-content">
56
+ </div>
44
57
  </div>
45
58
  </div>
46
- </div>
59
+ </main>
47
60
 
48
61
  <template id="setting-row-template" style="display:none;">
49
62
  <tr>
@@ -65,11 +78,15 @@
65
78
  <div class="js-value-placeholder super-settings-max-height-text"></div>
66
79
  </td>
67
80
 
81
+ <td class="super-settings-last-modified super-settings-text-nowrap">
82
+ <div class="js-value-placeholder"></div>
83
+ </td>
84
+
68
85
  <td class="super-settings-controls">
69
- <%= icon_button(:info, title: "Setting Info", color: "royalblue", js_class: "js-show-history") %>
70
- <%= icon_button(:edit, title: "Edit Setting", color: "green", js_class: "js-edit-setting") %>
71
- <%= icon_button(:trash, title: "Remove Setting", color: "firebrick", js_class: "js-remove-setting") %>
72
- <%= icon_button(:slash, title: "Cancel Changes", color: "firebrick", js_class: "js-restore-setting", link_style: "display:none;") %>
86
+ <%= icon_button("info-circle", title: "Setting Info", color: "#0d7ff0", js_class: "js-show-history") %>
87
+ <%= icon_button("pencil-square", title: "Edit Setting", color: "#0c8024", js_class: "js-edit-setting") %>
88
+ <%= icon_button("trash3", title: "Remove Setting", color: "#dc3545", js_class: "js-remove-setting") %>
89
+ <%= icon_button("x-circle", title: "Cancel Changes", color: "#dc3545", js_class: "js-restore-setting", link_style: "display:none;") %>
73
90
  </td>
74
91
  </tr>
75
92
  </template>
@@ -79,7 +96,7 @@
79
96
  <td class="super-settings-key">
80
97
  <div>
81
98
  <label for="settings_{{id}}_key" class="super-settings-sr-only">Key</label>
82
- <input type="text" id="settings_{{id}}_key" name="settings[{{id}}][key]" value="" maxlength="190" class="form-control js-setting-key" required>
99
+ <input type="text" id="settings_{{id}}_key" name="settings[{{id}}][key]" value="" maxlength="190" class="super-settings-form-control js-setting-key" required>
83
100
  </div>
84
101
  </td>
85
102
 
@@ -88,72 +105,72 @@
88
105
  <label for="settings_{{id}}_value" class="super-settings-sr-only">Value</label>
89
106
  <span class="js-value-placeholder"></span>
90
107
  </div>
91
- <div class="container text-danger js-setting-errors" style="display:none;">
108
+ <div class="super-settings-container super-settings-text-danger js-setting-errors" style="display:none;">
92
109
  </div>
93
110
  </td>
94
111
 
95
112
  <td class="super-settings-value-type">
96
113
  <div>
97
114
  <label for="settings_{{id}}_value_type" class="super-settings-sr-only">Value Type</label>
98
- <select name="settings[{{id}}][value_type]" class="form-control js-setting-value-type" id="settings_{{id}}_value_type">
115
+ <select name="settings[{{id}}][value_type]" class="super-settings-form-control js-setting-value-type" id="settings_{{id}}_value_type">
99
116
  <% SuperSettings::Setting::VALUE_TYPES.each do |value_type| %>
100
- <option value="<%= ERB::Util.html_escape(value_type) %>"><%= ERB::Util.html_escape(value_type) %></option>
117
+ <option value="<%= html_escape(value_type) %>"><%= html_escape(value_type) %></option>
101
118
  <% end %>
102
119
  </select>
103
120
  </div>
104
121
  </td>
105
122
 
106
- <td class="super-settings-description">
123
+ <td class="super-settings-description" colspan="2">
107
124
  <div>
108
125
  <label for="settings_{{id}}_description" class="super-settings-sr-only">Description</label>
109
- <textarea id="settings_{{id}}_description" name="settings[{{id}}][description]" value="" class="form-control" rows="4"></textarea>
126
+ <textarea id="settings_{{id}}_description" name="settings[{{id}}][description]" value="" class="super-settings-form-control" rows="4"></textarea>
110
127
  </div>
111
128
  </td>
112
129
 
113
130
  <td class="super-settings-controls">
114
- <%= icon_button(:info, title: "Setting Info", color: "royalblue", js_class: "js-show-history") %>
115
- <%= icon_button(:edit, title: "Edit Setting", color: "gray", js_class: "js-no-op", disabled: true) %>
116
- <%= icon_button(:slash, title: "Cancel Changes", color: "firebrick", js_class: "js-restore-setting") %>
131
+ <%= icon_button("info-circle", title: "Setting Info", color: "#0d7ff0", js_class: "js-show-history") %>
132
+ <%= icon_button("pencil-square", title: "Edit Setting", color: "#c0c0c0", js_class: "js-no-op", disabled: true) %>
133
+ <%= icon_button("x-circle", title: "Cancel Changes", color: "#dc3545", js_class: "js-restore-setting") %>
117
134
  </td>
118
135
  </tr>
119
136
  </template>
120
137
 
121
138
  <template id="setting-value-field-template" style="display:none;">
122
- <textarea id="settings_{{id}}_value" name="settings[{{id}}][value]" class="form-control js-setting-value" rows="4"></textarea>
139
+ <textarea id="settings_{{id}}_value" name="settings[{{id}}][value]" class="super-settings-form-control js-setting-value" rows="4"></textarea>
123
140
  </template>
124
141
 
125
142
  <template id="setting-value-field-integer-template" style="display:none;">
126
- <input type="number" step="1" id="settings_{{id}}_value" name="settings[{{id}}][value]" value="" class="form-control js-setting-value">
143
+ <input type="number" step="1" id="settings_{{id}}_value" name="settings[{{id}}][value]" value="" class="super-settings-form-control js-setting-value">
127
144
  </template>
128
145
 
129
146
  <template id="setting-value-field-float-template" style="display:none;">
130
- <input type="number" step="any" id="settings_{{id}}_value" name="settings[{{id}}][value]" value="" class="form-control js-setting-value">
147
+ <input type="number" step="any" id="settings_{{id}}_value" name="settings[{{id}}][value]" value="" class="super-settings-form-control js-setting-value">
131
148
  </template>
132
149
 
133
150
  <template id="setting-value-field-datetime-template" style="display:none;">
134
151
  <span>
135
- <input type="date" id="settings_{{id}}_value" name="_settings[{{id}}][date]" value="" class="form-control js-date-input">
152
+ <input type="date" id="settings_{{id}}_value" name="_settings[{{id}}][date]" value="" class="super-settings-form-control js-date-input">
136
153
  <label for="settings_{{id}}_value_time" class="super-settings-sr-only">Time</label>
137
- <input type="time" id="settings_{{id}}_value_time" name="_settings[{{id}}][time]" value="" class="form-control js-time-input" aria-label="Time">
154
+ <input type="time" id="settings_{{id}}_value_time" name="_settings[{{id}}][time]" value="" class="super-settings-form-control js-time-input" aria-label="Time">
138
155
  <input type="hidden" name="settings[{{id}}][value]" value="" class="js-setting-value">
139
- <small class="text-muted">Time Zone: <span class="timezone"></span></small>
156
+ <small class="super-settings-text-muted">Time Zone: <span class="timezone"></span></small>
140
157
  </span>
141
158
  </template>
142
159
 
143
160
  <template id="setting-value-field-boolean-template" style="display:none;">
144
- <span class="form-check">
161
+ <span class="super-settings-form-check">
145
162
  <input type="checkbox" id="settings_{{id}}_value" name="settings[{{id}}][value]" value="true" class="js-setting-value">
146
163
  <label for="settings_{{id}}_value">Enabled</label>
147
164
  </span>
148
165
  </template>
149
166
 
150
167
  <template id="setting-value-field-array-template" style="display:none;">
151
- <textarea id="settings_{{id}}_value" name="settings[{{id}}][value]" value="" class="form-control js-setting-value" rows="8" placeholder="one entry per line"></textarea>
168
+ <textarea id="settings_{{id}}_value" name="settings[{{id}}][value]" value="" class="super-settings-form-control js-setting-value" rows="8" placeholder="one entry per line"></textarea>
152
169
  </template>
153
170
 
154
171
  <template id="setting-history-table" style="display:none">
155
172
  <h3>Setting History: <span class="super-settings-history-key"></span></h3>
156
- <table class="table table-striped" id="super-settings-history">
173
+ <table class="super-settings-table super-settings-table-striped" id="super-settings-history">
157
174
  <thead>
158
175
  <tr>
159
176
  <th scope="col" class="super-settings-text-nowrap">Time</th>
@@ -2,9 +2,12 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <title><%= application_name %> Settings</title>
5
+ <meta charset="utf-8">
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
7
  <meta name="pinterest" content="nopin" />
7
- <meta name="format-detection" content="telephone=no">
8
+ <meta name="format-detection" content="telephone=no email=no date=no address=no">
9
+ <meta name="robots" content="noindex, nofollow">
10
+ <meta name="referrer" content="no-referrer-when-downgrade">
8
11
  <%= layout_style_tag %>
9
12
  <%= add_to_head %>
10
13
  </head>
@@ -15,7 +18,7 @@
15
18
  <%= application_header %>
16
19
  </h1>
17
20
  </header>
18
- <div class="container">
21
+ <div class="super-settings-container">
19
22
  <%= yield %>
20
23
  </div>
21
24
  </body>
@@ -1,12 +1,14 @@
1
- * {box-sizing:border-box;}
1
+ * {
2
+ box-sizing: border-box;
3
+ }
2
4
 
3
5
  body {
4
6
  font-family: sans-serif;
5
7
  font-size: 1rem;
6
8
  line-height: 1.5;
7
9
  text-align: left;
8
- color: #212529;
9
- background-color: #ffffff;
10
+ color: var(--text-color);
11
+ background-color: var(--background-color);
10
12
  margin: 0;
11
13
  padding: 0;
12
14
  }
@@ -37,157 +39,11 @@ header h1.logo img {
37
39
  margin-right: 1rem;
38
40
  }
39
41
 
40
- .align-center {
41
- text-align: center;
42
- }
43
-
44
- .container {
45
- padding-left: 15px;
46
- padding-right: 15px;
47
- margin-left: auto;
48
- margin-right: auto;
49
- }
50
-
51
42
  a {
52
43
  text-decoration: none;
53
- color: #369;
44
+ color: var(--link-color);
54
45
  }
55
46
 
56
47
  a:visited {
57
- color: #369;n
58
- }
59
-
60
- .btn {
61
- box-shadow:inset 0px 1px 0px 0px #ffffff;
62
- background:linear-gradient(to bottom, #f9f9f9 5%, #e9e9e9 100%);
63
- background-color:#f9f9f9;
64
- border-radius:6px;
65
- border:1px solid #dcdcdc;
66
- display:inline-block;
67
- color:#666666;
68
- font-family:Arial;
69
- font-size:15px;
70
- font-weight:bold;
71
- padding:9px 16px;
72
- text-decoration:none;
73
- text-shadow:0px 1px 0px #ffffff;
74
- vertical-align: middle;
75
- }
76
- .btn:hover {
77
- background:linear-gradient(to bottom, #e9e9e9 5%, #f9f9f9 100%);
78
- background-color:#e9e9e9;
79
- }
80
- .btn:active {
81
- position:relative;
82
- top:1px;
83
- }
84
- .btn:disabled {
85
- background:linear-gradient(to bottom, #f9f9f9 5%, #e9e9e9 100%);
86
- background-color:#f9f9f9;
87
- opacity: 0.65;
88
- box-shadow: none;
89
- }
90
-
91
- .btn-primary {
92
- box-shadow:inset 0px 1px 0px 0px #9fb4f2;
93
- background:linear-gradient(to bottom, #7892c2 5%, #476e9e 100%);
94
- background-color:#7892c2;
95
- color:#ffffff;
96
- text-shadow:0px 1px 0px #283966;
97
- }
98
- .btn-primary:hover {
99
- background:linear-gradient(to bottom, #476e9e 5%, #7892c2 100%);
100
- background-color:#476e9e;
101
- }
102
- .btn-primary:disabled {
103
- background:linear-gradient(to bottom, #7892c2 5%, #476e9e 100%);
104
- background-color:#7892c2;
105
- }
106
-
107
- .btn:not(:disabled) {
108
- cursor: pointer;
109
- }
110
- .btn:disabled {
111
- opacity: .65;
112
- }
113
-
114
- .table {
115
- width: 100%;
116
- max-width: 100%;
117
- margin-bottom: 1rem;
118
- border-collapse: collapse;
119
- }
120
-
121
- .table thead th {
122
- vertical-align: bottom;
123
- border-bottom: 2px solid #dee2e6;
124
- }
125
-
126
- .table td, .table th {
127
- padding: 0.75rem;
128
- vertical-align: top;
129
- border-top: 1px solid #dee2e6;
130
- }
131
-
132
- .table-striped tbody tr:nth-of-type(odd) {
133
- background-color: rgba(0, 0, 0, .05);
134
- }
135
-
136
- textarea {
137
- font-family: inherit;
138
- margin: 0;
139
- overflow: auto;
140
- resize: vertical;
141
- }
142
-
143
- .form-control {
144
- font-family: inherit;
145
- margin: 0;
146
- overflow: visible;
147
- display: block;
148
- width: 100%;
149
- padding: .375rem .75rem;
150
- font-size: 1rem;
151
- line-height: 1.5;
152
- color: #495057;
153
- background-color: #fff;
154
- background-clip: padding-box;
155
- border: 1px solid #ced4da;
156
- border-radius: .25rem;
157
- transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
158
- }
159
-
160
- select.form-control:not([size]):not([multiple]) {
161
- height: calc(2.25rem + 2px);
162
- }
163
-
164
- .form-check {
165
- margin-top: .5rem;
166
- display: inline-block;
167
- }
168
-
169
- .form-check input[type=checkbox] {
170
- vertical-align: middle;
171
- }
172
-
173
- .form-inline {
174
- display: inline-block;
175
- }
176
-
177
- .form-inline .form-control {
178
- display: inline-block;
179
- width: auto;
180
- vertical-align: middle;
181
- }
182
-
183
- .text-success {
184
- color: green !important;
185
- }
186
-
187
- .text-danger {
188
- color: firebrick !important;
189
- }
190
-
191
- .text-muted {
192
- color: #666 !important;
48
+ color: var(--link-color);
193
49
  }
@@ -0,0 +1,21 @@
1
+ <% unless color_scheme != :dark %>
2
+ :root {
3
+ --text-color: #212529;
4
+ --background-color: #ffffff;
5
+ --link-color: #369;
6
+ }
7
+ <% end %>
8
+
9
+ <% if color_scheme == :system %>
10
+ @media (prefers-color-scheme: dark) {
11
+ <% end %>
12
+ <% if color_scheme == :system || color_scheme == :dark %>
13
+ :root {
14
+ --text-color: #fff;
15
+ --background-color: #333;
16
+ --link-color: rgb(133, 179, 225);
17
+ }
18
+ <% end %>
19
+ <% if color_scheme == :system %>
20
+ }
21
+ <% end %>
@@ -15,8 +15,8 @@
15
15
 
16
16
  // Set the enabled status of the save button for submitting the form.
17
17
  function enableSaveButton() {
18
- const saveButton = document.querySelector("#save-settings");
19
- const discardButton = document.querySelector("#discard-changes");
18
+ const saveButton = document.querySelector("#super-settings-save-settings");
19
+ const discardButton = document.querySelector("#super-settings-discard-changes");
20
20
  if (saveButton) {
21
21
  const count = changesCount();
22
22
  const countSpan = saveButton.querySelector(".count");
@@ -117,6 +117,14 @@
117
117
  if (setting.description !== null && setting.description !== undefined) {
118
118
  row.querySelector(".super-settings-description .js-value-placeholder").innerHTML = escapeHTML(setting.description).replaceAll("\n", "<br>");
119
119
  }
120
+ if (setting.updated_at !== null && setting.updated_at !== undefined) {
121
+ const lastModified = new Date(Date.parse(setting.updated_at));
122
+ const lastModifiedFormatter = new Intl.DateTimeFormat(navigator.language, {month: "short", day: "numeric", year: "numeric"});
123
+ const lastModifiedString = lastModifiedFormatter.format(lastModified);
124
+ const lastModifiedElement = row.querySelector(".super-settings-last-modified .js-value-placeholder")
125
+ lastModifiedElement.innerText = lastModifiedString;
126
+ lastModifiedElement.title = dateFormatter().format(lastModified);
127
+ }
120
128
 
121
129
  return row
122
130
  }
@@ -182,6 +190,7 @@
182
190
  function editSettingRow(setting) {
183
191
  const row = elementFromSettingTemplate(setting, "#setting-row-edit-template");
184
192
  row.dataset.id = setting.id
193
+ row.dataset.key = setting.key
185
194
 
186
195
  row.querySelector(".super-settings-key input").value = setting.key;
187
196
  if (setting.description) {
@@ -239,7 +248,7 @@
239
248
  document.querySelector("#settings-table tbody").prepend(row);
240
249
  }
241
250
  bindSettingControlEvents(row);
242
- filterSettings(document.querySelector("#filter").value);
251
+ filterSettings(document.querySelector("#super-settings-filter").value);
243
252
  row.scrollIntoView({block: "nearest"});
244
253
  enableSaveButton();
245
254
  return row;
@@ -301,7 +310,7 @@
301
310
 
302
311
  // Programatically apply the filter again to keep it up to date with other changes.
303
312
  function applyFilter(value) {
304
- const filter = document.querySelector("#filter");
313
+ const filter = document.querySelector("#super-settings-filter");
305
314
  if (filter) {
306
315
  if (value) {
307
316
  filter.value = value;
@@ -330,11 +339,11 @@
330
339
  function showFlash(message, success) {
331
340
  const flash = document.querySelector(".js-flash");
332
341
  if (success) {
333
- flash.classList.add("text-success");
334
- flash.classList.remove("text-danger");
342
+ flash.classList.add("super-settings-text-success");
343
+ flash.classList.remove("super-settings-text-danger");
335
344
  } else {
336
- flash.classList.add("text-danger");
337
- flash.classList.remove("text-success");
345
+ flash.classList.add("tsuper-settings-ext-danger");
346
+ flash.classList.remove("super-settings-text-success");
338
347
  }
339
348
  flash.innerText = message;
340
349
  flash.style.display = "inline-block";
@@ -378,7 +387,7 @@
378
387
  tbody.insertAdjacentHTML("beforeend", rowsHTML);
379
388
 
380
389
  if (payload.previous_page_params || payload.next_page_params) {
381
- let paginationHTML = `<div class="align-center">`;
390
+ let paginationHTML = `<div class="super-settings-align-center">`;
382
391
  if (payload.previous_page_params) {
383
392
  paginationHTML += `<div style="float:left;"><a href="#" class="js-show-history" title="Newer" data-offset="${payload.previous_page_params.offset}" data-limit="${payload.previous_page_params.limit}" data-key="${payload.previous_page_params.key}")>&#8592; Newer</a></div>`;
384
393
  }
@@ -393,7 +402,7 @@
393
402
 
394
403
  // Show a modal window overlayed on the page.
395
404
  function showModal() {
396
- const modal = document.querySelector("#modal");
405
+ const modal = document.querySelector("#super-settings-modal");
397
406
  const content = document.querySelector(".super-settings-modal-content");
398
407
  modal.style.display = "block";
399
408
  modal.setAttribute("aria-hidden", "false");
@@ -409,7 +418,7 @@
409
418
 
410
419
  // Hide the modal window overlayed on the page.
411
420
  function hideModal() {
412
- const modal = document.querySelector("#modal");
421
+ const modal = document.querySelector("#super-settings-modal");
413
422
  const content = document.querySelector(".super-settings-modal-content");
414
423
  modal.style.display = "none";
415
424
  modal.setAttribute("aria-hidden", "true");
@@ -456,7 +465,7 @@
456
465
  return;
457
466
  }
458
467
 
459
- const modal = document.querySelector("#modal");
468
+ const modal = document.querySelector("#super-settings-modal");
460
469
  const content = document.querySelector(".super-settings-modal-content");
461
470
  let key = event.target.dataset.key;
462
471
  if (!key) {
@@ -592,7 +601,12 @@
592
601
  document.querySelectorAll("#settings-table tbody tr[data-edited=true]").forEach(function(row) {
593
602
  const data = {};
594
603
  settingsData.push(data);
604
+
595
605
  data.key = row.querySelector(".js-setting-key").value;
606
+ if (data.key != row.dataset.key) {
607
+ data.key_was = row.dataset.key;
608
+ }
609
+
596
610
  const deleted = row.querySelector(".js-setting-deleted");
597
611
  if (deleted && deleted.value === "1") {
598
612
  data.deleted = true;
@@ -635,7 +649,7 @@
635
649
  function refreshPage(event) {
636
650
  event.preventDefault();
637
651
  let url = window.location.href.replace(/\?.*/, "");
638
- const filter = document.querySelector("#filter").value;
652
+ const filter = document.querySelector("#super-settings-filter").value;
639
653
  if (filter !== "") {
640
654
  url += "?filter=" + escape(filter);
641
655
  }
@@ -694,8 +708,9 @@
694
708
  const tbody = document.querySelector("#settings-table tbody");
695
709
  tbody.innerHTML = "";
696
710
  let count = settings.length;
697
- settings.forEach(function(setting) {
698
- const randomId = "setting" + Math.floor((Math.random() * 0xFFFFFFFFFFFFFF)).toString(16);
711
+
712
+ sortSettings(settings).forEach(function(setting) {
713
+ const randomId = "setting" + Math.floor((Math.random() * 0xFFFFFFFFFFFFF)).toString(16);
699
714
  setting.id = (setting.id || randomId);
700
715
  const row = settingRow(setting);
701
716
  tbody.appendChild(row);
@@ -703,12 +718,73 @@
703
718
  });
704
719
  document.querySelector(".js-settings-count").textContent = `${count} ${count === 1 ? "Setting" : "Settings"}`;
705
720
 
706
- const filter = document.querySelector("#filter");
721
+ const filter = document.querySelector("#super-settings-filter");
707
722
  if (filter) {
708
723
  filter.dispatchEvent(new Event("input"));
709
724
  }
710
725
  }
711
726
 
727
+ function sortOrder() {
728
+ const selectedSort = document.querySelector(".super-settings-sort-control[data-selected=true]");
729
+ const field = selectedSort.dataset.field;
730
+ const order = selectedSort.dataset.order;
731
+ return {field: field, order: order};
732
+ }
733
+
734
+ // Sort settings by the selected sort option.
735
+ function sortSettings(settings) {
736
+ const sort = sortOrder();
737
+ return settings.sort(function(a, b) {
738
+ let aValue = a[sort.field];
739
+ let bValue = b[sort.field];
740
+ if (sort.field == "updated_at") {
741
+ aValue = new Date(Date.parse(aValue));
742
+ bValue = new Date(Date.parse(bValue));
743
+ }
744
+
745
+ if (aValue === bValue) {
746
+ return 0;
747
+ } else if (sort.order === "asc") {
748
+ return (aValue < bValue) ? -1 : 1;
749
+ } else {
750
+ return (aValue > bValue) ? -1 : 1;
751
+ }
752
+ })
753
+ }
754
+
755
+ function setSortOrder(event) {
756
+ event.preventDefault();
757
+
758
+ const target = event.target.closest(".super-settings-sort-control");
759
+ let prevSelection = document.querySelector(".super-settings-sort-control[data-selected=true]");
760
+
761
+ if (prevSelection == target) {
762
+ selectSortElement(prevSelection, false);
763
+ target.querySelector(`[data-order=${target.dataset.order}]`).style.display = "none";
764
+ target.dataset.order = (target.dataset.order === "asc" ? "desc" : "asc");
765
+ target.querySelector(`[data-order=${target.dataset.order}]`).style.display = "inline-block";
766
+ } else {
767
+ selectSortElement(prevSelection, false);
768
+ }
769
+
770
+ selectSortElement(target, true);
771
+
772
+ renderSettingsTable(activeSettings);
773
+ }
774
+
775
+ function selectSortElement(element, selected) {
776
+ element.dataset.selected = selected;
777
+
778
+ const svg = element.querySelector(`[data-order=${element.dataset.order}]`).querySelector("svg");
779
+ if (selected) {
780
+ svg.style.backgroundColor = getComputedStyle(document.querySelector(".super-settings")).getPropertyValue("--primary-color");
781
+ svg.style.fill = "white";
782
+ } else {
783
+ svg.style.backgroundColor = null;
784
+ svg.style.fill = null;
785
+ }
786
+ }
787
+
712
788
  function promptUnsavedChanges(event) {
713
789
  if (changesCount() > 0) {
714
790
  return "Are you sure you want to leave?";
@@ -740,15 +816,18 @@
740
816
  docReady(function() {
741
817
  storeAccessToken();
742
818
 
743
- addListener(document.querySelector("#filter"), "input", filterListener);
744
- addListener(document.querySelector("#add-setting"), "click", addSetting);
745
- addListener(document.querySelector("#discard-changes"), "click", refreshPage);
746
- addListener(document.querySelector("#save-settings"), "click", updateSettings);
747
- addListener(document.querySelector("#modal"), "click", closeModal);
819
+ addListener(document.querySelector("#super-settings-filter"), "input", filterListener);
820
+ addListener(document.querySelector("#super-settings-add-setting"), "click", addSetting);
821
+ addListener(document.querySelector("#super-settings-discard-changes"), "click", refreshPage);
822
+ addListener(document.querySelector("#super-settings-save-settings"), "click", updateSettings);
823
+ addListener(document.querySelector("#super-settings-modal"), "click", closeModal);
824
+ addListener(document.querySelectorAll(".super-settings-sort-control"), "click", setSortOrder);
748
825
 
749
826
  const queryParams = new URLSearchParams(window.location.search);
750
827
  applyFilter(queryParams.get("filter"));
751
828
 
829
+ selectSortElement(document.querySelector(".super-settings-sort-control[data-selected=true]"), true);
830
+
752
831
  fetchActiveSettings();
753
832
 
754
833
  window.onbeforeunload = promptUnsavedChanges;