Merge pull request #9903 from colinux/attestation-ux
ETQ admin, je peux tester l'attestation v2
This commit is contained in:
commit
70e92f7c6b
50 changed files with 1776 additions and 609 deletions
BIN
app/assets/images/attestation-template-schema-official.jpg
Normal file
BIN
app/assets/images/attestation-template-schema-official.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -36,24 +36,39 @@
|
|||
|
||||
#attestation {
|
||||
@media screen {
|
||||
max-width: 21cm;
|
||||
padding: 17mm;
|
||||
margin: 0 auto;
|
||||
.a4-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between; // This will push the footer down
|
||||
max-width: 21cm;
|
||||
height: 29.7cm;
|
||||
padding: 17mm;
|
||||
margin: 0 auto;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); // Optional: for better visualization
|
||||
}
|
||||
}
|
||||
|
||||
font-family: Marianne;
|
||||
|
||||
.header {
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
.official-layout & {
|
||||
.direction {
|
||||
margin-top: 5.25mm;
|
||||
}
|
||||
}
|
||||
|
||||
.bloc-marque {
|
||||
margin-bottom: 14mm;
|
||||
margin-right: 17mm; // 4x 4.25mm
|
||||
}
|
||||
|
||||
.marianne {
|
||||
|
@ -65,6 +80,7 @@
|
|||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
margin: 0 0 1mm;
|
||||
line-height: 12pt;
|
||||
}
|
||||
|
||||
.devise {
|
||||
|
@ -72,36 +88,83 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.issuer {
|
||||
font-size: 10pt;
|
||||
margin: 0 0 14mm; // pas sur, pour mettre une marge si issuer plus bas que date
|
||||
// weasyprint flexbox with img is broken
|
||||
// so we're using old inline tricks
|
||||
.logo-co-emetteur,
|
||||
.direction {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.logo-co-emetteur {
|
||||
img {
|
||||
max-height: 28mm;
|
||||
margin-right: 5mm;
|
||||
}
|
||||
}
|
||||
|
||||
.direction {
|
||||
font-size: 12pt;
|
||||
margin: 5.25mm 0 23.3mm;
|
||||
line-height: 14pt;
|
||||
font-weight: bold;
|
||||
margin: 0 0 23.3mm;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 8pt;
|
||||
margin: 0 0 14mm;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin: 0 0 12.6mm;
|
||||
.body-start {
|
||||
margin-top: 12.6mm; // from masque traitement de texte
|
||||
}
|
||||
|
||||
.main {
|
||||
font-size: 10pt;
|
||||
|
||||
.header {
|
||||
&:first-of-type {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
font-size: 8pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
font-size: 10pt;
|
||||
font-style: italic;
|
||||
h1,
|
||||
h2 {
|
||||
// both titles have the same size
|
||||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 14mm 0 8mm;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
line-height: 8pt;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 10pt; // same as text
|
||||
font-weight: bold;
|
||||
line-height: 4pt;
|
||||
}
|
||||
|
||||
li p {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.signature {
|
||||
text-align: right;
|
||||
margin-top: 14mm;
|
||||
margin-right: 25mm;
|
||||
}
|
||||
|
||||
.signature,
|
||||
.logo-free-layout {
|
||||
img {
|
||||
max-height: 50mm;
|
||||
max-width: 50mm;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
|
|
@ -1,7 +1,72 @@
|
|||
@import "constants";
|
||||
|
||||
#attestation-edit {
|
||||
.attestation-schema {
|
||||
width: 100%;
|
||||
margin-top: 3em;
|
||||
top: 3em;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.tiptap {
|
||||
padding: 8px;
|
||||
padding: $default-spacer;
|
||||
overflow-y: scroll;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.editor {
|
||||
// Visual zones
|
||||
.header .flex-1,
|
||||
h1 {
|
||||
border: 1px solid var(--background-contrast-grey-hover);
|
||||
padding: $default-spacer / 2;
|
||||
}
|
||||
|
||||
.header,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
margin-bottom: $default-spacer;
|
||||
}
|
||||
|
||||
// Styles
|
||||
.header {
|
||||
align-content: center;
|
||||
|
||||
p {
|
||||
margin-bottom: 0rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1rem; // same as text
|
||||
font-weight: bold;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
li p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// Tags
|
||||
.fr-menu__list {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.fr-tag:not(.fr-menu .fr-tag) {
|
||||
// style span rendered by tiptap like a button/link tag
|
||||
color: var(--text-action-high-blue-france);
|
||||
background-color: var(--background-action-low-blue-france);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// How to add a new remix icon and work with DSFR markup:
|
||||
// 1. Find it on https://remixicon.com/
|
||||
// 2. Take its DataURL (copy the url background-image value).
|
||||
|
@ -136,6 +135,22 @@
|
|||
// 4. Keep this list alphabetic :)
|
||||
.fr-icon {
|
||||
// scss-lint:disable VendorPrefix
|
||||
&-align-center {
|
||||
&:before,
|
||||
&:after {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 4H21V6H3V4ZM5 19H19V21H5V19ZM3 14H21V16H3V14ZM5 9H19V11H5V9Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 4H21V6H3V4ZM5 19H19V21H5V19ZM3 14H21V16H3V14ZM5 9H19V11H5V9Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
&-align-right {
|
||||
&:before,
|
||||
&:after {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 4H21V6H3V4ZM7 19H21V21H7V19ZM3 14H21V16H3V14ZM7 9H21V11H7V9Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 4H21V6H3V4ZM7 19H21V21H7V19ZM3 14H21V16H3V14ZM7 9H21V11H7V9Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
&-calendar-close-fill {
|
||||
&:before,
|
||||
&:after {
|
||||
|
@ -167,5 +182,13 @@
|
|||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12.917 13C12.441 15.8377 9.973 18 7 18C3.68629 18 1 15.3137 1 12C1 8.68629 3.68629 6 7 6C9.973 6 12.441 8.16229 12.917 11H23V13H21V17H19V13H17V17H15V13H12.917ZM7 16C9.20914 16 11 14.2091 11 12C11 9.79086 9.20914 8 7 8C4.79086 8 3 9.79086 3 12C3 14.2091 4.79086 16 7 16Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
&-underline {
|
||||
&:before,
|
||||
&:after {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 3V12C8 14.2091 9.79086 16 12 16C14.2091 16 16 14.2091 16 12V3H18V12C18 15.3137 15.3137 18 12 18C8.68629 18 6 15.3137 6 12V3H8ZM4 20H20V22H4V20Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 3V12C8 14.2091 9.79086 16 12 16C14.2091 16 16 14.2091 16 12V3H18V12C18 15.3137 15.3137 18 12 18C8.68629 18 6 15.3137 6 12V3H8ZM4 20H20V22H4V20Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
// scss-lint:enable VendorPrefix
|
||||
}
|
||||
|
|
17
app/components/tags_button_list_component.rb
Normal file
17
app/components/tags_button_list_component.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TagsButtonListComponent < ApplicationComponent
|
||||
attr_reader :tags
|
||||
|
||||
def initialize(tags:)
|
||||
@tags = tags
|
||||
end
|
||||
|
||||
def button_label(tag)
|
||||
tag[:libelle].truncate_words(12)
|
||||
end
|
||||
|
||||
def button_title(tag)
|
||||
tag[:description].presence || tag[:libelle]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
en:
|
||||
categories:
|
||||
individual: Identity
|
||||
etablissement: Establishment
|
||||
dossier: File
|
||||
champ_public: Form data
|
||||
champ_private: Private annotations
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
fr:
|
||||
categories:
|
||||
individual: Identité
|
||||
etablissement: Établissement
|
||||
dossier: Dossier
|
||||
champ_public: Formulaire
|
||||
champ_private: Annotations privées
|
|
@ -0,0 +1,9 @@
|
|||
- tags.each_pair do |category, tags|
|
||||
%p.fr-label.fr-text--sm.fr-text--bold.fr-mb-1w= t(category, scope: ".categories")
|
||||
%ul.fr-tags-group
|
||||
- tags.each do |tag|
|
||||
%li
|
||||
- label = button_label(tag)
|
||||
%button.fr-tag.fr-tag--sm{ type: "button", title: button_title(tag), data: { action: 'click->tiptap#insertTag', tiptap_target: 'tag', tag_id: tag[:id], tag_label: label } }
|
||||
= label
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
module Administrateurs
|
||||
class AttestationTemplateV2sController < AdministrateurController
|
||||
include UninterlacePngConcern
|
||||
|
||||
before_action :retrieve_procedure, :retrieve_attestation_template, :ensure_feature_active
|
||||
|
||||
def show
|
||||
json_body = @attestation_template.json_body&.deep_symbolize_keys
|
||||
@body = TiptapService.to_html(json_body, {})
|
||||
preview_dossier = @procedure.dossier_for_preview(current_user)
|
||||
|
||||
@body = @attestation_template.render_attributes_for(dossier: preview_dossier).fetch(:body)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
@ -31,9 +34,9 @@ module Administrateurs
|
|||
['Souligner', 'underline', 'underline']
|
||||
],
|
||||
[
|
||||
['Titre', 'title', 'h-1'],
|
||||
['Sous titre', 'heading2', 'h-2'],
|
||||
['Titre de section', 'heading3', 'h-3']
|
||||
['Titre', 'title', :hidden], # only for "title" section, without any action possible
|
||||
['Sous titre', 'heading2', 'h-1'],
|
||||
['Titre de section', 'heading3', 'h-2']
|
||||
],
|
||||
[
|
||||
['Liste à puces', 'bulletList', 'list-unordered'],
|
||||
|
@ -49,12 +52,32 @@ module Administrateurs
|
|||
['Redo', 'redo', 'arrow-go-forward-line']
|
||||
]
|
||||
]
|
||||
|
||||
@attestation_template.validate
|
||||
end
|
||||
|
||||
def update
|
||||
@attestation_template.update!(editor_params)
|
||||
attestation_params = editor_params
|
||||
logo_file = attestation_params.delete(:logo)
|
||||
signature_file = attestation_params.delete(:signature)
|
||||
|
||||
if logo_file
|
||||
attestation_params[:logo] = uninterlace_png(logo_file)
|
||||
end
|
||||
|
||||
if signature_file
|
||||
attestation_params[:signature] = uninterlace_png(signature_file)
|
||||
end
|
||||
|
||||
if !@attestation_template.update(attestation_params)
|
||||
flash.alert = "Le modèle de l’attestation contient des erreurs et n'a pas pu être enregistré. Corriger les erreurs."
|
||||
end
|
||||
|
||||
render :update
|
||||
end
|
||||
|
||||
def create = update
|
||||
|
||||
private
|
||||
|
||||
def ensure_feature_active
|
||||
|
@ -62,11 +85,11 @@ module Administrateurs
|
|||
end
|
||||
|
||||
def retrieve_attestation_template
|
||||
@attestation_template = @procedure.attestation_template || @procedure.build_attestation_template
|
||||
@attestation_template = @procedure.attestation_template_v2 || @procedure.build_attestation_template_v2(json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT)
|
||||
end
|
||||
|
||||
def editor_params
|
||||
params.required(:attestation_template).permit(:tiptap_body)
|
||||
params.required(:attestation_template).permit(:official_layout, :label_logo, :label_direction, :tiptap_body, :footer, :logo, :signature, :activated)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ module Administrateurs
|
|||
end
|
||||
|
||||
def update
|
||||
@attestation_template = @procedure.attestation_template
|
||||
@attestation_template = @procedure.attestation_template_v1
|
||||
|
||||
if @attestation_template.update(activated_attestation_params)
|
||||
flash.notice = "Le modèle de l’attestation a bien été modifié"
|
||||
|
@ -50,7 +50,7 @@ module Administrateurs
|
|||
private
|
||||
|
||||
def build_attestation_template(attributes = {})
|
||||
attestation_template = @procedure.attestation_template || @procedure.build_attestation_template
|
||||
attestation_template = @procedure.attestation_template_v1 || @procedure.build_attestation_template_v1
|
||||
attestation_template.attributes = attributes
|
||||
attestation_template
|
||||
end
|
||||
|
|
|
@ -111,7 +111,8 @@ module Administrateurs
|
|||
types_de_champ: [],
|
||||
revision_types_de_champ: { type_de_champ: { piece_justificative_template_attachment: :blob } }
|
||||
},
|
||||
attestation_template: [],
|
||||
attestation_template_v1: [],
|
||||
attestation_template_v2: [],
|
||||
initiated_mail: [],
|
||||
received_mail: [],
|
||||
closed_mail: [],
|
||||
|
|
44
app/javascript/controllers/attestation_controller.ts
Normal file
44
app/javascript/controllers/attestation_controller.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { toggle } from '@utils';
|
||||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class AttestationController extends ApplicationController {
|
||||
static targets = [
|
||||
'layoutToggle',
|
||||
'logoMarianneLabelFieldset',
|
||||
'logoAttachmentFieldset'
|
||||
];
|
||||
static values = {
|
||||
logoAttachmentOfficialLabel: String,
|
||||
logoAttachmentFreeLabel: String
|
||||
};
|
||||
|
||||
declare readonly layoutToggleTarget: HTMLInputElement;
|
||||
declare readonly logoMarianneLabelFieldsetTarget: HTMLElement;
|
||||
declare readonly logoAttachmentFieldsetTarget: HTMLElement;
|
||||
|
||||
declare readonly logoAttachmentOfficialLabelValue: string;
|
||||
declare readonly logoAttachmentFreeLabelValue: string;
|
||||
|
||||
connect() {
|
||||
this.layoutToggleTarget.addEventListener('change', () => {
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
private get isStateLayout() {
|
||||
return this.layoutToggleTarget.checked;
|
||||
}
|
||||
|
||||
private update() {
|
||||
toggle(this.logoMarianneLabelFieldsetTarget, this.isStateLayout);
|
||||
|
||||
const logoAttachmentLabel =
|
||||
this.logoAttachmentFieldsetTarget.querySelector('label');
|
||||
|
||||
if (logoAttachmentLabel) {
|
||||
logoAttachmentLabel.innerText = this.isStateLayout
|
||||
? this.logoAttachmentOfficialLabelValue
|
||||
: this.logoAttachmentFreeLabelValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,17 +2,20 @@ import { isFormInputElement, matchInputElement } from '@coldwired/utils';
|
|||
|
||||
import { ApplicationController } from './application_controller';
|
||||
|
||||
const AUTOSUBMIT_DEBOUNCE_DELAY = 500;
|
||||
const AUTOSUBMIT_DATE_DEBOUNCE_DELAY = 5000;
|
||||
const AUTOSUBMIT_EVENTS = ['input', 'change', 'blur'];
|
||||
|
||||
export class AutosubmitController extends ApplicationController {
|
||||
static targets = ['submitter', 'input'];
|
||||
static values = {
|
||||
debounceDelay: { type: Number, default: 500 }
|
||||
};
|
||||
|
||||
declare readonly submitterTarget: HTMLButtonElement | HTMLInputElement;
|
||||
declare readonly hasSubmitterTarget: boolean;
|
||||
declare readonly inputTarget: HTMLInputElement;
|
||||
declare readonly hasInputTarget: boolean;
|
||||
declare readonly debounceDelayValue: number;
|
||||
|
||||
#dateTimeChangedInputs = new WeakSet<HTMLElement>();
|
||||
|
||||
|
@ -46,8 +49,8 @@ export class AutosubmitController extends ApplicationController {
|
|||
|
||||
matchInputElement(target, {
|
||||
date: () => {},
|
||||
inputable: () => this.debounce(this.submit, AUTOSUBMIT_DEBOUNCE_DELAY),
|
||||
hidden: () => this.debounce(this.submit, AUTOSUBMIT_DEBOUNCE_DELAY)
|
||||
inputable: () => this.debounce(this.submit, this.debounceDelayValue),
|
||||
hidden: () => this.debounce(this.submit, this.debounceDelayValue)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
44
app/javascript/controllers/textarea_controller.ts
Normal file
44
app/javascript/controllers/textarea_controller.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class TextareaController extends ApplicationController {
|
||||
static values = {
|
||||
maxRows: Number
|
||||
};
|
||||
|
||||
declare readonly maxRowsValue: number;
|
||||
|
||||
connect() {
|
||||
if (this.maxRowsValue) {
|
||||
this.attachEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private attachEvents() {
|
||||
this.on('keyup', (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
this.processTextareaContent(event);
|
||||
}
|
||||
});
|
||||
|
||||
this.on('paste', (event: ClipboardEvent) => {
|
||||
// Wait for the paste event to complete
|
||||
setTimeout(() => this.processTextareaContent(event), 0);
|
||||
});
|
||||
}
|
||||
|
||||
private processTextareaContent(event: Event) {
|
||||
const target = event.target as HTMLTextAreaElement;
|
||||
let lines = target.value.split('\n');
|
||||
|
||||
if (lines.length > this.maxRowsValue) {
|
||||
// Truncate lines to the maximum allowed
|
||||
lines = lines.slice(0, this.maxRowsValue);
|
||||
target.value = lines.join('\n');
|
||||
|
||||
if (event instanceof KeyboardEvent) {
|
||||
// Prevent the default action only for KeyboardEvent (enter key)
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,11 +9,15 @@ import { createEditor } from '../shared/tiptap/editor';
|
|||
|
||||
export class TiptapController extends ApplicationController {
|
||||
static targets = ['editor', 'input', 'button', 'tag'];
|
||||
static values = {
|
||||
insertAfterTag: { type: String, default: '' }
|
||||
};
|
||||
|
||||
declare editorTarget: Element;
|
||||
declare inputTarget: HTMLInputElement;
|
||||
declare buttonTargets: HTMLButtonElement[];
|
||||
declare tagTargets: HTMLElement[];
|
||||
declare insertAfterTagValue: string;
|
||||
|
||||
#initializing = true;
|
||||
#editor?: Editor;
|
||||
|
@ -58,11 +62,15 @@ export class TiptapController extends ApplicationController {
|
|||
insertTag(event: MouseEvent) {
|
||||
if (this.#editor && isHTMLElement(event.target)) {
|
||||
const tag = tagSchema.parse(event.target.dataset);
|
||||
this.#editor
|
||||
const editor = this.#editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent({ type: 'mention', attrs: tag })
|
||||
.run();
|
||||
.insertContent({ type: 'mention', attrs: tag });
|
||||
|
||||
if (this.insertAfterTagValue != '') {
|
||||
editor.insertContent({ type: 'text', text: this.insertAfterTagValue });
|
||||
}
|
||||
editor.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ const EDITOR_ACTIONS: Record<string, (editor: Editor) => EditorAction> = {
|
|||
isDisabled: () => !editor.isActive('title')
|
||||
}),
|
||||
heading2: (editor) => ({
|
||||
run: () => editor.chain().focus().setHeading({ level: 2 }).run(),
|
||||
run: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||
isActive: () => editor.isActive('heading', { level: 2 }),
|
||||
isDisabled: () =>
|
||||
editor.isActive('title') ||
|
||||
|
@ -29,7 +29,7 @@ const EDITOR_ACTIONS: Record<string, (editor: Editor) => EditorAction> = {
|
|||
editor.isActive('footer')
|
||||
}),
|
||||
heading3: (editor) => ({
|
||||
run: () => editor.chain().focus().setHeading({ level: 3 }).run(),
|
||||
run: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
||||
isActive: () => editor.isActive('heading', { level: 3 }),
|
||||
isDisabled: () =>
|
||||
editor.isActive('title') ||
|
||||
|
|
|
@ -25,13 +25,7 @@ import {
|
|||
type Extensions
|
||||
} from '@tiptap/core';
|
||||
|
||||
import {
|
||||
DocumentWithHeader,
|
||||
Title,
|
||||
Header,
|
||||
Footer,
|
||||
HeaderColumn
|
||||
} from './nodes';
|
||||
import { DocumentWithHeader, Title, Header, HeaderColumn } from './nodes';
|
||||
import { createSuggestionMenu, type TagSchema } from './tags';
|
||||
|
||||
export function createEditor({
|
||||
|
@ -83,24 +77,8 @@ function getEditorOptions(
|
|||
case 'orderedList':
|
||||
extensions.push(OrderedList);
|
||||
break;
|
||||
case 'left':
|
||||
case 'center':
|
||||
case 'right':
|
||||
case 'justify':
|
||||
extensions.push(
|
||||
TextAlign.configure({
|
||||
types: actions.includes('title')
|
||||
? ['headerColumn', 'title', 'footer', 'heading', 'paragraph']
|
||||
: ['heading', 'paragraph']
|
||||
})
|
||||
);
|
||||
break;
|
||||
case 'title':
|
||||
extensions.push(Header, HeaderColumn, Title, Footer);
|
||||
break;
|
||||
case 'heading2':
|
||||
case 'heading3':
|
||||
extensions.push(Heading.configure({ levels: [2, 3] }));
|
||||
extensions.push(Header, HeaderColumn, Title);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -108,14 +86,34 @@ function getEditorOptions(
|
|||
if (actions.includes('bulletList') || actions.includes('orderedList')) {
|
||||
extensions.push(ListItem);
|
||||
}
|
||||
|
||||
if (actions.includes('heading2') || actions.includes('heading3')) {
|
||||
extensions.push(Heading.configure({ levels: [2, 3] }));
|
||||
}
|
||||
|
||||
if (
|
||||
actions.includes('left') ||
|
||||
actions.includes('center') ||
|
||||
actions.includes('right') ||
|
||||
actions.includes('justify')
|
||||
) {
|
||||
extensions.push(
|
||||
TextAlign.configure({
|
||||
types: actions.includes('title')
|
||||
? ['headerColumn', 'title', 'heading', 'paragraph']
|
||||
: ['heading', 'paragraph']
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (tags.length > 0) {
|
||||
extensions.push(
|
||||
Mention.configure({
|
||||
renderLabel({ node }) {
|
||||
return `--${node.attrs.label}--`;
|
||||
return node.attrs.label;
|
||||
},
|
||||
HTMLAttributes: {
|
||||
class: 'fr-badge fr-badge--sm fr-badge--info fr-badge--no-icon'
|
||||
class: 'fr-tag fr-tag--sm'
|
||||
},
|
||||
suggestion: createSuggestionMenu(tags, element)
|
||||
})
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Node, mergeAttributes } from '@tiptap/core';
|
|||
export const DocumentWithHeader = Node.create({
|
||||
name: 'doc',
|
||||
topNode: true,
|
||||
content: 'header title block+ footer'
|
||||
content: 'header title block+'
|
||||
});
|
||||
|
||||
export const Title = Node.create({
|
||||
|
@ -31,28 +31,15 @@ export const Header = Node.create({
|
|||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
'header',
|
||||
mergeAttributes(HTMLAttributes, { class: 'header flex' }),
|
||||
mergeAttributes(HTMLAttributes, { class: 'header flex flex-gap-1' }),
|
||||
0
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
export const Footer = Node.create({
|
||||
name: 'footer',
|
||||
content: 'paragraph+',
|
||||
defining: true,
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: `footer` }];
|
||||
},
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['footer', mergeAttributes(HTMLAttributes, { class: 'footer' }), 0];
|
||||
}
|
||||
});
|
||||
|
||||
export const HeaderColumn = Node.create({
|
||||
name: 'headerColumn',
|
||||
content: 'paragraph',
|
||||
content: 'paragraph{1,2}',
|
||||
defining: true,
|
||||
|
||||
parseHTML() {
|
||||
|
|
|
@ -84,35 +84,56 @@ class SuggestionMenu {
|
|||
destroy() {
|
||||
this.#popup?.destroy();
|
||||
this.#element?.remove();
|
||||
this.#element?.removeEventListener('click', this.handleItemClick);
|
||||
}
|
||||
|
||||
private render() {
|
||||
if (this.#props.items.length == 0) {
|
||||
this.#element?.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
this.#element ??= this.createMenu();
|
||||
const list = this.#element.firstChild as HTMLUListElement;
|
||||
|
||||
const html = this.#props.items
|
||||
.map((item, i) => {
|
||||
return `<li class="fr-badge fr-badge--sm fr-badge--no-icon${
|
||||
i == this.#selectedIndex ? ' fr-badge--info' : ''
|
||||
}">${item.label}</li>`;
|
||||
return `<li><button class="fr-tag fr-tag--sm" aria-pressed="${
|
||||
i == this.#selectedIndex ? 'true' : 'false'
|
||||
}" data-tag-index="${i}">${item.label}</button></li>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
this.#element.classList.add('fr-menu__list');
|
||||
list.innerHTML = html;
|
||||
const hint =
|
||||
'<li><span class="fr-hint-text">Tapez le nom d’une balise, naviguez avec les flèches, validez avec Entrée ou en cliquant sur la balise.</span></li>';
|
||||
list.innerHTML = hint + html;
|
||||
list.querySelector<HTMLElement>('.selected')?.focus();
|
||||
}
|
||||
|
||||
private createMenu() {
|
||||
const menu = document.createElement('div');
|
||||
const list = document.createElement('ul');
|
||||
menu.classList.add('fr-menu');
|
||||
list.classList.add('fr-menu__list');
|
||||
|
||||
const list = document.createElement('ul');
|
||||
list.classList.add('fr-menu__list', 'fr-tag-list', 'list-style-type-none');
|
||||
|
||||
menu.appendChild(list);
|
||||
|
||||
menu.addEventListener('click', this.handleItemClick);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private handleItemClick = (event: Event) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (!target || target.dataset.tagIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#props.command(this.#props.items[Number(target.dataset.tagIndex)]);
|
||||
this.#popup?.hide();
|
||||
};
|
||||
|
||||
private up() {
|
||||
this.#selectedIndex =
|
||||
(this.#selectedIndex + this.#props.items.length - 1) %
|
||||
|
@ -145,6 +166,8 @@ export function createSuggestionMenu(
|
|||
): Omit<SuggestionOptions<TagSchema>, 'editor'> {
|
||||
return {
|
||||
char: '@',
|
||||
allowedPrefixes: null,
|
||||
allowSpaces: true,
|
||||
items: ({ query }) => {
|
||||
return matchSorter(tags, query, { keys: ['label'] }).slice(0, 6);
|
||||
},
|
||||
|
|
|
@ -2,13 +2,14 @@ class AttestationTemplate < ApplicationRecord
|
|||
include ActionView::Helpers::NumberHelper
|
||||
include TagsSubstitutionConcern
|
||||
|
||||
belongs_to :procedure, inverse_of: :attestation_template
|
||||
belongs_to :procedure, inverse_of: :attestation_template_v2
|
||||
|
||||
has_one_attached :logo
|
||||
has_one_attached :signature
|
||||
|
||||
validates :title, tags: true, if: -> { procedure.present? }
|
||||
validates :body, tags: true, if: -> { procedure.present? }
|
||||
validates :title, tags: true, if: -> { procedure.present? && version == 1 }
|
||||
validates :body, tags: true, if: -> { procedure.present? && version == 1 }
|
||||
validates :json_body, tags: true, if: -> { procedure.present? && version == 2 }
|
||||
validates :footer, length: { maximum: 190 }
|
||||
|
||||
FILE_MAX_SIZE = 1.megabytes
|
||||
|
@ -17,6 +18,54 @@ class AttestationTemplate < ApplicationRecord
|
|||
|
||||
DOSSIER_STATE = Dossier.states.fetch(:accepte)
|
||||
|
||||
scope :v1, -> { where(version: 1) }
|
||||
scope :v2, -> { where(version: 2) }
|
||||
|
||||
TIPTAP_BODY_DEFAULT = {
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{
|
||||
"type" => "header",
|
||||
"content" => [
|
||||
{
|
||||
"type" => "headerColumn",
|
||||
"content" => [
|
||||
{
|
||||
"type" => "paragraph",
|
||||
"attrs" => { "textAlign" => "left" },
|
||||
"content" => [{ "type" => "mention", "attrs" => { "id" => "dossier_service_name", "label" => "nom du service" } }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type" => "headerColumn",
|
||||
"content" => [
|
||||
{
|
||||
"type" => "paragraph",
|
||||
"attrs" => { "textAlign" => "left" },
|
||||
"content" => [
|
||||
{ "text" => "Fait le ", "type" => "text" },
|
||||
{ "type" => "mention", "attrs" => { "id" => "dossier_processed_at", "label" => "date de décision" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "type" => "title", "attrs" => { "textAlign" => "center" }, "content" => [{ "text" => "Titre de l’attestation", "type" => "text" }] },
|
||||
{
|
||||
"type" => "paragraph",
|
||||
"attrs" => { "textAlign" => "left" },
|
||||
"content" => [
|
||||
{
|
||||
"text" => "Vous pouvez éditer ce texte pour personnaliser votre attestation. Pour ajouter du contenu issu du dossier, utilisez les balises situées sous cette zone de saisie.",
|
||||
"type" => "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}.freeze
|
||||
|
||||
def attestation_for(dossier)
|
||||
attestation = Attestation.new(title: replace_tags(title, dossier, escape: false))
|
||||
attestation.pdf.attach(
|
||||
|
@ -60,26 +109,19 @@ class AttestationTemplate < ApplicationRecord
|
|||
end
|
||||
|
||||
def render_attributes_for(params = {})
|
||||
attributes = {
|
||||
created_at: Time.zone.now,
|
||||
groupe_instructeur = params[:groupe_instructeur]
|
||||
groupe_instructeur ||= params[:dossier]&.groupe_instructeur
|
||||
|
||||
base_attributes = {
|
||||
created_at: Time.current,
|
||||
footer: params.fetch(:footer, footer),
|
||||
logo: params.fetch(:logo, logo.attached? ? logo : nil)
|
||||
signature: signature_to_render(groupe_instructeur)
|
||||
}
|
||||
|
||||
dossier = params[:dossier]
|
||||
|
||||
if dossier.present?
|
||||
attributes.merge({
|
||||
title: replace_tags(title, dossier, escape: false),
|
||||
body: replace_tags(body, dossier, escape: false),
|
||||
signature: signature_to_render(dossier.groupe_instructeur)
|
||||
})
|
||||
if version == 2
|
||||
render_attributes_for_v2(params, base_attributes)
|
||||
else
|
||||
attributes.merge({
|
||||
title: params.fetch(:title, title),
|
||||
body: params.fetch(:body, body),
|
||||
signature: signature_to_render(params[:groupe_instructeur])
|
||||
})
|
||||
render_attributes_for_v1(params, base_attributes)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -109,6 +151,48 @@ class AttestationTemplate < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def render_attributes_for_v1(params, base_attributes)
|
||||
attributes = base_attributes.merge(
|
||||
logo: params.fetch(:logo, logo.attached? ? logo : nil)
|
||||
)
|
||||
|
||||
dossier = params[:dossier]
|
||||
|
||||
if dossier.present?
|
||||
attributes.merge(
|
||||
title: replace_tags(title, dossier, escape: false),
|
||||
body: replace_tags(body, dossier, escape: false)
|
||||
)
|
||||
else
|
||||
attributes.merge(
|
||||
title: params.fetch(:title, title),
|
||||
body: params.fetch(:body, body)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def render_attributes_for_v2(params, base_attributes)
|
||||
dossier = params[:dossier]
|
||||
|
||||
json = json_body&.deep_symbolize_keys
|
||||
tiptap = TiptapService.new
|
||||
|
||||
if dossier.present?
|
||||
# 2x faster this way than with `replace_tags` which would reparse text
|
||||
used_tags = tiptap.used_tags_and_libelle_for(json.deep_symbolize_keys)
|
||||
substitutions = tags_substitutions(used_tags, dossier, escape: false)
|
||||
body = tiptap.to_html(json, substitutions)
|
||||
|
||||
attributes.merge(
|
||||
body:
|
||||
)
|
||||
else
|
||||
attributes.merge(
|
||||
body: params.fetch(:body) { tiptap.to_html(json) }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def signature_to_render(groupe_instructeur)
|
||||
if groupe_instructeur&.signature&.attached?
|
||||
groupe_instructeur.signature
|
||||
|
|
|
@ -155,14 +155,14 @@ module TagsSubstitutionConcern
|
|||
available_for_states: Dossier::SOUMIS
|
||||
},
|
||||
{
|
||||
id: 'individual_first_name',
|
||||
id: 'individual_last_name',
|
||||
libelle: 'nom',
|
||||
description: "nom de l'usager",
|
||||
target: :nom,
|
||||
available_for_states: Dossier::SOUMIS
|
||||
},
|
||||
{
|
||||
id: 'individual_last_name',
|
||||
id: 'individual_first_name',
|
||||
libelle: 'prénom',
|
||||
description: "prénom de l'usager",
|
||||
target: :prenom,
|
||||
|
@ -240,8 +240,26 @@ module TagsSubstitutionConcern
|
|||
tags_for_dossier_state(identity_tags + dossier_tags + champ_public_tags + champ_private_tags + routage_tags)
|
||||
end
|
||||
|
||||
def used_type_de_champ_tags(text)
|
||||
used_tags_and_libelle_for(text).filter_map do |(tag, libelle)|
|
||||
def tags_categorized
|
||||
identity_key = procedure.for_individual? ? :individual : :etablissement
|
||||
|
||||
{
|
||||
identity_key => tags_for_dossier_state(identity_tags),
|
||||
dossier: tags_for_dossier_state(dossier_tags + routage_tags),
|
||||
champ_public: tags_for_dossier_state(champ_public_tags),
|
||||
champ_private: tags_for_dossier_state(champ_private_tags)
|
||||
}.reject { |_, ary| ary.empty? }
|
||||
end
|
||||
|
||||
def used_type_de_champ_tags(text_or_tiptap)
|
||||
used_tags =
|
||||
if text_or_tiptap.respond_to?(:deconstruct_keys) # hash pattern matching
|
||||
TiptapService.new.used_tags_and_libelle_for(text_or_tiptap.deep_symbolize_keys)
|
||||
else
|
||||
used_tags_and_libelle_for(text_or_tiptap.to_s)
|
||||
end
|
||||
|
||||
used_tags.filter_map do |(tag, libelle)|
|
||||
if tag.nil?
|
||||
[libelle]
|
||||
elsif !tag.in?(SHARED_TAG_IDS) && tag.start_with?('tdc')
|
||||
|
@ -254,6 +272,34 @@ module TagsSubstitutionConcern
|
|||
used_tags_and_libelle_for(text).map { _1.first.nil? ? _1.second : _1.first }
|
||||
end
|
||||
|
||||
def tags_substitutions(tags_and_libelles, dossier, escape: true)
|
||||
# NOTE:
|
||||
# - tags_and_libelles est un simple Set de couples (tag_id, libelle) (pas la même structure que dans replace_tags)
|
||||
# - dans `replace_tags`, on fait référence à des tags avec ou sans id, mais pas ici,
|
||||
# (inutile car tiptap ne référence que des ids)
|
||||
|
||||
@escape_unsafe_tags = escape
|
||||
|
||||
flat_tags = tags_and_datas_list(dossier).each_with_object({}) do |(tags, data), result|
|
||||
next if data.nil?
|
||||
|
||||
valid_tags = tags_for_dossier_state(tags)
|
||||
|
||||
valid_tags.each do |tag|
|
||||
result[tag[:id]] = [tag, data]
|
||||
end
|
||||
end
|
||||
|
||||
tags_and_libelles.each_with_object({}) do |(tag_id, libelle), substitutions|
|
||||
substitutions[tag_id] = case flat_tags[tag_id]
|
||||
in tag, data
|
||||
replace_tag(tag, data)
|
||||
else # champ not in dossier, for example during preview on draft revision
|
||||
libelle
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format_date(date)
|
||||
|
@ -323,14 +369,7 @@ module TagsSubstitutionConcern
|
|||
|
||||
tokens = parse_tags(text)
|
||||
|
||||
tags_and_datas = [
|
||||
[champ_public_tags(dossier: dossier), dossier.champs_public],
|
||||
[champ_private_tags(dossier: dossier), dossier.champs_private],
|
||||
[dossier_tags, dossier],
|
||||
[ROUTAGE_TAGS, dossier],
|
||||
[INDIVIDUAL_TAGS, dossier.individual],
|
||||
[ENTREPRISE_TAGS, dossier.etablissement&.entreprise]
|
||||
].filter_map do |(tags, data)|
|
||||
tags_and_datas = tags_and_datas_list(dossier).filter_map do |(tags, data)|
|
||||
data && [tags_for_dossier_state(tags).index_by { _1[:id] }, data]
|
||||
end
|
||||
|
||||
|
@ -408,4 +447,15 @@ module TagsSubstitutionConcern
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def tags_and_datas_list(dossier)
|
||||
[
|
||||
[champ_public_tags(dossier:), dossier.champs_public],
|
||||
[champ_private_tags(dossier:), dossier.champs_private],
|
||||
[dossier_tags, dossier],
|
||||
[ROUTAGE_TAGS, dossier],
|
||||
[INDIVIDUAL_TAGS, dossier.individual],
|
||||
[ENTREPRISE_TAGS, dossier.etablissement&.entreprise]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -47,7 +47,11 @@ class Procedure < ApplicationRecord
|
|||
foreign_key: "replaced_by_procedure_id", dependent: :nullify
|
||||
|
||||
has_one :module_api_carto, dependent: :destroy
|
||||
has_one :attestation_template, dependent: :destroy
|
||||
has_many :attestation_templates, dependent: :destroy
|
||||
has_one :attestation_template_v1, -> { AttestationTemplate.v1 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure
|
||||
has_one :attestation_template_v2, -> { AttestationTemplate.v2 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure
|
||||
|
||||
has_one :attestation_template, -> { AttestationTemplate.v1.or(AttestationTemplate.v2) }, dependent: :destroy, inverse_of: :procedure
|
||||
|
||||
belongs_to :parent_procedure, class_name: 'Procedure', optional: true
|
||||
belongs_to :canonical_procedure, class_name: 'Procedure', optional: true
|
||||
|
@ -988,6 +992,14 @@ class Procedure < ApplicationRecord
|
|||
draft_revision.revision_types_de_champ_public.filter { _1.type_de_champ.header_section? }
|
||||
end
|
||||
|
||||
def dossier_for_preview(user)
|
||||
# Try to use a preview or a dossier filled by current user
|
||||
dossiers.where(for_procedure_preview: true).or(dossiers.not_brouillon)
|
||||
.order(Arel.sql("CASE WHEN for_procedure_preview = True THEN 1 ELSE 0 END DESC,
|
||||
CASE WHEN user_id = #{user.id} THEN 1 ELSE 0 END DESC")) \
|
||||
.first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pieces_jointes_list
|
||||
|
|
|
@ -1,74 +1,99 @@
|
|||
class TiptapService
|
||||
class << self
|
||||
def to_html(node, tags)
|
||||
return '' if node.nil?
|
||||
def to_html(node, substitutions = {})
|
||||
return '' if node.nil?
|
||||
|
||||
children(node[:content], tags)
|
||||
children(node[:content], substitutions, 0)
|
||||
end
|
||||
|
||||
# NOTE: node must be deep symbolized keys
|
||||
def used_tags_and_libelle_for(node, tags = Set.new)
|
||||
case node
|
||||
in type: 'mention', attrs: { id:, label: }, **rest
|
||||
tags << [id, label]
|
||||
in { content:, **rest } if content.is_a?(Array)
|
||||
content.each { used_tags_and_libelle_for(_1, tags) }
|
||||
in type:, **rest
|
||||
# noop
|
||||
end
|
||||
|
||||
private
|
||||
tags
|
||||
end
|
||||
|
||||
def children(content, tags)
|
||||
content.map { node_to_html(_1, tags) }.join
|
||||
private
|
||||
|
||||
def initialize
|
||||
@body_started = false
|
||||
end
|
||||
|
||||
def children(content, substitutions, level)
|
||||
content.map { node_to_html(_1, substitutions, level) }.join
|
||||
end
|
||||
|
||||
def node_to_html(node, substitutions, level)
|
||||
if level == 0 && !@body_started && node[:type].in?(['paragraph', 'heading']) && node.key?(:content)
|
||||
@body_started = true
|
||||
body_start_mark = " class=\"body-start\""
|
||||
end
|
||||
|
||||
def node_to_html(node, tags)
|
||||
case node
|
||||
in type: 'header', content:
|
||||
"<header>#{children(content, tags)}</header>"
|
||||
in type: 'footer', content:, **rest
|
||||
"<footer#{text_align(rest[:attrs])}>#{children(content, tags)}</footer>"
|
||||
in type: 'headerColumn', content:, **rest
|
||||
"<div#{text_align(rest[:attrs])} class=\"column\">#{children(content, tags)}</div>"
|
||||
in type: 'paragraph', content:, **rest
|
||||
"<p#{text_align(rest[:attrs])}>#{children(content, tags)}</p>"
|
||||
in type: 'title', content:, **rest
|
||||
"<h1#{text_align(rest[:attrs])}>#{children(content, tags)}</h1>"
|
||||
in type: 'heading', attrs: { level:, **attrs }, content:
|
||||
"<h#{level}#{text_align(attrs)}>#{children(content, tags)}</h#{level}>"
|
||||
in type: 'bulletList', content:
|
||||
"<ul>#{children(content, tags)}</ul>"
|
||||
in type: 'orderedList', content:
|
||||
"<ol>#{children(content, tags)}</ol>"
|
||||
in type: 'listItem', content:
|
||||
"<li>#{children(content, tags)}</li>"
|
||||
in type: 'text', text:, **rest
|
||||
if rest[:marks].present?
|
||||
apply_marks(text, rest[:marks])
|
||||
else
|
||||
text
|
||||
end
|
||||
in type: 'mention', attrs: { id: }, **rest
|
||||
if rest[:marks].present?
|
||||
apply_marks(tags[id], rest[:marks])
|
||||
else
|
||||
tags[id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def text_align(attrs)
|
||||
if attrs.present? && attrs[:textAlign].present?
|
||||
" style=\"text-align: #{attrs[:textAlign]}\""
|
||||
case node
|
||||
in type: 'header', content:
|
||||
"<header>#{children(content, substitutions, level + 1)}</header>"
|
||||
in type: 'footer', content:, **rest
|
||||
"<footer#{text_align(rest[:attrs])}>#{children(content, substitutions, level + 1)}</footer>"
|
||||
in type: 'headerColumn', content:, **rest
|
||||
"<div#{text_align(rest[:attrs])}>#{children(content, substitutions, level + 1)}</div>"
|
||||
in type: 'paragraph', content:, **rest
|
||||
"<p#{body_start_mark}#{text_align(rest[:attrs])}>#{children(content, substitutions, level + 1)}</p>"
|
||||
in type: 'title', content:, **rest
|
||||
"<h1#{text_align(rest[:attrs])}>#{children(content, substitutions, level + 1)}</h1>"
|
||||
in type: 'heading', attrs: { level: hlevel, **attrs }, content:
|
||||
"<h#{hlevel}#{body_start_mark}#{text_align(attrs)}>#{children(content, substitutions, level + 1)}</h#{hlevel}>"
|
||||
in type: 'bulletList', content:
|
||||
"<ul>#{children(content, substitutions, level + 1)}</ul>"
|
||||
in type: 'orderedList', content:
|
||||
"<ol>#{children(content, substitutions, level + 1)}</ol>"
|
||||
in type: 'listItem', content:
|
||||
"<li>#{children(content, substitutions, level + 1)}</li>"
|
||||
in type: 'text', text:, **rest
|
||||
if rest[:marks].present?
|
||||
apply_marks(text, rest[:marks])
|
||||
else
|
||||
""
|
||||
text
|
||||
end
|
||||
end
|
||||
in type: 'mention', attrs: { id: }, **rest
|
||||
text = substitutions.fetch(id) { "--#{id}--" }
|
||||
|
||||
def apply_marks(text, marks)
|
||||
marks.reduce(text) do |text, mark|
|
||||
case mark
|
||||
in type: 'bold'
|
||||
"<strong>#{text}</strong>"
|
||||
in type: 'italic'
|
||||
"<em>#{text}</em>"
|
||||
in type: 'underline'
|
||||
"<u>#{text}</u>"
|
||||
in type: 'strike'
|
||||
"<s>#{text}</s>"
|
||||
in type: 'highlight'
|
||||
"<mark>#{text}</mark>"
|
||||
end
|
||||
if rest[:marks].present?
|
||||
apply_marks(text, rest[:marks])
|
||||
else
|
||||
text
|
||||
end
|
||||
in { type: type } if ["paragraph", "title", "heading"].include?(type) && !node.key?(:content)
|
||||
# noop
|
||||
end
|
||||
end
|
||||
|
||||
def text_align(attrs)
|
||||
if attrs.present? && attrs[:textAlign].present?
|
||||
" style=\"text-align: #{attrs[:textAlign]}\""
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def apply_marks(text, marks)
|
||||
marks.reduce(text) do |text, mark|
|
||||
case mark
|
||||
in type: 'bold'
|
||||
"<strong>#{text}</strong>"
|
||||
in type: 'italic'
|
||||
"<em>#{text}</em>"
|
||||
in type: 'underline'
|
||||
"<u>#{text}</u>"
|
||||
in type: 'strike'
|
||||
"<s>#{text}</s>"
|
||||
in type: 'highlight'
|
||||
"<mark>#{text}</mark>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
class TagsValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
procedure = record.procedure
|
||||
tags = record.used_type_de_champ_tags(value || '')
|
||||
tags = record.used_type_de_champ_tags(value)
|
||||
|
||||
invalid_tags = tags.filter_map do |(tag, stable_id)|
|
||||
tag if stable_id.nil?
|
||||
end
|
||||
|
||||
invalid_for_draft_revision = invalid_tags_for_revision(record, attribute, tags, procedure.draft_revision)
|
||||
invalid_for_draft_revision = invalid_tags_for_revision(record, tags, procedure.draft_revision)
|
||||
|
||||
invalid_for_published_revision = if procedure.published_revision_id.present?
|
||||
invalid_tags_for_revision(record, attribute, tags, procedure.published_revision)
|
||||
invalid_tags_for_revision(record, tags, procedure.published_revision)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -18,7 +18,7 @@ class TagsValidator < ActiveModel::EachValidator
|
|||
invalid_for_previous_revision = procedure
|
||||
.revisions_with_pending_dossiers
|
||||
.flat_map do |revision|
|
||||
invalid_tags_for_revision(record, attribute, tags, revision)
|
||||
invalid_tags_for_revision(record, tags, revision)
|
||||
end.uniq
|
||||
|
||||
# champ is added in draft revision but not yet published
|
||||
|
@ -48,7 +48,7 @@ class TagsValidator < ActiveModel::EachValidator
|
|||
end
|
||||
end
|
||||
|
||||
def invalid_tags_for_revision(record, attribute, tags, revision)
|
||||
def invalid_tags_for_revision(record, tags, revision)
|
||||
revision_stable_ids = revision
|
||||
.revision_types_de_champ
|
||||
.filter { !_1.child? }
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#autosave-notice.fr-badge.fr-badge--sm.fr-badge--success= t(".form_saved")
|
||||
- success = local_assigns.fetch(:success, true)
|
||||
#autosave-notice.fr-badge.fr-badge--sm{ class: class_names("fr-badge--success" => success, "fr-badge--error" => !success) }= success ? t(".form_saved") : t(".form_error")
|
||||
|
|
|
@ -1,16 +1,134 @@
|
|||
#attestation-edit.fr-container.mt-2{ data: { controller: 'tiptap' } }
|
||||
= form_for @attestation_template, url: admin_procedure_attestation_template_v2_path(@procedure), data: { turbo: 'true', controller: 'autosubmit' } do |form|
|
||||
.flex.flex-gap-2
|
||||
- @buttons.each do |buttons|
|
||||
.flex.flex-gap-1
|
||||
- buttons.each do |(label, action, icon)|
|
||||
%button.fr-btn.fr-btn--secondary.fr-btn--sm{ type: 'button', title: label, class: "fr-icon-#{icon}", data: { action: 'click->tiptap#menuButton', tiptap_target: 'button', tiptap_action: action } }
|
||||
= label
|
||||
= render partial: 'administrateurs/breadcrumbs',
|
||||
locals: { steps: [['Démarches', admin_procedures_path],
|
||||
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
|
||||
['Attestation']] }
|
||||
|
||||
.editor.mt-2{ data: { tiptap_target: 'editor' } }
|
||||
= form.hidden_field :tiptap_body, data: { tiptap_target: 'input' }
|
||||
= render NestedForms::FormOwnerComponent.new
|
||||
= form_for @attestation_template, url: admin_procedure_attestation_template_v2_path(@procedure), html: { multipart: true },
|
||||
data: { turbo: 'true',
|
||||
controller: 'autosubmit attestation',
|
||||
autosubmit_debounce_delay_value: 1000,
|
||||
attestation_logo_attachment_official_label_value: AttestationTemplate.human_attribute_name(:logo_additional),
|
||||
attestation_logo_attachment_free_label_value: AttestationTemplate.human_attribute_name(:logo) } do |f|
|
||||
|
||||
%ul.mt-2.flex.wrap.flex-gap-1
|
||||
- @attestation_template.tags.each do |tag|
|
||||
%li.fr-badge.fr-badge--sm{ role: 'button', title: tag[:description], data: { action: 'click->tiptap#insertTag', tiptap_target: 'tag', tag_id: tag[:id], tag_label: tag[:libelle] } }
|
||||
= tag[:libelle]
|
||||
#attestation-edit.fr-container.fr-mt-4w{ data: { controller: 'tiptap', tiptap_insert_after_tag_value: ' ' } }
|
||||
.fr-mb-6w
|
||||
= render Dsfr::AlertComponent.new(state: :info, title: "Nouvel éditeur d’attestation", heading_level: 'h3') do |c|
|
||||
- c.with_body do
|
||||
Cette page permet la mise en forme de l’attestation avec un nouvel éditeur plus flexible
|
||||
tout en respectant la charte de l’état. Essayez-la et donnez-nous votre avis
|
||||
en nous envoyant un email à #{mail_to(CONTACT_EMAIL, subject: "Feedback attestation v2")}.
|
||||
%br
|
||||
%strong Les attestations délivrées suivent encore l’ancien format :
|
||||
l’activation des attestations basées sur ce format sera bientôt disponible.
|
||||
%br
|
||||
|
||||
= link_to("Suivez ce lien pour revenir aux attestations actuellement délivrées", edit_admin_procedure_attestation_template_path(@procedure))
|
||||
|
||||
.fr-grid-row.fr-grid-row--gutters
|
||||
.fr-col-12.fr-col-md-8
|
||||
%fieldset.fr-fieldset{ aria: { labelledby: 'edit-attestation' } }
|
||||
%legend.fr-fieldset__legend#edit-attestation
|
||||
%h1.fr-h2 Attestation
|
||||
%p.fr-text--regular
|
||||
L’attestation est émise au moment où un dossier est accepté, elle est jointe à l’email d’accusé d’acceptation.
|
||||
Elle est également disponible au téléchargement depuis l’espace personnel de l’usager.
|
||||
|
||||
.fr-fieldset__element
|
||||
%h2.fr-h4 En-tête
|
||||
|
||||
.fr-fieldset__element
|
||||
.fr-toggle
|
||||
= f.check_box :official_layout, class: "fr-toggle-input", id: dom_id(@attestation_template, :official_layout), data: { "attestation-target": "layoutToggle"}
|
||||
%label.fr-toggle__label{ for: dom_id(@attestation_template, :official_layout), data: { fr_checked_label: "Activé", fr_unchecked_label: "Désactivé" } }
|
||||
Je souhaite générer une attestation à la charte de l’état (logo avec Marianne)
|
||||
|
||||
.fr-fieldset__element{ class: class_names("hidden" => !@attestation_template.official_layout?), data: { "attestation-target": 'logoMarianneLabelFieldset'} }
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :label_logo, input_type: :text_area, required: @attestation_template.official_layout?, opts: { rows: 3, data: { controller: :textarea, textarea_max_rows_value: 3 } }) do |c|
|
||||
- c.with_hint { "Exemple: Ministère de la Mer. 3 lignes maximum" }
|
||||
|
||||
.fr-fieldset__element{ data: { attestation_target: 'logoAttachmentFieldset' } }
|
||||
%label.fr-label{ for: field_id(@attestation_template, :logo) }
|
||||
- if @attestation_template.official_layout?
|
||||
= AttestationTemplate.human_attribute_name(:logo_additional)
|
||||
- else
|
||||
= AttestationTemplate.human_attribute_name(:logo)
|
||||
|
||||
%span.fr-hint-text
|
||||
Dimensions conseillées : au minimum 500px de largeur ou de hauteur.
|
||||
|
||||
%div{ id: dom_id(@attestation_template, :logo_attachment) }
|
||||
= render Attachment::EditComponent.new(attached_file: @attestation_template.logo, direct_upload: false)
|
||||
|
||||
.fr-fieldset__element
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :label_direction, input_type: :text_area, required: false, opts: { rows: 2, data: { controller: :textarea, textarea_max_rows_value: 2 } }) do |c|
|
||||
- c.with_hint { "Exemple: Direction interministérielle du numérique. 2 lignes maximum" }
|
||||
|
||||
.fr-fieldset__element.fr-mt-2w
|
||||
.fr-input-group{ class: class_names("fr-input-group--error" => f.object.errors.include?(:json_body)) }
|
||||
%label.fr-label.fr-h4
|
||||
= AttestationTemplate.human_attribute_name :body
|
||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||
|
||||
#editor.editor{ data: { tiptap_target: 'editor' }, aria: { describedby: dom_id(f.object, "json-body-messages")} }
|
||||
= f.hidden_field :tiptap_body, data: { tiptap_target: 'input' }
|
||||
|
||||
.fr-error-text{ id: dom_id(f.object, "json-body-messages"), class: class_names("hidden" => !f.object.errors.include?(:json_body)) }
|
||||
- if f.object.errors.include?(:json_body)
|
||||
= render partial: "shared/errors_list", locals: { object: f.object, attribute: :json_body }
|
||||
|
||||
.fr-fieldset__element
|
||||
.flex.flex-gap-2
|
||||
- @buttons.each do |buttons|
|
||||
.flex.flex-gap-1
|
||||
- buttons.each do |(label, action, icon)|
|
||||
%button.fr-btn.fr-btn--secondary.fr-btn--sm{ type: 'button', title: label, class: icon == :hidden ? "hidden" : "fr-icon-#{icon}", data: { action: 'click->tiptap#menuButton', tiptap_target: 'button', tiptap_action: action } }
|
||||
= label
|
||||
|
||||
.fr-fieldset__element
|
||||
%p.fr-hint-text
|
||||
Tapez le caractère
|
||||
%strong.fr-text-title--grey @
|
||||
suivi du nom de la balise, ou cliquez sur les boutons ci-dessous. Les champs conditionnés ne sont pas disponibles.
|
||||
|
||||
= render TagsButtonListComponent.new(tags: @attestation_template.tags_categorized)
|
||||
|
||||
.fr-fieldset__element.fr-mt-2w
|
||||
%h2.fr-h4 Pied de page
|
||||
|
||||
.fr-fieldset__element
|
||||
%label.fr-label{ for: field_id(@attestation_template, :signature) } Tampon ou signature
|
||||
%span.fr-hint-text
|
||||
Dimensions conseillées : au minimum 500px de largeur ou de hauteur.
|
||||
|
||||
%div{ id: dom_id(@attestation_template, :signature_attachment) }
|
||||
= render Attachment::EditComponent.new(attached_file: @attestation_template.signature, direct_upload: false)
|
||||
|
||||
.fr-fieldset__element
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :footer, input_type: :text_area, required: false, opts: { rows: 3, data: { controller: :textarea, textarea_max_rows_value: 3 } }) do |c|
|
||||
- c.with_hint { "Exemple: 20 avenue de Ségur, 75007 Paris" }
|
||||
|
||||
.fr-col-12.fr-col-md-4.fr-background-alt--blue-france
|
||||
= image_tag("attestation-template-schema-official.jpg", class: "attestation-schema", alt: "Schéma d’une attestation au modèle de l’état")
|
||||
|
||||
|
||||
.padded-fixed-footer
|
||||
.fixed-footer
|
||||
.fr-container
|
||||
.fr-grid-row
|
||||
.fr-col-12.fr-col-md-7
|
||||
%ul.fr-btns-group.fr-btns-group--inline-md
|
||||
%li
|
||||
= link_to 'Prévisualiser l’attestation PDF', admin_procedure_attestation_template_v2_path(@procedure, format: :pdf), class: 'fr-btn fr-btn', target: '_blank', rel: 'noopener'
|
||||
%li
|
||||
= link_to admin_procedure_path(id: @procedure), class: 'fr-btn fr-btn--secondary' do
|
||||
%span.fr-icon-arrow-go-back-line.fr-icon--sm.fr-mr-1v
|
||||
Revenir à la démarche
|
||||
|
||||
.fr-col-12.fr-col-md-5
|
||||
-# .fr-toggle
|
||||
-# = f.check_box :activated, class: "fr-toggle-input", disabled: true, id: dom_id(@attestation_template, :activated)
|
||||
-# %label.fr-toggle__label{ for: dom_id(@attestation_template, :activated), data: { fr_checked_label: "Attestation activée", fr_unchecked_label: "Attestation désactivée" } }
|
||||
.text-right
|
||||
%span#autosave-notice
|
||||
%p.fr-hint-text L’activation de cette attestation sera bientôt disponible.
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
= image_tag('centered_marianne.svg', alt: '', class: 'marianne')
|
||||
.header
|
||||
.left
|
||||
.bloc-marque
|
||||
%p.intitule
|
||||
PRÉFET<br />
|
||||
DU VAL-<br />
|
||||
DE-MARNE
|
||||
= image_tag('liberte2.svg', alt: '', class: 'devise')
|
||||
.a4-container{ class: class_names("official-layout": @attestation_template.official_layout?) }
|
||||
.content
|
||||
%header.first-header
|
||||
.left
|
||||
- if @attestation_template.official_layout?
|
||||
= image_tag('centered_marianne.svg', alt: '', class: 'marianne')
|
||||
.bloc-marque
|
||||
= simple_format @attestation_template.label_logo.presence || "INTITULE de\nVOTRE INSTITUTION", class: "intitule"
|
||||
= image_tag('liberte2.svg', alt: '', class: 'devise')
|
||||
- elsif @attestation_template.logo.present?
|
||||
.bloc-marque.logo-free-layout
|
||||
= image_tag(@attestation_template.logo_url)
|
||||
|
||||
%p.issuer
|
||||
Service Hébergement et Accès au Logement<br />
|
||||
Bureau de l'Accès au Logement
|
||||
.right
|
||||
- if @attestation_template.official_layout? && @attestation_template.logo.present?
|
||||
.logo-co-emetteur
|
||||
= image_tag(@attestation_template.logo_url)
|
||||
|
||||
.right
|
||||
%p.direction
|
||||
Direction Régionale et Interdépartementale<br />
|
||||
de l'Hébergement et du Logement<br />
|
||||
DRIHL Val-de-Marne
|
||||
- if @attestation_template.label_direction.present?
|
||||
= simple_format @attestation_template.label_direction, class: "direction"
|
||||
|
||||
%p.date Créteil, le 20 mars 2023
|
||||
.main
|
||||
= sanitize(@body, attributes: %w[class style], tags: Rails.configuration.action_view.sanitized_allowed_tags + %w[header])
|
||||
|
||||
%h1.title ATTESTATION
|
||||
- if @attestation_template.signature.present?
|
||||
.signature
|
||||
= image_tag(@attestation_template.signature_url)
|
||||
|
||||
.main
|
||||
= sanitize(@body)
|
||||
|
||||
%p.footer
|
||||
12/14 rue des Archives 94000 Créteil<br />
|
||||
www.drihl.ile-de-france.developpement-durable.gouv.fr
|
||||
- if @attestation_template.footer.present?
|
||||
= simple_format @attestation_template.footer, class: "footer"
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
= turbo_stream.show 'autosave-notice'
|
||||
= turbo_stream.replace 'autosave-notice', render(partial: 'administrateurs/autosave_notice', locals: { success: !@attestation_template.changed? })
|
||||
= turbo_stream.hide 'autosave-notice', delay: 15000
|
||||
|
||||
- if @attestation_template.logo_blob&.previously_new_record?
|
||||
= turbo_stream.update dom_id(@attestation_template, :logo_attachment) do
|
||||
= render(Attachment::EditComponent.new(attached_file: @attestation_template.logo, direct_upload: false))
|
||||
|
||||
- if @attestation_template.signature_blob&.previously_new_record?
|
||||
= turbo_stream.update dom_id(@attestation_template, :signature_attachment) do
|
||||
= render(Attachment::EditComponent.new(attached_file: @attestation_template.signature, direct_upload: false))
|
||||
|
||||
- body_id = dom_id(@attestation_template, "json-body-messages")
|
||||
- if @attestation_template.errors.include?(:json_body)
|
||||
= turbo_stream.update body_id do
|
||||
= render partial: "shared/errors_list", locals: { object: @attestation_template, attribute: :json_body }
|
||||
= turbo_stream.show body_id
|
||||
- else
|
||||
= turbo_stream.hide body_id
|
||||
= turbo_stream.update body_id, nil
|
|
@ -5,46 +5,54 @@
|
|||
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
|
||||
['Attestation']] }
|
||||
|
||||
.procedure-form#attestation-template-edit
|
||||
.procedure-form__columns.container
|
||||
= render NestedForms::FormOwnerComponent.new
|
||||
= form_for @attestation_template,
|
||||
url: admin_procedure_attestation_template_path(@procedure),
|
||||
html: { multipart: true, class: 'form procedure-form__column--form fr-background-alt--blue-france' } do |f|
|
||||
.fr-container
|
||||
- if @procedure.feature_enabled?(:attestation_v2)
|
||||
.fr-mb-6w
|
||||
= render Dsfr::AlertComponent.new(state: :info, title: "Nouvel éditeur d’attestation", heading_level: 'h3') do |c|
|
||||
- c.with_body do
|
||||
Cette page concerne l’attestation actuellement délivrée aux usagers.
|
||||
= link_to("Suivez ce lien pour tester le nouvel éditeur d’attestation", edit_admin_procedure_attestation_template_v2_path(@procedure))
|
||||
|
||||
%h1.page-title
|
||||
Délivrance d’attestation
|
||||
- if @attestation_template.activated?
|
||||
%span.text-active activée
|
||||
- else
|
||||
%span.text-inactive désactivée
|
||||
.procedure-form#attestation-template-edit
|
||||
.procedure-form__columns
|
||||
= render NestedForms::FormOwnerComponent.new
|
||||
= form_for @attestation_template,
|
||||
url: admin_procedure_attestation_template_path(@procedure),
|
||||
html: { multipart: true, class: 'form procedure-form__column--form fr-background-alt--blue-france' } do |f|
|
||||
|
||||
%p.notice
|
||||
L’attestation, si elle est activée, est émise au moment où un dossier est accepté.
|
||||
%br
|
||||
L’email d’accusé d’acceptation envoyé à l’usager comporte alors un lien vers l’attestation ;
|
||||
celle-ci est également disponible au téléchargement depuis l’espace personnel de l’usager.
|
||||
%h1.page-title
|
||||
Délivrance d’attestation
|
||||
- if @attestation_template.activated?
|
||||
%span.text-active activée
|
||||
- else
|
||||
%span.text-inactive désactivée
|
||||
|
||||
= render partial: 'administrateurs/attestation_templates/informations', locals: { f: f }
|
||||
%p.notice
|
||||
L’attestation, si elle est activée, est émise au moment où un dossier est accepté.
|
||||
%br
|
||||
L’email d’accusé d’acceptation envoyé à l’usager comporte alors un lien vers l’attestation ;
|
||||
celle-ci est également disponible au téléchargement depuis l’espace personnel de l’usager.
|
||||
|
||||
.procedure-form__actions.sticky--bottom
|
||||
.actions-left
|
||||
%label.toggle-switch
|
||||
= f.check_box :activated, class: 'toggle-switch-checkbox'
|
||||
%span.toggle-switch-control.round
|
||||
%span.toggle-switch-label.on Attestation activée
|
||||
%span.toggle-switch-label.off Attestation désactivée
|
||||
= render partial: 'administrateurs/attestation_templates/informations', locals: { f: f }
|
||||
|
||||
.actions-right
|
||||
= link_to 'Annuler', edit_admin_procedure_attestation_template_path(id: @procedure), class: 'fr-btn fr-btn--secondary fr-mr-2w', data: { confirm: 'Êtes-vous sûr de vouloir annuler les modifications effectuées ?'}
|
||||
= f.button 'Enregistrer', class: 'fr-btn'
|
||||
.procedure-form__actions.sticky--bottom
|
||||
.actions-left
|
||||
%label.toggle-switch
|
||||
= f.check_box :activated, class: 'toggle-switch-checkbox'
|
||||
%span.toggle-switch-control.round
|
||||
%span.toggle-switch-label.on Attestation activée
|
||||
%span.toggle-switch-label.off Attestation désactivée
|
||||
|
||||
.procedure-form__column--preview
|
||||
.procedure-form__preview.sticky--top
|
||||
%h3
|
||||
.procedure-form__preview-title
|
||||
Aperçu
|
||||
.notice
|
||||
Cet aperçu est mis à jour après chaque sauvegarde.
|
||||
.procedure-preview
|
||||
= render partial: 'administrateurs/attestation_templates/apercu', locals: { procedure: @procedure }
|
||||
.actions-right
|
||||
= link_to 'Annuler', edit_admin_procedure_attestation_template_path(id: @procedure), class: 'fr-btn fr-btn--secondary fr-mr-2w', data: { confirm: 'Êtes-vous sûr de vouloir annuler les modifications effectuées ?'}
|
||||
= f.button 'Enregistrer', class: 'fr-btn'
|
||||
|
||||
.procedure-form__column--preview
|
||||
.procedure-form__preview.sticky--top
|
||||
%h3
|
||||
.procedure-form__preview-title
|
||||
Aperçu
|
||||
.notice
|
||||
Cet aperçu est mis à jour après chaque sauvegarde.
|
||||
.procedure-preview
|
||||
= render partial: 'administrateurs/attestation_templates/apercu', locals: { procedure: @procedure }
|
||||
|
|
3
app/views/shared/_errors_list.html.haml
Normal file
3
app/views/shared/_errors_list.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
%ul.list-style-type-none.fr-pl-0
|
||||
- object.errors.full_messages_for(attribute).map do |error_message|
|
||||
%li= error_message
|
|
@ -4,9 +4,13 @@ fr:
|
|||
attestation_template: 'Attestation'
|
||||
attributes:
|
||||
attestation_template:
|
||||
label_logo: Intitulé de votre institution
|
||||
label_direction: Intitulé de la direction
|
||||
logo: Logo
|
||||
logo_additional: Logo additionnel
|
||||
title: Titre de l’attestation
|
||||
body: Contenu de l’attestation
|
||||
footer: Pied de page
|
||||
footer: Contenu du pied de page
|
||||
|
||||
errors:
|
||||
models:
|
||||
|
@ -16,8 +20,8 @@ fr:
|
|||
one: contient la balise "%{tags}" qui n’existe pas. Supprimer la balise
|
||||
other: contient %{count} balises (%{tags}) qui n’existent pas. Supprimer les balises
|
||||
champ_missing_in_draft_revision:
|
||||
one: contient la balise "%{tags}" qui a été supprimée mais la suppression n’est pas encore publiée. Publier la nouvelle version de la démarche et recommencer
|
||||
other: contient %{count} balises (%{tags}) qui ont été supprimées mais la suppression n’est pas encore publiée. Publier la nouvelle version de la démarche et recommencer
|
||||
one: contient la balise "%{tags}" qui a été supprimée dans les modifications en cours du formulaire. Supprimer cette balise ou réinitialiser les modifications du formulaire puis recommencer
|
||||
other: contient %{count} balises (%{tags}) qui ont été supprimées dans les modifications en coure du formumlaire. Supprimer cette balise ou réinitialiser les modifications du formulaire puis recommencer
|
||||
champ_missing_in_published_revision:
|
||||
one: contient la balise "%{tags}" qui n’est pas encore publiée. Publier la nouvelle version de la démarche et recommencer
|
||||
other: contient %{count} balises (%{tags}) qui ne sont pas encore publiées. Publier la nouvelle version de la démarche et recommencer
|
||||
|
@ -34,3 +38,6 @@ fr:
|
|||
body:
|
||||
format: Le champ « Contenu de l’attestation » %{message}
|
||||
<<: *tags_errors
|
||||
json_body:
|
||||
format: Le champ « Contenu de l’attestation » %{message}
|
||||
<<: *tags_errors
|
||||
|
|
|
@ -53,3 +53,4 @@ en:
|
|||
submit: Publish
|
||||
autosave_notice:
|
||||
form_saved: "Form saved"
|
||||
form_error: "Form in error"
|
||||
|
|
|
@ -53,3 +53,4 @@ fr:
|
|||
submit: Publier
|
||||
autosave_notice:
|
||||
form_saved: "Formulaire enregistré"
|
||||
form_error: "Formulaire en erreur"
|
||||
|
|
|
@ -650,7 +650,7 @@ Rails.application.routes.draw do
|
|||
get 'add_champ_engagement_juridique'
|
||||
end
|
||||
|
||||
resource :attestation_template_v2, only: [:show, :edit, :update]
|
||||
resource :attestation_template_v2, only: [:show, :edit, :update, :create]
|
||||
|
||||
resource :dossier_submitted_message, only: [:edit, :update, :create]
|
||||
# ADDED TO ACCESS IT FROM THE IFRAME
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class AddLabelsToAttestationTemplates < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :attestation_templates, :label_logo, :string, default: nil
|
||||
add_column :attestation_templates, :label_direction, :string, default: nil
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddLayoutToAttestationTemplates < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :attestation_templates, :official_layout, :boolean, default: true, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class AddVersionToAttestationTemplates < ActiveRecord::Migration[7.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_column :attestation_templates, :version, :integer, default: 1, null: false
|
||||
add_index :attestation_templates, [:procedure_id, :version], unique: true, algorithm: :concurrently
|
||||
remove_index :attestation_templates, :procedure_id, unique: true, algorithm: :concurrently
|
||||
end
|
||||
end
|
|
@ -151,10 +151,14 @@ ActiveRecord::Schema[7.0].define(version: 2024_01_26_071130) do
|
|||
t.datetime "created_at", precision: nil, null: false
|
||||
t.text "footer"
|
||||
t.jsonb "json_body"
|
||||
t.string "label_direction"
|
||||
t.string "label_logo"
|
||||
t.boolean "official_layout", default: true, null: false
|
||||
t.integer "procedure_id"
|
||||
t.text "title"
|
||||
t.datetime "updated_at", precision: nil, null: false
|
||||
t.index ["procedure_id"], name: "index_attestation_templates_on_procedure_id", unique: true
|
||||
t.integer "version", default: 1, null: false
|
||||
t.index ["procedure_id", "version"], name: "index_attestation_templates_on_procedure_id_and_version", unique: true
|
||||
end
|
||||
|
||||
create_table "attestations", id: :serial, force: :cascade do |t|
|
||||
|
|
|
@ -102,6 +102,7 @@ namespace :benchmarks do
|
|||
controller.request = ActionDispatch::TestRequest.create
|
||||
controller.response = ActionDispatch::TestResponse.new
|
||||
controller.request.env['warden'] = warden
|
||||
# controller.request.path_parameters[:format] = 'pdf'
|
||||
|
||||
params = ENV.fetch("PARAMS") { "" }.split(",")
|
||||
params.each do |param|
|
||||
|
|
42
package.json
42
package.json
|
@ -19,27 +19,27 @@
|
|||
"@reach/slider": "^0.17.0",
|
||||
"@sentry/browser": "7.66.0",
|
||||
"@stimulus/polyfills": "^2.0.0",
|
||||
"@tiptap/core": "^2.1.12",
|
||||
"@tiptap/extension-bold": "^2.1.12",
|
||||
"@tiptap/extension-bullet-list": "^2.1.12",
|
||||
"@tiptap/extension-document": "^2.1.12",
|
||||
"@tiptap/extension-gapcursor": "^2.1.12",
|
||||
"@tiptap/extension-heading": "^2.1.12",
|
||||
"@tiptap/extension-highlight": "^2.1.12",
|
||||
"@tiptap/extension-history": "^2.1.12",
|
||||
"@tiptap/extension-italic": "^2.1.12",
|
||||
"@tiptap/extension-link": "^2.1.12",
|
||||
"@tiptap/extension-list-item": "^2.1.12",
|
||||
"@tiptap/extension-mention": "^2.1.12",
|
||||
"@tiptap/extension-ordered-list": "^2.1.12",
|
||||
"@tiptap/extension-paragraph": "^2.1.12",
|
||||
"@tiptap/extension-strike": "^2.1.12",
|
||||
"@tiptap/extension-text": "^2.1.12",
|
||||
"@tiptap/extension-text-align": "^2.1.12",
|
||||
"@tiptap/extension-typography": "^2.1.12",
|
||||
"@tiptap/extension-underline": "^2.1.12",
|
||||
"@tiptap/pm": "^2.1.12",
|
||||
"@tiptap/suggestion": "^2.1.12",
|
||||
"@tiptap/core": "^2.2.0",
|
||||
"@tiptap/extension-bold": "^2.2.0",
|
||||
"@tiptap/extension-bullet-list": "^2.2.0",
|
||||
"@tiptap/extension-document": "^2.2.0",
|
||||
"@tiptap/extension-gapcursor": "^2.2.0",
|
||||
"@tiptap/extension-heading": "^2.2.0",
|
||||
"@tiptap/extension-highlight": "^2.2.0",
|
||||
"@tiptap/extension-history": "^2.2.0",
|
||||
"@tiptap/extension-italic": "^2.2.0",
|
||||
"@tiptap/extension-link": "^2.2.0",
|
||||
"@tiptap/extension-list-item": "^2.2.0",
|
||||
"@tiptap/extension-mention": "^2.2.0",
|
||||
"@tiptap/extension-ordered-list": "^2.2.0",
|
||||
"@tiptap/extension-paragraph": "^2.2.0",
|
||||
"@tiptap/extension-strike": "^2.2.0",
|
||||
"@tiptap/extension-text": "^2.2.0",
|
||||
"@tiptap/extension-text-align": "^2.2.0",
|
||||
"@tiptap/extension-typography": "^2.2.0",
|
||||
"@tiptap/extension-underline": "^2.2.0",
|
||||
"@tiptap/pm": "^2.2.0",
|
||||
"@tiptap/suggestion": "^2.2.0",
|
||||
"@tmcw/togeojson": "^5.6.0",
|
||||
"chartkick": "^5.0.1",
|
||||
"core-js": "^3.31.0",
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TagsButtonListComponentPreview < ViewComponent::Preview
|
||||
include TagsSubstitutionConcern
|
||||
|
||||
def default
|
||||
render(TagsButtonListComponent.new(tags:
|
||||
{
|
||||
individual: TagsSubstitutionConcern::INDIVIDUAL_TAGS,
|
||||
etablissement: TagsSubstitutionConcern::ENTREPRISE_TAGS,
|
||||
dossier: TagsSubstitutionConcern::DOSSIER_TAGS,
|
||||
champ_public: [
|
||||
{
|
||||
id: 'tdc12',
|
||||
libelle: 'Votre avis',
|
||||
description: 'Détaillez votre avis'
|
||||
},
|
||||
{
|
||||
id: 'tdc13',
|
||||
libelle: 'Votre avis très ' + 'long ' * 12,
|
||||
description: 'Ce libellé a été tronqué'
|
||||
}
|
||||
],
|
||||
|
||||
champ_private: [
|
||||
{
|
||||
id: 'tdc22',
|
||||
libelle: 'Montant accordé'
|
||||
}
|
||||
]
|
||||
}))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,216 @@
|
|||
describe Administrateurs::AttestationTemplateV2sController, type: :controller do
|
||||
let(:admin) { create(:administrateur) }
|
||||
let(:attestation_template) { build(:attestation_template, :v2) }
|
||||
let!(:procedure) { create(:procedure, administrateur: admin, attestation_template: attestation_template, libelle: "Ma démarche") }
|
||||
let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') }
|
||||
let(:signature) { fixture_file_upload('spec/fixtures/files/black.png', 'image/png') }
|
||||
|
||||
let(:update_params) do
|
||||
{
|
||||
official_layout: true,
|
||||
label_logo: "Ministère des specs",
|
||||
label_direction: "RSPEC",
|
||||
footer: "en bas",
|
||||
activated: false,
|
||||
tiptap_body: {
|
||||
type: :doc,
|
||||
content: [
|
||||
{
|
||||
type: :paragraph,
|
||||
content: [{ text: "Yo from spec", type: :text }]
|
||||
}
|
||||
]
|
||||
}.to_json
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(admin.user)
|
||||
Flipper.enable(:attestation_v2)
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
subject do
|
||||
get :show, params: { procedure_id: procedure.id }
|
||||
response.body
|
||||
end
|
||||
|
||||
context 'if an attestation template exists on the procedure' do
|
||||
render_views
|
||||
|
||||
context 'with preview dossier' do
|
||||
let!(:dossier) { create(:dossier, :en_construction, procedure:, for_procedure_preview: true) }
|
||||
|
||||
it do
|
||||
is_expected.to include("Mon titre pour Ma démarche")
|
||||
is_expected.to include("n° #{dossier.id}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'without preview dossier' do
|
||||
it do
|
||||
is_expected.to include("Mon titre pour --dossier_procedure_libelle--")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with logo label' do
|
||||
it do
|
||||
is_expected.to include("Ministère des devs")
|
||||
is_expected.to match(/centered_marianne-\w+\.svg/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with label direction' do
|
||||
let(:attestation_template) { build(:attestation_template, :v2, label_direction: "calé à droite") }
|
||||
|
||||
it do
|
||||
is_expected.to include("calé à droite")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with footer' do
|
||||
let(:attestation_template) { build(:attestation_template, :v2, footer: "c'est le pied") }
|
||||
|
||||
it do
|
||||
is_expected.to include("c'est le pied")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with additional logo' do
|
||||
let(:attestation_template) { build(:attestation_template, :v2, logo:) }
|
||||
|
||||
it do
|
||||
is_expected.to include("Ministère des devs")
|
||||
is_expected.to include("white.png")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with signature' do
|
||||
let(:attestation_template) { build(:attestation_template, :v2, signature:) }
|
||||
|
||||
it do
|
||||
is_expected.to include("black.png")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET edit' do
|
||||
subject do
|
||||
get :edit, params: { procedure_id: procedure.id }
|
||||
response.body
|
||||
end
|
||||
|
||||
context 'if an attestation template does not exists yet on the procedure' do
|
||||
let(:attestation_template) { nil }
|
||||
|
||||
it 'creates new v2 attestation template' do
|
||||
subject
|
||||
expect(assigns(:attestation_template).version).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'if an attestation template already exist on v1' do
|
||||
let(:attestation_template) { build(:attestation_template, version: 1) }
|
||||
|
||||
it 'build new v2 attestation template' do
|
||||
subject
|
||||
expect(assigns(:attestation_template).version).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'if attestation template already exist on v2' do
|
||||
it 'assigns v2 attestation template' do
|
||||
subject
|
||||
expect(assigns(:attestation_template)).to eq(attestation_template)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
let(:attestation_template) { nil }
|
||||
|
||||
subject do
|
||||
post :create, params: { procedure_id: procedure.id, attestation_template: update_params }, format: :turbo_stream
|
||||
response.body
|
||||
end
|
||||
|
||||
context 'when attestation template is valid' do
|
||||
render_views
|
||||
|
||||
it "create template" do
|
||||
subject
|
||||
attestation_template = procedure.reload.attestation_template
|
||||
|
||||
expect(attestation_template.official_layout).to eq(true)
|
||||
expect(attestation_template.label_logo).to eq("Ministère des specs")
|
||||
expect(attestation_template.label_direction).to eq("RSPEC")
|
||||
expect(attestation_template.footer).to eq("en bas")
|
||||
expect(attestation_template.activated).to eq(false)
|
||||
expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body])
|
||||
|
||||
expect(response.body).to include("Formulaire enregistré")
|
||||
end
|
||||
|
||||
context "with files" do
|
||||
let(:update_params) { super().merge(logo:, signature:) }
|
||||
|
||||
it "upload files" do
|
||||
subject
|
||||
attestation_template = procedure.reload.attestation_template
|
||||
|
||||
expect(attestation_template.logo.download).to eq(logo.read)
|
||||
expect(attestation_template.signature.download).to eq(signature.read)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH update' do
|
||||
render_views
|
||||
subject do
|
||||
patch :update, params: { procedure_id: procedure.id, attestation_template: update_params }, format: :turbo_stream
|
||||
response.body
|
||||
end
|
||||
|
||||
context 'when attestation template is valid' do
|
||||
it "update template" do
|
||||
subject
|
||||
attestation_template.reload
|
||||
|
||||
expect(attestation_template.official_layout).to eq(true)
|
||||
expect(attestation_template.label_logo).to eq("Ministère des specs")
|
||||
expect(attestation_template.label_direction).to eq("RSPEC")
|
||||
expect(attestation_template.footer).to eq("en bas")
|
||||
expect(attestation_template.activated).to eq(false)
|
||||
expect(attestation_template.tiptap_body).to eq(update_params[:tiptap_body])
|
||||
|
||||
expect(response.body).to include("Formulaire enregistré")
|
||||
end
|
||||
|
||||
context "with files" do
|
||||
let(:update_params) { super().merge(logo:, signature:) }
|
||||
|
||||
it "upload files" do
|
||||
subject
|
||||
attestation_template.reload
|
||||
|
||||
expect(attestation_template.logo.download).to eq(logo.read)
|
||||
expect(attestation_template.signature.download).to eq(signature.read)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with error' do
|
||||
let(:update_params) do
|
||||
super().merge(tiptap_body: { type: :doc, content: [{ type: :mention, attrs: { id: "tdc12", label: "oops" } }] }.to_json)
|
||||
end
|
||||
|
||||
it "render error" do
|
||||
subject
|
||||
expect(response.body).to include("Formulaire en erreur")
|
||||
expect(response.body).to include('Supprimer cette balise')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -241,7 +241,7 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
|
|||
render_views
|
||||
let(:body) { "body --#{removed_type_de_champ.libelle}--" }
|
||||
|
||||
it { expect(response.body).to have_content("Le champ « Contenu de l’attestation » contient la balise \"#{removed_type_de_champ.libelle}\" qui a été supprimée mais la suppression n’est pas encore publiée. Publier la nouvelle version de la démarche et recommencer") }
|
||||
it { expect(response.body).to have_content("Le champ « Contenu de l’attestation » contient la balise \"#{removed_type_de_champ.libelle}\" qui a été supprimée dans les modifications en cours du formulaire. Supprimer cette balise ou réinitialiser les modifications du formulaire puis recommencer") }
|
||||
end
|
||||
|
||||
context 'with removed and published' do
|
||||
|
|
|
@ -2,11 +2,46 @@ FactoryBot.define do
|
|||
factory :attestation_template do
|
||||
title { 'title' }
|
||||
body { 'body' }
|
||||
json_body { nil }
|
||||
footer { 'footer' }
|
||||
activated { true }
|
||||
version { 1 }
|
||||
official_layout { true }
|
||||
label_direction { nil }
|
||||
label_logo { nil }
|
||||
association :procedure
|
||||
end
|
||||
|
||||
trait :v2 do
|
||||
version { 2 }
|
||||
body { nil }
|
||||
title { nil }
|
||||
label_logo { "Ministère des devs" }
|
||||
|
||||
json_body do
|
||||
{
|
||||
"type" => "doc",
|
||||
"content" => [
|
||||
{
|
||||
"type" => "header", "content" => [
|
||||
{ "type" => "headerColumn", "attrs" => { "textAlign" => "left" }, "content" => [{ "type" => "paragraph", "attrs" => { "textAlign" => "left" } }] },
|
||||
{ "type" => "headerColumn", "attrs" => { "textAlign" => "left" }, "content" => [{ "type" => "paragraph", "attrs" => { "textAlign" => "left" } }] }
|
||||
]
|
||||
},
|
||||
{ "type" => "title", "attrs" => { "textAlign" => "center" }, "content" => [{ "text" => "Mon titre pour ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_procedure_libelle", "label" => "libellé démarche" } }] },
|
||||
{ "type" => "paragraph", "attrs" => { "textAlign" => "left" }, "content" => [{ "text" => "Dossier: n° ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "dossier_number", "label" => "numéro du dossier" } }] },
|
||||
{
|
||||
"type" => "paragraph",
|
||||
"content" => [
|
||||
{ "text" => "Nom: ", "type" => "text" }, { "type" => "mention", "attrs" => { "id" => "individual_last_name", "label" => "prénom" } }, { "text" => " ", "type" => "text" },
|
||||
{ "type" => "mention", "attrs" => { "id" => "individual_first_name", "label" => "nom" } }, { "text" => " ", "type" => "text" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
trait :with_files do
|
||||
logo { Rack::Test::UploadedFile.new('spec/fixtures/files/logo_test_procedure.png', 'image/png') }
|
||||
signature { Rack::Test::UploadedFile.new('spec/fixtures/files/logo_test_procedure.png', 'image/png') }
|
||||
|
|
|
@ -173,5 +173,16 @@ describe AttestationTemplate, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'body v2' do
|
||||
let(:attestation) { create(:attestation_template, :v2) }
|
||||
let(:dossier) { create(:dossier, procedure: attestation.procedure, individual: build(:individual, nom: 'Doe', prenom: 'John')) }
|
||||
|
||||
it do
|
||||
body = attestation.render_attributes_for(dossier: dossier)[:body]
|
||||
expect(body).to include("Mon titre pour #{dossier.procedure.libelle}")
|
||||
expect(body).to include("Doe John")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,48 @@ describe TagsSubstitutionConcern, type: :model do
|
|||
end).new(procedure, state)
|
||||
end
|
||||
|
||||
describe 'tags_substitutions' do
|
||||
let(:individual) { nil }
|
||||
let(:etablissement) { create(:etablissement) }
|
||||
let(:dossier) { create(:dossier, :en_construction, procedure:, individual:, etablissement:) }
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:tags) { Set.new([["dossier_number", "numéro de dossier"]]) }
|
||||
|
||||
subject { template_concern.tags_substitutions(tags, dossier) }
|
||||
|
||||
context 'dossiers metadata' do
|
||||
before { travel_to(Time.zone.local(2024, 1, 15, 12)) }
|
||||
let(:tags) do
|
||||
Set.new([
|
||||
["dossier_number", "n° de dossier"],
|
||||
["dossier_depose_at", "date de dépôt"],
|
||||
["dossier_processed_at", "date d’instruction"],
|
||||
["dossier_procedure_libelle", "Nom de la démarche"],
|
||||
["tdc_123", "Un champ"]
|
||||
])
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to eq(
|
||||
"dossier_number" => dossier.id.to_s,
|
||||
"dossier_depose_at" => "15/01/2024",
|
||||
"dossier_processed_at" => "",
|
||||
"dossier_procedure_libelle" => procedure.libelle,
|
||||
"tdc_123" => "Un champ"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the dossier and the procedure has an individual' do
|
||||
let(:for_individual) { true }
|
||||
let(:individual) { Individual.create(nom: 'Adama', prenom: 'William', gender: 'M') }
|
||||
|
||||
let(:tags) { Set.new(['individual_gender', 'individual_last_name']) }
|
||||
|
||||
it { is_expected.to eq({ "individual_gender" => 'M', "individual_last_name" => "Adama" }) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'replace_tags' do
|
||||
let(:individual) { nil }
|
||||
let(:etablissement) { create(:etablissement) }
|
||||
|
@ -523,6 +565,23 @@ describe TagsSubstitutionConcern, type: :model do
|
|||
it { is_expected.to eq([["public", procedure.draft_revision.types_de_champ.first.stable_id], ['yolo']]) }
|
||||
end
|
||||
|
||||
describe 'tags_categorized' do
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ libelle: 'public' },
|
||||
{ type: :email, libelle: 'email' }
|
||||
]
|
||||
end
|
||||
|
||||
it do
|
||||
categories = template_concern.tags_categorized
|
||||
expect(categories.keys).to match([:etablissement, :dossier, :champ_public])
|
||||
expect(categories[:etablissement].map { _1[:id] }).to include("entreprise_siren")
|
||||
expect(categories[:dossier].map { _1[:id] }).to include("dossier_number")
|
||||
expect(categories[:champ_public].map { _1[:libelle] }).to match_array(["public", "email"])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'parser' do
|
||||
it do
|
||||
tokens = TagsSubstitutionConcern::TagsParser.parse("hello world --public--, --numéro du dossier--, un test--yolo-- encore du text\n---\n encore du text --- et encore du text\n--tag--")
|
||||
|
|
|
@ -1,158 +1,159 @@
|
|||
RSpec.describe TiptapService do
|
||||
let(:json) do
|
||||
{
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'header',
|
||||
content: [
|
||||
{
|
||||
type: 'headerColumn',
|
||||
content: [{ type: 'text', text: 'Left' }]
|
||||
},
|
||||
{
|
||||
type: 'headerColumn',
|
||||
content: [{ type: 'text', text: 'Right' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
content: [{ type: 'text', text: 'Title' }]
|
||||
},
|
||||
{
|
||||
type: 'title' # remained empty in editor
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: { level: 2, textAlign: 'center' },
|
||||
content: [{ type: 'text', text: 'Heading 2' }]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: { level: 3, textAlign: 'center' },
|
||||
content: [{ type: 'text', text: 'Heading 3' }]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: { level: 3 } # remained empty in editor
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: { textAlign: 'right' },
|
||||
content: [{ type: 'text', text: 'First paragraph' }]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Bonjour ',
|
||||
marks: [{ type: 'italic' }, { type: 'strike' }]
|
||||
},
|
||||
{
|
||||
type: 'mention',
|
||||
attrs: { id: 'name', label: 'Nom' },
|
||||
marks: [{ type: 'bold' }, { type: 'underline' }]
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '!',
|
||||
marks: [{ type: 'highlight' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph'
|
||||
# no content, empty line
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Item 1'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Item 2'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'orderedList',
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Item 1'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Item 2'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'footer',
|
||||
content: [{ type: 'text', text: 'Footer' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
describe '.to_html' do
|
||||
let(:json) do
|
||||
{
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'header',
|
||||
content: [
|
||||
{
|
||||
type: 'headerColumn',
|
||||
content: [{ type: 'text', text: 'Left' }]
|
||||
},
|
||||
{
|
||||
type: 'headerColumn',
|
||||
content: [{ type: 'text', text: 'Right' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'title',
|
||||
content: [{ type: 'text', text: 'Title' }]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: { textAlign: 'right' },
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello world!'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Bonjour ',
|
||||
marks: [{ type: 'italic' }, { type: 'strike' }]
|
||||
},
|
||||
{
|
||||
type: 'mention',
|
||||
attrs: { id: 'name' },
|
||||
marks: [{ type: 'bold' }, { type: 'underline' }]
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '!',
|
||||
marks: [{ type: 'highlight' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: { level: 1 },
|
||||
content: [{ type: 'text', text: 'Heading 1' }]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: { level: 2, textAlign: 'center' },
|
||||
content: [{ type: 'text', text: 'Heading 2' }]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: { level: 3 },
|
||||
content: [{ type: 'text', text: 'Heading 3' }]
|
||||
},
|
||||
{
|
||||
type: 'bulletList',
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Item 1'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Item 2'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'orderedList',
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Item 1'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Item 2'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'footer',
|
||||
content: [{ type: 'text', text: 'Footer' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
let(:tags) { { 'name' => 'Paul' } }
|
||||
let(:substitutions) { { 'name' => 'Paul' } }
|
||||
let(:html) do
|
||||
[
|
||||
'<header><div class="column">Left</div><div class="column">Right</div></header>',
|
||||
'<header><div>Left</div><div>Right</div></header>',
|
||||
'<h1>Title</h1>',
|
||||
'<p style="text-align: right">Hello world!</p>',
|
||||
'<h2 class="body-start" style="text-align: center">Heading 2</h2>',
|
||||
'<h3 style="text-align: center">Heading 3</h3>',
|
||||
'<p style="text-align: right">First paragraph</p>',
|
||||
'<p><s><em>Bonjour </em></s><u><strong>Paul</strong></u> <mark>!</mark></p>',
|
||||
'<h1>Heading 1</h1>',
|
||||
'<h2 style="text-align: center">Heading 2</h2>',
|
||||
'<h3>Heading 3</h3>',
|
||||
'<ul><li><p>Item 1</p></li><li><p>Item 2</p></li></ul>',
|
||||
'<ol><li><p>Item 1</p></li><li><p>Item 2</p></li></ol>',
|
||||
'<footer>Footer</footer>'
|
||||
|
@ -160,7 +161,35 @@ RSpec.describe TiptapService do
|
|||
end
|
||||
|
||||
it 'returns html' do
|
||||
expect(described_class.to_html(json, tags)).to eq(html)
|
||||
expect(described_class.new.to_html(json, substitutions)).to eq(html)
|
||||
end
|
||||
|
||||
context 'body start on paragraph' do
|
||||
let(:json) do
|
||||
{
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'title',
|
||||
content: [{ type: 'text', text: 'The Title' }]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'First paragraph' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'defines stat body on first paragraph' do
|
||||
expect(described_class.new.to_html(json, substitutions)).to eq("<h1>The Title</h1><p class=\"body-start\">First paragraph</p>")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#used_tags' do
|
||||
it 'returns used tags' do
|
||||
expect(described_class.new.used_tags_and_libelle_for(json)).to eq(Set.new([['name', 'Nom']]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,9 +5,6 @@ require 'selenium/webdriver'
|
|||
|
||||
def setup_driver(app, download_path, options)
|
||||
Capybara::Selenium::Driver.new(app, browser: :chrome, options:).tap do |driver|
|
||||
# Set download dir for Chrome < 77
|
||||
driver.browser.download_path = download_path
|
||||
|
||||
if ENV['MAKE_IT_SLOW'].present?
|
||||
driver.browser.network_conditions = {
|
||||
offline: false,
|
||||
|
|
|
@ -28,6 +28,8 @@ describe 'As an administrateur, I want to manage the procedure’s attestation',
|
|||
expect(page).to have_content('Désactivée')
|
||||
find_attestation_card(with_nested_selector: ".fr-badge")
|
||||
|
||||
expect(page).not_to have_content("Nouvel éditeur d’attestation")
|
||||
|
||||
# now process to enable attestation
|
||||
find_attestation_card.click
|
||||
fill_in "Titre de l’attestation", with: 'BOOM'
|
||||
|
@ -61,4 +63,99 @@ describe 'As an administrateur, I want to manage the procedure’s attestation',
|
|||
find_attestation_card(with_nested_selector: ".fr-badge")
|
||||
end
|
||||
end
|
||||
|
||||
context 'Update attestation v2' do
|
||||
before { Flipper.enable(:attestation_v2) }
|
||||
|
||||
scenario do
|
||||
visit admin_procedure_path(procedure)
|
||||
find_attestation_card(with_nested_selector: ".fr-badge")
|
||||
|
||||
find_attestation_card.click
|
||||
within(".fr-alert", text: /Nouvel éditeur/) do
|
||||
find("a").click
|
||||
end
|
||||
|
||||
expect(procedure.reload.attestation_template_v2).to be_nil
|
||||
|
||||
expect(page).to have_css("label", text: "Logo additionnel")
|
||||
|
||||
fill_in "Intitulé de votre institution", with: "System Test"
|
||||
fill_in "Intitulé de la direction", with: "The boss"
|
||||
|
||||
attestation = nil
|
||||
wait_until {
|
||||
attestation = procedure.reload.attestation_template_v2
|
||||
attestation.present?
|
||||
}
|
||||
expect(attestation.label_logo).to eq("System Test")
|
||||
expect(attestation.activated?).to be_falsey
|
||||
expect(page).to have_content("Formulaire enregistré")
|
||||
|
||||
click_on "date de décision"
|
||||
|
||||
# TODO find a way to fill in tiptap
|
||||
|
||||
attach_file('Tampon ou signature', Rails.root + 'spec/fixtures/files/white.png')
|
||||
wait_until { attestation.reload.signature.attached? }
|
||||
|
||||
fill_in "Contenu du pied de page", with: "Footer"
|
||||
|
||||
wait_until {
|
||||
body = JSON.parse(attestation.reload.tiptap_body)
|
||||
first_content = body.dig("content").first&.dig("content")&.first&.dig("content")&.first&.dig("content")
|
||||
|
||||
first_content == [
|
||||
{ "type" => "mention", "attrs" => { "id" => "dossier_processed_at", "label" => "date de décision" } }, # added by click above
|
||||
{ "type" => "text", "text" => " " },
|
||||
{ "type" => "mention", "attrs" => { "id" => "dossier_service_name", "label" => "nom du service" } } # defaut initial content
|
||||
]
|
||||
}
|
||||
|
||||
find("label", text: /à la charte de l’état/).click
|
||||
|
||||
expect(page).not_to have_css("label", text: "Logo additionnel", visible: true)
|
||||
expect(page).not_to have_css("label", text: "Intitulé du logo", visible: true)
|
||||
|
||||
attach_file('Logo', Rails.root + 'spec/fixtures/files/black.png')
|
||||
|
||||
wait_until {
|
||||
attestation.reload.logo.attached? && attestation.signature.attached? && !attestation.official_layout?
|
||||
}
|
||||
|
||||
# footer is rows-limited
|
||||
fill_in "Contenu du pied de page", with: ["line1", "line2", "line3", "line4"].join("\n")
|
||||
expect(page).to have_field("Contenu du pied de page", with: "line1\nline2\nline3line4")
|
||||
end
|
||||
|
||||
context "tag in error" do
|
||||
before do
|
||||
tdc = procedure.active_revision.add_type_de_champ(type_champ: :integer_number, libelle: 'age')
|
||||
procedure.publish_revision!
|
||||
|
||||
attestation = procedure.build_attestation_template_v2(json_body: AttestationTemplate::TIPTAP_BODY_DEFAULT, label_logo: "test")
|
||||
attestation.json_body["content"] << { type: :mention, attrs: { id: "tdc#{tdc.stable_id}", label: tdc.libelle } }
|
||||
attestation.save!
|
||||
|
||||
procedure.draft_revision.remove_type_de_champ(tdc)
|
||||
end
|
||||
|
||||
scenario do
|
||||
visit edit_admin_procedure_attestation_template_v2_path(procedure)
|
||||
expect(page).to have_content("Le champ « Contenu de l’attestation » contient la balise \"age\"")
|
||||
|
||||
click_on "date de décision"
|
||||
|
||||
expect(page).to have_content("Formulaire en erreur")
|
||||
expect(page).to have_content("Le champ « Contenu de l’attestation » contient la balise \"age\"")
|
||||
|
||||
page.execute_script("document.getElementById('attestation_template_tiptap_body').type = 'text'")
|
||||
fill_in "attestation_template[tiptap_body]", with: AttestationTemplate::TIPTAP_BODY_DEFAULT.to_json
|
||||
|
||||
expect(page).to have_content("Formulaire enregistré")
|
||||
expect(page).not_to have_content("Formulaire en erreur")
|
||||
expect(page).not_to have_content("Le champ « Contenu de l’attestation » contient la balise \"age\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
331
yarn.lock
331
yarn.lock
|
@ -2190,131 +2190,131 @@
|
|||
eventlistener-polyfill "^1.0.5"
|
||||
mutation-observer-inner-html-shim "^1.0.0"
|
||||
|
||||
"@tiptap/core@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.12.tgz#904fdf147e91b5e60561c76e7563c1b5a32f54ab"
|
||||
integrity sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==
|
||||
"@tiptap/core@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.2.0.tgz#6cad2026a0535edcea4b985595d29c9929f5548a"
|
||||
integrity sha512-ped7XlQ9k5VyE2xUwyRegn1yVF/CAsaF+riBUBJ9+71/gSo2mCZ+6gQvee+LVN1+rD1qN/vWgKhKNDVaU+VaFg==
|
||||
|
||||
"@tiptap/extension-bold@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.1.12.tgz#5dbf41105fc0fbde8adbff629312187fbebc39b0"
|
||||
integrity sha512-AZGxIxcGU1/y6V2YEbKsq6BAibL8yQrbRm6EdcBnby41vj1WziewEKswhLGmZx5IKM2r2ldxld03KlfSIlKQZg==
|
||||
"@tiptap/extension-bold@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.2.0.tgz#7d2da171a8ecb70fff0d9e5f42579ba71690684a"
|
||||
integrity sha512-GlrI0FzUSzYhXoYbctcXALbyc22uKfZ0nv1k0qTw8qkKbWsz6qT/rm+rcB2YgugW3r6cu5n5HDQbzbgjapNAEA==
|
||||
|
||||
"@tiptap/extension-bullet-list@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.12.tgz#7c905a577ce30ef2cb335870a23f9d24fd26f6aa"
|
||||
integrity sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ==
|
||||
"@tiptap/extension-bullet-list@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.2.0.tgz#8bcfe18234989d3d2409d14e828737f55bdb1a38"
|
||||
integrity sha512-V/jVw5g1c7EIKo+44Rw1WeQ9NUzXjtecnCclDALXOlhZ3Kz+a6mBOkuMSeoZhlH0sZ7Cq5u4W+64IuDOstA9yg==
|
||||
|
||||
"@tiptap/extension-document@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.1.12.tgz#e19e4716dfad60cbeb6abaf2f362fed759963529"
|
||||
integrity sha512-0QNfAkCcFlB9O8cUNSwTSIQMV9TmoEhfEaLz/GvbjwEq4skXK3bU+OQX7Ih07waCDVXIGAZ7YAZogbvrn/WbOw==
|
||||
"@tiptap/extension-document@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.2.0.tgz#b9d3133119a5c186180380afc12ee46f840ba6bf"
|
||||
integrity sha512-P21yqZP8DQQ03Q84jO4l73XPswCqjCPb318/eSdovF7m7xXcY55HkHtWU5Fvt/KJxhwvG6WWGmvoox9/bDW0DQ==
|
||||
|
||||
"@tiptap/extension-gapcursor@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.12.tgz#63844c3abd1a38af915839cf0c097b6d2e5a86fe"
|
||||
integrity sha512-zFYdZCqPgpwoB7whyuwpc8EYLYjUE5QYKb8vICvc+FraBUDM51ujYhFSgJC3rhs8EjI+8GcK8ShLbSMIn49YOQ==
|
||||
"@tiptap/extension-gapcursor@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.2.0.tgz#0b8fe3c56c9ab0661bd6b8ea7bc3d545155217d9"
|
||||
integrity sha512-FsIoLA2xC1tUCK2cw5jWOKlwYNZgex3puMRwQaZjbph2oz8Jel8SRAzAwsfoi4JkaN9TpNlRP1i00xzGGaezow==
|
||||
|
||||
"@tiptap/extension-heading@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.1.12.tgz#05ae4684d6f29ae611495ab114038e14a5d1dff6"
|
||||
integrity sha512-MoANP3POAP68Ko9YXarfDKLM/kXtscgp6m+xRagPAghRNujVY88nK1qBMZ3JdvTVN6b/ATJhp8UdrZX96TLV2w==
|
||||
"@tiptap/extension-heading@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.2.0.tgz#112e2917f11fe1f2cdc9f93a14de73c1dfba5d52"
|
||||
integrity sha512-4VwRtGDhRhUtP/c8BB6pPMS6g8PRv5cs+pYxCp466en5awSBhM4AN2cxDD6ruXP4YvNTDprky9H0IKjhs6Ym4Q==
|
||||
|
||||
"@tiptap/extension-highlight@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.1.12.tgz#184efb75238c9cbc6c18d523b735de4329f78ecc"
|
||||
integrity sha512-buen31cYPyiiHA2i0o2i/UcjRTg/42mNDCizGr1OJwvv3AELG3qOFc4Y58WJWIvWNv+1Dr4ZxHA3GNVn0ANWyg==
|
||||
"@tiptap/extension-highlight@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.2.0.tgz#d9a6290203d1886b1252c0dd2338bd5f2ba56fe4"
|
||||
integrity sha512-88W04AW8MCtoepmNn3343+VF1zBNlAY0tSfU+XqlYhhLo1IvbAYHeS7BN9XbqN8CUx1R3X5F2eUbEFsbxOAr3A==
|
||||
|
||||
"@tiptap/extension-history@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.1.12.tgz#03bcb9422e8ea2b82dc45207d1a1b0bc0241b055"
|
||||
integrity sha512-6b7UFVkvPjq3LVoCTrYZAczt5sQrQUaoDWAieVClVZoFLfjga2Fwjcfgcie8IjdPt8YO2hG/sar/c07i9vM0Sg==
|
||||
"@tiptap/extension-history@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.2.0.tgz#fcd376ec6a1f5d980dd31bfa4aff38cd0913f543"
|
||||
integrity sha512-djHQxD5KP4EI3U6cri0/wcJxyMspU1B6+UVXL1G7867JiV9mvAw/HUoZsHTOsn+kajTi8szFfl2exa6dCYVrmA==
|
||||
|
||||
"@tiptap/extension-italic@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.1.12.tgz#e99480eb77f8b4e5444fc236add8a831d5aa2343"
|
||||
integrity sha512-/XYrW4ZEWyqDvnXVKbgTXItpJOp2ycswk+fJ3vuexyolO6NSs0UuYC6X4f+FbHYL5VuWqVBv7EavGa+tB6sl3A==
|
||||
"@tiptap/extension-italic@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.2.0.tgz#82a73c8c485f7fc6141bea66464ac5b8ecc142cb"
|
||||
integrity sha512-eOesosmbf8mXCJ8E58PnuhO5gtDpviCTpi3ZaGn1yP0gLRV3wlo8wpCbxlGXLStgFT0tejN26MGbEHCJHkRkFQ==
|
||||
|
||||
"@tiptap/extension-link@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.1.12.tgz#a18f83a0b54342e6274ff9e5a5907ef7f15aa723"
|
||||
integrity sha512-Sti5hhlkCqi5vzdQjU/gbmr8kb578p+u0J4kWS+SSz3BknNThEm/7Id67qdjBTOQbwuN07lHjDaabJL0hSkzGQ==
|
||||
"@tiptap/extension-link@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.2.0.tgz#c277096854443802ea32e31a8568392ee69bc6af"
|
||||
integrity sha512-1evYv5Fjod47kobd/0RsHYyCFWrkU5IYZSIBC7bAGsnzG4fId2O5SEyn9SPsMYOVtEL2EIhiqb0i+wcwXf6jOQ==
|
||||
dependencies:
|
||||
linkifyjs "^4.1.0"
|
||||
|
||||
"@tiptap/extension-list-item@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.1.12.tgz#3eb28dc998490a98f14765783770b3cf6587d39e"
|
||||
integrity sha512-Gk7hBFofAPmNQ8+uw8w5QSsZOMEGf7KQXJnx5B022YAUJTYYxO3jYVuzp34Drk9p+zNNIcXD4kc7ff5+nFOTrg==
|
||||
"@tiptap/extension-list-item@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.2.0.tgz#3c3bf819ac4063ff10dac80cb097725fcc21d542"
|
||||
integrity sha512-st5S4z2+IXsaX9Otm+4y1NZhE/yLtfFELn5VIUX69PwnNThaN2/ioBXkO1o2ZdLex++D0oMaY5ILIW/PhCWP8Q==
|
||||
|
||||
"@tiptap/extension-mention@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.1.12.tgz#a395e7757b45630ec3047f14b0ba2dde8e1c9c93"
|
||||
integrity sha512-Nc8wFlyPp+/48IpOFPk2O3hYsF465wizcM3aihMvZM96Ahic7dvv9yVptyOfoOwgpExl2FIn1QPjRDXF60VAUg==
|
||||
"@tiptap/extension-mention@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.2.0.tgz#6eabf028d2fefc38811af21fb987f8589dd2c7bd"
|
||||
integrity sha512-fkePDuTYj+Nv99MZRUsjVeQaSsTA+qpRObftXACbxNr0Pp/UH3YF/arjv9c5bRHZhZrCy30U+QzrmXGTHDe18w==
|
||||
|
||||
"@tiptap/extension-ordered-list@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.12.tgz#f41a45bc66b4d19e379d4833f303f2e0cd6b9d60"
|
||||
integrity sha512-tF6VGl+D2avCgn9U/2YLJ8qVmV6sPE/iEzVAFZuOSe6L0Pj7SQw4K6AO640QBob/d8VrqqJFHCb6l10amJOnXA==
|
||||
"@tiptap/extension-ordered-list@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.2.0.tgz#b7fa60938ed11f597e91a8e9aaf098542d289efc"
|
||||
integrity sha512-LR+RQdv8yEqSYkTBGYuh099wr4r0QIosxfGe/ZlshwDJ4qFKVhyWSZ8qyaEiufmXDadTasj7WQvlbpVAhrVTBQ==
|
||||
|
||||
"@tiptap/extension-paragraph@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.1.12.tgz#922447b2aa1c7184787d351ceec593a74d24ed03"
|
||||
integrity sha512-hoH/uWPX+KKnNAZagudlsrr4Xu57nusGekkJWBcrb5MCDE91BS+DN2xifuhwXiTHxnwOMVFjluc0bPzQbkArsw==
|
||||
"@tiptap/extension-paragraph@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.2.0.tgz#468ed21f990a779ee49eafae808250d7186ec25e"
|
||||
integrity sha512-niuvuBEkhz9gnQvFHbxs5z044bpDXRH9zz8QW2bA8+IDSxWHfnVmSZ3AZsed7OJ4EK1AcgGxy+gFOpAcZ73XTw==
|
||||
|
||||
"@tiptap/extension-strike@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.1.12.tgz#2b049aedf2985e9c9e3c3f1cc0b203a574c85bd8"
|
||||
integrity sha512-HlhrzIjYUT8oCH9nYzEL2QTTn8d1ECnVhKvzAe6x41xk31PjLMHTUy8aYjeQEkWZOWZ34tiTmslV1ce6R3Dt8g==
|
||||
"@tiptap/extension-strike@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.2.0.tgz#6fc3e056fb4512dbdf27c645439da48879a44ee8"
|
||||
integrity sha512-AMVC94mWNCAXLyZdkB5KqLai9FMUaQDkWAZSD1DKCRq2OJeA71nW9G1eDPa2TzQezl2IVTW9mU4m37hcsTmRfg==
|
||||
|
||||
"@tiptap/extension-text-align@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-text-align/-/extension-text-align-2.1.12.tgz#962dca4d284ce57ed345fd4b94ddb3a97944e0d1"
|
||||
integrity sha512-siMlwrkgVrAxxgmZn8GOc75J7UZi2CVrP9vDHkUPPyKm/fjssYekXwGCEk4Vswii1BbOh2gt+MDsRkeYRGyDlQ==
|
||||
"@tiptap/extension-text-align@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-text-align/-/extension-text-align-2.2.0.tgz#29d4a873d678bd312933691e983c462ecf02d349"
|
||||
integrity sha512-Fub3RyQlmwB10aWzmVFqyfi9ww0uQYLag76ae/bjkkqSjHo/1ET9/9vhEqpQQVR7kAifs4CyRQguaASYSPxKYg==
|
||||
|
||||
"@tiptap/extension-text@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.1.12.tgz#466e3244bdd9b2db2304c0c9a1d51ce59f5327d0"
|
||||
integrity sha512-rCNUd505p/PXwU9Jgxo4ZJv4A3cIBAyAqlx/dtcY6cjztCQuXJhuQILPhjGhBTOLEEL4kW2wQtqzCmb7O8i2jg==
|
||||
"@tiptap/extension-text@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.2.0.tgz#92ccaf3e06cd3deddd3334293fd7f7dc8bccbd03"
|
||||
integrity sha512-h3lJRUZaUBisqUSQDEO+NU4SgKW7rj/vvbsbML8klWdEhg8U9btdv4eZgoJxbsqOdpUc6Cy6dBhre4myWI8Y2w==
|
||||
|
||||
"@tiptap/extension-typography@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-typography/-/extension-typography-2.1.12.tgz#0bf3ba500b49b4f239205e76dda512bda7d3b4f7"
|
||||
integrity sha512-OFkQHmUbKQwVO0b/NP2MLuuqQIWxw/gHaWQF/atgZf3mG0YDV2x3P/u+RBpKnsIujPZFvoEBRJGnstvEAB7zfQ==
|
||||
"@tiptap/extension-typography@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-typography/-/extension-typography-2.2.0.tgz#283c602efd738fe3156843ad46db6e3ef90af35e"
|
||||
integrity sha512-te9JQqqjZbaEBPeNJeEDiy9HU/lvlG0lCq9Zz8EnHIJG69A3rXHpwLAbGY/WiQNk/MCx6HjBeVyjRCeHvo56Cw==
|
||||
|
||||
"@tiptap/extension-underline@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-2.1.12.tgz#abd59c4b6c8434dbadb4ff9bff23eefcc6bc095e"
|
||||
integrity sha512-NwwdhFT8gDD0VUNLQx85yFBhP9a8qg8GPuxlGzAP/lPTV8Ubh3vSeQ5N9k2ZF/vHlEvnugzeVCbmYn7wf8vn1g==
|
||||
"@tiptap/extension-underline@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-2.2.0.tgz#7ad7734f15a07268627fd4c9e90f5ff43dc110a3"
|
||||
integrity sha512-y+D9gUWa/sVeCftZBMCEpcD1tD4OgNXfjsS7/qxv6ge9t0HCRhAJfHWm6rX8P7QybjacinZuun/T6CudRdGbMg==
|
||||
|
||||
"@tiptap/pm@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.1.12.tgz#88a4b19be0eabb13d42ddd540c19ba1bbe74b322"
|
||||
integrity sha512-Q3MXXQABG4CZBesSp82yV84uhJh/W0Gag6KPm2HRWPimSFELM09Z9/5WK9RItAYE0aLhe4Krnyiczn9AAa1tQQ==
|
||||
"@tiptap/pm@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.2.0.tgz#18131f41220a87816f389db71de80d58a8de65a3"
|
||||
integrity sha512-CL/ys9rvUgYcRHyeQFuIQdw09+0LUgKAfYWzCr6Pu4DDdbRooiex/a9M29imnRMEgS9SwuHN2v17fKHH0w65Hg==
|
||||
dependencies:
|
||||
prosemirror-changeset "^2.2.0"
|
||||
prosemirror-collab "^1.3.0"
|
||||
prosemirror-commands "^1.3.1"
|
||||
prosemirror-dropcursor "^1.5.0"
|
||||
prosemirror-gapcursor "^1.3.1"
|
||||
prosemirror-history "^1.3.0"
|
||||
prosemirror-inputrules "^1.2.0"
|
||||
prosemirror-keymap "^1.2.0"
|
||||
prosemirror-markdown "^1.10.1"
|
||||
prosemirror-menu "^1.2.1"
|
||||
prosemirror-model "^1.18.1"
|
||||
prosemirror-schema-basic "^1.2.0"
|
||||
prosemirror-schema-list "^1.2.2"
|
||||
prosemirror-state "^1.4.1"
|
||||
prosemirror-tables "^1.3.0"
|
||||
prosemirror-trailing-node "^2.0.2"
|
||||
prosemirror-transform "^1.7.0"
|
||||
prosemirror-view "^1.28.2"
|
||||
prosemirror-changeset "^2.2.1"
|
||||
prosemirror-collab "^1.3.1"
|
||||
prosemirror-commands "^1.5.2"
|
||||
prosemirror-dropcursor "^1.8.1"
|
||||
prosemirror-gapcursor "^1.3.2"
|
||||
prosemirror-history "^1.3.2"
|
||||
prosemirror-inputrules "^1.3.0"
|
||||
prosemirror-keymap "^1.2.2"
|
||||
prosemirror-markdown "^1.12.0"
|
||||
prosemirror-menu "^1.2.4"
|
||||
prosemirror-model "^1.19.4"
|
||||
prosemirror-schema-basic "^1.2.2"
|
||||
prosemirror-schema-list "^1.3.0"
|
||||
prosemirror-state "^1.4.3"
|
||||
prosemirror-tables "^1.3.5"
|
||||
prosemirror-trailing-node "^2.0.7"
|
||||
prosemirror-transform "^1.8.0"
|
||||
prosemirror-view "^1.32.7"
|
||||
|
||||
"@tiptap/suggestion@^2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.1.12.tgz#a13782d1e625ec03b3f61b6839ecc95b6b685d3f"
|
||||
integrity sha512-rhlLWwVkOodBGRMK0mAmE34l2a+BqM2Y7q1ViuQRBhs/6sZ8d83O4hARHKVwqT5stY4i1l7d7PoemV3uAGI6+g==
|
||||
"@tiptap/suggestion@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.2.0.tgz#620bf053d1ee3ac4063ccbef555cc8564beeb195"
|
||||
integrity sha512-xcjGEVgRB0tx21LdNTr5F/HEKNlFuq9cnJlVGDLlNP43ixXM6aODAeY3VvEZ3XtEtLpHsiCVrinrjRNtpUf+eQ==
|
||||
|
||||
"@tmcw/togeojson@^5.6.0":
|
||||
version "5.6.0"
|
||||
|
@ -2465,14 +2465,14 @@
|
|||
integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
|
||||
|
||||
"@types/object.omit@^3.0.0":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/object.omit/-/object.omit-3.0.1.tgz#1b9de058cf94344b9284308a41b17e3a356ed18e"
|
||||
integrity sha512-24XD34UeRWw505TsMNBrQ4bES2s8IxiFC59mmNUFhTz9IX2hAtA7gQ8wVww1i17QmhBYILg5iqYP2y7aqA3pwQ==
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/object.omit/-/object.omit-3.0.3.tgz#cc52b1d9774c1619b5c6fc50229d087f01eabd68"
|
||||
integrity sha512-xrq4bQTBGYY2cw+gV4PzoG2Lv3L0pjZ1uXStRRDQoATOYW1lCsFQHhQ+OkPhIcQoqLjAq7gYif7D14Qaa6Zbew==
|
||||
|
||||
"@types/object.pick@^1.3.2":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/object.pick/-/object.pick-1.3.2.tgz#9eb28118240ad8f658b9c9c6caf35359fdb37150"
|
||||
integrity sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg==
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/object.pick/-/object.pick-1.3.4.tgz#1a38b6e69a35f36ec2dcc8b9f5ffd555c1c4d7fc"
|
||||
integrity sha512-5PjwB0uP2XDp3nt5u5NJAG2DORHIRClPzWT/TTZhJ2Ekwe8M5bA9tvPdi9NO/n2uvu2/ictat8kgqvLfcIE1SA==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.4"
|
||||
|
@ -3877,11 +3877,6 @@ entities@~2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
entities@~3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
|
||||
integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==
|
||||
|
||||
error-ex@^1.3.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||
|
@ -5937,12 +5932,12 @@ linkify-it@^3.0.1:
|
|||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
linkify-it@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec"
|
||||
integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==
|
||||
linkify-it@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421"
|
||||
integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==
|
||||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
uc.micro "^2.0.0"
|
||||
|
||||
linkifyjs@^4.1.0:
|
||||
version "4.1.2"
|
||||
|
@ -6118,16 +6113,17 @@ markdown-it@^12.2.0:
|
|||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
|
||||
markdown-it@^13.0.1:
|
||||
version "13.0.2"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.2.tgz#1bc22e23379a6952e5d56217fbed881e0c94d536"
|
||||
integrity sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==
|
||||
markdown-it@^14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.0.0.tgz#b4b2ddeb0f925e88d981f84c183b59bac9e3741b"
|
||||
integrity sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
entities "~3.0.1"
|
||||
linkify-it "^4.0.1"
|
||||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
entities "^4.4.0"
|
||||
linkify-it "^5.0.0"
|
||||
mdurl "^2.0.0"
|
||||
punycode.js "^2.3.1"
|
||||
uc.micro "^2.0.0"
|
||||
|
||||
marked@^4.0.12:
|
||||
version "4.3.0"
|
||||
|
@ -6157,6 +6153,11 @@ mdurl@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
|
||||
integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==
|
||||
|
||||
mdurl@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0"
|
||||
integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==
|
||||
|
||||
meow@^10.1.3:
|
||||
version "10.1.3"
|
||||
resolved "https://registry.yarnpkg.com/meow/-/meow-10.1.3.tgz#21689959a7d00e8901aff30d208acb2122eb8088"
|
||||
|
@ -6997,21 +6998,21 @@ prop-types@^15.8.1:
|
|||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
prosemirror-changeset@^2.2.0:
|
||||
prosemirror-changeset@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz#dae94b63aec618fac7bb9061648e6e2a79988383"
|
||||
integrity sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==
|
||||
dependencies:
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-collab@^1.3.0:
|
||||
prosemirror-collab@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33"
|
||||
integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==
|
||||
dependencies:
|
||||
prosemirror-state "^1.0.0"
|
||||
|
||||
prosemirror-commands@^1.0.0, prosemirror-commands@^1.3.1:
|
||||
prosemirror-commands@^1.0.0, prosemirror-commands@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz#e94aeea52286f658cd984270de9b4c3fff580852"
|
||||
integrity sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==
|
||||
|
@ -7020,7 +7021,7 @@ prosemirror-commands@^1.0.0, prosemirror-commands@^1.3.1:
|
|||
prosemirror-state "^1.0.0"
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-dropcursor@^1.5.0:
|
||||
prosemirror-dropcursor@^1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz#49b9fb2f583e0d0f4021ff87db825faa2be2832d"
|
||||
integrity sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==
|
||||
|
@ -7029,7 +7030,7 @@ prosemirror-dropcursor@^1.5.0:
|
|||
prosemirror-transform "^1.1.0"
|
||||
prosemirror-view "^1.1.0"
|
||||
|
||||
prosemirror-gapcursor@^1.3.1:
|
||||
prosemirror-gapcursor@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz#5fa336b83789c6199a7341c9493587e249215cb4"
|
||||
integrity sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==
|
||||
|
@ -7039,7 +7040,7 @@ prosemirror-gapcursor@^1.3.1:
|
|||
prosemirror-state "^1.0.0"
|
||||
prosemirror-view "^1.0.0"
|
||||
|
||||
prosemirror-history@^1.0.0, prosemirror-history@^1.3.0:
|
||||
prosemirror-history@^1.0.0, prosemirror-history@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.3.2.tgz#ce6ad7ab9db83e761aee716f3040d74738311b15"
|
||||
integrity sha512-/zm0XoU/N/+u7i5zepjmZAEnpvjDtzoPWW6VmKptcAnPadN/SStsBjMImdCEbb3seiNTpveziPTIrXQbHLtU1g==
|
||||
|
@ -7049,15 +7050,15 @@ prosemirror-history@^1.0.0, prosemirror-history@^1.3.0:
|
|||
prosemirror-view "^1.31.0"
|
||||
rope-sequence "^1.3.0"
|
||||
|
||||
prosemirror-inputrules@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.2.1.tgz#8faf3d78c16150aedac71d326a3e3947417ce557"
|
||||
integrity sha512-3LrWJX1+ULRh5SZvbIQlwZafOXqp1XuV21MGBu/i5xsztd+9VD15x6OtN6mdqSFI7/8Y77gYUbQ6vwwJ4mr6QQ==
|
||||
prosemirror-inputrules@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz#ef1519bb2cb0d1e0cec74bad1a97f1c1555068bb"
|
||||
integrity sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==
|
||||
dependencies:
|
||||
prosemirror-state "^1.0.0"
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.0:
|
||||
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz#14a54763a29c7b2704f561088ccf3384d14eb77e"
|
||||
integrity sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==
|
||||
|
@ -7065,15 +7066,15 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.0:
|
|||
prosemirror-state "^1.0.0"
|
||||
w3c-keyname "^2.2.0"
|
||||
|
||||
prosemirror-markdown@^1.10.1:
|
||||
version "1.11.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.11.2.tgz#f6e529e669d11fa3eec859e93c0d2c91788d6c80"
|
||||
integrity sha512-Eu5g4WPiCdqDTGhdSsG9N6ZjACQRYrsAkrF9KYfdMaCmjIApH75aVncsWYOJvEk2i1B3i8jZppv3J/tnuHGiUQ==
|
||||
prosemirror-markdown@^1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.12.0.tgz#d2de09d37897abf7adb6293d925ff132dac5b0a6"
|
||||
integrity sha512-6F5HS8Z0HDYiS2VQDZzfZP6A0s/I0gbkJy8NCzzDMtcsz3qrfqyroMMeoSjAmOhDITyon11NbXSzztfKi+frSQ==
|
||||
dependencies:
|
||||
markdown-it "^13.0.1"
|
||||
markdown-it "^14.0.0"
|
||||
prosemirror-model "^1.0.0"
|
||||
|
||||
prosemirror-menu@^1.2.1:
|
||||
prosemirror-menu@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz#3cfdc7c06d10f9fbd1bce29082c498bd11a0a79a"
|
||||
integrity sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==
|
||||
|
@ -7083,21 +7084,21 @@ prosemirror-menu@^1.2.1:
|
|||
prosemirror-history "^1.0.0"
|
||||
prosemirror-state "^1.0.0"
|
||||
|
||||
prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.19.0, prosemirror-model@^1.8.1:
|
||||
version "1.19.3"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.3.tgz#f0d55285487fefd962d0ac695f716f4ec6705006"
|
||||
integrity sha512-tgSnwN7BS7/UM0sSARcW+IQryx2vODKX4MI7xpqY2X+iaepJdKBPc7I4aACIsDV/LTaTjt12Z56MhDr9LsyuZQ==
|
||||
prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.19.0, prosemirror-model@^1.19.4, prosemirror-model@^1.8.1:
|
||||
version "1.19.4"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.4.tgz#e45e84480c97dd3922095dbe579e1c98c86c0704"
|
||||
integrity sha512-RPmVXxUfOhyFdayHawjuZCxiROsm9L4FCUA6pWI+l7n2yCBsWy9VpdE1hpDHUS8Vad661YLY9AzqfjLhAKQ4iQ==
|
||||
dependencies:
|
||||
orderedmap "^2.0.0"
|
||||
|
||||
prosemirror-schema-basic@^1.2.0:
|
||||
prosemirror-schema-basic@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz#6695f5175e4628aab179bf62e5568628b9cfe6c7"
|
||||
integrity sha512-/dT4JFEGyO7QnNTe9UaKUhjDXbTNkiWTq/N4VpKaF79bBjSExVV2NXmJpcM7z/gD7mbqNjxbmWW5nf1iNSSGnw==
|
||||
dependencies:
|
||||
prosemirror-model "^1.19.0"
|
||||
|
||||
prosemirror-schema-list@^1.2.2:
|
||||
prosemirror-schema-list@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.3.0.tgz#05374702cf35a3ba5e7ec31079e355a488d52519"
|
||||
integrity sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==
|
||||
|
@ -7106,7 +7107,7 @@ prosemirror-schema-list@^1.2.2:
|
|||
prosemirror-state "^1.0.0"
|
||||
prosemirror-transform "^1.7.3"
|
||||
|
||||
prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.1:
|
||||
prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.3.tgz#94aecf3ffd54ec37e87aa7179d13508da181a080"
|
||||
integrity sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==
|
||||
|
@ -7115,10 +7116,10 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, pr
|
|||
prosemirror-transform "^1.0.0"
|
||||
prosemirror-view "^1.27.0"
|
||||
|
||||
prosemirror-tables@^1.3.0:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.3.4.tgz#0b7cc16d49f90c5b834c9f29291c545478ce9ab0"
|
||||
integrity sha512-z6uLSQ1BLC3rgbGwZmpfb+xkdvD7W/UOsURDfognZFYaTtc0gsk7u/t71Yijp2eLflVpffMk6X0u0+u+MMDvIw==
|
||||
prosemirror-tables@^1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.3.5.tgz#80f03394f5b9991f9693bcb3a90b6dba6b16254d"
|
||||
integrity sha512-JSZ2cCNlApu/ObAhdPyotrjBe2cimniniTpz60YXzbL0kZ+47nEYk2LWbfKU2lKpBkUNquta2PjteoNi4YCluQ==
|
||||
dependencies:
|
||||
prosemirror-keymap "^1.1.2"
|
||||
prosemirror-model "^1.8.1"
|
||||
|
@ -7126,7 +7127,7 @@ prosemirror-tables@^1.3.0:
|
|||
prosemirror-transform "^1.2.1"
|
||||
prosemirror-view "^1.13.3"
|
||||
|
||||
prosemirror-trailing-node@^2.0.2:
|
||||
prosemirror-trailing-node@^2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.7.tgz#ba782a7929f18bcae650b1c7082a2d10443eab19"
|
||||
integrity sha512-8zcZORYj/8WEwsGo6yVCRXFMOfBo0Ub3hCUvmoWIZYfMP26WqENU0mpEP27w7mt8buZWuGrydBewr0tOArPb1Q==
|
||||
|
@ -7135,17 +7136,17 @@ prosemirror-trailing-node@^2.0.2:
|
|||
"@remirror/core-helpers" "^3.0.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
|
||||
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1, prosemirror-transform@^1.7.0, prosemirror-transform@^1.7.3:
|
||||
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1, prosemirror-transform@^1.7.3, prosemirror-transform@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.8.0.tgz#a47c64a3c373c1bd0ff46e95be3210c8dda0cd11"
|
||||
integrity sha512-BaSBsIMv52F1BVVMvOmp1yzD3u65uC3HTzCBQV1WDPqJRQ2LuHKcyfn0jwqodo8sR9vVzMzZyI+Dal5W9E6a9A==
|
||||
dependencies:
|
||||
prosemirror-model "^1.0.0"
|
||||
|
||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.28.2, prosemirror-view@^1.31.0:
|
||||
version "1.32.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.32.1.tgz#bcd0877f1673ffe5f94c1e966b6fbdadcd2d5bbf"
|
||||
integrity sha512-9SnB4HBgRczzTyIMZLPE1iszegL04hNfUyS8uPtP1RPxNM2NTCiIs8KwNsJU4nbZO9rxJTwVTv7Jm3zU4CR78A==
|
||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.32.7:
|
||||
version "1.32.7"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.32.7.tgz#b9c4e8471daeba79489befa59eaeaeb4cd2e2653"
|
||||
integrity sha512-pvxiOoD4shW41X5bYDjRQk3DSG4fMqxh36yPMt7VYgU3dWRmqFzWJM/R6zeo1KtC8nyk717ZbQND3CC9VNeptw==
|
||||
dependencies:
|
||||
prosemirror-model "^1.16.0"
|
||||
prosemirror-state "^1.0.0"
|
||||
|
@ -7166,6 +7167,11 @@ psl@^1.1.33:
|
|||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
|
||||
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
|
||||
|
||||
punycode.js@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7"
|
||||
integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==
|
||||
|
||||
punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
|
@ -8404,6 +8410,11 @@ uc.micro@^1.0.1, uc.micro@^1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
||||
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
|
||||
|
||||
uc.micro@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.0.0.tgz#84b3c335c12b1497fd9e80fcd3bfa7634c363ff1"
|
||||
integrity sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==
|
||||
|
||||
ufo@^1.3.0:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496"
|
||||
|
|
Loading…
Reference in a new issue