Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feedback button #527

Merged
merged 65 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
e7796d2
Init Feedback model
Splines Jun 4, 2023
9c66595
Add Feedback modal view and corresponding controller
Splines Jun 4, 2023
dfe9036
Merge branch 'mampf-next' into feature/feedback-button
Splines Jul 3, 2023
d41365e
Migrate feedback form to Bootstrap v5
Splines Jul 3, 2023
5836422
Add basic styling to Feedback form
Splines Jul 3, 2023
56c4741
Add "allow contact via mail" checkbox
Splines Jul 5, 2023
90d044e
Toggle "allow email contact" by default
Splines Jul 5, 2023
f8dcdd1
Merge branch 'mampf-next' into feature/feedback-button
Splines Aug 7, 2023
cd057ed
Improve submit button handler (outsource to function)
Splines Aug 7, 2023
27e49b9
Init feedback mailer
Splines Aug 7, 2023
58f4389
Adjust feedback mail in views
Splines Aug 7, 2023
8d8e2b2
Implement success/error flow with toast messages
Splines Aug 22, 2023
1ec66e2
Add missing database field "can_contact"
Splines Aug 22, 2023
2021b98
Add internationalization to feedback error/success
Splines Aug 22, 2023
5310512
Lint some files
Splines Aug 22, 2023
352543b
Set feedback text field as required with min 10 chars
Splines Aug 22, 2023
aa3b359
Add "optional" to title in email
Splines Aug 22, 2023
2fbab06
Adjust spacing around feedback button
Splines Aug 22, 2023
c31a6f1
Internationalize tooltip
Splines Aug 22, 2023
bef5016
Delete console log
Splines Aug 22, 2023
92a0b2c
Add comment describing hidden submit button handler
Splines Aug 22, 2023
86eaa8d
Delete default test specs
Splines Aug 22, 2023
4fbe953
Add proper validation for Feedback body
Splines Aug 26, 2023
d8a34f5
Merge branch 'mampf-next' into feature/feedback-button
Splines Aug 26, 2023
0d43345
Merge branch 'mampf-next' into feature/feedback-button
Splines Aug 26, 2023
ace2b08
Default `can_contact` to false in backend
Splines Aug 27, 2023
5cd1af2
Update bootstrap to v5.3.1
Splines Aug 27, 2023
4ed0757
Revert "Update bootstrap to v5.3.1" in favor of PR #537
Splines Aug 27, 2023
b348061
Submit form via Ctrl + Enter when modal is opened
Splines Aug 28, 2023
696a395
Remove default nil value from ENV.fetch()
Splines Aug 28, 2023
d7f0462
Merge branch 'mampf-next' into feature/feedback-button
Splines Oct 17, 2023
1261706
Revert "Remove default nil value from ENV.fetch()"
Splines Oct 17, 2023
dab8b3b
Rename button to 'Send' (not 'Save')
Splines Oct 17, 2023
6e8c769
Check if should register feedback event handlers
Splines Oct 22, 2023
7f60853
Make feedback button ID more specific
Splines Oct 22, 2023
26f9ae8
Fix line wrapping (code style)
Splines Oct 22, 2023
2b02a48
Use delete on cascade to be able to delete a user
Splines Oct 22, 2023
3a215fc
Move Send button before Cancel button
Splines Oct 30, 2023
07ba509
Replace "on delete cascade" with "dependent destroy"
Splines Oct 30, 2023
8884707
Add cypress rules to ESLint & ignore some patterns
Splines Dec 19, 2023
d1593f1
Allow usage of tempusDominus global variable
Splines Dec 20, 2023
144135e
Ignore JS files with Sprocket syntax
Splines Dec 20, 2023
19342f7
Further improve rules, e.g. allow common globals
Splines Jan 3, 2024
70dbfed
Ignore sprocket syntax in cable.js
Splines Jan 3, 2024
9b9c082
Autofix all `.js` and `.js.erb` files
Splines Jan 3, 2024
cedd0c4
Fix variables in turbolink fix
Splines Jan 3, 2024
f8366ab
Prepend unused variables with "_"
Splines Jan 3, 2024
bc87b65
Get rid of unused widget variable
Splines Jan 3, 2024
a8fc972
Fix specs comment tab alignment
Splines Jan 3, 2024
47b7cc6
Merge branch 'lint/eslint-all' into feature/feedback-button
Splines Jan 3, 2024
f636d0f
Warn about too long GitHub commit messages (#586)
Splines Jan 11, 2024
eb9484a
Fix comment status (#585)
Splines Jan 16, 2024
475fc04
Migrate `unread_comments` flag (fix inconsistencies) (#587)
Splines Jan 17, 2024
92d48d4
Use `warn` log level for migration (#588)
Splines Jan 19, 2024
13d685d
Merge branch 'dev' into feature/feedback-button
Splines Jan 25, 2024
ca14be2
Fix linting in feedback.js
Splines Jan 25, 2024
aaf9d37
Fix RuboCop errors
Splines Jan 25, 2024
db8173e
Fix remaining ESLint errors
Splines Jan 25, 2024
be140b5
Update timestamp of feedback migration
Splines Jan 25, 2024
5374cb2
Add missing Feedback email to prod docker.env
Splines Jan 25, 2024
4952d6e
Merge branch 'dev' into feature/feedback-button
Splines Mar 19, 2024
da0b409
Remove unnecessary Feedback env variables
Splines Mar 19, 2024
ebf95cb
Add validation message for empty body
Splines Mar 19, 2024
d5c2e0c
Change `const` to `var` to avoid "redefined" errors
Splines Mar 19, 2024
a36cf52
Update timestamp of feedback migration (again)
Splines Mar 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 50 additions & 6 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,76 @@ const stylistic = require("@stylistic/eslint-plugin");

const customizedStylistic = stylistic.configs.customize({
"indent": 2,
"quotes": "double",
"jsx": false,
"quote-props": "always",
"semi": "always",
"brace-style": "1tbs",
});

const cypressRules = {
"cypress/no-assigning-return-values": "error",
"cypress/no-unnecessary-waiting": "off", // TODO: fix this issue
"cypress/assertion-before-screenshot": "warn",
"cypress/no-force": "warn",
"cypress/no-async-tests": "error",
"cypress/no-pause": "error",
};

const ignoreFilesWithSprocketRequireSyntax = [
"app/assets/javascripts/application.js",
"app/assets/config/manifest.js",
"app/assets/javascripts/edit_clicker_assets.js",
"app/assets/javascripts/show_clicker_assets.js",
"app/assets/javascripts/geogebra_assets.js",
"vendor/assets/javascripts/thredded_timeago.js",
];

const customGlobals = {
TomSelect: "readable",
bootstrap: "readable",

// Rails globals
Routes: "readable",
App: "readable",
ActionCable: "readable",

// Common global methods
initBootstrapPopovers: "readable",
};

module.exports = {
root: true,
parserOptions: {
ecmaVersion: 2022,
sourceType: "module",
},
env: {
node: true,
browser: true,
jquery: true,
"node": true,
"browser": true,
"jquery": true,
"cypress/globals": true,
},
extends: [
"eslint:recommended",
// Allow linting of ERB files, see https://github.com/Splines/eslint-plugin-erb
"plugin:erb/recommended",
],
plugins: ["@stylistic", "erb"],
globals: customGlobals,
plugins: ["@stylistic", "erb", "cypress"],
rules: {
...customizedStylistic.rules,
"no-unused-vars": "warn",
"no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
...cypressRules,
// see https://github.com/eslint-stylistic/eslint-stylistic/issues/254
"@stylistic/quotes": ["error", "double", { avoidEscape: true }],
},
ignorePatterns: [
"node_modules/",
"pdfcomprezzor/",
"tmp/",
"public/packs/",
"public/packs-test/",
"public/uploads/",
...ignoreFilesWithSprocketRequireSyntax,
],
};
170 changes: 90 additions & 80 deletions app/assets/javascripts/_selectize_turbolinks_fix.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,130 +5,140 @@
// transfer knowledge about selected items from selectize to html options
var resetSelectized;

resetSelectized = function(index, select) {
resetSelectized = function (index, select) {
var i, len, selectedValue, val;
selectedValue = select.tomselect.getValue();
select.tomselect.destroy();
$(select).find('option').attr('selected', null);
if ($(select).prop('multiple')) {
$(select).find("option").attr("selected", null);
if ($(select).prop("multiple")) {
for (i = 0, len = selectedValue.length; i < len; i++) {
val = selectedValue[i];
if (val !== '') {
$(select).find("option[value='" + val + "']").attr('selected', true);
if (val !== "") {
$(select).find("option[value='" + val + "']").attr("selected", true);
}
}
} else {
if (selectedValue !== '') {
$(select).find("option[value='" + selectedValue + "']").attr('selected', true);
}
else {
if (selectedValue !== "") {
$(select).find("option[value='" + selectedValue + "']").attr("selected", true);
}
}
};

this.fillOptionsByAjax = function($selectizedSelection) {
$selectizedSelection.each(function() {
var courseId, existing_values, fill_path, loaded, locale, model_select, plugins, send_data, parent;
if (this.dataset.drag === 'true') {
plugins = ['remove_button', 'drag_drop'];
} else {
plugins = ['remove_button'];
function fillOptionsByAjax($selectizedSelection) {
// TODO: this function definitely needs some refactoring
$selectizedSelection.each(function () {
let plugins = [];
let send_data = false;
let fill_path = "";
let courseId = 0;
let loaded = false;
let locale = null;

if (this.dataset.drag === "true") {
plugins = ["remove_button", "drag_drop"];
}
else {
plugins = ["remove_button"];
}
if (this.dataset.ajax === 'true' && this.dataset.filled === 'false') {
model_select = this;
if (this.dataset.ajax === "true" && this.dataset.filled === "false") {
const model_select = this;
courseId = 0;
placeholder = this.dataset.placeholder;
no_result_msg = this.dataset.noResults;
existing_values = Array.apply(null, model_select.options).map(function(o) {
return o.value;
});
const placeholder = this.dataset.placeholder;
const no_result_msg = this.dataset.noResults;
send_data = false;
loaded = false;
parent = this.dataset.modal === undefined ? document.body : null;
if (this.dataset.model === 'tag') {
if (this.dataset.model === "tag") {
locale = this.dataset.locale;
fill_path = Routes.fill_tag_select_path({
locale: locale
locale: locale,
});
send_data = true;
} else if (this.dataset.model === 'user') {
}
else if (this.dataset.model === "user") {
fill_path = Routes.fill_user_select_path();
send_data = true;
} else if (this.dataset.model === 'user_generic') {
}
else if (this.dataset.model === "user_generic") {
fill_path = Routes.list_generic_users_path();
} else if (this.dataset.model === 'teachable') {
}
else if (this.dataset.model === "teachable") {
fill_path = Routes.fill_teachable_select_path();
} else if (this.dataset.model === 'medium') {
}
else if (this.dataset.model === "medium") {
fill_path = Routes.fill_media_select_path();
} else if (this.dataset.model === 'course_tag') {
}
else if (this.dataset.model === "course_tag") {
courseId = this.dataset.course;
fill_path = Routes.fill_course_tags_path();
}
(function() {
class MinimumLengthSelect extends TomSelect{

refreshOptions(triggerDropdown=true){
(function () {
class MinimumLengthSelect extends TomSelect {
refreshOptions(triggerDropdown = true) {
var query = this.inputValue();
if( query.length < 2){
if (query.length < 2) {
this.close(false);
return;
}

super.refreshOptions(triggerDropdown);
}

}
new MinimumLengthSelect("#" + model_select.id, {
plugins: plugins,
valueField: 'value',
labelField: 'name',
searchField: 'name',
maxOptions: null,
placeholder: placeholder,
closeAfterSelect: true,
load: function(query, callback) {
var url;
if (send_data || !loaded) {
url = fill_path + "?course_id=" + courseId + "&q=" + encodeURIComponent(query);
fetch(url).then(function(response) {
return response.json();
}).then(function(json) {
loaded = true;
return callback(json.map(function(item) {
return {
name: item.text,
value: item.value
};
}));
})["catch"](function() {
callback();
});
}
callback();
},
render: {
option: function(data, escape) {
return '<div>' + '<span class="title">' + escape(data.name) + '</span>' + '</div>';
plugins: plugins,
valueField: "value",
labelField: "name",
searchField: "name",
maxOptions: null,
placeholder: placeholder,
closeAfterSelect: true,
load: function (query, callback) {
var url;
if (send_data || !loaded) {
url = fill_path + "?course_id=" + courseId + "&q=" + encodeURIComponent(query);
fetch(url).then(function (response) {
return response.json();
}).then(function (json) {
loaded = true;
return callback(json.map(function (item) {
return {
name: item.text,
value: item.value,
};
}));
})["catch"](function () {
callback();
});
}
callback();
},
item: function(item, escape) {
return '<div title="' + escape(item.name) + '">' + escape(item.name) + '</div>';
render: {
option: function (data, escape) {
return "<div>" + '<span class="title">' + escape(data.name) + "</span>" + "</div>";
},
item: function (item, escape) {
return '<div title="' + escape(item.name) + '">' + escape(item.name) + "</div>";
},
no_results: function (data, escape) {
return '<div class="no-results">' + escape(no_result_msg) + "</div>";
},
},
no_results: function(data, escape) {
return '<div class="no-results">'+ escape(no_result_msg) + '</div>';
}
}
});
})();} else {
});
})();
}
else {
return new TomSelect("#" + this.id, {
plugins: plugins,
maxOptions: null
maxOptions: null,
});
}
});
};
}

$(document).on('turbolinks:before-cache', function() {
$('.tomselected').each(resetSelectized);
$(document).on("turbolinks:before-cache", function () {
$(".tomselected").each(resetSelectized);
});

$(document).on('turbolinks:load', function() {
fillOptionsByAjax($('.selectize'));
$(document).on("turbolinks:load", function () {
fillOptionsByAjax($(".selectize"));
});
26 changes: 13 additions & 13 deletions app/assets/javascripts/bootstrap_modal_turbolinks_fix.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
$(document).on('turbolinks:load', function () {
// show all active modals
$('.activeModal').modal('show');
// remove active status (this needs to be reestablished before caching)
$('.activeModal').removeClass('activeModal');
$(document).on("turbolinks:load", function () {
// show all active modals
$(".activeModal").modal("show");
// remove active status (this needs to be reestablished before caching)
$(".activeModal").removeClass("activeModal");
});

$(document).on('turbolinks:before-cache', function () {
// if some modal is open
if ($('body').hasClass('modal-open')) {
$('.modal.show').addClass('activeModal');
$('.modal.show').modal('hide');
// remove the greyed out background
$('.modal-backdrop').remove();
}
$(document).on("turbolinks:before-cache", function () {
// if some modal is open
if ($("body").hasClass("modal-open")) {
$(".modal.show").addClass("activeModal");
$(".modal.show").modal("hide");
// remove the greyed out background
$(".modal-backdrop").remove();
}
});
16 changes: 8 additions & 8 deletions app/assets/javascripts/bootstrap_popovers.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
$(document).on('turbolinks:load', function () {
initBootstrapPopovers();
$(document).on("turbolinks:load", function () {
initBootstrapPopovers();
});

/**
* Initializes all Bootstrap popovers on the page.
*
*
* This function might be used for the first initialization of popovers as well
* as for reinitialization on page changes.
*
* See: https://getbootstrap.com/docs/5.3/components/popovers/#enable-popovers
*/
function initBootstrapPopovers() {
const popoverHtmlElements = document.querySelectorAll('[data-bs-toggle="popover"]');
for (const element of popoverHtmlElements) {
new bootstrap.Popover(element);
}
}
const popoverHtmlElements = document.querySelectorAll('[data-bs-toggle="popover"]');
for (const element of popoverHtmlElements) {
new bootstrap.Popover(element);
}
}
6 changes: 4 additions & 2 deletions app/assets/javascripts/cable.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
//
// disable eslint
/* eslint-disable */
//= require action_cable
//= require_self
//= require_tree ./channels
/* eslint-enable */

(function() {
(function () {
this.App || (this.App = {});

App.cable = ActionCable.createConsumer();

}).call(this);
Loading
Loading