1
1
<script setup>
2
2
import { Link , useForm } from ' @inertiajs/vue3' ;
3
- import { watch , ref } from ' vue' ;
3
+ import { watch , ref , defineProps , computed } from ' vue' ;
4
4
import { debounce } from ' lodash' ;
5
5
import { trans } from ' laravel-vue-i18n' ;
6
6
import { DatePicker } from ' v-calendar' ;
@@ -30,7 +30,20 @@ const form = useForm({
30
30
sections: props .data .sections .map ((section ) => ({
31
31
id: section .id ,
32
32
label: section .label ,
33
- content: section .content ,
33
+ content: (section .content || ' ' ).replace (
34
+ // not defined when dealing with a new post
35
+ / \{\{\{ CONTACT-ID:([a-f0-9 -] + )\| (. *? )\}\}\} / g ,
36
+ (match , contactId , fallbackName ) => {
37
+ let contact = props .data .contacts .find ((c ) => c .id === contactId);
38
+
39
+ if (contact) {
40
+ return ` @"${ contact .name .trim ()} "` ;
41
+ }
42
+
43
+ // If contact is missing, use fallback name
44
+ return ` @${ fallbackName} (contact no longer linked to post)` ;
45
+ },
46
+ ),
34
47
})),
35
48
uuid: null ,
36
49
name: null ,
@@ -40,6 +53,22 @@ const form = useForm({
40
53
size: null ,
41
54
});
42
55
56
+ const hasInvalidMentions = ref (false ); // Track if there are invalid mentions
57
+ const mentionErrorMessage = ref (' ' ); // Error message for invalid mentions
58
+
59
+ const tributeOptions = computed (() => ({
60
+ trigger: ' @' ,
61
+ allowSpaces: true ,
62
+ values: form .contacts .map ((contact ) => ({
63
+ key: contact .name ,
64
+ value: contact .name ,
65
+ id: contact .id ,
66
+ original: contact,
67
+ })),
68
+ selectTemplate : function (item ) {
69
+ return ` @"${ item .original .key .trim ()} "` ;
70
+ },
71
+ }));
43
72
const saveInProgress = ref (false );
44
73
const statistics = ref (props .data .statistics );
45
74
const deletePhotoModalShown = ref (false );
@@ -131,8 +160,54 @@ const destroyPhoto = () => {
131
160
const update = () => {
132
161
saveInProgress .value = true ;
133
162
163
+ // Clone form to avoid modifying the UI content
164
+ let processedForm = JSON .parse (JSON .stringify (form));
165
+
166
+ let invalidMentionsFound = false ;
167
+ let invalidMentionText = ' ' ;
168
+
169
+ processedForm .sections .forEach ((section ) => {
170
+ if (section .content ) {
171
+ section .content = section .content .replace (/ @"([A-Za-z . '’] + (?:\s + [A-Za-z . '’] + )* )"/ g , (match , name ) => {
172
+ name = name .trim (); // Trim spaces from the mention name
173
+ console .log (' Matched Name:' , name);
174
+
175
+ let contact = processedForm .contacts .find ((c ) => c .name .trim () === name); // Trim contact names before matching
176
+
177
+ if (contact) {
178
+ // Check if there are duplicate contacts with the same name and different IDs
179
+ const duplicateContacts = processedForm .contacts .filter ((c ) => c .name .trim () === name && c .id !== contact .id );
180
+
181
+ if (duplicateContacts .length > 0 ) {
182
+ // If there are duplicate contacts with the same name and different IDs, mark as invalid
183
+ invalidMentionsFound = true ;
184
+ invalidMentionText = ` ${ trans (' Cannot mention a contact when there are 2 identical contacts linked: @name' , { name })} ` ;
185
+ return ` @${ name} (duplicate contacts exist)` ;
186
+ }
187
+
188
+ return ` {{{CONTACT-ID:${ contact .id } |${ name} }}}` ;
189
+ }
190
+
191
+ // If no contact is found, mark as invalid
192
+ invalidMentionsFound = true ;
193
+ invalidMentionText = trans (' Invalid mention' ) + ` : @${ name} ` ;
194
+
195
+ // If no contact is found, remove apostrophes and add fallback text
196
+ return ` @${ name} (contact not linked to post)` ;
197
+ });
198
+ }
199
+ });
200
+
201
+ // If invalid mentions were found, set the error state and stop the upload
202
+ if (invalidMentionsFound) {
203
+ hasInvalidMentions .value = true ;
204
+ mentionErrorMessage .value = invalidMentionText;
205
+ saveInProgress .value = false ;
206
+ return ; // Prevent the form from being uploaded
207
+ }
208
+
134
209
axios
135
- .put (props .data .url .update , form )
210
+ .put (props .data .url .update , processedForm )
136
211
.then ((response ) => {
137
212
setTimeout (() => (saveInProgress .value = false ), 350 );
138
213
statistics .value = response .data .data ;
@@ -326,7 +401,15 @@ const destroy = () => {
326
401
:required =" true"
327
402
:maxlength =" 65535"
328
403
:markdown =" true"
329
- :textarea-class =" 'block w-full'" />
404
+ :tribute-options =" tributeOptions"
405
+ :textarea-class =" {
406
+ 'block w-full': true,
407
+ 'border-red-500': hasInvalidMentions, // Add the red border if invalid mentions
408
+ }" />
409
+ </div >
410
+ <!-- Show error message if invalid mentions exist -->
411
+ <div v-if =" hasInvalidMentions" class =" text-red-500 text-sm mt-2" >
412
+ {{ mentionErrorMessage }}
330
413
</div >
331
414
</div >
332
415
</div >
@@ -343,7 +426,8 @@ const destroy = () => {
343
426
344
427
<!-- auto save -->
345
428
<div class =" mb-6 text-sm" >
346
- <div v-if =" !saveInProgress" class =" flex items-center justify-center" >
429
+ <!-- Show the auto-saved message unless there's an error with mentions -->
430
+ <div v-if =" !hasInvalidMentions && !saveInProgress" class =" flex items-center justify-center" >
347
431
<svg
348
432
class =" me-2 h-4 w-4 text-green-700"
349
433
xmlns =" http://www.w3.org/2000/svg"
@@ -371,6 +455,10 @@ const destroy = () => {
371
455
</div >
372
456
</div >
373
457
458
+ <div v-if =" hasInvalidMentions" class =" text-red-500 text-sm mt-2 flex items-center justify-center" >
459
+ <span >{{ $t('Not saving until errors are fixed') }}</span >
460
+ </div >
461
+
374
462
<!-- written at -->
375
463
<p class =" mb-2 flex items-center font-bold" >
376
464
<span >{{ $t('Written on') }}</span >
0 commit comments