data-primals-engine 1.2.0 → 1.2.2
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.
- package/README.md +78 -14
- package/client/src/App.scss +11 -6
- package/client/src/ConditionBuilder2.jsx +47 -6
- package/client/src/DataLayout.jsx +4 -21
- package/client/src/DataTable.jsx +3 -1
- package/client/src/Field.jsx +1 -1
- package/client/src/ModelCreator.jsx +4 -4
- package/client/src/ModelCreator.scss +10 -0
- package/client/src/ModelCreatorField.jsx +34 -34
- package/client/src/RTE.jsx +2 -1
- package/client/src/RestoreConfirmationModal.jsx +0 -137
- package/client/src/filter.js +2 -0
- package/client/src/hooks/useTutorials.jsx +3 -3
- package/package.json +4 -3
- package/src/core.js +6 -0
- package/src/data.js +4 -4
- package/src/defaultModels.js +33 -0
- package/src/email.js +2 -2
- package/src/engine.js +10 -7
- package/src/events.js +25 -3
- package/src/modules/bucket.js +115 -5
- package/src/modules/data.js +166 -93
- package/src/modules/file.js +98 -57
- package/src/modules/test +147 -0
- package/src/modules/user.js +111 -56
- package/test/data.backup.integration.test.js +1 -1
- package/test/events.test.js +202 -0
- package/test/file.test.js +196 -0
- package/test/user.test.js +203 -0
package/README.md
CHANGED
|
@@ -9,19 +9,27 @@
|
|
|
9
9
|
|
|
10
10
|
> Whether you're building a CRM, e-commerce site, CMS, or SaaS platform, **data-primals-engine** provides the strong foundations so you can focus on what makes your application unique.
|
|
11
11
|
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://data.primals.net/prez1.jpg" target="_blank"><img alt="Light" src="https://data.primals.net/prez1.jpg" width="35%"></a>
|
|
14
|
+
<a href="https://data.primals.net/prez6.jpg" target="_blank"><img alt="Light" src="https://data.primals.net/prez6.jpg" width="35%"></a>
|
|
15
|
+
<a href="https://data.primals.net/api-docs" target="_blank"><img alt="Dark" src="https://data.primals.net/prez5.jpg" width="25%"></a>
|
|
16
|
+
</p>
|
|
12
17
|
---
|
|
13
18
|
|
|
14
19
|
## 🚀 Key Features
|
|
15
20
|
|
|
16
|
-
- **Dynamic
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
21
|
+
- **Dynamic Data Modeling**: Define and update schemas using JSON, no migrations required.
|
|
22
|
+
- **Custom API Endpoints**: Create server-side logic and new API endpoints directly from the UI in a secure, sandboxed environment.
|
|
23
|
+
- **Automation Workflows**: Trigger complex actions based on data events (create, update, delete) or schedules (cron).
|
|
24
|
+
- **Advanced Querying & Aggregation**: Go beyond simple filters with deep relation expansion, complex lookups, and dynamic calculated fields.
|
|
25
|
+
- **Integrated Backup & Restore**: Secure, encrypted user data backups with rotation policies, supporting both local and AWS S3 storage.
|
|
26
|
+
- **Event-Driven & Extensible**: A core event system allows for deep customization and the easy creation of new modules or plugins.
|
|
27
|
+
- **Authentication & Authorization**: Robust role-based access control (RBAC) and pluggable user providers.
|
|
28
|
+
- **Built-in File Management**: Handle file uploads seamlessly with integrated support for AWS S3 storage.
|
|
29
|
+
- **🧠 AI Integration**: Natively supports OpenAI and Google Gemini models via LangChain for content generation, analysis, and more.
|
|
30
|
+
- **🌐 Internationalization (i18n)**: Fully supports multilingual interfaces and user-specific translated data.
|
|
31
|
+
- **📦 Starter Packs**: Quickly bootstrap applications with pre-built data packs for CRM, e-commerce, and more.
|
|
32
|
+
- **📄Auto-Generated API Documentation**: Interactive API documentation available via the interface or at `/api-docs`.
|
|
25
33
|
|
|
26
34
|
|
|
27
35
|
## 🌟 Why Choose data-primals-engine?
|
|
@@ -85,8 +93,8 @@ MONGO_DB_URL=mongodb://127.0.0.1:27017
|
|
|
85
93
|
| SMTP_USER | Username for SMTP authentication. | user@example.com |
|
|
86
94
|
| SMTP_PASS | Password for SMTP authentication. | password |
|
|
87
95
|
| TLS | Encrypted connection (TLS) mode. Disabled by default | 0/1 false/true |
|
|
88
|
-
|
|
|
89
|
-
|
|
|
96
|
+
| CA_CERT | Path to CA cert file. | certs/ca.crt |
|
|
97
|
+
| CERT | Path to cert file. | certs/cert.pem |
|
|
90
98
|
| CERT_KEY | Path to the key file for your certificate. | certs/key.pem |
|
|
91
99
|
|
|
92
100
|
### Start the server
|
|
@@ -168,12 +176,11 @@ Activatable features:
|
|
|
168
176
|
### 🎫 Support Ticket System
|
|
169
177
|
- Create ticket model with [open, pending, resolved] statuses
|
|
170
178
|
- Configure notification workflows
|
|
171
|
-
- Add custom endpoints for analytics
|
|
179
|
+
- Add custom endpoints or dashboards/kpi for analytics
|
|
172
180
|
|
|
173
181
|
### 🤖 AI Chatbot
|
|
174
182
|
- Define your model
|
|
175
183
|
- Set up workflow: "When new entry → generate AI content"
|
|
176
|
-
- Connect to frontend chat interface
|
|
177
184
|
|
|
178
185
|
---
|
|
179
186
|
|
|
@@ -599,11 +606,68 @@ Expected response :
|
|
|
599
606
|
},
|
|
600
607
|
"publishedPosts": 15
|
|
601
608
|
}
|
|
609
|
+
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
## Extensibility
|
|
614
|
+
|
|
615
|
+
### Events (Triggers) Table
|
|
616
|
+
> You can use the events below to access the engine and manipulate API responses.
|
|
617
|
+
> It is useful for custom modules or middlewares for your application.
|
|
618
|
+
|
|
619
|
+
Just use
|
|
620
|
+
|
|
621
|
+
```javascript
|
|
622
|
+
Event.Listen("OnDataAdded", (data) => {
|
|
623
|
+
my_callback()
|
|
624
|
+
}, "event", "user");
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
or the system version
|
|
628
|
+
```javascript
|
|
629
|
+
Event.Listen("OnDataAdded", (engine, data) => {
|
|
630
|
+
my_callback()
|
|
631
|
+
}, "event", "system");
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
| Event | Description | Scope | Triggered by | Arguments (Payload) |
|
|
635
|
+
| :--- |:------------------------------------------------------------------------| :--- | :--- | :--- |
|
|
636
|
+
| OnServerStart | Triggered once the HTTP server is started and listening. | System | engine.start() | engine |
|
|
637
|
+
| OnServerStop | Triggered right after the HTTP server is stopped. | System | engine.stop() | engine |
|
|
638
|
+
| OnModelsLoaded | Triggered after the initial models are loaded and validated at startup. | System | setupInitialModels() | engine, dbModels |
|
|
639
|
+
| OnModelsDeleted | Triggered after all models are deleted via the reset function. | System | engine.resetModels() | engine |
|
|
640
|
+
| OnUserDataDumped | Triggered after a user's data has been backed up (dumped). | System | jobDumpUserData() | engine |
|
|
641
|
+
| OnDataRestored | Triggered after a user's data has been restored from a backup. | System | loadFromDump() | (none) |
|
|
642
|
+
| OnPackInstalled | Triggered after a data pack has been successfully installed. | System | installPack() | pack |
|
|
643
|
+
| OnModelEdited | Triggered after a model definition has been modified. | System & User | editModel() | System: engine, newModel (Pipeline*)<br>User: newModel (or the version modified by the system) |
|
|
644
|
+
| OnDataAdded | Triggered after new data has been inserted. | System & User | insertData() | System: engine, insertedIds (Pipeline*)<br>User: insertedIds (or the version modified by the system) |
|
|
645
|
+
| OnDataDeleted | Triggered just after data is actually deleted. | System & User | deleteData() | System: engine, {model, filter} (Pipeline*)<br>User: {model, filter} |
|
|
646
|
+
| OnDataSearched | Triggered after a data search. | System & User | searchData() | System: engine, {data, count} (Pipeline*)<br>User: {data, count} (or the version modified by the system) |
|
|
647
|
+
| OnDataExported | Triggered after a data export. | System & User | exportData() | System: engine, exportResults, modelsToExport (Pipeline*)<br>User: exportResults, modelsToExport (or the version modified by the system) |
|
|
648
|
+
|
|
649
|
+
### Triggering events
|
|
650
|
+
|
|
651
|
+
If you want to provide your own hooks, you can call :
|
|
652
|
+
```javascript
|
|
653
|
+
const result = Event.Trigger("OnMyCustomEvent", "event", "user", ...args);
|
|
602
654
|
```
|
|
655
|
+
Results are merged together if multiple events are triggered.
|
|
656
|
+
- strings are concatenated
|
|
657
|
+
- numbers are added
|
|
658
|
+
- booleans are ANDed
|
|
659
|
+
- arrays are concatenated
|
|
660
|
+
- objects are merged using spread operator
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
603
664
|
## 🤝 Contributing
|
|
604
665
|
|
|
666
|
+
Find the issues available for [contributions here](https://github.com/anonympins/data-primals-engine/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22%20no%3Aassignee)
|
|
667
|
+
|
|
605
668
|
1. Fork the repo
|
|
606
669
|
2. Create your feature branch: `git checkout -b feature/your-feature`
|
|
670
|
+
3. Launch ```npm run dev``` and make your changes with hot-reload on local port
|
|
607
671
|
3. Commit changes: `git commit -m "Add new feature"`
|
|
608
672
|
4. Push to your branch: `git push origin feature/your-feature`
|
|
609
673
|
5. Open a pull request
|
|
@@ -617,4 +681,4 @@ Distributed under the **MIT License**. See `LICENSE` file.
|
|
|
617
681
|
|
|
618
682
|
---
|
|
619
683
|
|
|
620
|
-
## [🔼](#) Back to Top
|
|
684
|
+
## [🔼](https://github.com/anonympins/data-primals-engine?tab=readme-ov-file#data-primals-engine) Back to Top
|
package/client/src/App.scss
CHANGED
|
@@ -1083,11 +1083,15 @@ h2.shadow {
|
|
|
1083
1083
|
}
|
|
1084
1084
|
|
|
1085
1085
|
.condition-details {
|
|
1086
|
-
padding-left:
|
|
1086
|
+
padding-left: 4px;
|
|
1087
|
+
margin-left:2px;
|
|
1088
|
+
@media only screen and (min-width: 480px) {
|
|
1089
|
+
padding-left: 20px; // Indentation
|
|
1090
|
+
margin-left: 10px;
|
|
1091
|
+
margin-top: 5px;
|
|
1092
|
+
margin-bottom: 10px;
|
|
1093
|
+
}
|
|
1087
1094
|
border-left: 2px solid #eee; // Ligne visuelle
|
|
1088
|
-
margin-left: 10px;
|
|
1089
|
-
margin-top: 5px;
|
|
1090
|
-
margin-bottom: 10px;
|
|
1091
1095
|
gap: 5px; // Espacement entre les éléments de condition
|
|
1092
1096
|
}
|
|
1093
1097
|
|
|
@@ -1290,9 +1294,10 @@ select[multiple]{
|
|
|
1290
1294
|
}
|
|
1291
1295
|
|
|
1292
1296
|
.datalayout {
|
|
1293
|
-
|
|
1297
|
+
@media only screen and (min-width: 480px) {
|
|
1298
|
+
padding: 0 16px;
|
|
1299
|
+
}
|
|
1294
1300
|
}
|
|
1295
|
-
|
|
1296
1301
|
.main-menu {
|
|
1297
1302
|
padding: 16px 0;
|
|
1298
1303
|
@media only screen and (max-width: 479px) {
|
|
@@ -8,6 +8,30 @@ import {FaRepeat} from "react-icons/fa6";
|
|
|
8
8
|
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
// Déterminer si le champ doit être une date
|
|
12
|
+
const isDateArg = (operator, argIndex) => {
|
|
13
|
+
if (!operator) return false;
|
|
14
|
+
|
|
15
|
+
const opConfig = MONGO_CALC_OPERATORS[operator];
|
|
16
|
+
if (!opConfig) {
|
|
17
|
+
console.warn(`Opérateur non trouvé: ${operator}`);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Cas spécial pour les opérateurs de date
|
|
22
|
+
if (operator === '$dateAdd' || operator === '$dateSubtract') {
|
|
23
|
+
return argIndex === 0; // Seul le premier argument est une date
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Pour les autres opérateurs marqués isDate (comme $hour, $second, etc.)
|
|
27
|
+
if (opConfig.isDate) {
|
|
28
|
+
return true; // Tous les arguments sont des dates pour ces opérateurs
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
|
|
11
35
|
const OperatorSelector = ({ onSelect }) => {
|
|
12
36
|
const [search, setSearch] = useState('');
|
|
13
37
|
|
|
@@ -117,10 +141,12 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
|
|
|
117
141
|
setEditing(false);
|
|
118
142
|
};
|
|
119
143
|
|
|
144
|
+
console.log(path);
|
|
145
|
+
console.log(isDateArg(path[path.length - 1]));
|
|
120
146
|
|
|
121
147
|
const renderArgument = (arg, index, parentOperator, args) => {
|
|
122
148
|
const isSimpleValue = typeof arg !== 'object' || arg === null || Array.isArray(arg);
|
|
123
|
-
|
|
149
|
+
const parentOpConfig = MONGO_CALC_OPERATORS[parentOperator];
|
|
124
150
|
// Fonction pour mettre à jour cet argument spécifique
|
|
125
151
|
const handleArgChange = (newArgValue) => {
|
|
126
152
|
const updatedArgs = [...args];
|
|
@@ -148,8 +174,6 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
|
|
|
148
174
|
onChange({ [parentOperator]: updatedArgs }, path);
|
|
149
175
|
}
|
|
150
176
|
};
|
|
151
|
-
const parentOpConfig = MONGO_CALC_OPERATORS[parentOperator];
|
|
152
|
-
|
|
153
177
|
return (
|
|
154
178
|
<div key={`arg-${index}`} className="expression-arg">
|
|
155
179
|
<div className="arg-content">
|
|
@@ -163,10 +187,11 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
|
|
|
163
187
|
updatedArgs[index] = newValue;
|
|
164
188
|
onChange({ [parentOperator]: updatedArgs }, path);
|
|
165
189
|
}}
|
|
190
|
+
isDate={isDateArg(path[path.length - 1], 0)} // Utilis
|
|
166
191
|
fieldNames={fieldNames}
|
|
167
192
|
isInFindContext={isInFindContext}
|
|
168
193
|
/>
|
|
169
|
-
{!parentOpConfig?.disableAdvancedValue && (<button
|
|
194
|
+
{!parentOpConfig?.disableAdvancedValue && !isDateArg(path[path.length -1]) && (<button
|
|
170
195
|
type="button"
|
|
171
196
|
className="switch-to-expr"
|
|
172
197
|
onClick={() => handleArgChange({ $eq: [arg, ""] })}
|
|
@@ -281,6 +306,7 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
|
|
|
281
306
|
onChange={handleFieldChange}
|
|
282
307
|
fieldNames={fieldNames}
|
|
283
308
|
isFieldName={true}
|
|
309
|
+
isDate={isDateArg(path[path.length - 1])}
|
|
284
310
|
currentModelFields={currentModelFields} // Passer les champs complets
|
|
285
311
|
onlyRelations={true}
|
|
286
312
|
/>
|
|
@@ -607,12 +633,13 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
|
|
|
607
633
|
}}
|
|
608
634
|
fieldNames={fieldNames}
|
|
609
635
|
isInFindContext={isInFindContext}
|
|
636
|
+
isDate={isDateArg(path[path.length - 1])}
|
|
610
637
|
currentModelFields={currentModelFields} // On propage le contexte
|
|
611
638
|
/>
|
|
612
639
|
|
|
613
640
|
{/* --- MODIFICATION --- */}
|
|
614
641
|
{/* On affiche le bouton de conversion uniquement si l'opérateur parent le permet */}
|
|
615
|
-
{parentOpConfig?.disableAdvancedValue !== true && (
|
|
642
|
+
{parentOpConfig?.disableAdvancedValue !== true && !isDateArg(path[path.length - 1]) && (
|
|
616
643
|
<button
|
|
617
644
|
type="button"
|
|
618
645
|
className="switch-to-expr"
|
|
@@ -637,11 +664,13 @@ const FieldInput = ({
|
|
|
637
664
|
isFieldName = false,
|
|
638
665
|
isInFindContext = false,
|
|
639
666
|
currentModelFields = [],
|
|
640
|
-
onlyRelations = false
|
|
667
|
+
onlyRelations = false,
|
|
668
|
+
isDate = false
|
|
641
669
|
}) => {
|
|
642
670
|
const [inputValue, setInputValue] = useState(value);
|
|
643
671
|
const [suggestions, setSuggestions] = useState([]);
|
|
644
672
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
673
|
+
const [showDatePicker, setShowDatePicker] = useState(false);
|
|
645
674
|
|
|
646
675
|
useEffect(() => {
|
|
647
676
|
setInputValue(value);
|
|
@@ -685,6 +714,18 @@ const FieldInput = ({
|
|
|
685
714
|
setShowSuggestions(false);
|
|
686
715
|
};
|
|
687
716
|
|
|
717
|
+
// Si c'est un champ date, on affiche le datepicker
|
|
718
|
+
if (isDate) {
|
|
719
|
+
return (
|
|
720
|
+
<div className="date-input-container">
|
|
721
|
+
<input
|
|
722
|
+
type="datetime-local"
|
|
723
|
+
value={inputValue ? new Date(inputValue).toISOString().slice(0, 16) : ''}
|
|
724
|
+
onChange={(e) => handleDateChange(e.target.value)}
|
|
725
|
+
/>
|
|
726
|
+
</div>
|
|
727
|
+
);
|
|
728
|
+
}
|
|
688
729
|
return (
|
|
689
730
|
<div className="field-input-container">
|
|
690
731
|
<input
|
|
@@ -2,35 +2,19 @@ import React, {forwardRef, useCallback, useEffect, useMemo, useReducer, useRef,
|
|
|
2
2
|
|
|
3
3
|
import "./App.scss";
|
|
4
4
|
import {useMutation, useQuery, useQueryClient} from "react-query";
|
|
5
|
-
import {CheckboxField, FileField, NumberField, SelectField, TextField} from "./Field.jsx";
|
|
6
5
|
import ModelCreator from "./ModelCreator.jsx";
|
|
7
6
|
import {useModelContext} from "./contexts/ModelContext.jsx";
|
|
8
|
-
import {FaMagnifyingGlass, FaPencil} from "react-icons/fa6";
|
|
9
7
|
import {Dialog, DialogProvider} from "./Dialog.jsx";
|
|
10
8
|
import {Pagination} from "./Pagination.jsx";
|
|
9
|
+
import {Event} from "../../src/events.js";
|
|
10
|
+
|
|
11
11
|
import {
|
|
12
|
-
elementsPerPage, kilobytes,
|
|
13
|
-
mainFieldsTypes,
|
|
14
|
-
maxBytesPerSecondThrottleData,
|
|
15
|
-
maxFileSize,
|
|
16
|
-
maxRequestData, metaModels
|
|
17
|
-
} from "../../src/constants.js";
|
|
18
|
-
import {
|
|
19
|
-
FaArrowDown,
|
|
20
|
-
FaArrowUp, FaBell,
|
|
21
|
-
FaBook, FaCopy,
|
|
22
|
-
FaEdit,
|
|
23
|
-
FaFileExport, FaFileImport,
|
|
24
12
|
FaFilter, FaInfo,
|
|
25
|
-
FaLanguage,
|
|
26
|
-
FaLock,
|
|
27
|
-
FaPlus,
|
|
28
|
-
FaTrash
|
|
29
13
|
} from "react-icons/fa";
|
|
30
14
|
import {getDefaultForType, getUserId} from "../../src/data.js";
|
|
31
15
|
import {Trans, useTranslation} from "react-i18next";
|
|
32
16
|
|
|
33
|
-
import {
|
|
17
|
+
import {getObjectHash} from "../../src/core.js";
|
|
34
18
|
import Button from "./Button.jsx";
|
|
35
19
|
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
36
20
|
import APIInfo from "./APIInfo.jsx";
|
|
@@ -49,7 +33,6 @@ import CalendarConfigModal from "./CalendarConfigModal.jsx";
|
|
|
49
33
|
import KanbanView from "./KanbanView.jsx";
|
|
50
34
|
|
|
51
35
|
|
|
52
|
-
// --- AJOUT : Composants Placeholders pour la démonstration ---
|
|
53
36
|
const CalendarView = ({ settings, model }) => (
|
|
54
37
|
<div className="p-4 border rounded-md mt-4 bg-gray-50">
|
|
55
38
|
<h3 className="font-bold">Vue Calendrier</h3>
|
|
@@ -349,7 +332,7 @@ function DataLayout() {
|
|
|
349
332
|
|
|
350
333
|
console.log('Données enregistrées:', data, selectedModel);
|
|
351
334
|
|
|
352
|
-
|
|
335
|
+
Event.Trigger(recordToEdit ? 'API_ADD_DATA' : 'API_ADD_DATA', "custom", "data", {
|
|
353
336
|
model: selectedModel.name,
|
|
354
337
|
});
|
|
355
338
|
|
package/client/src/DataTable.jsx
CHANGED
|
@@ -6,6 +6,8 @@ import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
|
6
6
|
import React, {useEffect, useMemo, useRef, useState} from "react";
|
|
7
7
|
import {getUserId} from "../../src/data.js";
|
|
8
8
|
import cronstrue from 'cronstrue/i18n';
|
|
9
|
+
import {Event} from "../../src/events.js";
|
|
10
|
+
|
|
9
11
|
import {
|
|
10
12
|
FaBook,
|
|
11
13
|
FaCopy,
|
|
@@ -238,7 +240,7 @@ export function DataTable({
|
|
|
238
240
|
console.log('Données non trouvées');
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
|
|
243
|
+
Event.Trigger('API_DELETE_DATA', "custom", "data",{ model: item._model, id: item._id });
|
|
242
244
|
queryClient.invalidateQueries(['api/data', item._model, 'page', page, elementsPerPage, pagedFilters[item._model], pagedSort[item._model]]);
|
|
243
245
|
|
|
244
246
|
} else {
|
package/client/src/Field.jsx
CHANGED
|
@@ -493,7 +493,7 @@ const CheckboxField = forwardRef(
|
|
|
493
493
|
};
|
|
494
494
|
return (
|
|
495
495
|
<>
|
|
496
|
-
<div className={cn({field: true, "field-checkbox": true})}>
|
|
496
|
+
<div className={cn({field: true, "field-checkbox": true,"field-bg": true})}>
|
|
497
497
|
<div className="inline"><input
|
|
498
498
|
aria-required={required}
|
|
499
499
|
aria-readonly={readOnly}
|
|
@@ -526,7 +526,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
526
526
|
{/* Colonne de droite: Formulaire du modèle sélectionné */}
|
|
527
527
|
<div className="model-form-container">
|
|
528
528
|
{/* Le formulaire existant est placé ici */}
|
|
529
|
-
<div className="flex">
|
|
529
|
+
<div className="flex field-bg">
|
|
530
530
|
<label htmlFor="modelName"><Trans i18nKey={"modelcreator.name"}>Nom:</Trans></label>
|
|
531
531
|
</div>
|
|
532
532
|
<TextField
|
|
@@ -539,7 +539,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
539
539
|
required
|
|
540
540
|
/>
|
|
541
541
|
|
|
542
|
-
<div className="flex">
|
|
542
|
+
<div className="flex field-bg">
|
|
543
543
|
<label htmlFor="modelDescription"><Trans i18nKey={"modelcreator.description"}>Description:</Trans></label>
|
|
544
544
|
</div>
|
|
545
545
|
<TextField
|
|
@@ -570,7 +570,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
570
570
|
{/* Affichage du formulaire en mode manuel ou édition */}
|
|
571
571
|
{(!useAI || initialModel) && (
|
|
572
572
|
<div className="model-form-container">
|
|
573
|
-
<div className="flex">
|
|
573
|
+
<div className="flex field-bg">
|
|
574
574
|
<label htmlFor="modelName"><Trans i18nKey={"modelcreator.name"}>Nom:</Trans></label>
|
|
575
575
|
</div>
|
|
576
576
|
<TextField
|
|
@@ -583,7 +583,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
583
583
|
required
|
|
584
584
|
/>
|
|
585
585
|
|
|
586
|
-
<div className="flex">
|
|
586
|
+
<div className="flex field-bg">
|
|
587
587
|
<label htmlFor="modelDescription"><Trans i18nKey={"modelcreator.description"}>Description:</Trans></label>
|
|
588
588
|
</div>
|
|
589
589
|
<TextField
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
flex: 1;
|
|
3
3
|
max-width: 720px;
|
|
4
4
|
form {
|
|
5
|
+
padding: 0;
|
|
5
6
|
@media only screen and (min-width: 480px){
|
|
6
7
|
padding: 16px;
|
|
7
8
|
}
|
|
@@ -151,4 +152,13 @@
|
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.field-bg {
|
|
158
|
+
background: #fcfcfc;
|
|
159
|
+
background: linear-gradient(180deg, rgba(252, 252, 252, 1) 0%, rgba(242, 242, 242, 1) 55%, rgba(242, 242, 242, 1) 100%);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.mg-item{
|
|
163
|
+
margin: 10px 0;
|
|
154
164
|
}
|
|
@@ -61,7 +61,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
61
61
|
</div>
|
|
62
62
|
<div className="flex flex-row flex-stretch">
|
|
63
63
|
|
|
64
|
-
<div className="flex fieldName">{hint('modelcreator.name.hint')}
|
|
64
|
+
<div className="flex fieldName field-bg">{hint('modelcreator.name.hint')}
|
|
65
65
|
<div className="flex flex-no-gap flex-no-wrap flex-1">
|
|
66
66
|
<TextField
|
|
67
67
|
label={t('modelcreator.fieldName')}
|
|
@@ -88,7 +88,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
88
88
|
|
|
89
89
|
<div className="flex">
|
|
90
90
|
{hint('modelcreator.type.hint')}
|
|
91
|
-
<div className="flex flex-1 flex-stretch flex-no-gap">
|
|
91
|
+
<div className="flex flex-1 flex-stretch field-bg flex-no-gap">
|
|
92
92
|
|
|
93
93
|
<SelectField
|
|
94
94
|
label={t('modelcreator.type', 'Type de champ')}
|
|
@@ -453,16 +453,15 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
453
453
|
</div>
|
|
454
454
|
|
|
455
455
|
{!['file', 'relation', 'array', 'calculated'].includes(field.type) && (<div
|
|
456
|
-
className="flex flex-no-wrap">
|
|
457
|
-
{hint('modelcreator.default.hint')}
|
|
456
|
+
className="flex flex-no-wrap field-bg mg-item">
|
|
458
457
|
|
|
459
458
|
{['string_t', 'string', 'richtext', 'password', 'url', 'phone', 'email'].includes(field.type) && (<>
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
<
|
|
463
|
-
type="text"
|
|
459
|
+
{hint('modelcreator.default.hint')}
|
|
460
|
+
<div className="flex flex-1">
|
|
461
|
+
<TextField
|
|
464
462
|
className="flex-1"
|
|
465
463
|
value={field.default}
|
|
464
|
+
label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
|
|
466
465
|
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
467
466
|
onChange={(e) => {
|
|
468
467
|
const newFields = [...fields];
|
|
@@ -470,9 +469,10 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
470
469
|
setFields(newFields);
|
|
471
470
|
}}
|
|
472
471
|
/>
|
|
473
|
-
</
|
|
472
|
+
</div>
|
|
474
473
|
</>)}
|
|
475
474
|
{['number'].includes(field.type) && (<>
|
|
475
|
+
{hint('modelcreator.default.hint')}
|
|
476
476
|
<NumberField
|
|
477
477
|
label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
|
|
478
478
|
type="number"
|
|
@@ -491,6 +491,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
491
491
|
/>
|
|
492
492
|
</>)}
|
|
493
493
|
{['enum'].includes(field.type) && (<>
|
|
494
|
+
{hint('modelcreator.default.hint')}
|
|
494
495
|
<SelectField
|
|
495
496
|
label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
|
|
496
497
|
value={field.default}
|
|
@@ -505,6 +506,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
505
506
|
/>
|
|
506
507
|
</>)}
|
|
507
508
|
{['code'].includes(field.type) && (<>
|
|
509
|
+
{hint('modelcreator.default.hint')}
|
|
508
510
|
<CodeField
|
|
509
511
|
label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
|
|
510
512
|
value={field.language === 'json' ? JSON.stringify(field.default, 2, null) : field.default}
|
|
@@ -516,6 +518,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
516
518
|
/>
|
|
517
519
|
</>)}
|
|
518
520
|
{['boolean'].includes(field.type) && (<>
|
|
521
|
+
{hint('modelcreator.default.hint')}
|
|
519
522
|
<div className="checkbox-label flex flex-1">
|
|
520
523
|
<CheckboxField
|
|
521
524
|
label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
|
|
@@ -530,6 +533,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
530
533
|
</div>
|
|
531
534
|
</>)}
|
|
532
535
|
{['color'].includes(field.type) && (<>
|
|
536
|
+
{hint('modelcreator.default.hint')}
|
|
533
537
|
<ColorField
|
|
534
538
|
label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
|
|
535
539
|
value={field.default || null} name={field.name}
|
|
@@ -543,6 +547,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
543
547
|
/>
|
|
544
548
|
</>)}
|
|
545
549
|
{['date', 'datetime'].includes(field.type) && (<>
|
|
550
|
+
{hint('modelcreator.default.hint')}
|
|
546
551
|
<label className="flex flex-1">
|
|
547
552
|
<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>
|
|
548
553
|
<input
|
|
@@ -562,7 +567,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
562
567
|
|
|
563
568
|
|
|
564
569
|
{(['number', 'datetime', 'date'].includes(field.itemsType || field.type)) && (
|
|
565
|
-
<><label className="flex">
|
|
570
|
+
<><label className="flex field-bg mg-item">
|
|
566
571
|
{hint('modelcreator.min.hint')}
|
|
567
572
|
<span><Trans
|
|
568
573
|
i18nKey={"modelcreator.min"}>Valeur minimale :</Trans></span>
|
|
@@ -581,7 +586,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
581
586
|
}}
|
|
582
587
|
/></div>
|
|
583
588
|
</label>
|
|
584
|
-
<label className="flex">
|
|
589
|
+
<label className="flex field-bg mg-item">
|
|
585
590
|
{hint('modelcreator.max.hint')}
|
|
586
591
|
<span><Trans i18nKey={"modelcreator.max"}>Valeur maximale :</Trans></span>
|
|
587
592
|
<div className="flex flex-1"><input
|
|
@@ -601,13 +606,12 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
601
606
|
</label>
|
|
602
607
|
</>
|
|
603
608
|
)}
|
|
604
|
-
<div className="flex flex-no-wrap">
|
|
609
|
+
<div className="flex flex-no-wrap mg-item">
|
|
605
610
|
{hint('modelcreator.condition.hint')}
|
|
606
611
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
type="checkbox"
|
|
612
|
+
<CheckboxField
|
|
613
|
+
label={<Trans
|
|
614
|
+
i18nKey={"modelcreator.condition"}>Condition</Trans>}
|
|
611
615
|
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
612
616
|
checked={field.condition !== undefined}
|
|
613
617
|
onChange={(e) => {
|
|
@@ -620,7 +624,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
620
624
|
setFields(newFields);
|
|
621
625
|
}}
|
|
622
626
|
/>
|
|
623
|
-
</label>
|
|
624
627
|
</div>
|
|
625
628
|
{field.condition !== undefined && (
|
|
626
629
|
<div className={"condition-details flex flex-start"}>
|
|
@@ -691,10 +694,10 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
691
694
|
|
|
692
695
|
|
|
693
696
|
{mainFieldsTypes.includes(field.itemsType || field.type) && (<div
|
|
694
|
-
className="flex flex-no-wrap"
|
|
697
|
+
className="flex flex-no-wrap mg-item"
|
|
695
698
|
title={t("modelcreator.field.asMain", "Une information principale sera affichée dans le titre de l'enregistrement")}>
|
|
696
699
|
{hint('modelcreator.asMain.hint')}
|
|
697
|
-
<
|
|
700
|
+
<div className="flex flex-1">
|
|
698
701
|
|
|
699
702
|
<CheckboxField
|
|
700
703
|
label={<Trans i18nKey={"modelcreator.asMain"}>Information principale :</Trans>}
|
|
@@ -707,7 +710,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
707
710
|
}}
|
|
708
711
|
help={field.asMain && t('modelcreator.asMain.hint')}
|
|
709
712
|
/>
|
|
710
|
-
</
|
|
713
|
+
</div>
|
|
711
714
|
</div>)}
|
|
712
715
|
|
|
713
716
|
<div className="flex flex-row flex-stretch">
|
|
@@ -729,7 +732,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
729
732
|
</div>
|
|
730
733
|
|
|
731
734
|
|
|
732
|
-
<label className="flex">
|
|
735
|
+
<label className="flex mg-item ">
|
|
733
736
|
{hint('modelcreator.color.hint')}
|
|
734
737
|
<Trans i18nKey={"field.color"}>Color :</Trans>
|
|
735
738
|
<ColorField
|
|
@@ -763,19 +766,16 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
763
766
|
|
|
764
767
|
<div className={"flex flex-no-wrap"}>
|
|
765
768
|
{hint('modelcreator.anonymized.hint')}
|
|
766
|
-
<
|
|
767
|
-
<Trans i18nKey={"modelcreator.anonymized"}>Donnée anonymisée :</Trans>
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}}
|
|
777
|
-
/>
|
|
778
|
-
</label>
|
|
769
|
+
<CheckboxField
|
|
770
|
+
label={<Trans i18nKey={"modelcreator.anonymized"}>Donnée anonymisée :</Trans>}
|
|
771
|
+
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
772
|
+
checked={field.anonymized}
|
|
773
|
+
onChange={(e) => {
|
|
774
|
+
const newFields = [...fields];
|
|
775
|
+
newFields[index].anonymized = e.target.checked;
|
|
776
|
+
setFields(newFields);
|
|
777
|
+
}}
|
|
778
|
+
/>
|
|
779
779
|
</div>
|
|
780
780
|
|
|
781
781
|
|
package/client/src/RTE.jsx
CHANGED
|
@@ -28,6 +28,7 @@ import css from 'highlight.js/lib/languages/css'
|
|
|
28
28
|
import js from 'highlight.js/lib/languages/javascript'
|
|
29
29
|
import ts from 'highlight.js/lib/languages/typescript'
|
|
30
30
|
import html from 'highlight.js/lib/languages/xml'
|
|
31
|
+
import {escapeHtml, escapeRegex} from "data-primals-engine/core";
|
|
31
32
|
// create a lowlight instance
|
|
32
33
|
const lowlight = createLowlight(all)
|
|
33
34
|
|
|
@@ -43,7 +44,7 @@ const code = (c) => {
|
|
|
43
44
|
const doc = document.createElement('div');
|
|
44
45
|
doc.innerHTML = c;
|
|
45
46
|
doc.querySelectorAll('code').forEach(cc =>{
|
|
46
|
-
cc.innerHTML = cc.textContent
|
|
47
|
+
cc.innerHTML = escapeHtml(cc.textContent)
|
|
47
48
|
.replace(/\r\n|\n/g, "<br />")
|
|
48
49
|
.replace(/\t/g, " ");
|
|
49
50
|
})
|