Browse Source

Nc fix: Form view bug fixes (#8322)

* fix(nc-gui): form view rich text link option issue

* chore(nc-gui): lint

* fix(nc-gui): pw test fail issue

* fix(nc-gui): add checkbox required form validation rule
pull/8330/head
Ramesh Mane 2 months ago committed by GitHub
parent
commit
6075611c0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 44
      packages/nc-gui/components/cell/RichText.vue
  2. 13
      packages/nc-gui/components/cell/RichText/LinkOptions.vue
  3. 1
      packages/nc-gui/components/smartsheet/Form.vue
  4. 8
      packages/nc-gui/composables/useSharedFormViewStore.ts

44
packages/nc-gui/components/cell/RichText.vue

@ -59,6 +59,10 @@ const isFocused = ref(false)
const keys = useMagicKeys() const keys = useMagicKeys()
const shouldShowLinkOption = computed(() => {
return isFormField.value ? isFocused.value : true
})
const turndownService = new TurndownService({}) const turndownService = new TurndownService({})
turndownService.addRule('lineBreak', { turndownService.addRule('lineBreak', {
@ -133,6 +137,8 @@ marked.use({ extensions: [checkListItem] })
const editorDom = ref<HTMLElement | null>(null) const editorDom = ref<HTMLElement | null>(null)
const richTextLinkOptionRef = ref<HTMLElement | null>(null)
const vModel = useVModel(props, 'value', emits, { defaultValue: '' }) const vModel = useVModel(props, 'value', emits, { defaultValue: '' })
const tiptapExtensions = [ const tiptapExtensions = [
@ -167,7 +173,7 @@ const editor = useEditor({
emits('focus') emits('focus')
}, },
onBlur: (e) => { onBlur: (e) => {
if (!(e?.event?.relatedTarget as HTMLElement)?.closest('.bubble-menu, .nc-textarea-rich-editor')) { if (!(e?.event?.relatedTarget as HTMLElement)?.closest('.bubble-menu, .nc-textarea-rich-editor, .nc-rich-text')) {
isFocused.value = false isFocused.value = false
emits('blur') emits('blur')
} }
@ -239,13 +245,37 @@ useEventListener(
'focusout', 'focusout',
(e: FocusEvent) => { (e: FocusEvent) => {
const targetEl = e?.relatedTarget as HTMLElement const targetEl = e?.relatedTarget as HTMLElement
if (targetEl?.classList?.contains('tiptap') || !targetEl?.closest('.bubble-menu, .nc-textarea-rich-editor')) { if (targetEl?.classList?.contains('tiptap') || !targetEl?.closest('.bubble-menu, .tippy-content, .nc-textarea-rich-editor')) {
isFocused.value = false
emits('blur')
}
},
true,
)
useEventListener(
richTextLinkOptionRef,
'focusout',
(e: FocusEvent) => {
const targetEl = e?.relatedTarget as HTMLElement
if (!targetEl && (e.target as HTMLElement)?.closest('.bubble-menu, .tippy-content, .nc-textarea-rich-editor')) return
if (!targetEl?.closest('.bubble-menu, .tippy-content, .nc-textarea-rich-editor')) {
isFocused.value = false isFocused.value = false
emits('blur') emits('blur')
} }
}, },
true, true,
) )
onClickOutside(editorDom, (e) => {
if (!isFocused.value) return
const targetEl = e?.target as HTMLElement
if (!targetEl?.closest('.bubble-menu,.tippy-content, .nc-textarea-rich-editor')) {
isFocused.value = false
emits('blur')
}
})
</script> </script>
<template> <template>
@ -281,7 +311,15 @@ useEventListener(
</div> </div>
<CellRichTextSelectedBubbleMenuPopup v-if="editor && !isFormField && !isForm" :editor="editor" /> <CellRichTextSelectedBubbleMenuPopup v-if="editor && !isFormField && !isForm" :editor="editor" />
<CellRichTextLinkOptions v-if="editor" :editor="editor" /> <template v-if="shouldShowLinkOption">
<CellRichTextLinkOptions
v-if="editor"
ref="richTextLinkOptionRef"
:editor="editor"
:is-form-field="isFormField"
@blur="isFocused = false"
/>
</template>
<EditorContent <EditorContent
ref="editorDom" ref="editorDom"

13
packages/nc-gui/components/cell/RichText/LinkOptions.vue

@ -6,11 +6,14 @@ import type { Mark } from 'prosemirror-model'
const props = defineProps<Props>() const props = defineProps<Props>()
const emits = defineEmits(['blur'])
interface Props { interface Props {
editor: Editor editor: Editor
isFormField?: boolean
} }
const editor = computed(() => props.editor) const { editor, isFormField } = toRefs(props)
const inputRef = ref<HTMLInputElement>() const inputRef = ref<HTMLInputElement>()
const linkNodeMark = ref<Mark | undefined>() const linkNodeMark = ref<Mark | undefined>()
@ -164,6 +167,10 @@ const onMountLinkOptions = (e) => {
e.popper.style.width = '95%' e.popper.style.width = '95%'
} }
} }
const tabIndex = computed(() => {
return isFormField.value ? -1 : 0
})
</script> </script>
<template> <template>
@ -188,17 +195,20 @@ const onMountLinkOptions = (e) => {
<a-input <a-input
ref="inputRef" ref="inputRef"
v-model:value="href" v-model:value="href"
:tabindex="tabIndex"
class="nc-text-area-rich-link-option-input flex-1 !mx-0.5 !px-1.5 !py-0.5 !rounded-md z-10" class="nc-text-area-rich-link-option-input flex-1 !mx-0.5 !px-1.5 !py-0.5 !rounded-md z-10"
:bordered="false" :bordered="false"
placeholder="Enter a link" placeholder="Enter a link"
@change="onChange" @change="onChange"
@press-enter="onInputBoxEnter" @press-enter="onInputBoxEnter"
@keydown="handleInputBoxKeyDown" @keydown="handleInputBoxKeyDown"
@blur="emits('blur')"
/> />
</div> </div>
<NcTooltip overlay-class-name="nc-text-area-rich-link-options"> <NcTooltip overlay-class-name="nc-text-area-rich-link-options">
<template #title> Open link </template> <template #title> Open link </template>
<NcButton <NcButton
:tabindex="tabIndex"
:class="{ :class="{
'!text-gray-300 cursor-not-allowed': href.length === 0, '!text-gray-300 cursor-not-allowed': href.length === 0,
}" }"
@ -213,6 +223,7 @@ const onMountLinkOptions = (e) => {
<NcTooltip overlay-class-name="nc-text-area-rich-link-options"> <NcTooltip overlay-class-name="nc-text-area-rich-link-options">
<template #title> Delete link </template> <template #title> Delete link </template>
<NcButton <NcButton
:tabindex="tabIndex"
class="!duration-0 !hover:(text-red-400 bg-red-50)" class="!duration-0 !hover:(text-red-400 bg-red-50)"
data-testid="nc-text-area-rich-link-options-open-delete" data-testid="nc-text-area-rich-link-options-open-delete"
size="small" size="small"

1
packages/nc-gui/components/smartsheet/Form.vue

@ -622,6 +622,7 @@ const formElementValidationRules = (element) => {
{ {
required: isRequired(element, element.required), required: isRequired(element, element.required),
message: t('msg.error.fieldRequired', { value: 'This field' }), message: t('msg.error.fieldRequired', { value: 'This field' }),
...(element.uidt === UITypes.Checkbox && isRequired(element, element.required) ? { type: 'enum', enum: [1, true] } : {}),
}, },
] ]

8
packages/nc-gui/composables/useSharedFormViewStore.ts

@ -1,5 +1,5 @@
import useVuelidate from '@vuelidate/core' import useVuelidate from '@vuelidate/core'
import { helpers, minLength, required } from '@vuelidate/validators' import { helpers, minLength, required, sameAs } from '@vuelidate/validators'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { import type {
@ -100,8 +100,8 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
}), }),
) )
const fieldRequired = (fieldName = 'This field') => const fieldRequired = (fieldName = 'This field', isBoolean = false) =>
helpers.withMessage(t('msg.error.fieldRequired', { value: fieldName }), required) helpers.withMessage(t('msg.error.fieldRequired', { value: fieldName }), isBoolean ? sameAs(true) : required)
const formColumns = computed(() => const formColumns = computed(() =>
columns.value columns.value
@ -217,7 +217,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
((column.rqd && !column.cdf) || (column.pk && !(column.ai || column.cdf)) || column.required) ((column.rqd && !column.cdf) || (column.pk && !(column.ai || column.cdf)) || column.required)
) { ) {
obj.localState[column.title!] = { obj.localState[column.title!] = {
required: fieldRequired(), required: fieldRequired(undefined, column.uidt === UITypes.Checkbox && column.required ? true : false),
} }
} else if ( } else if (
isLinksOrLTAR(column) && isLinksOrLTAR(column) &&

Loading…
Cancel
Save