react-bootstrap-front 3.10.2 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG +9 -0
  2. package/dist/react-bootstrap-front.js +14 -14
  3. package/doc/editor/feuille-de-route.md +36 -0
  4. package/package.json +42 -8
  5. package/src/features/advanced/DropdownWrapper.js +12 -2
  6. package/src/features/basic/Dropdown.js +112 -31
  7. package/src/features/basic/computePlacement.js +66 -0
  8. package/src/features/editor/HtmlEditor.js +681 -0
  9. package/src/features/editor/HtmlEditor.scss +189 -0
  10. package/src/features/editor/ImageDialog.js +195 -0
  11. package/src/features/editor/LinkDialog.js +148 -0
  12. package/src/features/editor/MergeMenu.js +120 -0
  13. package/src/features/editor/SourceView.js +61 -0
  14. package/src/features/editor/Toolbar.js +355 -0
  15. package/src/features/editor/Toolbar.scss +130 -0
  16. package/src/features/editor/ToolbarButton.js +59 -0
  17. package/src/features/editor/ToolbarDropdown.js +46 -0
  18. package/src/features/editor/ToolbarGroup.js +27 -0
  19. package/src/features/editor/buildExtensions.js +77 -0
  20. package/src/features/editor/defaultIcons.js +45 -0
  21. package/src/features/editor/extensions/CleanPaste.js +44 -0
  22. package/src/features/editor/extensions/FontSize.js +60 -0
  23. package/src/features/editor/extensions/LegacyImageAttrs.js +43 -0
  24. package/src/features/editor/extensions/MergeVariable.js +104 -0
  25. package/src/features/editor/extensions/index.js +4 -0
  26. package/src/features/editor/html/cleanHtml.constants.js +279 -0
  27. package/src/features/editor/html/cleanHtml.js +691 -0
  28. package/src/features/editor/html/index.js +4 -0
  29. package/src/features/editor/html/normalizeLegacyHtml.js +196 -0
  30. package/src/features/editor/html/serialize.js +130 -0
  31. package/src/features/editor/html/sourceHelpers.js +86 -0
  32. package/src/features/editor/index.js +20 -0
  33. package/src/features/editor/style.scss +2 -0
  34. package/src/features/editor/toolbarPresets.js +74 -0
  35. package/src/features/page/DefaultHeader.jsx +1 -1
  36. package/src/index.js +8 -0
  37. package/src/styles/index.scss +1 -0
@@ -0,0 +1,77 @@
1
+ import StarterKit from '@tiptap/starter-kit';
2
+ import Underline from '@tiptap/extension-underline';
3
+ import Subscript from '@tiptap/extension-subscript';
4
+ import Superscript from '@tiptap/extension-superscript';
5
+ import TextStyle from '@tiptap/extension-text-style';
6
+ import Color from '@tiptap/extension-color';
7
+ import FontFamily from '@tiptap/extension-font-family';
8
+ import TextAlign from '@tiptap/extension-text-align';
9
+ import Link from '@tiptap/extension-link';
10
+ import Image from '@tiptap/extension-image';
11
+ import Placeholder from '@tiptap/extension-placeholder';
12
+ import { FontSize, LegacyImageAttrs, CleanPaste, MergeVariable, collectMergeCodes } from './extensions';
13
+
14
+ /**
15
+ * Jeu d'extensions de l'éditeur.
16
+ *
17
+ * Le schéma est délibérément restreint à ce que tolère un client de messagerie
18
+ * ancien : p, h1-h3, strong, em, u, s, sub, sup, a, ul, ol, li, blockquote, hr,
19
+ * img, br et span[style]. Ni `<pre>` ni `<code>` : ils ne servent à rien dans
20
+ * un courriel et produisent une mise en forme que les clients ignorent.
21
+ */
22
+ export function buildExtensions(props) {
23
+ const options = props || {};
24
+ return [
25
+ StarterKit.configure({
26
+ heading: { levels: [1, 2, 3] },
27
+ codeBlock: false,
28
+ code: false,
29
+ history: { depth: 100 },
30
+ dropcursor: { width: 2 },
31
+ }),
32
+ Underline,
33
+ Subscript,
34
+ Superscript,
35
+ /* TextStyle porte color, font-family et font-size : les trois attributs se
36
+ combinent dans un seul <span>, au lieu de trois imbriqués. */
37
+ TextStyle,
38
+ Color,
39
+ FontFamily.configure({ types: ['textStyle'] }),
40
+ FontSize,
41
+ TextAlign.configure({
42
+ types: ['heading', 'paragraph'],
43
+ alignments: ['left', 'center', 'right', 'justify'],
44
+ }),
45
+ /* target et rel explicitement à null, et non un objet vide : les options
46
+ sont FUSIONNÉES avec les défauts de l'extension, qui posent
47
+ target="_blank" rel="noopener noreferrer nofollow" sur tout lien. Un
48
+ objet vide laisserait donc ces défauts réécrire l'intégralité des liens
49
+ de la base au premier enregistrement. La cible se choisit dans la boîte
50
+ de dialogue, lien par lien. */
51
+ Link.configure({
52
+ openOnClick: false,
53
+ autolink: true,
54
+ linkOnPaste: true,
55
+ HTMLAttributes: { target: null, rel: null, class: null },
56
+ }),
57
+ Image.configure({
58
+ inline: false,
59
+ allowBase64: true,
60
+ }),
61
+ LegacyImageAttrs,
62
+ Placeholder.configure({ placeholder: options.placeholder || '' }),
63
+ /* knownCodes n'est renseigné qu'en mode strict : tant que le catalogue est
64
+ tenu à la main côté application, il est forcément incomplet, et signaler
65
+ en rouge une variable parfaitement valide serait pire qu'inutile. À
66
+ basculer le jour où le catalogue vient de l'API et fait autorité. */
67
+ MergeVariable.configure({
68
+ knownCodes: options.strictMergeVars ? collectMergeCodes(options.mergeVars) : null,
69
+ }),
70
+ CleanPaste.configure({
71
+ enabled: options.cleanOnPaste !== false,
72
+ profile: options.pasteProfile || 'paste',
73
+ }),
74
+ ];
75
+ }
76
+
77
+ export default buildExtensions;
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Repli textuel des icônes de la barre d'outils.
5
+ *
6
+ * La librairie ne dépend d'aucune bibliothèque d'icônes : l'application
7
+ * fournit les siennes via la prop `icons`, un objet plat de nœuds React —
8
+ * le motif déjà employé partout ici (`clearIcon`, `toolbarIcon`, `sortUpIcon`…).
9
+ * Ce repli garantit qu'un éditeur reste utilisable même sans icônes fournies.
10
+ */
11
+ export const defaultIcons = {
12
+ bold: <b>B</b>,
13
+ italic: <i>I</i>,
14
+ underline: <u>U</u>,
15
+ strike: <s>S</s>,
16
+ subscript: <span>x₂</span>,
17
+ superscript: <span>x²</span>,
18
+ paragraph: <span>¶</span>,
19
+ heading1: <span>H1</span>,
20
+ heading2: <span>H2</span>,
21
+ heading3: <span>H3</span>,
22
+ bulletList: <span>•</span>,
23
+ orderedList: <span>1.</span>,
24
+ blockquote: <span>❝</span>,
25
+ horizontalRule: <span>―</span>,
26
+ alignLeft: <span>⇤</span>,
27
+ alignCenter: <span>↔</span>,
28
+ alignRight: <span>⇥</span>,
29
+ alignJustify: <span>≡</span>,
30
+ color: <span>A</span>,
31
+ fontSize: <span>Aa</span>,
32
+ fontFamily: <span>F</span>,
33
+ link: <span>↗</span>,
34
+ unlink: <span>⊘</span>,
35
+ image: <span>▣</span>,
36
+ mergeVar: <span>{'{ }'}</span>,
37
+ clean: <span>≀</span>,
38
+ formatClear: <span>Ø</span>,
39
+ source: <span>{'</>'}</span>,
40
+ undo: <span>↶</span>,
41
+ redo: <span>↷</span>,
42
+ more: <span>…</span>,
43
+ };
44
+
45
+ export default defaultIcons;
@@ -0,0 +1,44 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import { Plugin, PluginKey } from '@tiptap/pm/state';
3
+ import { cleanHtml } from '../html/cleanHtml';
4
+
5
+ /**
6
+ * Nettoyage automatique du collage.
7
+ *
8
+ * C'est la vraie réponse au « je dois souvent nettoyer des spans, couleur,
9
+ * font, taille inutiles » : le HTML de Word, d'Outlook ou de Google Docs
10
+ * n'entre jamais dans le document. Rien à cliquer, rien à penser.
11
+ *
12
+ * Le profil PASTE conserve la mise en forme voulue (couleurs, tailles
13
+ * explicites) et ne retire que le bruit. Le bouton « Nettoyer » reste là pour
14
+ * aller plus loin à la demande.
15
+ */
16
+ export const CleanPaste = Extension.create({
17
+ name: 'cleanPaste',
18
+
19
+ addOptions() {
20
+ return {
21
+ enabled: true,
22
+ profile: 'paste',
23
+ };
24
+ },
25
+
26
+ addProseMirrorPlugins() {
27
+ const { enabled, profile } = this.options;
28
+ if (!enabled) {
29
+ return [];
30
+ }
31
+ return [
32
+ new Plugin({
33
+ key: new PluginKey('cleanPaste'),
34
+ props: {
35
+ transformPastedHTML(html) {
36
+ return cleanHtml(html, profile);
37
+ },
38
+ },
39
+ }),
40
+ ];
41
+ },
42
+ });
43
+
44
+ export default CleanPaste;
@@ -0,0 +1,60 @@
1
+ import { Extension } from '@tiptap/core';
2
+
3
+ /**
4
+ * Taille de police.
5
+ *
6
+ * TipTap 2 ne fournit pas cette extension — `@tiptap/extension-font-size`
7
+ * n'existe qu'à partir de la v3. Sans elle, toutes les tailles des contenus
8
+ * déjà en base (`<span style="font-size:14px">`, écrit par l'ancien éditeur)
9
+ * seraient silencieusement perdues à la première ouverture.
10
+ *
11
+ * L'attribut se greffe sur la marque `textStyle`, comme le font `Color` et
12
+ * `FontFamily` : les trois se combinent alors dans un seul `<span>`.
13
+ */
14
+ export const FontSize = Extension.create({
15
+ name: 'fontSize',
16
+
17
+ addOptions() {
18
+ return {
19
+ types: ['textStyle'],
20
+ };
21
+ },
22
+
23
+ addGlobalAttributes() {
24
+ return [
25
+ {
26
+ types: this.options.types,
27
+ attributes: {
28
+ fontSize: {
29
+ default: null,
30
+ parseHTML: element => element.style.fontSize || null,
31
+ renderHTML: attributes => {
32
+ if (!attributes.fontSize) {
33
+ return {};
34
+ }
35
+ return { style: 'font-size: ' + attributes.fontSize };
36
+ },
37
+ },
38
+ },
39
+ },
40
+ ];
41
+ },
42
+
43
+ addCommands() {
44
+ return {
45
+ setFontSize: size => ({ chain }) => {
46
+ return chain()
47
+ .setMark('textStyle', { fontSize: size })
48
+ .run();
49
+ },
50
+ unsetFontSize: () => ({ chain }) => {
51
+ return chain()
52
+ .setMark('textStyle', { fontSize: null })
53
+ .removeEmptyTextStyle()
54
+ .run();
55
+ },
56
+ };
57
+ },
58
+ });
59
+
60
+ export default FontSize;
@@ -0,0 +1,43 @@
1
+ import { Extension } from '@tiptap/core';
2
+
3
+ /**
4
+ * Dimensions des images.
5
+ *
6
+ * `@tiptap/extension-image` ne relit ni `width` ni `height` : une image de la
7
+ * base écrite `<img src="…" height="120" width="200">` ressortirait sans ses
8
+ * dimensions et casserait la mise en page du courriel.
9
+ *
10
+ * Les attributs sont volontairement rendus en attributs HTML et non en style :
11
+ * c'est la seule forme que comprennent les clients de messagerie anciens.
12
+ */
13
+ export const LegacyImageAttrs = Extension.create({
14
+ name: 'legacyImageAttrs',
15
+
16
+ addOptions() {
17
+ return {
18
+ types: ['image'],
19
+ };
20
+ },
21
+
22
+ addGlobalAttributes() {
23
+ return [
24
+ {
25
+ types: this.options.types,
26
+ attributes: {
27
+ width: {
28
+ default: null,
29
+ parseHTML: element => element.getAttribute('width') || null,
30
+ renderHTML: attributes => (attributes.width ? { width: attributes.width } : {}),
31
+ },
32
+ height: {
33
+ default: null,
34
+ parseHTML: element => element.getAttribute('height') || null,
35
+ renderHTML: attributes => (attributes.height ? { height: attributes.height } : {}),
36
+ },
37
+ },
38
+ },
39
+ ];
40
+ },
41
+ });
42
+
43
+ export default LegacyImageAttrs;
@@ -0,0 +1,104 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import { Plugin, PluginKey } from '@tiptap/pm/state';
3
+ import { Decoration, DecorationSet } from '@tiptap/pm/view';
4
+
5
+ /**
6
+ * Mise en évidence des variables de fusion `[[:objet.champ:]]`.
7
+ *
8
+ * Le choix structurant : ce sont de simples **décorations**, pas des nœuds ni
9
+ * des marques. Le HTML stocké reste donc rigoureusement identique à celui
10
+ * qu'écrivait l'ancien éditeur — le backend PHP continue son `str_replace`,
11
+ * aucune migration de données, aucun changement côté serveur.
12
+ *
13
+ * L'apport réel est la variante « inconnue » : un `[[:cli.fullname:]]` mal
14
+ * orthographié se voit à la frappe, alors qu'aujourd'hui l'erreur ne se
15
+ * découvre qu'à l'envoi du courriel.
16
+ */
17
+ export const MERGE_VARIABLE_PATTERN = /\[\[:([a-zA-Z0-9_.\-]+):\]\]/g;
18
+
19
+ function buildDecorations(doc, knownCodes) {
20
+ const decorations = [];
21
+ doc.descendants((node, pos) => {
22
+ if (!node.isText || !node.text) {
23
+ return;
24
+ }
25
+ MERGE_VARIABLE_PATTERN.lastIndex = 0;
26
+ let match = MERGE_VARIABLE_PATTERN.exec(node.text);
27
+ while (match !== null) {
28
+ const known = !knownCodes || knownCodes.indexOf(match[0]) !== -1;
29
+ decorations.push(
30
+ Decoration.inline(pos + match.index, pos + match.index + match[0].length, {
31
+ class: known ? 'rbf-mergevar' : 'rbf-mergevar rbf-mergevar-unknown',
32
+ title: match[1],
33
+ }),
34
+ );
35
+ match = MERGE_VARIABLE_PATTERN.exec(node.text);
36
+ }
37
+ });
38
+ return DecorationSet.create(doc, decorations);
39
+ }
40
+
41
+ export const MergeVariable = Extension.create({
42
+ name: 'mergeVariable',
43
+
44
+ addOptions() {
45
+ return {
46
+ /** Liste plate des codes connus, ou null pour ne rien signaler comme inconnu. */
47
+ knownCodes: null,
48
+ };
49
+ },
50
+
51
+ addProseMirrorPlugins() {
52
+ const knownCodes = this.options.knownCodes;
53
+ const key = new PluginKey('mergeVariable');
54
+ return [
55
+ new Plugin({
56
+ key,
57
+ state: {
58
+ init(config, state) {
59
+ return buildDecorations(state.doc, knownCodes);
60
+ },
61
+ apply(transaction, previous) {
62
+ return transaction.docChanged ? buildDecorations(transaction.doc, knownCodes) : previous;
63
+ },
64
+ },
65
+ props: {
66
+ decorations(state) {
67
+ return key.getState(state);
68
+ },
69
+ },
70
+ }),
71
+ ];
72
+ },
73
+ });
74
+
75
+ /**
76
+ * Aplatit une structure `mergeVars` groupée en simple liste de codes.
77
+ * Tolère l'ancien format plat `[{ text_code, text_content }]` de `presetTexts`.
78
+ */
79
+ export function collectMergeCodes(mergeVars) {
80
+ if (!mergeVars || !Array.isArray(mergeVars) || mergeVars.length === 0) {
81
+ return null;
82
+ }
83
+ const codes = [];
84
+ for (let i = 0; i < mergeVars.length; i++) {
85
+ const entry = mergeVars[i];
86
+ if (!entry) {
87
+ continue;
88
+ }
89
+ if (Array.isArray(entry.items)) {
90
+ for (let j = 0; j < entry.items.length; j++) {
91
+ if (entry.items[j] && entry.items[j].code) {
92
+ codes.push(entry.items[j].code);
93
+ }
94
+ }
95
+ } else if (entry.code) {
96
+ codes.push(entry.code);
97
+ } else if (entry.text_code) {
98
+ codes.push(entry.text_code);
99
+ }
100
+ }
101
+ return codes.length > 0 ? codes : null;
102
+ }
103
+
104
+ export default MergeVariable;
@@ -0,0 +1,4 @@
1
+ export { FontSize } from './FontSize';
2
+ export { LegacyImageAttrs } from './LegacyImageAttrs';
3
+ export { CleanPaste } from './CleanPaste';
4
+ export { MergeVariable, collectMergeCodes, MERGE_VARIABLE_PATTERN } from './MergeVariable';
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Profils et listes blanches du nettoyeur HTML.
3
+ *
4
+ * Trois profils, du plus doux au plus agressif :
5
+ *
6
+ * - LOAD : à l'ouverture d'un contenu existant. Supprime uniquement le bruit
7
+ * indiscutable (scripts, styles, balises Office). Ne retire JAMAIS
8
+ * de mise en forme : on ne modifie pas la base sans demande.
9
+ * - PASTE : à chaque collage. Supprime tout le bruit Word / Google Docs /
10
+ * Outlook, mais conserve les couleurs, polices et tailles voulues.
11
+ * - STRICT : le bouton « Nettoyer ». PASTE + suppression des couleurs,
12
+ * polices, tailles, interlignes et espacements.
13
+ */
14
+
15
+ /**
16
+ * Balises supprimées avec leur contenu.
17
+ */
18
+ export const DROP_TAGS = [
19
+ 'script',
20
+ 'style',
21
+ 'link',
22
+ 'meta',
23
+ 'base',
24
+ 'title',
25
+ 'head',
26
+ 'noscript',
27
+ 'iframe',
28
+ 'frame',
29
+ 'frameset',
30
+ 'object',
31
+ 'embed',
32
+ 'applet',
33
+ 'param',
34
+ 'form',
35
+ 'input',
36
+ 'button',
37
+ 'select',
38
+ 'option',
39
+ 'optgroup',
40
+ 'textarea',
41
+ 'audio',
42
+ 'video',
43
+ 'source',
44
+ 'track',
45
+ 'canvas',
46
+ 'svg',
47
+ 'math',
48
+ 'template',
49
+ 'xml',
50
+ ];
51
+
52
+ /**
53
+ * Balises conservées. Tout ce qui n'est ni ici ni dans DROP_TAGS est déballé
54
+ * (l'élément disparaît, ses enfants sont remontés) : le texte n'est jamais perdu.
55
+ *
56
+ * Le jeu correspond à ce que tolère un client de messagerie ancien. Les tableaux
57
+ * en sont volontairement absents : le schéma de l'éditeur ne les gère pas plus
58
+ * que l'ancien, les déballer préserve au moins le texte.
59
+ */
60
+ export const ALLOWED_TAGS = [
61
+ 'p',
62
+ 'br',
63
+ 'hr',
64
+ 'h1',
65
+ 'h2',
66
+ 'h3',
67
+ 'h4',
68
+ 'h5',
69
+ 'h6',
70
+ 'strong',
71
+ 'em',
72
+ 'u',
73
+ 's',
74
+ 'sub',
75
+ 'sup',
76
+ 'span',
77
+ 'a',
78
+ 'ul',
79
+ 'ol',
80
+ 'li',
81
+ 'blockquote',
82
+ 'img',
83
+ ];
84
+
85
+ /**
86
+ * Balises historiques remplacées par leur équivalent moderne.
87
+ */
88
+ export const TAG_ALIASES = {
89
+ b: 'strong',
90
+ i: 'em',
91
+ strike: 's',
92
+ del: 's',
93
+ ins: 'u',
94
+ cite: 'em',
95
+ big: 'span',
96
+ small: 'span',
97
+ };
98
+
99
+ /**
100
+ * Balises vides conservées : elles portent du sens sans contenir de texte.
101
+ */
102
+ export const KEEP_EMPTY_TAGS = ['br', 'img', 'hr', 'td', 'th'];
103
+
104
+ /**
105
+ * Attributs conservés, par balise. La clé '*' s'applique à toutes.
106
+ */
107
+ export const ALLOWED_ATTRIBUTES = {
108
+ '*': ['style'],
109
+ a: ['href', 'title', 'target', 'rel', 'name'],
110
+ img: ['src', 'alt', 'title', 'width', 'height'],
111
+ ol: ['start', 'type'],
112
+ li: ['value'],
113
+ };
114
+
115
+ /**
116
+ * Schémas d'URL autorisés. Un href hors liste perd son attribut, un src hors
117
+ * liste fait disparaître l'élément (on ne garde pas une image inerte).
118
+ */
119
+ export const ALLOWED_HREF_SCHEMES = ['http:', 'https:', 'mailto:', 'tel:', 'ftp:'];
120
+ export const ALLOWED_SRC_SCHEMES = ['http:', 'https:'];
121
+
122
+ /**
123
+ * Propriétés CSS conservées. Tout le reste saute, `mso-*` compris.
124
+ */
125
+ export const ALLOWED_STYLES = [
126
+ 'color',
127
+ 'background-color',
128
+ 'font-family',
129
+ 'font-size',
130
+ 'font-weight',
131
+ 'font-style',
132
+ 'text-decoration',
133
+ 'text-decoration-line',
134
+ 'text-align',
135
+ 'vertical-align',
136
+ ];
137
+
138
+ /**
139
+ * Propriétés supprimées en plus par le profil STRICT : c'est exactement ce que
140
+ * l'utilisateur veut faire sauter d'un clic.
141
+ */
142
+ export const STRICT_DROP_STYLES = [
143
+ 'color',
144
+ 'background-color',
145
+ 'font-family',
146
+ 'font-size',
147
+ 'line-height',
148
+ 'letter-spacing',
149
+ 'word-spacing',
150
+ 'text-transform',
151
+ ];
152
+
153
+ /**
154
+ * Valeurs neutres : présentes, elles n'apportent rien et alourdissent le HTML.
155
+ * Comparaison faite sur la valeur normalisée (minuscules, espaces compactés).
156
+ */
157
+ export const NEUTRAL_STYLE_VALUES = {
158
+ color: ['inherit', 'initial', 'unset', 'currentcolor', 'windowtext', 'black', '#000', '#000000', 'rgb(0,0,0)'],
159
+ 'background-color': [
160
+ 'inherit',
161
+ 'initial',
162
+ 'unset',
163
+ 'transparent',
164
+ 'none',
165
+ 'white',
166
+ '#fff',
167
+ '#ffffff',
168
+ 'rgb(255,255,255)',
169
+ ],
170
+ 'font-weight': ['normal', '400', 'inherit', 'initial', 'unset'],
171
+ 'font-style': ['normal', 'inherit', 'initial', 'unset'],
172
+ 'text-decoration': ['none', 'inherit', 'initial', 'unset'],
173
+ 'text-decoration-line': ['none', 'inherit', 'initial', 'unset'],
174
+ 'text-align': ['inherit', 'initial', 'unset', 'start'],
175
+ 'vertical-align': ['baseline', 'inherit', 'initial', 'unset'],
176
+ 'font-family': ['inherit', 'initial', 'unset'],
177
+ 'font-size': ['inherit', 'initial', 'unset', 'medium'],
178
+ };
179
+
180
+ /**
181
+ * Tailles de police par défaut des traitements de texte : les retirer évite de
182
+ * figer tout un contenu collé sur la taille de Word plutôt que celle du gabarit.
183
+ */
184
+ export const DEFAULT_FONT_SIZES = ['11pt', '12pt', '10pt', '1rem', '1em', '16px', '14.6667px', '14.667px'];
185
+
186
+ /**
187
+ * Polices par défaut des traitements de texte, normalisées (guillemets retirés,
188
+ * minuscules, espaces compactés).
189
+ */
190
+ export const DEFAULT_FONT_FAMILIES = [
191
+ 'calibri,sans-serif',
192
+ 'calibri',
193
+ 'times new roman,serif',
194
+ 'times new roman',
195
+ 'aptos,sans-serif',
196
+ 'aptos',
197
+ 'arial,sans-serif',
198
+ 'arial,helvetica,sans-serif',
199
+ 'docs-calibri',
200
+ ];
201
+
202
+ const BASE_OPTIONS = {
203
+ dropTags: DROP_TAGS,
204
+ allowedTags: ALLOWED_TAGS,
205
+ tagAliases: TAG_ALIASES,
206
+ keepEmptyTags: KEEP_EMPTY_TAGS,
207
+ allowedAttributes: ALLOWED_ATTRIBUTES,
208
+ allowedHrefSchemes: ALLOWED_HREF_SCHEMES,
209
+ allowedSrcSchemes: ALLOWED_SRC_SCHEMES,
210
+ allowDataImages: true,
211
+ allowedStyles: ALLOWED_STYLES,
212
+ dropStyles: [],
213
+ neutralStyleValues: NEUTRAL_STYLE_VALUES,
214
+ defaultFontSizes: DEFAULT_FONT_SIZES,
215
+ defaultFontFamilies: DEFAULT_FONT_FAMILIES,
216
+ /** Classes conservées. Vide par défaut : c'est ce qui fait sauter les MsoNormal. */
217
+ keepClasses: [],
218
+ /** Remplace les <div> par des <p> lorsque le contexte le permet. */
219
+ convertDivToP: true,
220
+ /** Déballe les <span> qui ne portent plus aucun attribut. */
221
+ unwrapEmptySpans: true,
222
+ /** Supprime les éléments devenus vides. */
223
+ removeEmptyElements: true,
224
+ /** Fusionne les marques identiques imbriquées. */
225
+ mergeNestedMarks: true,
226
+ /** Compacte les espaces insécables en série et les blancs multiples. */
227
+ normalizeWhitespace: true,
228
+ /** Remonte les paragraphes imbriqués et supprime les <br> superflus. */
229
+ normalizeBlocks: true,
230
+ /** Nombre maximal de passes de purge des éléments vides. */
231
+ maxPasses: 5,
232
+ };
233
+
234
+ /**
235
+ * Profil LOAD — ouverture d'un contenu existant.
236
+ *
237
+ * Volontairement minimal : scripts, styles, balises Office et gestionnaires
238
+ * d'événements partent, rien d'autre. On ne modifie jamais la mise en forme
239
+ * d'un contenu que l'utilisateur n'a pas demandé à nettoyer, sinon ouvrir puis
240
+ * enregistrer un formulaire réécrirait la base en silence.
241
+ */
242
+ export const CLEAN_LOAD = {
243
+ ...BASE_OPTIONS,
244
+ allowedTags: null,
245
+ tagAliases: null,
246
+ allowedAttributes: null,
247
+ keepClasses: null,
248
+ convertDivToP: false,
249
+ unwrapEmptySpans: false,
250
+ removeEmptyElements: false,
251
+ mergeNestedMarks: false,
252
+ normalizeWhitespace: false,
253
+ normalizeBlocks: false,
254
+ allowedStyles: null,
255
+ neutralStyleValues: null,
256
+ defaultFontSizes: [],
257
+ defaultFontFamilies: [],
258
+ };
259
+
260
+ /**
261
+ * Profil PASTE — collage. Le bruit part, la mise en forme voulue reste.
262
+ */
263
+ export const CLEAN_PASTE = {
264
+ ...BASE_OPTIONS,
265
+ };
266
+
267
+ /**
268
+ * Profil STRICT — bouton « Nettoyer ».
269
+ */
270
+ export const CLEAN_STRICT = {
271
+ ...BASE_OPTIONS,
272
+ dropStyles: STRICT_DROP_STYLES,
273
+ };
274
+
275
+ export const CLEAN_PROFILES = {
276
+ load: CLEAN_LOAD,
277
+ paste: CLEAN_PASTE,
278
+ strict: CLEAN_STRICT,
279
+ };