mirror of https://github.com/docusealco/docuseal
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							240 lines
						
					
					
						
							6.6 KiB
						
					
					
				
			
		
		
	
	
							240 lines
						
					
					
						
							6.6 KiB
						
					
					
				| <template>
 | |
|   <div
 | |
|     class="modal modal-open items-start !animate-none overflow-y-auto"
 | |
|   >
 | |
|     <div
 | |
|       class="absolute top-0 bottom-0 right-0 left-0"
 | |
|       @click.prevent="$emit('close')"
 | |
|     />
 | |
|     <div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
 | |
|       <div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
 | |
|         <span>
 | |
|           {{ t('formula') }} - {{ field.name || buildDefaultName(field, template.fields) }}
 | |
|         </span>
 | |
|         <a
 | |
|           href="#"
 | |
|           class="text-xl"
 | |
|           @click.prevent="$emit('close')"
 | |
|         >×</a>
 | |
|       </div>
 | |
|       <div>
 | |
|         <div
 | |
|           v-if="!withFormula"
 | |
|           class="bg-base-300 rounded-xl py-2 px-3 text-center"
 | |
|         >
 | |
|           <a
 | |
|             href="https://www.docuseal.co/pricing"
 | |
|             target="_blank"
 | |
|             class="link"
 | |
|           >{{ t('available_in_pro') }}</a>
 | |
|         </div>
 | |
|         <div class="flex-inline mb-2 gap-2 space-y-1">
 | |
|           <button
 | |
|             v-for="f in fields"
 | |
|             :key="f.uuid"
 | |
|             class="mr-1 btn btn-neutral btn-outline border-base-content/20 btn-sm normal-case font-normal bg-white !rounded-xl"
 | |
|             @click.prevent="insertTextUnderCursor(`{{${f.name || buildDefaultName(f, template.fields)}}}`)"
 | |
|           >
 | |
|             <IconCodePlus
 | |
|               width="20"
 | |
|               height="20"
 | |
|               stroke-width="1.5"
 | |
|             />
 | |
|             {{ f.name || buildDefaultName(f, template.fields) }}
 | |
|           </button>
 | |
|         </div>
 | |
|         <div>
 | |
|           <div class="flex">
 | |
|             <textarea
 | |
|               ref="textarea"
 | |
|               v-model="formula"
 | |
|               class="base-textarea !rounded-xl !text-base font-mono w-full !outline-0 !ring-0 !px-3"
 | |
|               :readonly="!editable"
 | |
|               required="true"
 | |
|               @input="resizeTextarea"
 | |
|             />
 | |
|           </div>
 | |
|           <div class="mb-3 mt-1">
 | |
|             <div
 | |
|               target="blank"
 | |
|               class="text-sm mb-2 inline space-x-2"
 | |
|             >
 | |
|               <button
 | |
|                 class="bg-base-200 px-2 rounded-xl"
 | |
|                 @click="insertTextUnderCursor(' + ')"
 | |
|               >
 | |
|                 +
 | |
|               </button>
 | |
|               <button
 | |
|                 class="bg-base-200 px-2 rounded-xl"
 | |
|                 @click="insertTextUnderCursor(' - ')"
 | |
|               >
 | |
|                 -
 | |
|               </button>
 | |
|               <button
 | |
|                 class="bg-base-200 px-2 rounded-xl"
 | |
|                 @click="insertTextUnderCursor(' * ')"
 | |
|               >
 | |
|                 *
 | |
|               </button>
 | |
|               <button
 | |
|                 class="bg-base-200 px-2 rounded-xl"
 | |
|                 @click="insertTextUnderCursor(' / ')"
 | |
|               >
 | |
|                 /
 | |
|               </button>
 | |
|               <button
 | |
|                 class="bg-base-200 px-2 rounded-xl"
 | |
|                 @click="insertTextUnderCursor(' % ')"
 | |
|               >
 | |
|                 %
 | |
|               </button>
 | |
|               <button
 | |
|                 class="bg-base-200 px-2 rounded-xl"
 | |
|                 @click="insertTextUnderCursor('^')"
 | |
|               >
 | |
|                 ^
 | |
|               </button>
 | |
|               <button
 | |
|                 class="bg-base-200 px-2 rounded-xl"
 | |
|                 @click="insertTextUnderCursor('round()')"
 | |
|               >
 | |
|                 round(n, d)
 | |
|               </button>
 | |
|               <button
 | |
|                 class="bg-base-200 px-2 rounded-xl"
 | |
|                 @click="insertTextUnderCursor('abs()')"
 | |
|               >
 | |
|                 abs(n)
 | |
|               </button>
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|         <button
 | |
|           class="base-button w-full"
 | |
|           @click.prevent="validateSaveAndClose"
 | |
|         >
 | |
|           {{ t('save') }}
 | |
|         </button>
 | |
|       </div>
 | |
|     </div>
 | |
|   </div>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| import { IconCodePlus } from '@tabler/icons-vue'
 | |
| 
 | |
| export default {
 | |
|   name: 'FormulaModal',
 | |
|   components: {
 | |
|     IconCodePlus
 | |
|   },
 | |
|   inject: ['t', 'save', 'template', 'withFormula'],
 | |
|   props: {
 | |
|     field: {
 | |
|       type: Object,
 | |
|       required: true
 | |
|     },
 | |
|     editable: {
 | |
|       type: Boolean,
 | |
|       required: false,
 | |
|       default: true
 | |
|     },
 | |
|     buildDefaultName: {
 | |
|       type: Function,
 | |
|       required: true
 | |
|     }
 | |
|   },
 | |
|   emits: ['close'],
 | |
|   data () {
 | |
|     return {
 | |
|       formula: ''
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     fields () {
 | |
|       return this.template.fields.reduce((acc, f) => {
 | |
|         if (f !== this.field && f.submitter_uuid === this.field.submitter_uuid && ['number'].includes(f.type) && !f.preferences?.formula) {
 | |
|           acc.push(f)
 | |
|         }
 | |
| 
 | |
|         return acc
 | |
|       }, [])
 | |
|     }
 | |
|   },
 | |
|   created () {
 | |
|     this.field.preferences ||= {}
 | |
|   },
 | |
|   mounted () {
 | |
|     this.formula = this.humanizeFormula(this.field.preferences.formula || '')
 | |
|   },
 | |
|   methods: {
 | |
|     humanizeFormula (text) {
 | |
|       return text.replace(/{{(.*?)}}/g, (match, uuid) => {
 | |
|         const foundField = this.fields.find((f) => f.uuid === uuid)
 | |
| 
 | |
|         if (foundField) {
 | |
|           return `{{${foundField.name || this.buildDefaultName(foundField, this.template.fields)}}}`
 | |
|         } else {
 | |
|           return '{{FIELD NOT FOUND}}'
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     normalizeFormula (text) {
 | |
|       return text.replace(/{{(.*?)}}/g, (match, name) => {
 | |
|         const foundField = this.fields.find((f) => {
 | |
|           return (f.name || this.buildDefaultName(f, this.template.fields)).trim() === name.trim()
 | |
|         })
 | |
| 
 | |
|         if (foundField) {
 | |
|           return `{{${foundField.uuid}}}`
 | |
|         } else {
 | |
|           return '{{FIELD NOT FOUND}}'
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     validateSaveAndClose () {
 | |
|       if (!this.withFormula) {
 | |
|         return alert(this.t('available_only_in_pro'))
 | |
|       }
 | |
| 
 | |
|       const normalizedFormula = this.normalizeFormula(this.formula)
 | |
| 
 | |
|       if (normalizedFormula.includes('FIELD NOT FOUND')) {
 | |
|         alert(this.t('some_fields_are_missing_in_the_formula'))
 | |
|       } else {
 | |
|         this.field.preferences.formula = normalizedFormula
 | |
| 
 | |
|         if (this.field.type !== 'payment') {
 | |
|           this.field.readonly = !!normalizedFormula
 | |
|         }
 | |
| 
 | |
|         this.save()
 | |
| 
 | |
|         this.$emit('close')
 | |
|       }
 | |
|     },
 | |
|     insertTextUnderCursor (textToInsert) {
 | |
|       const textarea = this.$refs.textarea
 | |
| 
 | |
|       const selectionEnd = textarea.selectionEnd
 | |
|       const cursorPos = selectionEnd
 | |
| 
 | |
|       const newText = textarea.value.substring(0, cursorPos) + textToInsert + textarea.value.substring(cursorPos)
 | |
| 
 | |
|       this.formula = newText
 | |
| 
 | |
|       textarea.setSelectionRange(cursorPos + textToInsert.length, cursorPos + textToInsert.length)
 | |
| 
 | |
|       textarea.focus()
 | |
|     },
 | |
|     resizeTextarea () {
 | |
|       const textarea = this.$refs.textarea
 | |
| 
 | |
|       textarea.style.height = 'auto'
 | |
|       textarea.style.height = textarea.scrollHeight + 'px'
 | |
|     }
 | |
|   }
 | |
| }
 | |
| </script>
 |