Carica oggetto e corpo dell'email appena si sceglie un template A/B.
CI / scan_ruby (push) Failing after 10m36s
CI / scan_js (push) Successful in 10m31s
CI / lint (push) Failing after 11m0s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-17 23:24:39 +02:00
co-authored by Cursor
parent c4e5f289cf
commit 4ddf534a0c
5 changed files with 150 additions and 29 deletions
+5 -6
View File
@@ -166,19 +166,18 @@ class MailingsController < ApplicationController
end
def apply_template_if_needed
copy_template(params.dig(:mailing, :mail_template_id), :subject, :body_html, params[:use_template_content])
copy_template(params.dig(:mailing, :mail_template_b_id), :subject_b, :body_html_b, params[:use_template_b_content])
copy_template_if_blank(params.dig(:mailing, :mail_template_id), :subject, :body_html)
copy_template_if_blank(params.dig(:mailing, :mail_template_b_id), :subject_b, :body_html_b)
end
def copy_template(template_id, subject_attr, body_attr, flag)
def copy_template_if_blank(template_id, subject_attr, body_attr)
return if template_id.blank?
return unless ActiveModel::Type::Boolean.new.cast(flag)
template = MailTemplate.for_project(current_project).find_by(id: template_id)
return unless template
@mailing.public_send("#{subject_attr}=", template.subject)
@mailing.public_send("#{body_attr}=", template.body_html)
@mailing.public_send("#{subject_attr}=", template.subject) if @mailing.public_send(subject_attr).blank?
@mailing.public_send("#{body_attr}=", template.body_html) if @mailing.public_send(body_attr).blank?
end
def rebuild_recipients_if_needed
+4
View File
@@ -21,6 +21,10 @@ module ApplicationHelper
"w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none focus:ring-1 focus:ring-zinc-400 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500 dark:focus:ring-zinc-500"
end
def mail_templates_picker_payload(templates)
Array(templates).map { |template| { id: template.id, subject: template.subject, body_html: template.body_html } }
end
def card_class
"rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100"
end
@@ -0,0 +1,74 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["select", "subject"]
static values = { templates: Array }
connect() {
this.pendingHtml = null
this.onEditorReady = this.onEditorReady.bind(this)
this.editorElement?.addEventListener("trix-initialize", this.onEditorReady)
if (this.shouldPrefill()) this.apply()
}
disconnect() {
this.editorElement?.removeEventListener("trix-initialize", this.onEditorReady)
}
apply() {
const template = this.currentTemplate()
if (!template) return
if (this.hasSubjectTarget) {
this.subjectTarget.value = template.subject || ""
this.subjectTarget.dispatchEvent(new Event("input", { bubbles: true }))
}
this.setBody(template.body_html || "")
}
shouldPrefill() {
if (!this.selectTarget.value) return false
const subjectBlank = !this.hasSubjectTarget || this.subjectTarget.value.trim() === ""
return subjectBlank && this.bodyIsBlank()
}
currentTemplate() {
const id = Number(this.selectTarget.value)
if (!id) return null
return (this.templatesValue || []).find((template) => Number(template.id) === id) || null
}
setBody(html) {
this.pendingHtml = html
const editor = this.editorElement?.editor
if (editor) {
editor.loadHTML(html)
this.pendingHtml = null
return
}
if (this.hiddenInput) this.hiddenInput.value = html
}
onEditorReady() {
if (this.pendingHtml != null) {
this.editorElement.editor?.loadHTML(this.pendingHtml)
this.pendingHtml = null
return
}
if (this.shouldPrefill()) this.apply()
}
bodyIsBlank() {
const value = this.hiddenInput?.value || ""
return value.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").trim() === ""
}
get editorElement() {
return this.element.querySelector("trix-editor")
}
get hiddenInput() {
return this.element.querySelector("[data-wysiwyg-target='input']")
}
}
+18 -23
View File
@@ -37,7 +37,7 @@
<%= f.check_box :ab_test, { data: { ab_test_target: "checkbox", action: "ab-test#toggle" } } %>
<span>
<span class="font-medium">Test A/B su questa comunicazione</span>
<span class="mt-1 block text-xs text-zinc-500">Due versioni di oggetto e HTML. Ogni destinatario riceve A o B.</span>
<span class="mt-1 block text-xs text-zinc-500">Due versioni della stessa email. Carica un template A e un template B nelle colonne: ogni destinatario ne riceve una.</span>
</span>
</label>
@@ -48,48 +48,43 @@
</div>
</div>
<% templates_payload = mail_templates_picker_payload(@mail_templates) %>
<div class="grid gap-6 <%= "lg:grid-cols-2" if mailing.ab_test? %>" data-ab-test-target="grid">
<div class="space-y-4">
<%= tag.div class: "space-y-4", data: { controller: "template-picker", template_picker_templates_value: templates_payload } do %>
<h2 class="text-sm font-semibold uppercase tracking-wide text-zinc-500">Variante A</h2>
<div>
<label class="mb-1 block text-sm font-medium">Template A (opzionale)</label>
<%= f.collection_select :mail_template_id, @mail_templates, :id, :name, { include_blank: "Nessuno" }, { class: input_class } %>
<label class="mb-1 block text-sm font-medium">Template A</label>
<%= f.collection_select :mail_template_id, @mail_templates, :id, :name,
{ include_blank: "Nessuno — scrivi a mano" },
{ class: input_class, data: { template_picker_target: "select", action: "change->template-picker#apply" } } %>
<p class="mt-1 text-xs text-zinc-500">Scegliendo un template, oggetto e testo si copiano qui sotto. Poi puoi modificarli solo per questo invio.</p>
</div>
<% unless mailing.new_record? %>
<label class="flex items-center gap-2 text-sm">
<%= check_box_tag :use_template_content, "1", false %>
Sostituisci oggetto e HTML A dal template
</label>
<% end %>
<div>
<label class="mb-1 block text-sm font-medium">Oggetto A</label>
<%= f.text_field :subject, required: true, class: input_class %>
<%= f.text_field :subject, required: true, class: input_class, data: { template_picker_target: "subject" } %>
</div>
<div>
<%= render "shared/wysiwyg_field", form: f, method: :body_html, label: "Corpo HTML A" %>
</div>
</div>
<% end %>
<div class="space-y-4 <%= "hidden" unless mailing.ab_test? %>" data-ab-test-target="panel">
<%= tag.div class: "space-y-4 #{'hidden' unless mailing.ab_test?}", data: { controller: "template-picker", template_picker_templates_value: templates_payload, ab_test_target: "panel" } do %>
<h2 class="text-sm font-semibold uppercase tracking-wide text-zinc-500">Variante B</h2>
<div>
<label class="mb-1 block text-sm font-medium">Template B (opzionale)</label>
<%= f.collection_select :mail_template_b_id, @mail_templates, :id, :name, { include_blank: "Nessuno" }, { class: input_class } %>
<label class="mb-1 block text-sm font-medium">Template B</label>
<%= f.collection_select :mail_template_b_id, @mail_templates, :id, :name,
{ include_blank: "Nessuno — scrivi a mano" },
{ class: input_class, data: { template_picker_target: "select", action: "change->template-picker#apply" } } %>
<p class="mt-1 text-xs text-zinc-500">Stesso meccanismo della variante A: il template riempie oggetto e corpo B.</p>
</div>
<% unless mailing.new_record? %>
<label class="flex items-center gap-2 text-sm">
<%= check_box_tag :use_template_b_content, "1", false %>
Sostituisci oggetto e HTML B dal template
</label>
<% end %>
<div>
<label class="mb-1 block text-sm font-medium">Oggetto B</label>
<%= f.text_field :subject_b, class: input_class %>
<%= f.text_field :subject_b, class: input_class, data: { template_picker_target: "subject" } %>
</div>
<div>
<%= render "shared/wysiwyg_field", form: f, method: :body_html_b, label: "Corpo HTML B" %>
</div>
</div>
<% end %>
</div>
<div>