diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index b40ef6990a..ac2689fb50 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -11,4 +11,5 @@ //= require admin/category-order //= require admin/censor-rules //= require admin/holidays +//= require admin/notes //= require jquery_ujs diff --git a/app/assets/javascripts/admin/notes.js b/app/assets/javascripts/admin/notes.js new file mode 100644 index 0000000000..6d8c766c9d --- /dev/null +++ b/app/assets/javascripts/admin/notes.js @@ -0,0 +1,19 @@ +document.addEventListener('DOMContentLoaded', function () { + var selectElement = document.getElementById('note_style'); + var bodyInput = document.getElementById('bodyInput'); + var richBodyInput = document.getElementById('richBodyInput'); + + function updateBodyVisibility() { + if (selectElement.value === 'original') { + bodyInput.style.display = 'block'; + richBodyInput.style.display = 'none'; + } else { + bodyInput.style.display = 'none'; + richBodyInput.style.display = 'block'; + } + } + + selectElement.addEventListener('change', updateBodyVisibility); + + updateBodyVisibility(); +}); diff --git a/app/assets/stylesheets/actiontext.css b/app/assets/stylesheets/actiontext.css new file mode 100644 index 0000000000..b7e9e58575 --- /dev/null +++ b/app/assets/stylesheets/actiontext.css @@ -0,0 +1,35 @@ +/* + * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and + * the trix-editor content (whether displayed or under editing). Feel free to incorporate this + * inclusion directly in any other asset bundle and remove this file. + * + *= require trix +*/ + +/* + * We need to override trix.cssā€™s image gallery styles to accommodate the + * element we wrap around attachments. Otherwise, + * images in galleries will be squished by the max-width: 33%; rule. +*/ +.trix-content .attachment-gallery > action-text-attachment, +.trix-content .attachment-gallery > .attachment { + flex: 1 0 33%; + padding: 0 0.5em; + max-width: 33%; +} + +.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment, +.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment, +.trix-content .attachment-gallery.attachment-gallery--4 > .attachment { + flex-basis: 50%; + max-width: 50%; +} + +.trix-content action-text-attachment .attachment { + padding: 0 !important; + max-width: 100% !important; +} + +.trix-button-group--file-tools { + display: none !important; +} diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index c2f1cb9726..c1e7df9d91 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -1,3 +1,5 @@ +//= require "actiontext" + /* As we're namespacing bootstrap to class admin, which is applied to the body element in the admin interface (no id or class allowed on the HTML element in HTML 4.01) and to the navbar also, so it can be styled with bootstrap diff --git a/app/assets/stylesheets/responsive/_notes_layout.scss b/app/assets/stylesheets/responsive/_notes_layout.scss index 1398cb7719..776fca8d1f 100644 --- a/app/assets/stylesheets/responsive/_notes_layout.scss +++ b/app/assets/stylesheets/responsive/_notes_layout.scss @@ -1,20 +1,21 @@ #notes { - // Variables to keep consistency speacilly with the spacing. + // Variables to keep consistency specially with the spacing. $padding-x: 1em; - $border: 1px solid rgba(0, 0, 0, 0.1); + $border: 1px solid rgba(0, 0, 0, 0); + $border-top-size: 8px; - padding: 0 $padding-x; + padding: $border-top-size $padding-x 0; margin-bottom: 2em; - border-top: 8px solid $primary-color; - - article { + .note { margin: 0 -#{$padding-x}; padding: $padding-x $padding-x; border: $border; - border-top: none; word-break: break-word; + &:first-child { box-shadow: 0 -#{$border-top-size} 0 0 rgba(0, 0, 0, 0); } + border-top-width: 0; + h1, h2, h3, h4, h5, h6 { margin-bottom: 0.5em; margin-top: 0; diff --git a/app/assets/stylesheets/responsive/_notes_styles.scss b/app/assets/stylesheets/responsive/_notes_styles.scss index 0f4e3aa93d..2faaf5c90e 100644 --- a/app/assets/stylesheets/responsive/_notes_styles.scss +++ b/app/assets/stylesheets/responsive/_notes_styles.scss @@ -1,7 +1,61 @@ +$note-text: $oil; +$note-bg: lighten($primary-color, 60%); +$note-border: $primary-color; + +$note-red-text: darken($alert-color, 10%); +$note-red-bg: lighten($alert-color, 40%); +$note-red-border: $alert-color; + +$note-green-text: darken($success-color, 10%); +$note-green-bg: lighten($success-color, 40%); +$note-green-border: $success-color; + +$note-blue-text: darken($primary-color, 10%); +$note-blue-bg: lighten($primary-color, 60%); +$note-blue-border: $primary-color; + +$note-yellow-text: darken($warning-color, 10%); +$note-yellow-bg: lighten($warning-color, 40%); +$note-yellow-border: $warning-color; + +$border-top-size: 8px; + #notes { - background-color: lighten($primary-color, 60%); + .note { + color: $note-text; + background-color: $note-bg; + border-color: rgba(0, 0, 0, 0.1); + + &:first-child { box-shadow: 0 -#{$border-top-size} 0 0 $note-border; } + + &.note--style-red { + color: $note-red-text; + background-color: $note-red-bg; + &:first-child { box-shadow: 0 -#{$border-top-size} 0 0 $note-red-border; } + h1 { color: $note-red-text; } + } + + &.note--style-green { + color: $note-green-text; + background-color: $note-green-bg; + &:first-child { box-shadow: 0 -#{$border-top-size} 0 0 $note-green-border; } + h1 { color: $note-green-text; } + } + + &.note--style-blue { + color: $note-blue-text; + background-color: $note-blue-bg; + &:first-child { box-shadow: 0 -#{$border-top-size} 0 0 $note-blue-border; } + h1 { color: $note-blue-text; } + } + + &.note--style-yellow { + color: $note-yellow-text; + background-color: $note-yellow-bg; + &:first-child { box-shadow: 0 -#{$border-top-size} 0 0 $note-yellow-border; } + h1 { color: $note-yellow-text; } + } - article { h1 { font-size: 1.5em; } @@ -21,5 +75,14 @@ font-size: 0.9em; text-transform: uppercase; } + + &.note--style-red, + &.note--style-green, + &.note--style-blue, + &.note--style-yellow { + h1 { + font-size: 1.3em; + } + } } } diff --git a/app/controllers/admin/notes_controller.rb b/app/controllers/admin/notes_controller.rb index d09f834a3c..be7e3129ac 100644 --- a/app/controllers/admin/notes_controller.rb +++ b/app/controllers/admin/notes_controller.rb @@ -54,8 +54,8 @@ def scope def note_params translatable_params( params.require(:note), - translated_keys: [:locale, :body], - general_keys: [:notable_tag, :notable_id, :notable_type] + translated_keys: [:locale, :body, :rich_body], + general_keys: [:notable_tag, :notable_id, :notable_type, :style] ) end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 0ae51ccd91..e43d7cae63 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -1,15 +1,20 @@ module NotesHelper + def note_as_html(note, batch: false) + allowed_tags = batch ? batch_notes_allowed_tags : notes_allowed_tags + content = note.original_style? ? note.body : note.rich_body.to_trix_html + sanitize(content, tags: allowed_tags) + end + def render_notes(notes, batch: false, **options) return unless notes.present? - allowed_tags = batch ? batch_notes_allowed_tags : notes_allowed_tags - tag.aside(**options.merge(id: 'notes')) do - notes.each do |note| + Note.sort(notes).each do |note| note_classes = ['note'] + note_classes << "note--style-#{note.style}" note_classes << "tag-#{note.notable_tag}" if note.notable_tag - concat tag.article sanitize(note.body, tags: allowed_tags), + concat tag.article note_as_html(note, batch: batch), id: dom_id(note), class: note_classes end diff --git a/app/models/note.rb b/app/models/note.rb index ff8a5bc1b5..bdc97080eb 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20220720085105 +# Schema version: 20240227080436 # # Table name: notes # @@ -9,6 +9,7 @@ # notable_tag :string # created_at :datetime not null # updated_at :datetime not null +# style :string default("original"), not null # body :text # @@ -16,16 +17,47 @@ class Note < ApplicationRecord include AdminColumn translates :body + translates :rich_body, touch: true include Translatable + delegate :rich_body, :rich_body=, :rich_body?, to: :translation + after_save { rich_body.save if rich_body.changed? } + + cattr_accessor :default_style, default: 'original' + cattr_accessor :style_labels, default: { + 'šŸ”µ Blue': 'blue', + 'šŸ”“ Red': 'red', + 'šŸŸ¢ Green': 'green', + 'šŸŸ” Yellow': 'yellow', + 'Original': 'original' + } + + enum :style, Note.style_labels.values.index_by(&:itself), + default: Note.default_style, + suffix: true belongs_to :notable, polymorphic: true - validates :body, presence: true + validates :body, presence: true, if: ->(n) { n.original_style? } + validates :rich_body, presence: true, unless: ->(n) { n.original_style? } + validates :style, presence: true validates :notable_or_notable_tag, presence: true + def self.sort(notes) + notes.sort_by! { Note.style_labels.values.index(_1.style) } + end + + def to_plain_text + b = original_style? ? ActionText::Fragment.wrap(body) : rich_body + b.to_plain_text + end + private def notable_or_notable_tag notable || notable_tag end + + class Translation # :nodoc: + has_rich_text :rich_body + end end diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 151a9aeb96..7d5f0073bb 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -662,11 +662,11 @@ def self.extract_domain_from_email(email) end def notes - all_notes + Note.sort(all_notes) end def notes_as_string - notes.map(&:body).join(' ') + notes.map(&:to_plain_text).join(' ') end def has_notes? diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb new file mode 100644 index 0000000000..49ba357dd1 --- /dev/null +++ b/app/views/active_storage/blobs/_blob.html.erb @@ -0,0 +1,14 @@ +
attachment--<%= blob.filename.extension %>"> + <% if blob.representable? %> + <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> + <% end %> + +
+ <% if caption = blob.try(:caption) %> + <%= caption %> + <% else %> + <%= blob.filename %> + <%= number_to_human_size blob.byte_size %> + <% end %> +
+
diff --git a/app/views/admin/notes/_form.html.erb b/app/views/admin/notes/_form.html.erb index 073586b65c..79320f2e90 100644 --- a/app/views/admin/notes/_form.html.erb +++ b/app/views/admin/notes/_form.html.erb @@ -11,6 +11,13 @@ <% end %> +
+ <%= f.label :style, class: 'control-label' %> +
+ <%= f.select :style, Note.style_labels %> +
+
+ <% f.translated_fields do |t| %> <%= render partial: 'locale_fields', locals: { t: t } %> <% end %> diff --git a/app/views/admin/notes/_locale_fields.html.erb b/app/views/admin/notes/_locale_fields.html.erb index adff16268c..df2c9f851d 100644 --- a/app/views/admin/notes/_locale_fields.html.erb +++ b/app/views/admin/notes/_locale_fields.html.erb @@ -1,6 +1,13 @@ -
+
<%= t.label :body, class: 'control-label' %>
- <%= t.text_area :body, class: 'span6', rows: 10 %> + <%= t.text_area :body, class: 'input-block-level', rows: 10 %> +
+
+ +
+ <%= t.label :rich_body, 'Body', class: 'control-label' %> +
+ <%= t.rich_text_area :rich_body %>
diff --git a/app/views/admin/notes/_note.html.erb b/app/views/admin/notes/_note.html.erb index 0607754d93..ae8da6b132 100644 --- a/app/views/admin/notes/_note.html.erb +++ b/app/views/admin/notes/_note.html.erb @@ -2,7 +2,7 @@
<%= link_to chevron_right, "##{dom_id(note)}", data: { toggle: 'collapse', parent: 'notes' } %> - <%= link_to(note.body, edit_admin_note_path(note), title: 'view full details') %> + <%= link_to(note.to_plain_text, edit_admin_note_path(note), title: 'view full details') %>