refactor(stimulus): use stimulus in message forms
This commit is contained in:
parent
8eb4cf6d51
commit
906eea188e
13 changed files with 127 additions and 87 deletions
|
@ -15,7 +15,15 @@ class Dossiers::MessageComponent < ApplicationComponent
|
|||
end
|
||||
|
||||
def highlight_if_unseen_class
|
||||
helpers.highlight_if_unseen_class(@messagerie_seen_at, commentaire.created_at)
|
||||
if highlight?
|
||||
'highlighted'
|
||||
end
|
||||
end
|
||||
|
||||
def scroll_to_target
|
||||
if highlight?
|
||||
{ scroll_to_target: 'to' }
|
||||
end
|
||||
end
|
||||
|
||||
def icon_path
|
||||
|
@ -55,4 +63,10 @@ class Dossiers::MessageComponent < ApplicationComponent
|
|||
sanitize(body_formatted)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def highlight?
|
||||
commentaire.created_at.present? && @messagerie_seen_at&.<(commentaire.created_at)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
= commentaire_issuer
|
||||
- if commentaire_from_guest?
|
||||
%span.guest= t('.guest')
|
||||
%span.date{ class: highlight_if_unseen_class }
|
||||
%span.date{ class: highlight_if_unseen_class, data: scroll_to_target }
|
||||
= commentaire_date
|
||||
.rich-text= commentaire_body
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@ import { AutosaveController } from './autosave_controller';
|
|||
import { AutosaveStatusController } from './autosave_status_controller';
|
||||
import { GeoAreaController } from './geo_area_controller';
|
||||
import { MenuButtonController } from './menu_button_controller';
|
||||
import { PersistedFormController } from './persisted_form_controller';
|
||||
import { ReactController } from './react_controller';
|
||||
import { ScrollToController } from './scroll_to_controller';
|
||||
import { TurboEventController } from './turbo_event_controller';
|
||||
import { TurboInputController } from './turbo_input_controller';
|
||||
import { TurboPollController } from './turbo_poll_controller';
|
||||
|
@ -14,7 +16,9 @@ Stimulus.register('autosave-status', AutosaveStatusController);
|
|||
Stimulus.register('autosave', AutosaveController);
|
||||
Stimulus.register('geo-area', GeoAreaController);
|
||||
Stimulus.register('menu-button', MenuButtonController);
|
||||
Stimulus.register('persisted-form', PersistedFormController);
|
||||
Stimulus.register('react', ReactController);
|
||||
Stimulus.register('scroll-to', ScrollToController);
|
||||
Stimulus.register('turbo-event', TurboEventController);
|
||||
Stimulus.register('turbo-input', TurboInputController);
|
||||
Stimulus.register('turbo-poll', TurboPollController);
|
||||
|
|
59
app/javascript/controllers/persisted_form_controller.ts
Normal file
59
app/javascript/controllers/persisted_form_controller.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class PersistedFormController extends ApplicationController {
|
||||
static values = {
|
||||
key: String
|
||||
};
|
||||
|
||||
declare readonly keyValue: string;
|
||||
|
||||
connect() {
|
||||
this.on('submit', () => this.onSubmit());
|
||||
this.on('input', () => this.debounce(this.onInput, 500));
|
||||
|
||||
this.restoreInputValues();
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
try {
|
||||
for (const input of this.inputs) {
|
||||
localStorage.removeItem(this.storageKey(input.name));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
onInput() {
|
||||
try {
|
||||
for (const input of this.inputs) {
|
||||
localStorage.setItem(this.storageKey(input.name), input.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private restoreInputValues() {
|
||||
try {
|
||||
for (const input of this.inputs) {
|
||||
const value = localStorage.getItem(this.storageKey(input.name));
|
||||
if (value) {
|
||||
input.value = value;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private get inputs() {
|
||||
return this.element.querySelectorAll<
|
||||
HTMLInputElement | HTMLTextAreaElement
|
||||
>('input[type="text"], input[type="email"], textarea');
|
||||
}
|
||||
|
||||
private storageKey(name: string) {
|
||||
return `persisted-value-${this.keyValue}:${name}`;
|
||||
}
|
||||
}
|
39
app/javascript/controllers/scroll_to_controller.ts
Normal file
39
app/javascript/controllers/scroll_to_controller.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class ScrollToController extends ApplicationController {
|
||||
static targets = ['to'];
|
||||
declare readonly toTarget: HTMLInputElement;
|
||||
declare readonly hasToTarget: boolean;
|
||||
|
||||
connect() {
|
||||
if (this.hasToTarget) {
|
||||
this.scrollToElement();
|
||||
} else {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
private scrollTo(top: number) {
|
||||
this.element.scrollTop = top;
|
||||
}
|
||||
|
||||
private scrollToElement() {
|
||||
this.scrollTo(
|
||||
offset(this.toTarget).top -
|
||||
offset(this.element).top +
|
||||
this.element.scrollTop
|
||||
);
|
||||
}
|
||||
|
||||
private scrollToBottom() {
|
||||
this.scrollTo(this.element.scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
function offset(element: Element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
top: rect.top + document.body.scrollTop,
|
||||
left: rect.left + document.body.scrollLeft
|
||||
};
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
import { scrollTo, scrollToBottom } from '@utils';
|
||||
|
||||
function scrollMessagerie() {
|
||||
const ul = document.querySelector('.messagerie ul');
|
||||
|
||||
if (ul) {
|
||||
const elementToScroll = document.querySelector('.date.highlighted');
|
||||
|
||||
if (elementToScroll) {
|
||||
scrollTo(ul, elementToScroll);
|
||||
} else {
|
||||
scrollToBottom(ul);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveMessageContent() {
|
||||
const commentaireForms = Array.from(
|
||||
document.querySelectorAll('form[data-persisted-content-id]')
|
||||
);
|
||||
|
||||
if (commentaireForms.length) {
|
||||
const commentaireInputs = Array.from(
|
||||
document.querySelectorAll('.persisted-input')
|
||||
);
|
||||
|
||||
const persistedContentIds = commentaireForms.map(
|
||||
(form) => form.dataset.persistedContentId
|
||||
);
|
||||
|
||||
const keys = persistedContentIds.map((key) => `persisted-value-${key}`);
|
||||
|
||||
const object = commentaireInputs.map((input, index) => {
|
||||
return {
|
||||
input: input,
|
||||
form: commentaireForms[index],
|
||||
key: keys[index]
|
||||
};
|
||||
});
|
||||
|
||||
for (const el of object) {
|
||||
if (localStorage.getItem(el.key)) {
|
||||
el.input.value = localStorage.getItem(el.key);
|
||||
}
|
||||
|
||||
el.input.addEventListener('change', (event) => {
|
||||
localStorage.setItem(el.key, event.target.value);
|
||||
});
|
||||
|
||||
el.form.addEventListener('submit', () => {
|
||||
localStorage.removeItem(el.key);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('DOMContentLoaded', scrollMessagerie);
|
||||
addEventListener('DOMContentLoaded', saveMessageContent);
|
|
@ -18,7 +18,6 @@ import '../new_design/procedure-context';
|
|||
import '../new_design/procedure-form';
|
||||
import '../new_design/spinner';
|
||||
import '../new_design/support';
|
||||
import '../new_design/messagerie';
|
||||
|
||||
import '../new_design/champs/linked-drop-down-list';
|
||||
import '../new_design/champs/drop-down-list';
|
||||
|
|
|
@ -203,24 +203,7 @@ function createAbortController(controller?: AbortController) {
|
|||
return;
|
||||
}
|
||||
|
||||
export function scrollTo(container: HTMLElement, scrollTo: HTMLElement) {
|
||||
container.scrollTop =
|
||||
offset(scrollTo).top - offset(container).top + container.scrollTop;
|
||||
}
|
||||
|
||||
export function scrollToBottom(container: HTMLElement) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
export function isNumeric(s: string) {
|
||||
const n = parseFloat(s);
|
||||
return !isNaN(n) && isFinite(n);
|
||||
}
|
||||
|
||||
function offset(element: HTMLElement) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
top: rect.top + document.body.scrollTop,
|
||||
left: rect.left + document.body.scrollLeft
|
||||
};
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
= render Attachment::ShowComponent.new(attachment: @avis.introduction_file.attachment)
|
||||
%br/
|
||||
|
||||
= form_for @avis, url: expert_avis_path(@avis.procedure, @avis), html: { class: 'form', data: { persisted_content_id: @avis.id } } do |f|
|
||||
= f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true, class: 'persisted-input'
|
||||
= form_for @avis, url: expert_avis_path(@avis.procedure, @avis), html: { class: 'form', data: { controller: 'persisted-form', persisted_form_key_value: dom_id(@avis) } } do |f|
|
||||
= f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true
|
||||
= render Attachment::EditComponent.text(f, @avis.piece_justificative_file)
|
||||
|
||||
.flex.justify-between.align-baseline
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%h1.tab-title Inviter des personnes à donner leur avis
|
||||
%p.avis-notice Les invités pourront consulter le dossier, donner un avis et contribuer au fil de messagerie. Ils ne pourront pas modifier le dossier.
|
||||
|
||||
= form_for avis, url: url, html: { class: 'form', data: { persisted_content_id: "expert-ask-avis-for-dossier-#{@avis.dossier.id}" } } do |f|
|
||||
= form_for avis, url: url, html: { class: 'form', data: { controller: 'persisted-form', persisted_form_key_value: dom_id(@avis.dossier, :avis_by_expert) } } do |f|
|
||||
= hidden_field_tag 'avis[emails]', nil
|
||||
= react_component("ComboMultiple",
|
||||
options: [], selected: [], disabled: [],
|
||||
|
@ -11,7 +11,7 @@
|
|||
name: 'emails',
|
||||
label: 'Emails',
|
||||
acceptNewValues: true)
|
||||
= f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true, class: 'persisted-input'
|
||||
= f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true
|
||||
%p.tab-title Ajouter une pièce jointe
|
||||
.form-group
|
||||
= render Attachment::EditComponent.text(f, avis.introduction_file)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
%p#avis-emails-description.avis-notice
|
||||
Entrez les adresses email des experts à qui vous souhaitez demander un avis
|
||||
|
||||
= form_for avis, url: url, html: { class: 'form', data: { persisted_content_id: "instructeur-ask-avis-for-dossier-#{@dossier.id}" } } do |f|
|
||||
= form_for avis, url: url, html: { class: 'form', data: { controller: 'persisted-form', persisted_form_key_value: dom_id(@dossier, :avis_by_instructeur) } } do |f|
|
||||
= hidden_field_tag 'avis[emails]', nil
|
||||
= react_component("ComboMultiple",
|
||||
options: @dossier.procedure.experts_require_administrateur_invitation ? @experts_emails : [],
|
||||
|
@ -18,7 +18,7 @@
|
|||
name: 'emails',
|
||||
describedby: 'avis-emails-description',
|
||||
acceptNewValues: !@dossier.procedure.experts_require_administrateur_invitation)
|
||||
= f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true, class: "persisted-input"
|
||||
= f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true
|
||||
%p.tab-title Ajouter une pièce jointe
|
||||
.form-group
|
||||
= render Attachment::EditComponent.text(f, avis.introduction_file)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.messagerie.container
|
||||
%ul.messages-list
|
||||
%ul.messages-list{ data: { controller: 'scroll-to' } }
|
||||
- dossier.commentaires.with_attached_piece_jointe.each do |commentaire|
|
||||
%li.message{ class: commentaire_is_from_me_class(commentaire, connected_user), id: dom_id(commentaire) }
|
||||
= render Dossiers::MessageComponent.new(commentaire: commentaire, connected_user: connected_user, messagerie_seen_at: messagerie_seen_at, show_reply_button: show_reply_button(commentaire, connected_user))
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
= form_for(commentaire, url: form_url, html: { class: 'form', data: { persisted_content_id: @dossier.present? ? @dossier.id : "bulk-message-#{@procedure.id}" } }) do |f|
|
||||
= form_for(commentaire, url: form_url, html: { class: 'form', data: { controller: 'persisted-form', persisted_form_key_value: @dossier.present? ? dom_id(@dossier) : dom_id(@procedure, :bulk_message) } }) do |f|
|
||||
- dossier = commentaire.dossier
|
||||
- placeholder = t('views.shared.dossiers.messages.form.write_message_to_administration_placeholder')
|
||||
- if instructeur_signed_in? || administrateur_signed_in? || expert_signed_in?
|
||||
- placeholder = t('views.shared.dossiers.messages.form.write_message_placeholder')
|
||||
= f.text_area :body, rows: 5, placeholder: placeholder, title: placeholder, required: true, class: 'message-textarea persisted-input'
|
||||
= f.text_area :body, rows: 5, placeholder: placeholder, title: placeholder, required: true, class: 'message-textarea'
|
||||
.flex.justify-between.wrap
|
||||
- disable_piece_jointe = defined?(disable_piece_jointe) ? disable_piece_jointe : false
|
||||
%div
|
||||
|
|
Loading…
Reference in a new issue