diff --git a/Gemfile b/Gemfile index 54c71cd68..228b9e901 100644 --- a/Gemfile +++ b/Gemfile @@ -14,7 +14,7 @@ gem "activerecord-nulldb-adapter" # Use sqlite3 as the database for Active Record gem "sqlite3", "~> 1.4" # Use Puma as the app server -gem "puma", "~> 4.1" +gem "puma", "< 7" # Use SCSS for stylesheets gem "sass-rails", ">= 6" # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker diff --git a/Gemfile.lock b/Gemfile.lock index 848f9a66e..c1296235d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/sprockets-rails - revision: 73e7351abff3506f6dca6b2da8abedfd5c7c0d77 + revision: 065cbe83989c44019eca7161782ed4fdb6473517 branch: master specs: sprockets-rails (3.4.2) @@ -142,9 +142,9 @@ GEM bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) - bootstrap (5.2.1) + bootstrap (5.3.1) autoprefixer-rails (>= 9.1.0) - popper_js (>= 2.11.6, < 3) + popper_js (>= 2.11.8, < 3) sassc-rails (>= 2.0.0) bootstrap_form (5.1.0) actionpack (>= 5.2) @@ -278,7 +278,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) - i18n (1.12.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) @@ -329,9 +329,9 @@ GEM listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - loofah (2.19.1) + loofah (2.21.3) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -344,7 +344,7 @@ GEM mime-types-data (3.2023.0218.1) mini_magick (4.12.0) mini_mime (1.1.2) - minitest (5.18.0) + minitest (5.19.0) msgpack (1.7.0) multi_json (1.15.0) multipart-post (2.3.0) @@ -360,7 +360,7 @@ GEM net-protocol netrc (0.11.0) nio4r (2.5.8) - nokogiri (1.14.3-x86_64-linux) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) onebox (2.2.19) addressable (~> 2.8.0) @@ -383,7 +383,7 @@ GEM ttfunk pg (1.4.6) pgreset (0.3) - popper_js (2.11.6) + popper_js (2.11.8) pr_geohash (1.0.0) premailer (1.21.0) addressable @@ -399,13 +399,13 @@ GEM prometheus_exporter (2.0.8) webrick public_suffix (5.0.1) - puma (4.3.12) + puma (6.3.1) nio4r (~> 2.0) pundit (2.3.0) activesupport (>= 3.0.0) raabro (1.4.0) - racc (1.6.2) - rack (2.2.7) + racc (1.7.1) + rack (2.2.8) rack-proxy (0.7.6) rack rack-test (2.1.0) @@ -424,16 +424,18 @@ GEM activesupport (= 7.0.4.3) bundler (>= 1.15.0) railties (= 7.0.4.3) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) rails-erd (1.7.2) activerecord (>= 4.2) activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails-i18n (7.0.6) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) @@ -587,7 +589,7 @@ GEM tins (~> 1.0) terser (1.1.14) execjs (>= 0.3.0, < 3) - thor (1.2.1) + thor (1.2.2) thredded (1.1.0) active_record_union (>= 1.3.0) autoprefixer-rails @@ -609,7 +611,7 @@ GEM sassc-rails (>= 2.0.0) sprockets-es6 timeago_js (>= 3.0.2.2) - tilt (2.1.0) + tilt (2.2.0) timeago_js (3.0.2.2) timeout (0.3.2) tins (1.32.1) @@ -648,7 +650,7 @@ GEM websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) will_paginate (3.3.1) - zeitwerk (2.6.7) + zeitwerk (2.6.11) PLATFORMS x86_64-linux @@ -705,7 +707,7 @@ DEPENDENCIES premailer-rails progress_bar prometheus_exporter - puma (~> 4.1) + puma (< 7) rack rails (~> 7.0.4.3) rails-erd @@ -734,7 +736,7 @@ DEPENDENCIES sunspot_rails! sunspot_solr terser - thredded! + thredded thredded-markdown_katex! trix-rails turbolinks (~> 5) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index a5bb9718e..9834213e1 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -24,6 +24,7 @@ //= require announcements //= require answers //= require bootstrap_modal_turbolinks_fix +//= require bootstrap_popovers //= require chapters //= require clickers //= require courses diff --git a/app/assets/javascripts/bootstrap_modal_turbolinks_fix.coffee b/app/assets/javascripts/bootstrap_modal_turbolinks_fix.coffee deleted file mode 100644 index 6df7fdc78..000000000 --- a/app/assets/javascripts/bootstrap_modal_turbolinks_fix.coffee +++ /dev/null @@ -1,24 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ - -$(document).on 'turbolinks:load', -> - # show all active modals - $('.activeModal').modal('show') - # remove active status (this needs to be reestablished before caching) - $('.activeModal').removeClass('activeModal') - - popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')) - popoverList = popoverTriggerList.map (popoverTriggerEl) -> - return new bootstrap.Popover(popoverTriggerEl) - - return - -$(document).on 'turbolinks:before-cache', -> - # 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() - return \ No newline at end of file diff --git a/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js b/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js new file mode 100644 index 000000000..d050707d7 --- /dev/null +++ b/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js @@ -0,0 +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: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(); + } +}); diff --git a/app/assets/javascripts/bootstrap_popovers.js b/app/assets/javascripts/bootstrap_popovers.js new file mode 100644 index 000000000..8bc3d7cd9 --- /dev/null +++ b/app/assets/javascripts/bootstrap_popovers.js @@ -0,0 +1,18 @@ +$(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); + } +} \ No newline at end of file diff --git a/app/assets/javascripts/datetimepicker.js b/app/assets/javascripts/datetimepicker.js new file mode 100644 index 000000000..7730f99a1 --- /dev/null +++ b/app/assets/javascripts/datetimepicker.js @@ -0,0 +1,126 @@ +// Initialize on page load (when js file is dynamically loaded) +$(document).ready(startInitialization); + +// On page change (e.g. go back and forth in browser) +$(document).on('turbolinks:before-cache', () => { + // Remove stale datetimepickers + $('.tempus-dominus-widget').remove(); +}); + +function startInitialization() { + const pickerElements = $('.td-picker'); + if (pickerElements.length == 0) { + console.error('No datetimepicker element found on page, although requested.'); + return; + } + + pickerElements.each((i, element) => { + element = $(element); + const datetimePicker = initDatetimePicker(element); + registerErrorHandlers(datetimePicker, element); + registerFocusHandlers(datetimePicker, element); + }); +} + +function getDateTimePickerIcons() { + // At the moment: continue to use FontAwesome 5 icons + // see https://getdatepicker.com/6/plugins/fa5.html + // see https://github.com/Eonasdan/tempus-dominus/blob/master/dist/plugins/fa-five.js + return { + type: 'icons', + time: 'fas fa-clock', + date: 'fas fa-calendar', + up: 'fas fa-arrow-up', + down: 'fas fa-arrow-down', + previous: 'fas fa-chevron-left', + next: 'fas fa-chevron-right', + today: 'fas fa-calendar-check', + clear: 'fas fa-trash', + close: 'fas fa-times', + } +} + +function initDatetimePicker(element) { + // see https://getdatepicker.com + return new tempusDominus.TempusDominus( + element.get(0), + { + display: { + sideBySide: true, // clock to the right of the calendar + icons: getDateTimePickerIcons(), + }, + localization: { + startOfTheWeek: 1, + // choose format to be compliant with backend time format + format: 'yyyy-MM-dd HH:mm', + hourCycle: 'h23', + } + } + ); +} + +function registerErrorHandlers(datetimePicker, element) { + // Catch Tempus Dominus error when user types in invalid date + // this is rather hacky at the moment, see this discussion: + // https://github.com/Eonasdan/tempus-dominus/discussions/2656 + datetimePicker.dates.oldParseInput = datetimePicker.dates.parseInput; + datetimePicker.dates.parseInput = (input) => { + try { + return datetimePicker.dates.oldParseInput(input); + } catch (err) { + const errorMsg = element.find('.td-error').data('td-invalid-date'); + element.find('.td-error').text(errorMsg).show(); + datetimePicker.dates.clear(); + } + }; + + datetimePicker.subscribe(tempusDominus.Namespace.events.change, (e) => { + // see https://getdatepicker.com/6/namespace/events.html#change + + // Clear error message + if (e.isValid && !e.isClear) { + element.find('.td-error').empty(); + } + + // If date was selected, close datetimepicker. + // However: leave the datetimepicker open if user only changed time + if (e.oldDate && e.date && !hasUserChangedDate(e.oldDate, e.date)) { + datetimePicker.hide(); + } + }); +} + +function hasUserChangedDate(oldDate, newDate) { + return oldDate.getHours() != newDate.getHours() + || oldDate.getMinutes() != newDate.getMinutes(); +} + +function registerFocusHandlers(datetimePicker, element) { + // Show datetimepicker when user clicks in text field next to button + // or when input field receives focus + var isButtonInvokingFocus = false; + + element.find('.td-input').on('click focusin', (e) => { + try { + if (!isButtonInvokingFocus) { + datetimePicker.show(); + } + } + finally { + isButtonInvokingFocus = false; + } + }); + + element.find('.td-picker-button').on('click', () => { + isButtonInvokingFocus = true; + element.find('.td-input').focus(); + }); + + // Hide datetimepicker when input field loses focus + element.find('.td-input').blur((e) => { + if (!e.relatedTarget) { + return; + } + datetimePicker.hide(); + }); +} diff --git a/app/assets/javascripts/lectures.coffee b/app/assets/javascripts/lectures.coffee index 721bd0222..15dfacca0 100644 --- a/app/assets/javascripts/lectures.coffee +++ b/app/assets/javascripts/lectures.coffee @@ -10,10 +10,7 @@ disableExceptOrganizational = -> return $(document).on 'turbolinks:load', -> - - # activate all popovers - $('[data-bs-toggle="popover"]').popover() - + initBootstrapPopovers() # if any input is given to the lecture form (for people in lecture), # disable other input $('#lecture-form :input').on 'change', -> @@ -82,6 +79,11 @@ $(document).on 'turbolinks:load', -> location.reload(true) return + # reload current page if lecture comments editing is cancelled + $('#cancel-lecture-comments').on 'click', -> + location.reload(true) + return + # reload current page if lecture preferences editing is cancelled $('#cancel-lecture-organizational').on 'click', -> location.reload(true) diff --git a/app/assets/javascripts/media.coffee b/app/assets/javascripts/media.coffee index d6aaebb90..8869ee596 100644 --- a/app/assets/javascripts/media.coffee +++ b/app/assets/javascripts/media.coffee @@ -17,13 +17,6 @@ fancyTimeFormat = (time) -> output $(document).on 'turbolinks:load', -> - - # init datetimepicker - $('#release_date').datetimepicker - format: 'd.m.Y H:i' - inline: false - lang: 'en' - # disable/enable search field on the media search page, depending on # whether 'all tags'/'all editors'/... are selected $('[id^="search_all_"]').on 'change', -> @@ -395,14 +388,8 @@ $(document).on 'turbolinks:load', -> return $('#release_date').on 'focus', -> + # Select other option if user clicks on release date input field $('#medium_release_now_0').prop('checked', true) - $('#release_date').datetimepicker('toggle') - return - - $('#medium_assignment_deadline').on 'focus', -> - $(this).datetimepicker - format: 'd.m.Y H:i' - inline: false return $('#medium_create_assignment').on 'click', -> diff --git a/app/assets/javascripts/upload.coffee b/app/assets/javascripts/upload.coffee index 836f08ecf..c6f40e7bc 100644 --- a/app/assets/javascripts/upload.coffee +++ b/app/assets/javascripts/upload.coffee @@ -339,8 +339,7 @@ directUpload provides an interface to upload (multiple) files to an endpoint hiddenInputElement single ) -> - # update helpdesk - $('[data-bs-toggle="popover"]').popover() + initBootstrapPopovers() hiddenInput = document.getElementById(hiddenInputElement) hiddenInput2 = document.getElementById('upload-userManuscript-hidden2') fileInput =document.getElementById(fileInputElement) @@ -439,8 +438,7 @@ directUpload provides an interface to upload (multiple) files to an endpoint ### @result = undefined @userManuscriptUpload = (fileInput) -> - # update helpdesk - $('[data-bs-toggle="popover"]').popover() + initBootstrapPopovers() hiddenInput = document.getElementById('upload-userManuscript-hidden') hiddenInput2 = document.getElementById('upload-userManuscript-hidden2') fileInput.style.display = 'none' diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss new file mode 100644 index 000000000..f0089aab4 --- /dev/null +++ b/app/assets/stylesheets/admin.scss @@ -0,0 +1,4 @@ +.admin-background { + background-size: 20px 20px; + background-image: radial-gradient(circle, #bebebe 1px, #ffffff00 1px); +} \ No newline at end of file diff --git a/app/assets/stylesheets/navbar.scss b/app/assets/stylesheets/navbar.scss index 0a49638d3..d58485f75 100644 --- a/app/assets/stylesheets/navbar.scss +++ b/app/assets/stylesheets/navbar.scss @@ -2,6 +2,10 @@ background: linear-gradient(110deg, #223e62 0%, #405d75 100%); } +.admin-navbars-container { + background: linear-gradient(110deg, #821A3B 0%, #8d1c40 100%); +} + #mampfbrand { color: white; font-weight: bold; @@ -12,10 +16,6 @@ width: 35px; } -.navbar-nav a { - filter: drop-shadow(1px 4px 7px rgba(0, 0, 0, 0.2)); -} - .navbar { box-shadow: 1px 4px 7px -2px rgba(0, 0, 0, 0.1); z-index: 1000; // place navbar and its shadow on top of everything @@ -24,21 +24,41 @@ .new-comment { color: #ffc107 !important; } -} -#navbar-buttons a { - text-decoration: none; - font-size: 1.35rem; - padding: 0 9px; - color: white; + .nav-item { + position: relative; + } + + .navbar-nav .nav-link { + filter: drop-shadow(1px 4px 7px rgba(0, 0, 0, 0.2)); - &::after { - &.active-item { - content: "\2022"; - font-size: 1rem; - position: absolute; - top: 21px; - left: 16px; + &:hover { + color: #cee2ff; } + + &:focus { + color: white; + } + + text-decoration: none; + font-size: 1.35rem; + padding: 0 9px; + color: white; + + &::after { + &.active-item { + content: "\2022"; // dot + font-size: 1rem; + position: absolute; + top: 21px; + left: 16px; + } + } + } +} + +.admin-navbars-container .navbar-nav .nav-link { + &:hover { + color: #ffe5ed; } } \ No newline at end of file diff --git a/app/controllers/commontator/comments_controller.rb b/app/controllers/commontator/comments_controller.rb index 641db8b0c..7f670c7e7 100644 --- a/app/controllers/commontator/comments_controller.rb +++ b/app/controllers/commontator/comments_controller.rb @@ -198,8 +198,25 @@ def update_unread_status return unless medium.released.in?(['all', 'users', 'subscribers']) relevant_users = medium.teachable.media_scope.users - relevant_users.where(unread_comments: false) + relevant_users.where.not(id: current_user.id) + .where(unread_comments: false) .update_all(unread_comments: true) - @update_icon = relevant_users.exists?(current_user.id) + + # make sure that the thread associated to this comment is marked as read + # by the comment creator (unless some other user posted a comment in it + # that has not yet been read) + @reader = Reader.find_or_create_by(user: current_user, + thread: @commontator_thread) + if unseen_comments? + @update_icon = true + return + end + @reader.update(updated_at: Time.current) + end + + def unseen_comments? + @commontator_thread.comments.any? do |c| + c.creator != current_user && c.created_at > @reader.updated_at + end end end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 461bafb87..5c9da133f 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -15,27 +15,12 @@ // const images = require.context('../images', true) // const imagePath = (name) => images(name, true) -// JQuery Datetimepicker fix -// import '...' does not work, random names for the import don't work either -// we use "css" and "myLib" imports although these strings do not show up anywhere -// in our codebase. This is just a hack to make the imports-loader work. Maybe it has -// to do with how webpacker resolves the imports or how jquery-datetimepicker exports -// its module. -import css from 'jquery-datetimepicker/build/jquery.datetimepicker.min.css' -import myLib from 'imports-loader?imports=default%20jquery%20$!./../../../node_modules/jquery-datetimepicker/build/jquery.datetimepicker.full.min.js' - -import moment from "moment"; // require -window.moment = moment; import { WidgetInstance } from "friendly-challenge"; var friendlyChallengeWidgetInstance = WidgetInstance -$.datetimepicker.setLocale('de'); - document.addEventListener("turbolinks:load", function () { - // ... - var doneCallback, element, options, widget; doneCallback = function (solution) { @@ -60,4 +45,11 @@ document.addEventListener("turbolinks:load", function () { //DO not uncomment, evil // widget.reset(); } + + // Init Masonry grid system + // see https://getbootstrap.com/docs/5.0/examples/masonry/ + // and official documentation: https://masonry.desandro.com/ + $('.masonry-grid').masonry({ + percentPosition: true + }); }) \ No newline at end of file diff --git a/app/javascript/packs/application.sass b/app/javascript/packs/application.sass index 0e08f8e6d..e69de29bb 100644 --- a/app/javascript/packs/application.sass +++ b/app/javascript/packs/application.sass @@ -1 +0,0 @@ -@import "jquery-datetimepicker/build/jquery.datetimepicker.min.css" \ No newline at end of file diff --git a/app/views/administration/_navbar.html.erb b/app/views/administration/_navbar.html.erb index 1aaa8c966..cf4959437 100644 --- a/app/views/administration/_navbar.html.erb +++ b/app/views/administration/_navbar.html.erb @@ -1,127 +1,119 @@ <% I18n.with_locale(current_user.try(:locale)) do %> - - -<% end %> \ No newline at end of file + + + +<% end %> diff --git a/app/views/announcements/new.coffee b/app/views/announcements/new.coffee index ea8034b61..76b3657c6 100644 --- a/app/views/announcements/new.coffee +++ b/app/views/announcements/new.coffee @@ -3,7 +3,5 @@ $('#new-announcement-modal-content').empty() .append('<%= j render partial: "announcements/form", locals: { announcement: @announcement, lecture: @lecture } %>') - -# activate popovers and show modal -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() $('#newAnnouncementModal').modal('show') \ No newline at end of file diff --git a/app/views/assignments/_form.html.erb b/app/views/assignments/_form.html.erb index 8377dc5f0..0eec8828a 100644 --- a/app/views/assignments/_form.html.erb +++ b/app/views/assignments/_form.html.erb @@ -1,3 +1,5 @@ +<%= javascript_include_tag 'datetimepicker' %> +
<%= form_with model: assignment do |f| %> @@ -10,15 +12,33 @@ id="assignment-title-error">
-
- <%= f.text_field :deadline, - class: 'form-control', - autocomplete: 'off', - id: "assignment_deadline_#{assignment.id}" %> -
+ + <%# Datetimepicker for assignment deadline %> +
+
+ <%= f.text_field :deadline, + class: 'form-control td-input', + autocomplete: 'off', + 'data-td-target': '#assignment-picker' %> + + + +
+
+ +
<%= f.select :medium_id, options_for_select(Medium.where(teachable: assignment.lecture, @@ -48,12 +68,13 @@ id="assignment-deletion-date-error">
-
+
<%= f.submit t('buttons.save'), class: 'btn btn-sm btn-primary ms-2' %> <%= link_to t('buttons.cancel'), cancel_editing_assignment_path(assignment), class: 'btn btn-sm btn-secondary', + role: 'button', remote: true %>
diff --git a/app/views/assignments/_row.html.erb b/app/views/assignments/_row.html.erb index e38e1dc53..d1fbf07d2 100644 --- a/app/views/assignments/_row.html.erb +++ b/app/views/assignments/_row.html.erb @@ -5,7 +5,7 @@
<%= assignment.title %>
-
+
<% if assignment.deadline %> <%= I18n.l(assignment.deadline, format: :long) %> <% end %> @@ -26,7 +26,7 @@ <%= I18n.l(assignment.deletion_date, format: :long) %> <% end %>
-
+
<% if assignment.persisted? %> <%= link_to edit_assignment_path(assignment), class: 'text-dark', diff --git a/app/views/assignments/create.coffee b/app/views/assignments/create.coffee index 3aab03b90..9e938c3f0 100644 --- a/app/views/assignments/create.coffee +++ b/app/views/assignments/create.coffee @@ -1,9 +1,9 @@ # clean up from previous error messages -$('#assignment_title').removeClass('is-invalid') +$('#assignment_title_').removeClass('is-invalid') $('#assignment-title-error').empty() $('#assignment_deadline').removeClass('is-invalid') $('#assignment-deadline-error').empty() -$('#assignment_deletion_date').removeClass('is-invalid') +$('#assignment_deletion_date_').removeClass('is-invalid') $('#assignment-deletion-date-error').empty() # display error message @@ -11,7 +11,7 @@ $('#assignment-deletion-date-error').empty() <% if @errors[:title].present? %> $('#assignment-title-error') .append('<%= @errors[:title].join(" ") %>').show() -$('#assignment_title').addClass('is-invalid') +$('#assignment_title_').addClass('is-invalid') <% end %> <% if @errors[:deadline].present? %> $('#assignment-deadline-error') @@ -21,7 +21,7 @@ $('#assignment_deadline').addClass('is-invalid') <% if @errors[:deletion_date].present? %> $('#assignment-deletion-date-error') .append('<%= @errors[:deletion_date].join(" ") %>').show() -$('#assignment_deletion_date').addClass('is-invalid') +$('#assignment_deletion_date_').addClass('is-invalid') <% end %> <% else %> $('.assignmentRow[data-id="0"') diff --git a/app/views/assignments/edit.coffee b/app/views/assignments/edit.coffee index 60d65a9ff..68dab3107 100644 --- a/app/views/assignments/edit.coffee +++ b/app/views/assignments/edit.coffee @@ -16,10 +16,3 @@ new TomSelect('#assignment_medium_id_<%= @assignment.id %>', # make sure that no medium is preselected $('#assignment_medium_id_<%= @assignment.id %>').val(null).trigger('change') <% end %> - -# using moment to format the date right -d = moment($("#assignment_deadline_<%= @assignment.id %>").val(),"YYYY-MM-DD hh:mm:ss z") -$("#assignment_deadline_<%= @assignment.id %>").val (d.format("DD.MM.Y H:mm")) -$("#assignment_deadline_<%= @assignment.id %>").datetimepicker - format:'d.m.Y H:i' - inline:false diff --git a/app/views/assignments/new.js.erb b/app/views/assignments/new.js.erb index df9a0beee..c7872b491 100644 --- a/app/views/assignments/new.js.erb +++ b/app/views/assignments/new.js.erb @@ -16,8 +16,3 @@ new TomSelect('#assignment_medium_id_', { }); $('#assignment_medium_id_').val(null).trigger('change'); - -$("#assignment_deadline_").datetimepicker({ - format: 'd.m.Y H:i', - inline: false -}); \ No newline at end of file diff --git a/app/views/assignments/update.coffee b/app/views/assignments/update.coffee index 55c4573c4..0378a3237 100644 --- a/app/views/assignments/update.coffee +++ b/app/views/assignments/update.coffee @@ -2,6 +2,8 @@ # clean up from previous error messages $('#assignment_title').removeClass('is-invalid') $('#assignment-title-error').empty() +$('#assignment_deadline').removeClass('is-invalid') +$('#assignment-deadline-error').empty() # display error message <% if @errors[:title].present? %> diff --git a/app/views/chapters/_edit.coffee b/app/views/chapters/_edit.coffee index 169d5eb51..852439396 100644 --- a/app/views/chapters/_edit.coffee +++ b/app/views/chapters/_edit.coffee @@ -8,5 +8,5 @@ $('#chapter-modal-content').empty() $('#chapterModalLabel').empty() .append('<%= t("admin.chapter.edit", chapter: @chapter.to_label) %>') -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() $('#chapterModal').modal('show') \ No newline at end of file diff --git a/app/views/chapters/new.coffee b/app/views/chapters/new.coffee index 767a2c544..ea4c92497 100644 --- a/app/views/chapters/new.coffee +++ b/app/views/chapters/new.coffee @@ -4,4 +4,4 @@ $('#chapter-modal-content').empty() locals: { lecture: @lecture, chapter: @chapter} %>').show() $('#chapterModal').modal('show') -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() diff --git a/app/views/clickers/new.coffee b/app/views/clickers/new.coffee index f865d8091..5a28fe177 100644 --- a/app/views/clickers/new.coffee +++ b/app/views/clickers/new.coffee @@ -3,7 +3,7 @@ $('#new-clicker-area').empty() .append('<%= j render partial: "clickers/new", locals: { clicker: @clicker } %>').show() -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() # hide all other buttons on admin index page $('.admin-index-button').hide() diff --git a/app/views/commontator/comments/new.js.erb b/app/views/commontator/comments/new.js.erb index 9ad5df9ea..0ec489036 100644 --- a/app/views/commontator/comments/new.js.erb +++ b/app/views/commontator/comments/new.js.erb @@ -8,13 +8,9 @@ var commontatorForm = $("#<%= id %>").html("<%= escape_javascript( ) %>").hide().fadeIn(); $('html, body').animate({ scrollTop: commontatorForm.offset().top - window.innerHeight/2 }, 'fast'); -<%# Initialize preview comment helpdesk popover %> -<%# See https://getbootstrap.com/docs/5.3/components/popovers/#enable-popovers %> -const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]') -const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)) +initBootstrapPopovers() $("#<%= id %>-link").hide(); - $('#<%= id %>-body').focus(); <%= javascript_proc %> diff --git a/app/views/courses/search.coffee b/app/views/courses/search.coffee index c525dc73a..0feb7330f 100644 --- a/app/views/courses/search.coffee +++ b/app/views/courses/search.coffee @@ -3,4 +3,4 @@ searchResults = document.getElementById('course-search-results') searchResults.innerHTML = '<%= j render partial: "courses/search/results", locals: { courses: @courses, total: @total } %>' -$('[data-bs-toggle="popover"]').popover() \ No newline at end of file +initBootstrapPopovers() diff --git a/app/views/items/display.coffee b/app/views/items/display.coffee index aad58a110..7b3a56b28 100644 --- a/app/views/items/display.coffee +++ b/app/views/items/display.coffee @@ -48,5 +48,4 @@ $('#link_reappearance_link').show() $('#link_details').show() <% end %> -# activate popovers -$('[data-bs-toggle="popover"]').popover() \ No newline at end of file +initBootstrapPopovers() diff --git a/app/views/items/edit.coffee b/app/views/items/edit.coffee index 4148207e3..7c31a104d 100644 --- a/app/views/items/edit.coffee +++ b/app/views/items/edit.coffee @@ -18,7 +18,7 @@ $('#item_number_field').hide() # activate selectize and popovers $('.selectize').each -> new TomSelect("#"+this.id,{ plugins: ['remove_button'] }) -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() # workaround for a selectize bug whwere the width of # the text area for the input prompt is miscalculated diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 94956f7e4..a40692503 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -9,6 +9,7 @@ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + + + + + + + + + +<%# Masonry: https://masonry.desandro.com/ %> + crossorigin="anonymous"> + + + + diff --git a/app/views/layouts/administration.html.erb b/app/views/layouts/administration.html.erb index 623859b69..e140d5aac 100644 --- a/app/views/layouts/administration.html.erb +++ b/app/views/layouts/administration.html.erb @@ -3,9 +3,10 @@ <%= render partial: 'layouts/head' %> <%= render partial: 'layouts/head_additional_js' %> + <%= stylesheet_link_tag 'admin' %> + class="admin-background">
<%= render partial: 'administration/navbar' %>
diff --git a/app/views/layouts/devise_mailer.html.erb b/app/views/layouts/devise_mailer.html.erb index 94b4641ac..6fb646ee2 100644 --- a/app/views/layouts/devise_mailer.html.erb +++ b/app/views/layouts/devise_mailer.html.erb @@ -16,7 +16,7 @@
- + <%= t('.reason', default: "", locale: @resource.locale) %> diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index 633e30f5d..fc38b5e24 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -1,7 +1,7 @@ - + <%= stylesheet_link_tag :email %> @@ -17,7 +17,7 @@
- + <%= t('mailer.unsubscribe_html', profile: link_to(t('notifications.profile'), edit_profile_url)) %> diff --git a/app/views/lectures/edit/_assignments.html.erb b/app/views/lectures/edit/_assignments.html.erb index 398f81d95..2bdea32f3 100644 --- a/app/views/lectures/edit/_assignments.html.erb +++ b/app/views/lectures/edit/_assignments.html.erb @@ -41,7 +41,7 @@ <%= t('basics.title') %>
-
+
<%= t('basics.deadline') %>
@@ -61,7 +61,7 @@ <%= t('assignment.deletion_date') %>
-
+
<%= t('basics.action') %> <%= helpdesk(t('assignment.destruction_info'), true) %> diff --git a/app/views/lectures/edit/_content.html.erb b/app/views/lectures/edit/_content.html.erb index 199f96b7a..c59ba2341 100644 --- a/app/views/lectures/edit/_content.html.erb +++ b/app/views/lectures/edit/_content.html.erb @@ -53,7 +53,7 @@
<% if lecture.chapters.any? %> -
+
<% lecture.chapters.each do |c| %> <% cache c do %> <%= render partial: 'lectures/edit/chapter', diff --git a/app/views/lectures/edit/_seminar_content.html.erb b/app/views/lectures/edit/_seminar_content.html.erb index b0bff1210..b7b74ead1 100644 --- a/app/views/lectures/edit/_seminar_content.html.erb +++ b/app/views/lectures/edit/_seminar_content.html.erb @@ -22,7 +22,7 @@
<% if lecture.talks.any? %> -
+
<% lecture.talks.each do |t| %> <%= render partial: 'lectures/edit/talk', locals: { talk: t } %> diff --git a/app/views/lectures/edit_structures.coffee b/app/views/lectures/edit_structures.coffee index 6e19d704e..14b57658b 100644 --- a/app/views/lectures/edit_structures.coffee +++ b/app/views/lectures/edit_structures.coffee @@ -30,4 +30,4 @@ renderMathInElement structuresBody, ] throwOnError: false -$('[data-bs-toggle="popover"]').popover() \ No newline at end of file +initBootstrapPopovers() diff --git a/app/views/lectures/new.coffee b/app/views/lectures/new.coffee index 6e1321720..f083716bc 100644 --- a/app/views/lectures/new.coffee +++ b/app/views/lectures/new.coffee @@ -6,7 +6,7 @@ $('#new-lecture-area').empty() from: @from, modal: @from == "course" } %>').show() fillOptionsByAjax($('#new-lecture-area .selectize')) -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() # hide all other buttons on admin index page $('.admin-index-button').hide() diff --git a/app/views/lectures/search.coffee b/app/views/lectures/search.coffee index 3756dab59..d5ab75983 100644 --- a/app/views/lectures/search.coffee +++ b/app/views/lectures/search.coffee @@ -3,4 +3,4 @@ searchResults = document.getElementById('lecture-search-results') searchResults.innerHTML = '<%= j render partial: "lectures/search/results", locals: { lectures: @lectures, total: @total } %>' -$('[data-bs-toggle="popover"]').popover() \ No newline at end of file +initBootstrapPopovers() diff --git a/app/views/lectures/show/_content.html.erb b/app/views/lectures/show/_content.html.erb index 37d212c98..d90926c04 100644 --- a/app/views/lectures/show/_content.html.erb +++ b/app/views/lectures/show/_content.html.erb @@ -9,7 +9,7 @@ <% if !lecture.talks.any? %> <%= t('admin.lecture.no_content_yet') %> <% else %> -
+
<%= render partial: 'lectures/show/talk', collection: lecture.talks, cached: false %> @@ -20,7 +20,7 @@ <% if !lecture.chapters.any? %> <%= t('admin.lecture.no_content_yet') %> <% else %> -
+
<%= render partial: 'lectures/show/chapter', collection: lecture.chapters .select { |c| c.hidden.in?([false, nil]) }, diff --git a/app/views/lectures/show/_structures.html.erb b/app/views/lectures/show/_structures.html.erb index 4ed00378d..398670217 100644 --- a/app/views/lectures/show/_structures.html.erb +++ b/app/views/lectures/show/_structures.html.erb @@ -18,7 +18,7 @@ <% end %>
-
+
<% structures.each do |s| %>
diff --git a/app/views/lessons/new.coffee b/app/views/lessons/new.coffee index e23815681..cab53fc39 100644 --- a/app/views/lessons/new.coffee +++ b/app/views/lessons/new.coffee @@ -4,8 +4,5 @@ $('#lesson-modal-content').empty() locals: { lesson: @lesson } %>').show() $('#lesson-modal-content .selectize').each -> new TomSelect("#"+this.id,{ plugins: ['remove_button'] }) - -# activate popovers -$('[data-bs-toggle="popover"]').popover() - -$('#lessonModal').modal('show') \ No newline at end of file +$('#lessonModal').modal('show') +initBootstrapPopovers() diff --git a/app/views/media/_publish_modal.html.erb b/app/views/media/_publish_modal.html.erb index 78d493ce9..01dc00c29 100644 --- a/app/views/media/_publish_modal.html.erb +++ b/app/views/media/_publish_modal.html.erb @@ -1,3 +1,5 @@ +<%= javascript_include_tag 'datetimepicker' %> + -
+ + <%# Datetimepicker for release date %> +
<%= f.text_field :release_date, - class: 'form-control', + class: 'form-control td-input', id: 'release_date', autocomplete: 'off', - value: medium.planned_release_date - .try(:strftime, - '%d.%m.%Y %H:%M') %> -
+ value: medium.planned_release_date, + 'data-td-target': '#release-date-picker'%> + + + +
+ <% if medium.sort == 'Nuesse' && medium.teachable.is_a?(Lecture) %>
<%= t('basics.assignment') %> @@ -88,21 +104,37 @@ id="assignment-title-error">
-
+ + <%# Datetimepicker for assignment deadline %> +
<%= f.label :assignment_deadline, t('basics.deadline'), class: "form-label" %> - <%= f.text_field :assignment_deadline, - class: 'form-control', - autocomplete: 'off', - value: medium.publisher - &.assignment_deadline - .try(:strftime, - '%d.%m.%Y %H:%M') %> -
+
+ <%= f.text_field :assignment_deadline, + class: 'form-control td-input', + autocomplete: 'off', + value: medium.publisher + &.assignment_deadline, + 'data-td-target': '#assignment-date-picker' %> + + + +
+
-
+ +
<%= f.label :assignment_file_type, t('submission.file_format'), class: "form-label" %> @@ -114,7 +146,8 @@
<%= f.label :assignment_deletion_date, - t('assignment.deletion_date') %> + t('assignment.deletion_date'), + class: "form-label" %> <%= f.select :assignment_deletion_date, Term.possible_deletion_dates_localized, {}, diff --git a/app/views/media/add_item.coffee b/app/views/media/add_item.coffee index e9cce8301..a1f3d6e34 100644 --- a/app/views/media/add_item.coffee +++ b/app/views/media/add_item.coffee @@ -7,6 +7,6 @@ $('#action-container').empty() # activate selectize and popovers $('.selectize').each -> new TomSelect("#"+this.id,{ plugins: ['remove_button'] }) -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() # bugfix for selectize (which sometimes renders the prompt with a zero width) $('input[id$="-selectized"]').css('width', '100%') \ No newline at end of file diff --git a/app/views/media/add_reference.coffee b/app/views/media/add_reference.coffee index 200263f64..bf77336be 100644 --- a/app/views/media/add_reference.coffee +++ b/app/views/media/add_reference.coffee @@ -9,6 +9,6 @@ $('#action-container').empty() # activate selectize and popovers $('.selectize').each -> new TomSelect("#"+this.id,{ plugins: ['remove_button'] }) -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() # bugfix for selectize (which sometimes renders the prompt with a zero width) $('input[id$="-selectized"]').css('width', '100%') \ No newline at end of file diff --git a/app/views/media/get_statistics.coffee b/app/views/media/get_statistics.coffee index e6d3072e7..d37c429f3 100644 --- a/app/views/media/get_statistics.coffee +++ b/app/views/media/get_statistics.coffee @@ -12,9 +12,7 @@ $('#calls-stats').empty() question_count: @question_count, local_success: @local_success } %>') .show().removeAttr('style') - -# activate popovers -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() <% if @medium.sort == 'Quiz' %> diff --git a/app/views/media/new.coffee b/app/views/media/new.coffee index 7403f6411..dcccc2084 100644 --- a/app/views/media/new.coffee +++ b/app/views/media/new.coffee @@ -3,5 +3,4 @@ $('#medium-modal-content').empty() .append('<%= j render partial: "media/new", locals: { medium: @medium } %>').show() $('#mediumModal').modal('show') - # activate popovers -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() diff --git a/app/views/quiz_certificates/claim.coffee b/app/views/quiz_certificates/claim.coffee index b72afe830..a82197b1c 100644 --- a/app/views/quiz_certificates/claim.coffee +++ b/app/views/quiz_certificates/claim.coffee @@ -1,4 +1,4 @@ $('#quizCertificateArea').empty() .append('<%= j render partial: "quiz_certificates/claim", locals: { certificate: @certificate } %>') -$('[data-bs-toggle="popover"]').popover() \ No newline at end of file +initBootstrapPopovers() diff --git a/app/views/quizzes/proceed.coffee b/app/views/quizzes/proceed.coffee index 3d2b6792f..00897cba4 100644 --- a/app/views/quizzes/proceed.coffee +++ b/app/views/quizzes/proceed.coffee @@ -24,7 +24,7 @@ changeBackground = -> renderFinale = (finale) -> $('#<%= quiz_id%>').append finale - $('[data-bs-toggle="popover"]').popover() + initBootstrapPopovers() $('#finale').delay(1000).slideDown 'slow' $('html, body').delay(500) .animate { scrollTop: document.body.scrollHeight }, 2000 @@ -79,7 +79,7 @@ displayNext = -> <% else %> renderNext('<%= j render partial: "quizzes/quiz_round", locals: { hidden: true } %>') - $('[data-bs-toggle="popover"]').popover() + initBootstrapPopovers() <% end %> return diff --git a/app/views/referrals/edit.coffee b/app/views/referrals/edit.coffee index def444f8f..7532df72b 100644 --- a/app/views/referrals/edit.coffee +++ b/app/views/referrals/edit.coffee @@ -9,4 +9,4 @@ $('#action-container').empty() # activate selectize and popovers $('.selectize').each -> new TomSelect("#"+this.id,{ plugins: ['remove_button'] }) -$('[data-bs-toggle="popover"]').popover() \ No newline at end of file +initBootstrapPopovers() diff --git a/app/views/sections/new.coffee b/app/views/sections/new.coffee index 7f87e0109..d68b59de4 100644 --- a/app/views/sections/new.coffee +++ b/app/views/sections/new.coffee @@ -5,5 +5,4 @@ $('#section-modal-content').empty() chapter: @chapter } %>').show() $('#sectionModal').modal('show') - # activate popovers -$('[data-bs-toggle="popover"]').popover() +initBootstrapPopovers() diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb index 6998fef47..f52aab1a9 100644 --- a/app/views/shared/_navbar.html.erb +++ b/app/views/shared/_navbar.html.erb @@ -6,12 +6,12 @@ class: 'me-2', id: 'mampf-logo'), home_path %> - <%= link_to "MaMpf", + <%= link_to t('mampf'), home_path, class: 'navbar-brand', style: "text-decoration: none;", id: 'mampfbrand' %> -