diff --git a/app/assets/stylesheets/commons/_summernote.sass b/app/assets/stylesheets/commons/_summernote.sass index 76df9b876f5b8ef29595a3ed7781bd7036677bb5..9be47b54ee1b72027294690ece978e2151634130 100644 --- a/app/assets/stylesheets/commons/_summernote.sass +++ b/app/assets/stylesheets/commons/_summernote.sass @@ -4,6 +4,7 @@ font-weight: bold .note-btn.dropdown-toggle:after content: none - .note-editable img - max-width: 100% - height: auto + .note-editable + img, video + max-width: 100% + height: auto diff --git a/app/models/communication/website/menu/item.rb b/app/models/communication/website/menu/item.rb index 23c54a029f65943d85ee1c644187db99ccd81370..d95a6563bdbd9b97618bb1d37934c2f0d2521df3 100644 --- a/app/models/communication/website/menu/item.rb +++ b/app/models/communication/website/menu/item.rb @@ -83,8 +83,9 @@ class Communication::Website::Menu::Item < ApplicationRecord return nil unless active # Les méthodes target_for_ sont définies dans le concern WithTarget method = "target_for_#{kind}" - target = respond_to?(method) ? send(method) - : about&.path + # Le true sert à examiner les méthodes protected + target = respond_to?(method, true) ? send(method) + : about&.path return nil if target.nil? target.end_with?('/') ? target : "#{target}/" diff --git a/app/models/concerns/with_inheritance.rb b/app/models/concerns/with_inheritance.rb index 7257f9c53b9d8fd5783d9475264c5034df5d8805..279fe33899a4e3f516b0cb0011c819feb360e9df 100644 --- a/app/models/concerns/with_inheritance.rb +++ b/app/models/concerns/with_inheritance.rb @@ -5,12 +5,28 @@ module WithInheritance def self.rich_text_areas_with_inheritance(*properties) properties.each do |property| has_rich_text property - define_method :"best_#{property}" do - best(property) - end - define_method :"best_#{property}_source" do - best_source(property) - end + has_summernote :"#{property}_new" + + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def best_#{property} + best("#{property}") + end + + def best_#{property}_source + best_source("#{property}") + end + + def best_#{property}_new + value = send("#{property}_new") + value.blank? ? parent&.best_#{property}_new : value + end + + def best_#{property}_new_source(is_ancestor: false) + value = send("#{property}_new") + return (is_ancestor ? self : nil) if value.present? + parent&.best_#{property}_new_source(is_ancestor: true) + end + CODE end end end diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb index 71830569ac0266c0cbcc2fde11db14334319771c..e823ece4caee828b2e1677c38b1cca44cb4fbbe2 100644 --- a/app/views/active_storage/blobs/_blob.html.erb +++ b/app/views/active_storage/blobs/_blob.html.erb @@ -2,7 +2,7 @@ <% if blob.image? %> <%= kamifusen_tag blob, width: 800 %> <% elsif blob.video? %> - <video> + <video controls> <source src="<%= rails_blob_path(blob) %>" type="<%= blob.content_type %>"> </video> <% else %> diff --git a/app/views/admin/administration/qualiopi/evaluations/_list.html.erb b/app/views/admin/administration/qualiopi/evaluations/_list.html.erb index f388a90fa19af1f56b7b5e29dc3c183795da08da..1fcc574770e95ac017d5698d0f971b2e2a373359 100644 --- a/app/views/admin/administration/qualiopi/evaluations/_list.html.erb +++ b/app/views/admin/administration/qualiopi/evaluations/_list.html.erb @@ -4,6 +4,7 @@ <th><%= Education::Program.model_name.human %></th> <% checks.each do |check| %> <th><%= Education::Program.human_attribute_name(check) %></th> + <th><%= Education::Program.human_attribute_name(check) %> (new)</th> <% end %> </tr> </thead> @@ -22,13 +23,21 @@ </td> <% checks.each do |check| %> <% valid = !program.public_send("best_#{check}").blank? %> - <th> + <% valid_new = !program.public_send("best_#{check}_new").blank? %> + <td> <% if valid %> <span class="fas fa-check text-success"></span> <% else %> <span class="fas fa-times text-danger"></span> <% end %> - </th> + </td> + <td> + <% if valid_new %> + <span class="fas fa-check text-success"></span> + <% else %> + <span class="fas fa-times text-danger"></span> + <% end %> + </td> <% end %> </tr> <% end %> diff --git a/app/views/admin/application/property/_text_new.html.erb b/app/views/admin/application/property/_text_new.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..ca2e506f40f34470125dfd1e5045128998e366c1 --- /dev/null +++ b/app/views/admin/application/property/_text_new.html.erb @@ -0,0 +1,23 @@ +<% +value = object.send("#{property}_new").to_s +if object.respond_to? "best_#{property}_new" + value = object.public_send("best_#{property}_new").to_s + source = object.public_send("best_#{property}_new_source") +end +%> +<h3 class="h5"> + <%= object.class.human_attribute_name(property) %> (new) + <% if source %> + <span class="small text-muted"> + <%= t 'admin.inheritance.sentence_html', + link: link_to(source, [:admin, source]) %> + </span> + <% end %> +</h3> +<p> + <% if value.blank? %> + <i class="fa fa-exclamation-circle text-danger"></i> + <% else %> + <%= strip_tags(value).truncate 200 %> + <% end %> +</p> diff --git a/app/views/admin/communication/website/pages/show.html.erb b/app/views/admin/communication/website/pages/show.html.erb index 99dc3aa489d6ebd86d7ace6460ba9e4717300921..f279d1316dbfb56056d1b3cd1f7dc0e556a923cb 100644 --- a/app/views/admin/communication/website/pages/show.html.erb +++ b/app/views/admin/communication/website/pages/show.html.erb @@ -11,6 +11,7 @@ <div class="card-body"> <%= render 'admin/application/property/text', object: @page, property: :description %> <%= render 'admin/application/property/text', object: @page, property: :text %> + <%= render 'admin/application/property/text_new', object: @page, property: :text %> </div> </div> </div> diff --git a/app/views/admin/communication/website/posts/show.html.erb b/app/views/admin/communication/website/posts/show.html.erb index e2fb1cdbffb9dd9cad4d5ef82571fa3abec771ac..9d4cfd400e71f4891dac4312847b126e3e5bd111 100644 --- a/app/views/admin/communication/website/posts/show.html.erb +++ b/app/views/admin/communication/website/posts/show.html.erb @@ -10,9 +10,8 @@ <div class="card-body"> <%= render 'admin/application/property/text', object: @post, property: :description %> <%= render 'admin/application/property/text', object: @post, property: :text %> + <%= render 'admin/application/property/text_new', object: @post, property: :text %> <%= render 'admin/application/property/summernote_embeds', object: @post, property: :medias %> - <hr> - <pre><%= @post.text_new.to_html %></pre> </div> </div> </div> diff --git a/app/views/admin/education/programs/_form.html.erb b/app/views/admin/education/programs/_form.html.erb index 461c20397edabcdfe9e4465e00516fa02e001774..4edfdab0734b4ae1cccf0513aa5dcb72ce2d9c1e 100644 --- a/app/views/admin/education/programs/_form.html.erb +++ b/app/views/admin/education/programs/_form.html.erb @@ -36,10 +36,15 @@ <div class="card-body"> <%= f.input :description %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :registration %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :registration %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :pricing %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :pricing %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :duration %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :duration %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :accessibility %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :accessibility %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :other %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :other %> </div> </div> </div> @@ -67,6 +72,7 @@ </div> <div class="card-body"> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :contacts %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :contacts %> <p><%= Education::Program.human_attribute_name('teachers') %></p> <%= link_to_add_association t('add'), f, :university_person_involvements, @@ -97,14 +103,21 @@ <div class="row"> <div class="col-md-6"> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :objectives %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :objectives %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :content %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :content %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :prerequisites %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :prerequisites %> </div> <div class="col-md-6"> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :pedagogy %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :pedagogy %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :evaluation %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :evaluation %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :opportunities %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :opportunities %> <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :results %> + <%= render 'admin/education/programs/forms/input_with_inheritance_new', f: f, property: :results %> </div> </div> </div> diff --git a/app/views/admin/education/programs/forms/_input_with_inheritance_new.html.erb b/app/views/admin/education/programs/forms/_input_with_inheritance_new.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..5cd24d79314cc1da5653be8bced5f4e8aa321cf4 --- /dev/null +++ b/app/views/admin/education/programs/forms/_input_with_inheritance_new.html.erb @@ -0,0 +1,29 @@ +<% +program = f.object +best_prop_value = program.public_send("best_#{property}_new") +best_prop_source = program.public_send("best_#{property}_new_source") +id = "#{property}_newCollapse" +%> +<% if best_prop_source %> + <div class="accordion mb-3"> + <div class="accordion-item"> + <a href="#<%= id %>" + class="accordion-button collapsed" + data-bs-toggle="collapse" + aria-expanded="false" + aria-controls="<%= id %>"> + <%= Education::Program.human_attribute_name(property) %> (new) + (<%= t 'admin.inheritance.status' %>) + </a> + </div> + <div class="collapse" id="<%= id %>"> + <%= f.input "#{property}_new", as: :summernote, label: false %> + <div class="bg-light p-2 mt-n2"> + <b><%= t 'admin.inheritance.sentence_html', link: link_to(best_prop_source, [:admin, best_prop_source]) %></b><br> + <%= best_prop_value %> + </div> + </div> + </div> +<% else %> + <%= f.input "#{property}_new", as: :summernote %> +<% end %> diff --git a/app/views/admin/education/programs/show.html.erb b/app/views/admin/education/programs/show.html.erb index bf523772252d6995f2ac6bc24dfcdccfb0f1ea35..8582de8e0437983ce28a8eb84d88216ad93e2640 100644 --- a/app/views/admin/education/programs/show.html.erb +++ b/app/views/admin/education/programs/show.html.erb @@ -46,7 +46,6 @@ </div> <div class="card-body"> <%= render 'admin/application/property/text', object: @program, property: :description %> - <p><%= @program.description %></p> <% [ :registration, :pricing, @@ -56,6 +55,7 @@ :other ].each_with_index do |property, index| %> <%= render 'admin/application/property/text', object: @program, property: property %> + <%= render 'admin/application/property/text_new', object: @program, property: property %> <% end %> </div> </div> @@ -67,22 +67,18 @@ <h5 class="card-title mb-0"><%= t('education.program.educational_informations') %></h5> </div> <div class="card-body"> - <div class="row"> - <div class="col-md-6"> - <% i = 0 %> - <% [ - :objectives, - :content, - :prerequisites, - :pedagogy, - :evaluation, - :opportunities, - :results - ].each do |property| %> - <%= render 'admin/application/property/text', object: @program, property: property %> - <% end %> - </div> - </div> + <% [ + :objectives, + :content, + :prerequisites, + :pedagogy, + :evaluation, + :opportunities, + :results + ].each do |property| %> + <%= render 'admin/application/property/text', object: @program, property: property %> + <%= render 'admin/application/property/text_new', object: @program, property: property %> + <% end %> </div> </div> </div> diff --git a/config/application.rb b/config/application.rb index 4066c05a38912f51842cc8d19be02f1350dc5aa7..f060b9d569b508db293dd9fbd462e39b42299cdf 100644 --- a/config/application.rb +++ b/config/application.rb @@ -52,10 +52,10 @@ module Osuny "a", "abbr", "acronym", "address", "b", "big", "blockquote", "br", "cite", "code", "dd", "del", "dfn", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "ins", "kbd", "li", "ol", - "p", "picture", "pre", "samp", "small", "source", "span", "strong", "sub", "sup", "tt", "u", "ul", "var" + "p", "picture", "pre", "samp", "small", "source", "span", "strong", "sub", "sup", "tt", "u", "ul", "var", "video" ] config.action_view.sanitized_allowed_attributes = [ - "abbr", "alt", "cite", "class", "datetime", "decoding", "height", "href", "loading", + "abbr", "alt", "cite", "class", "controls", "datetime", "decoding", "height", "href", "loading", "name", "sizes", "src", "srcset", "style", "target", "title", "type", "width", "xml:lang" ] diff --git a/docs/communication/websites/blocks.md b/docs/communication/websites/blocks.md new file mode 100644 index 0000000000000000000000000000000000000000..0f74062a506d173113666d4a81dbb38f7a0c027d --- /dev/null +++ b/docs/communication/websites/blocks.md @@ -0,0 +1,67 @@ +# Blocks + +Blocs de contenus ajoutés à un objet (page, post, program...), +avec des templates (organigramme, partenaires...). + +Il faut lister les dépendances des blocs et les ajouter à l'objet about. + +## Dev + +### Model + +``` +communication/website/Block +- university:references +- website:references +- about:references (polymorphic) +- template:integer (enum) +- data:jsonb +``` + +Pour commencer, les valeurs de l'enum seront : +- 100, organization_chart +- 200, partners + +### Partial about +Un partial que l'on peut ajouter à un show d'objet, avec : +- la liste des blocs utilisés (avec boutons show et edit) +- la possibilité de les sort +- un bouton pour ajouter un bloc + +``` +views/admin/communication/website/blocks/_list.html.erb +``` + +### Show +Le show du bloc utilise le partial de son template +``` +views/admin/communication/website/blocks/templates/partners/_show.html.erb +``` + +### Edit +L'edit du bloc utilise le partial de son template +``` +views/admin/communication/website/blocks/templates/partners/_edit.html.erb +``` + +### Concern +Tous les objets ayant des blocs utilisent le concern `WithBlocks`, qui ajoute la méthode suivante `blocks` (la liste des blocs, dans l'ordre). + +### Export statique +Les blocs sont exportés dans le frontmatter grâce au partial +``` +views/admin/communication/website/blocks/_static.html.erb +``` +qui donne ce type de résultat +``` +blocks: + - kind: partners + data: + - name: Partner 1 + url: https://partner1.com + logo: "e09f3794-44e5-4b51-be02-0e384616e791" +``` +Les générateurs de chaque type suivent l'organisation : +``` +views/admin/communication/website/blocks/templates/partners/_static.html.erb +``` diff --git a/docs/communication/websites/customs.md b/docs/communication/websites/customs.md new file mode 100644 index 0000000000000000000000000000000000000000..7f506fdaddb663307d12f7e8f8c6c5e5945dd363 --- /dev/null +++ b/docs/communication/websites/customs.md @@ -0,0 +1,34 @@ +# Customs + +## communication/website/custom/Type + +- university:references +- website:references +- name:string +- identifier:string +- position:integer +- order:boolean +- tree:boolean +- date:boolean + +## communication/website/custom/type/Property + +- university:references +- website:references +- type:references +- name:string +- kind:integer (enum) +- position + +## communication/website/custom/Element + +- university:references +- website:references +- type:references +- name:string +- slug:string +- published:boolean +- published_at:datetime +- parent:references +- position:integer +- data:jsonb diff --git a/docs/communication/websites/wysiwyg.md b/docs/communication/websites/wysiwyg.md new file mode 100644 index 0000000000000000000000000000000000000000..43a4073f8ce6b5f9dba18a129d93a3c481119621 --- /dev/null +++ b/docs/communication/websites/wysiwyg.md @@ -0,0 +1,135 @@ +# WYSIWYG + +## Quels enjeux ? + +Permettre l'édition, mais limiter les options graphiques (ni couleurs, ni tailles, ni typos). + +Fonctionnalités : +- intégration d'images dans le corps du texte, en gardant la trace du blob active storage, et en les intégrant dans la liste des dépendances. +- intégration de vidéos. +- intégration d'autres formats (Tweets...). + +## Solutions techniques + +### ActionText + +Avantages : +- active storage intégré +- Trix intégré + +Inconvénients : +- Pas de modèle (polymorphic) + +### Trix + +Avantages : +- intégré à ActionText +- très limité + +Inconvénients : +- très limité (target blank, 1 seul niveau de titre, pas d'embed, pas de code source) +- pas extensible + +### Summernote + +Avantages : +- vaste +- extensible + +Inconvénients : +- dépendance jQuery +- pas intégré à ActionText +- moyennement robuste quand on le torture + +### Page builder custom + +Avantages : +- puissant +- souple + +Inconvénients : +- compliqué à construire et maintenir +- compliqué à utiliser + + +## Benchmark + +[Refinery](https://www.refinerycms.com/) + + +[Spina](https://spinacms.com/) + + +[Alchemy CMS](https://alchemy-cms.com/) + + +[Locomotive CMS](https://www.locomotivecms.com/) + + +## Méthode + +### action-text-attachment + +Dans la BDD, on stocke cette balise : +``` +<action-text-attachment sgid="BAh[...]1df3" + content-type="image/jpeg" + url="http://demo.osuny:3000/rails/active_storage/blobs/redirect/eyJf[...]0f4a1/domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg" filename="domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg" + filesize="352931" + width="588" + height="746" + previewable="true" + presentation="gallery"> +</action-text-attachment> +``` + +A l'édition, la balise est "remplie" avant affichage, pour avoir une preview. +A l'enregistrement, la balise est vidée. + +Etapes normales +-[x] A l'import d'une image, ajouter l'action-text-attachement autour de l'img +-[ ] A la suppression d'une image dans l'éditeur, supprimer l'action-text-attachement autour de l'img +-[x] A l'enregistrement, déshydrater les action-text-attachements +-[x] A l'édition, réhydrater les action-text-attachements +-[ ] Après l'enregistrement mettre à jour les blobs attachés à l'objet parent (le post, par exemple) + +Si un programme a 5 champs Summernote avec 3 images dans chaque champ, cela fait 15 attachments à lier au programme. +Si on enlève une image d'un champ, il faut mettre à jour la liste pour avoir les 14 bons attachments. + +Actions de dev +-[ ] Coder les ajouts aux modèles dans Osuny +-[ ] Coder le JS dans Osuny +-[ ] Une fois que c'est fait, déplacer le Ruby et le JS dans summernote-rails + +Migration phase 1 +-[x] Ajouter des champs _new +-[x] Au rails app:fix, transformer le markup Trix en markup Summernote (application_record) dans les propriétés _new +Migration phase 2 +-[ ] Supprimer les champs ActionText dans les modèles +-[ ] Supprimer la table d'ActionText +-[ ] Renommer les champs en enlevant _new + +### Le pdf + +Autoriser dans summernote + +### Code HTML cible + +``` +<h2>Titre</h2> +<p> + Texte normal<br> + <b>Texte en gras</b><br> + <i>Texte en italique</i> + <action-text-attachment sgid="BAh7CEkiCGdpZAY6BkVUSSJUZ2lkOi8vb3N1bnkvQWN0aXZlU3RvcmFnZTo6QmxvYi9hYWUyNDI5OC1kNDE2LTQ2YWMtYTRlNS02ZjY4ZGU2MjFiZDE_ZXhwaXJlc19pbgY7AFRJIgxwdXJwb3NlBjsAVEkiD2F0dGFjaGFibGUGOwBUSSIPZXhwaXJlc19hdAY7AFQw--7e8ead4d79f455499f1d73e8d53a4b8e81a21df3" content-type="image/jpeg" url="http://demo.osuny:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibW...df8140070f4a1/domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg?website_id=6d8fb0bb-0445-46f0-8954-0e25143e7a58" filename="domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg" filesize="352931" width="588" height="746" previewable="true" presentation="gallery"> + <figure class="attachment attachment--preview attachment--jpg"> + <picture> + <source srcset="/rails/active_storage/representations/redirect/eyJfcmFpbHMiOns...XJpYXRpb24ifX0=--7d11fdd26322fef8959415f46d5e2c6d6763b4c0/domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg 100w, /rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaE...527eb11f95949a389acb1c/domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg 200w" type="image/webp"> + <source srcset="/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibW...9fd77765da7c4f647d453b2/domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg?website_id=6d8fb0bb-0445-46f0-8954-0e25143e7a58 100w, /rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibW...bb3bc14127bc06ce0d1e32/domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg?website_id=6d8fb0bb-0445-46f0-8954-0e25143e7a58 200w" type="image/jpeg"> + <img src="/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsi...190ffccd8/domenico_bruno_de_lobkowitz_watchingwindows_com_08.jpg?website_id=6d8fb0bb-0445-46f0-8954-0e25143e7a58" loading="lazy" decoding="async" width="800"> + </picture> + </figure> + </action-text-attachment> + <a href="https://www.u-bordeaux-montaigne.fr/fr/actualites/vie-etudiante/soutenir-les-etudiant-e-s-les-aides-de-l-universite.html">Lien</a> +</p> +``` diff --git a/docs/communication/wysiwyg.md b/docs/communication/wysiwyg.md index 73058a50ba26c94c8fea7fa8eec17c656336ca2d..b2794545f6c88223c171b2fbd093c6e63fb060b8 100644 --- a/docs/communication/wysiwyg.md +++ b/docs/communication/wysiwyg.md @@ -103,7 +103,8 @@ Actions de dev Migration phase 1 -[x] Ajouter des champs _new --[x] Au rails app:fix, transformer le markup Trix en markup Summernote (application_record) dans les propriétés _new +-[ ] Au rails app:fix, transformer le markup Trix en markup Summernote (application_record) dans les propriétés _new +-[ ] Vérifier les paramètres du sanitizer d'Action Text (ActionText::ContentHelper : allowed_tags & allowed_attributes) Migration phase 2 -[ ] Supprimer les champs ActionText dans les modèles -[ ] Supprimer la table d'ActionText