Skip to content

Commit 6276d52

Browse files
authored
Merge pull request #13397 from colinux/emails-tiptap
ETQ admin, j'ai éditeur riche pour éditer les modèles d'email
2 parents 4e3429e + fe43033 commit 6276d52

51 files changed

Lines changed: 1676 additions & 449 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#mail-template-edit {
2+
// Blue "backdrop" that hugs the preview (title + email card + hint) and follows
3+
// the scroll. Generous padding gives the white card room to breathe.
4+
.mail-preview-panel {
5+
position: sticky;
6+
top: 1.5rem;
7+
padding: 1rem;
8+
border-radius: 0.5rem;
9+
}
10+
11+
// The email fills this container edge to edge (it already carries its own white
12+
// background and content frame); a shadow + rounded corners let it float on the
13+
// blue backdrop. No border/padding here, so the only spacing around the email is
14+
// the panel's own — even on every side.
15+
//
16+
// `height: auto` lets the card hug the zoomed email, so it never wastes space below
17+
// it — even on tall screens. The cap is the space available once the panel is stuck
18+
// to the top (the 19rem reserves the title, hint, sticky offset and fixed footer);
19+
// a taller email scrolls there. No scrollbar gutter is reserved: the controller
20+
// computes the zoom from `offsetWidth`, which is unaffected by the scrollbar.
21+
.mail-preview {
22+
position: relative;
23+
overflow-x: hidden;
24+
overflow-y: auto;
25+
min-height: 18rem;
26+
max-height: calc(100vh - 19rem);
27+
background-color: #fff;
28+
border-radius: 0.25rem;
29+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
30+
}
31+
32+
// A live-preview update loads its iframe here, off-screen, until it is ready to
33+
// replace the visible one (see the mail-preview controller).
34+
.mail-preview-frame--pending {
35+
position: absolute;
36+
top: 0;
37+
left: 0;
38+
visibility: hidden;
39+
}
40+
41+
.mail-preview-frame {
42+
display: block;
43+
// `zoom` (not `transform: scale`) so the email's thin 1px borders stay crisp when
44+
// shrunk; it scales layout too, so the card sizes to the zoomed email on its own.
45+
zoom: var(--mail-preview-scale, 1);
46+
// The controller sets `height` to the email's own content height; with the global
47+
// `border-box`, the padding-top below would eat into the document's render area
48+
// and trigger the iframe scrollbar, so size `height` as content.
49+
box-sizing: content-box;
50+
width: 636px; // email content 620px + body margins; keep in sync with EMAIL_WIDTH in mail_preview_controller.ts
51+
// match horizontal inner spacing between table border & edges
52+
padding-top: 16px;
53+
border: 0;
54+
}
55+
}

app/assets/stylesheets/tiptap.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,23 @@
7272
#tiptap-link-modal .fr-modal__footer .fr-btns-group {
7373
width: auto;
7474
}
75+
76+
// Subtle panel grouping the tag buttons (shared by the mail body and subject
77+
// editors) so they read as one block attached to the field above.
78+
.tags-buttons-panel {
79+
padding: 0.75rem;
80+
background-color: var(--background-alt-grey);
81+
border-radius: 0.25rem;
82+
}
83+
84+
// Collapsed tags variant of the tiptap editor: a light toggle tucked under the
85+
// field whose chevron flips when its collapse (the tags panel) is open.
86+
.tiptap-tags-collapse {
87+
&__toggle::after {
88+
transition: transform 0.3s;
89+
}
90+
91+
&__toggle[aria-expanded='true']::after {
92+
transform: rotate(-180deg);
93+
}
94+
}

app/components/procedure/email_template_card_component.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ def title
1212
end
1313

1414
def desc
15-
@email_template.subject if edited?
15+
return unless edited?
16+
17+
subject_doc = @email_template.tiptap_subject_doc.deep_symbolize_keys
18+
sanitize(TiptapService.new.to_texts_and_tags(subject_doc, strip: false))
1619
end
1720

1821
def error

app/components/tags_button_list_component.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ def initialize(tags:)
77
@tags = tags
88
end
99

10+
# Unique per instance so several tag lists on one page (e.g. the mail subject
11+
# and body editors) don't wire their labels to another list's checkbox.
12+
def optional_toggle_id
13+
@optional_toggle_id ||= "show_optional_#{SecureRandom.hex(4)}"
14+
end
15+
1016
def button_label(tag)
1117
tag[:libelle].truncate_words(12)
1218
end
Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,60 @@
11
<% each_category do |category, tags, can_toggle_optional| %>
2-
<% if category.present? %>
3-
<div class="flex">
4-
<p class="fr-label fr-text--sm fr-text--bold fr-mb-1w">
5-
<%= t(category, scope: ".categories") %>
6-
</p>
7-
</div>
8-
<% if can_toggle_optional %>
9-
<div class="fr-fieldset__element fr-px-0">
10-
<div class="fr-checkbox-group fr-checkbox-group--sm">
11-
<%= check_box_tag("show_optional_#{category}", 1, false, data: { "no-autosubmit" => true, action: "change->attestation#toggleOptional"}) %>
2+
<div data-controller="tags-button-list">
3+
<% if category.present? %>
4+
<div class="flex">
5+
<p class="fr-label fr-text--sm fr-text--bold fr-mb-1w">
6+
<%= t(category, scope: ".categories") %>
7+
</p>
8+
</div>
9+
<% if can_toggle_optional %>
10+
<div class="fr-fieldset__element fr-px-0">
11+
<div class="fr-checkbox-group fr-checkbox-group--sm">
12+
<%= check_box_tag("#{optional_toggle_id}_#{category}", 1, false, data: { "no-autosubmit" => true, action: "change->tags-button-list#toggleOptional" }) %>
1213

13-
<%= label_tag "show_optional_#{category}", for: "show_optional_#{category}" do %>
14-
<%= category == :champ_public ? "Voir les champs facultatifs et/ou conditionnés" : "Voir les annotations facultatives et/ou conditionnées" %>
14+
<%= label_tag "#{optional_toggle_id}_#{category}", for: "#{optional_toggle_id}_#{category}" do %>
15+
<%= category == :champ_public ? "Voir les champs facultatifs et/ou conditionnés" : "Voir les annotations facultatives et/ou conditionnées" %>
1516

16-
<span class="hidden fr-hint-text">
17-
<%= category == :champ_public ? "Un champ non rempli par l’usager" : "Une annotation non remplie par l’instructeur" %>
18-
restera vide dans l’attestation.
19-
</span>
20-
<% end %>
17+
<span
18+
class="hidden fr-hint-text"
19+
data-tags-button-list-target="hint"
20+
>
21+
<%= category == :champ_public ? "Un champ non rempli par l’usager" : "Une annotation non remplie par l’instructeur" %>
22+
restera vide.
23+
</span>
24+
<% end %>
25+
</div>
2126
</div>
22-
</div>
27+
<% end %>
2328
<% end %>
24-
<% end %>
2529

26-
<ul class="fr-tags-group" data-category="<%= category %>">
27-
<% tags.each do |tag| %>
28-
<% is_optional = can_toggle_optional && optional_tag?(tag) %>
29-
<% label = button_label(tag) %>
30+
<ul class="fr-tags-group" data-category="<%= category %>">
31+
<% tags.each do |tag| %>
32+
<% is_optional = can_toggle_optional && optional_tag?(tag) %>
33+
<% label = button_label(tag) %>
3034

31-
<li
32-
class="<%= class_names("hidden" => is_optional) %>"
33-
data-optional-tag="<%= is_optional.presence %>"
34-
>
35-
<button
36-
class="fr-tag fr-tag--sm <%= class_names("fr-tag--blue-ecume" => tag[:highlight], "fr-tag--purple-glycine" => is_optional) %>"
37-
type="button"
38-
title="<%= button_title(tag) %>"
39-
data-action="click->tiptap#insertTag"
40-
data-tiptap-target="tag"
41-
data-tag-id="<%= tag[:id] %>"
42-
data-tag-label="<%= label %>"
43-
data-tag-mandatory="<%= tag[:mandatory].to_s %>"
44-
data-tag-category="<%= category %>"
35+
<li
36+
class="<%= class_names("hidden" => is_optional) %>"
37+
data-tags-button-list-target="<%= is_optional ? "optionalItem" : nil %>"
4538
>
46-
<%= label %>
39+
<button
40+
class="fr-tag fr-tag--sm <%= class_names("fr-tag--blue-ecume" => tag[:highlight], "fr-tag--purple-glycine" => is_optional) %>"
41+
type="button"
42+
title="<%= button_title(tag) %>"
43+
data-action="click->tiptap#insertTag"
44+
data-tiptap-target="tag"
45+
data-tag-id="<%= tag[:id] %>"
46+
data-tag-label="<%= label %>"
47+
data-tag-mandatory="<%= tag[:mandatory].to_s %>"
48+
data-tag-category="<%= category %>"
49+
>
50+
<%= label %>
4751

48-
<% if tag[:mandatory] %>
49-
*
50-
<% end %>
51-
</button>
52-
</li>
53-
<% end %>
54-
</ul>
52+
<% if tag[:mandatory] %>
53+
*
54+
<% end %>
55+
</button>
56+
</li>
57+
<% end %>
58+
</ul>
59+
</div>
5560
<% end %>

0 commit comments

Comments
 (0)