From 8eda26634033e491a43ee7f8f3447215573f2202 Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Thu, 9 Feb 2023 17:18:25 +0100 Subject: [PATCH] feat(messages): add html formatting to messages This commit allows users to use the trix editor to send messages with basic formatting and attachements. * add active storage * add actiontext * add richtext field to messages * add imageprocessing for message attachements * add html email layout and adjust translations to use html urls --- Gemfile | 2 ++ Gemfile.lock | 7 +++++ app/assets/stylesheets/actiontext.css | 31 +++++++++++++++++++ app/assets/stylesheets/application.css | 1 + app/javascript/application.js | 2 ++ app/views/active_storage/blobs/_blob.html.erb | 14 +++++++++ .../action_text/contents/_content.html.erb | 3 ++ app/views/layouts/email.html.haml | 12 +++++++ config/application.rb | 2 ++ config/importmap.rb | 4 ++- config/locales/de.yml | 1 + config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fr.yml | 1 + config/locales/nl.yml | 1 + ...6_create_action_text_tables.action_text.rb | 26 ++++++++++++++++ db/schema.rb | 12 ++++++- .../app/controllers/messages_controller.rb | 4 +-- .../messages/app/helpers/messages_helper.rb | 2 +- plugins/messages/app/models/message.rb | 2 ++ plugins/messages/app/views/messages/new.haml | 2 +- plugins/messages/app/views/messages/show.haml | 2 +- .../foodsoft_message.html.haml | 11 +++++++ plugins/messages/config/locales/de.yml | 3 ++ plugins/messages/config/locales/en.yml | 3 ++ plugins/messages/config/locales/fr.yml | 3 ++ plugins/messages/config/locales/nl.yml | 3 ++ 27 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 app/assets/stylesheets/actiontext.css create mode 100644 app/views/active_storage/blobs/_blob.html.erb create mode 100644 app/views/layouts/action_text/contents/_content.html.erb create mode 100644 app/views/layouts/email.html.haml create mode 100644 db/migrate/20230209105256_create_action_text_tables.action_text.rb create mode 100644 plugins/messages/app/views/messages_mailer/foodsoft_message.html.haml diff --git a/Gemfile b/Gemfile index 4d71513f..cf5c631a 100644 --- a/Gemfile +++ b/Gemfile @@ -128,3 +128,5 @@ group :test do end gem "importmap-rails", "~> 1.1" + +gem "image_processing", "~> 1.12" diff --git a/Gemfile.lock b/Gemfile.lock index 88fa2944..b7a61156 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -255,6 +255,9 @@ GEM i18n-spec (0.6.0) iso ice_cube (0.16.4) + image_processing (1.12.2) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) importmap-rails (1.1.5) actionpack (>= 6.0.0) railties (>= 6.0.0) @@ -318,6 +321,7 @@ GEM mime-types (3.4.1) mime-types-data (~> 3.2015) mime-types-data (3.2022.0105) + mini_magick (4.12.0) mini_mime (1.1.2) minitest (5.17.0) mono_logger (1.1.1) @@ -496,6 +500,8 @@ GEM ruby-prof (1.4.5) ruby-progressbar (1.11.0) ruby-units (3.0.0) + ruby-vips (2.1.4) + ffi (~> 1.12) ruby2_keywords (0.0.5) rubyzip (2.3.2) sass-rails (6.0.0) @@ -631,6 +637,7 @@ DEPENDENCIES i18n-js (~> 3.0.0.rc8) i18n-spec ice_cube + image_processing (~> 1.12) importmap-rails (~> 1.1) inherited_resources jquery-rails diff --git a/app/assets/stylesheets/actiontext.css b/app/assets/stylesheets/actiontext.css new file mode 100644 index 00000000..3cfcb2b7 --- /dev/null +++ b/app/assets/stylesheets/actiontext.css @@ -0,0 +1,31 @@ +/* + * 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; +} diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 6bdfecd2..01dba421 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -7,4 +7,5 @@ *= require list.unlist *= require list.missing *= require recurring_select +*= require actiontext */ diff --git a/app/javascript/application.js b/app/javascript/application.js index beff742e..ed5cae66 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1 +1,3 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "trix" +import "@rails/actiontext" 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 00000000..49ba357d --- /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/layouts/action_text/contents/_content.html.erb b/app/views/layouts/action_text/contents/_content.html.erb new file mode 100644 index 00000000..9e3c0d0d --- /dev/null +++ b/app/views/layouts/action_text/contents/_content.html.erb @@ -0,0 +1,3 @@ +
+ <%= yield -%> +
diff --git a/app/views/layouts/email.html.haml b/app/views/layouts/email.html.haml new file mode 100644 index 00000000..6bcf3b4a --- /dev/null +++ b/app/views/layouts/email.html.haml @@ -0,0 +1,12 @@ += yield +\ +%hr +%ul + %li + %a{href: root_url} Foodsoft + - if FoodsoftConfig[:homepage] + %li + %a{href: FoodsoftConfig[:homepage]} Foodcoop + - if FoodsoftConfig[:help_url] + %li + %a{href: FoodsoftConfig[:help_url]}= t '.help' \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 9c0ade99..f76faa95 100644 --- a/config/application.rb +++ b/config/application.rb @@ -67,6 +67,8 @@ module Foodsoft config.autoloader = :zeitwerk + config.active_storage.variant_processor = :mini_magick + # Ex:- :default =>'' # CORS for API diff --git a/config/importmap.rb b/config/importmap.rb index 050818ab..f882664b 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -1,2 +1,4 @@ # Pin npm packages by running ./bin/importmap -pin "application", preload: true \ No newline at end of file +pin "application", preload: true +pin "trix" +pin "@rails/actiontext", to: "actiontext.js" diff --git a/config/locales/de.yml b/config/locales/de.yml index 5a1a5b35..777244c1 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1221,6 +1221,7 @@ de: footer_2_foodsoft: 'Foodsoft: %{url}' footer_3_homepage: 'Foodcoop: %{url}' footer_4_help: 'Hilfe: %{url}' + help: 'Hilfe' foodsoft: Foodsoft footer: revision: Revision %{revision} diff --git a/config/locales/en.yml b/config/locales/en.yml index 59e94385..bf75a895 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1224,6 +1224,7 @@ en: footer_2_foodsoft: 'Foodsoft: %{url}' footer_3_homepage: 'Foodcoop: %{url}' footer_4_help: 'Help: %{url}' + help: 'Help' foodsoft: Foodsoft footer: revision: revision %{revision} diff --git a/config/locales/es.yml b/config/locales/es.yml index 620ec3bb..c713dd24 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1082,6 +1082,7 @@ es: layouts: email: footer_4_help: 'Ayuda: %{url}' + help: 'Ayuda' footer: revision: revisión %{revision} header: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4dbdb864..b1199dc7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -834,6 +834,7 @@ fr: email: footer_3_homepage: 'Boufcoop: %{url}' footer_4_help: 'Aide: %{url}' + help: 'Aide' footer: revision: révision %{revision} header: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 4c97dda4..e66bae80 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1194,6 +1194,7 @@ nl: footer_2_foodsoft: 'Foodsoft: %{url}' footer_3_homepage: 'Foodcoop: %{url}' footer_4_help: 'Help: %{url}' + help: 'Help' foodsoft: Foodsoft footer: revision: revisie %{revision} diff --git a/db/migrate/20230209105256_create_action_text_tables.action_text.rb b/db/migrate/20230209105256_create_action_text_tables.action_text.rb new file mode 100644 index 00000000..1be48d70 --- /dev/null +++ b/db/migrate/20230209105256_create_action_text_tables.action_text.rb @@ -0,0 +1,26 @@ +# This migration comes from action_text (originally 20180528164100) +class CreateActionTextTables < ActiveRecord::Migration[6.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :action_text_rich_texts, id: primary_key_type do |t| + t.string :name, null: false + t.text :body, size: :long + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + + t.timestamps + + t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true + end + end + + private + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [primary_key_type, foreign_key_type] + end +end diff --git a/db/schema.rb b/db/schema.rb index 50c24c41..9ba8eaf3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,17 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_01_06_144440) do +ActiveRecord::Schema[7.0].define(version: 2023_02_09_105256) do + create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false + t.text "body", size: :long + t.string "record_type", null: false + t.bigint "record_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true + end + create_table "active_storage_attachments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false diff --git a/plugins/messages/app/controllers/messages_controller.rb b/plugins/messages/app/controllers/messages_controller.rb index 628f145b..159984ed 100644 --- a/plugins/messages/app/controllers/messages_controller.rb +++ b/plugins/messages/app/controllers/messages_controller.rb @@ -20,8 +20,8 @@ class MessagesController < ApplicationController @message.group_id = original_message.group_id @message.private = original_message.private @message.subject = I18n.t('messages.model.reply_subject', :subject => original_message.subject) - @message.body = I18n.t('messages.model.reply_header', :user => original_message.sender.display, :when => I18n.l(original_message.created_at, :format => :short)) + "\n" - original_message.body.each_line { |l| @message.body += I18n.t('messages.model.reply_indent', :line => l) } + @message.body = I18n.t('messages.model.reply_header', :user => original_message.sender.display, :when => I18n.l(original_message.created_at, :format => :short)) + "\n" \ + + "
" + original_message.body.to_trix_html + "
" else redirect_to new_message_url, alert: I18n.t('messages.new.error_private') end diff --git a/plugins/messages/app/helpers/messages_helper.rb b/plugins/messages/app/helpers/messages_helper.rb index d5371fe4..d386e6df 100644 --- a/plugins/messages/app/helpers/messages_helper.rb +++ b/plugins/messages/app/helpers/messages_helper.rb @@ -5,7 +5,7 @@ module MessagesHelper body = "" else subject = message.subject - body = truncate(message.body, :length => length - subject.length) + body = truncate(message.body.to_plain_text, :length => length - subject.length) end "#{link_to(h(subject), message)} #{h(body)}".html_safe end diff --git a/plugins/messages/app/models/message.rb b/plugins/messages/app/models/message.rb index f6b03c10..b5087d0d 100644 --- a/plugins/messages/app/models/message.rb +++ b/plugins/messages/app/models/message.rb @@ -22,6 +22,8 @@ class Message < ApplicationRecord validates_presence_of :message_recipients, :subject, :body validates_length_of :subject, :in => 1..255 + has_rich_text :body + after_initialize do @recipients_ids ||= [] @send_method ||= 'recipients' diff --git a/plugins/messages/app/views/messages/new.haml b/plugins/messages/app/views/messages/new.haml index 57d6b452..d288cd72 100644 --- a/plugins/messages/app/views/messages/new.haml +++ b/plugins/messages/app/views/messages/new.haml @@ -110,7 +110,7 @@ = f.input :recipient_tokens, :input_html => { 'data-pre' => User.where(id: @message.recipients_ids).map(&:token_attributes).to_json } = f.input :private, inline_label: t('.hint_private') = f.input :subject, input_html: {class: 'input-xxlarge'} - = f.input :body, input_html: {class: 'input-xxlarge', rows: 13} + = f.rich_text_area :body, input_html: {class: 'input-xxlarge', rows: 13} .form-actions = f.submit class: 'btn btn-primary' = link_to t('ui.or_cancel'), :back diff --git a/plugins/messages/app/views/messages/show.haml b/plugins/messages/app/views/messages/show.haml index 36e7b570..8b3f7c1c 100644 --- a/plugins/messages/app/views/messages/show.haml +++ b/plugins/messages/app/views/messages/show.haml @@ -33,7 +33,7 @@ - if @message.can_toggle_private?(current_user) = link_to t('.change_visibility'), toggle_private_message_path(@message), method: :post, class: 'btn btn-mini' %hr/ - %p= simple_format(h(@message.body)) + .trix-content= @message.body %hr/ %p = link_to t('.reply'), new_message_path(:message => {:reply_to => @message.id}), class: 'btn' diff --git a/plugins/messages/app/views/messages_mailer/foodsoft_message.html.haml b/plugins/messages/app/views/messages_mailer/foodsoft_message.html.haml new file mode 100644 index 00000000..7ca572f3 --- /dev/null +++ b/plugins/messages/app/views/messages_mailer/foodsoft_message.html.haml @@ -0,0 +1,11 @@ += raw @message.body +%hr +%ul + - if @message.group + %li= t '.footer_group', group: @message.group.name + %li + %a{href: new_message_url('message[reply_to]' => @message.id)}= t '.reply' + %li + %a{href: message_url(@message)}= t '.see_message_online' + %li + %a{href: my_profile_url}= t '.messaging_options' diff --git a/plugins/messages/config/locales/de.yml b/plugins/messages/config/locales/de.yml index f1615163..eb8cff21 100644 --- a/plugins/messages/config/locales/de.yml +++ b/plugins/messages/config/locales/de.yml @@ -138,6 +138,9 @@ de: Antworten: %{reply_url} Nachricht online einsehen: %{msg_url} Nachrichten-Einstellungen: %{profile_url} + reply: Antworten + see_message_online: Nachricht online einsehen + messaging_options: Nachrichten-Einstellungen footer_group: | Gesendet an Gruppe: %{group} navigation: diff --git a/plugins/messages/config/locales/en.yml b/plugins/messages/config/locales/en.yml index ede3f88c..ccd8bb6c 100644 --- a/plugins/messages/config/locales/en.yml +++ b/plugins/messages/config/locales/en.yml @@ -140,6 +140,9 @@ en: Reply: %{reply_url} See message online: %{msg_url} Messaging options: %{profile_url} + reply: Reply + see_message_online: See message online + messaging_options: Messaging options footer_group: | Sent to group: %{group} navigation: diff --git a/plugins/messages/config/locales/fr.yml b/plugins/messages/config/locales/fr.yml index 54584b48..67d452c5 100644 --- a/plugins/messages/config/locales/fr.yml +++ b/plugins/messages/config/locales/fr.yml @@ -67,6 +67,9 @@ fr: Répondre: %{reply_url} Afficher ce message dans ton navigateur: %{msg_url} Préférences des messages: %{profile_url} + reply: Répondre + see_message_online: Afficher ce message dans ton navigateur + messaging_options: Préférences des messages simple_form: labels: settings: diff --git a/plugins/messages/config/locales/nl.yml b/plugins/messages/config/locales/nl.yml index d3960a23..56738c0b 100644 --- a/plugins/messages/config/locales/nl.yml +++ b/plugins/messages/config/locales/nl.yml @@ -140,6 +140,9 @@ nl: Antwoorden: %{reply_url} Bericht online lezen: %{msg_url} Berichtinstellingen: %{profile_url} + reply: Antwoorden + see_message_online: Bericht online lezen + messaging_options: Berichtinstellingen footer_group: | Verzenden aan groep: %{group} navigation: