diff --git a/.github/workflows/publish-docs-index-typesense.yml b/.github/workflows/publish-docs-index-typesense.yml index 6b4aedf3b8..0a6777a58e 100644 --- a/.github/workflows/publish-docs-index-typesense.yml +++ b/.github/workflows/publish-docs-index-typesense.yml @@ -3,7 +3,9 @@ name: "Publish : Docs search index (Typesense)" on: # Triggered manually workflow_dispatch: - + repository_dispatch: + types: trigger-docs-index + jobs: doc-indexer: runs-on: ubuntu-latest @@ -15,12 +17,12 @@ jobs: uses: celsiusnarhwal/typesense-scraper@v2 with: # The secret containing your Typesense API key. Required. - api-key: ${{ secrets.TYPESENSE_API_KEY }} + api-key: ${{ secrets.TYPESENSE_API_KEY }} # The hostname or IP address of your Typesense server. Required. - host: ${{ secrets.TYPESENSE_HOST }} + host: ${{ secrets.TYPESENSE_HOST }} # The port on which your Typesense server is listening. Optional. Default: 8108. - port: 443 + port: 443 # The protocol to use when connecting to your Typesense server. Optional. Default: http. - protocol: https + protocol: https # The path to your DocSearch config file. Optional. Default: docsearch.config.json. - config: ./packages/noco-docs/typesense-scrape-config.json \ No newline at end of file + config: ./packages/noco-docs/typesense-scrape-config.json diff --git a/package.json b/package.json index a0fce7e026..8f55e00b6b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ ] }, "scripts": { - "bootstrap": "pnpm --filter=nocodb-sdk install && pnpm --filter=nocodb-sdk run build && pnpm --filter=nocodb --filter=nc-gui --filter=playwright install", + "bootstrap": "pnpm --filter=nocodb-sdk install && pnpm --filter=nocodb-sdk run build && pnpm --filter=nocodb --filter=nc-mail-templates --filter=nc-gui --filter=playwright install", "start:frontend": "pnpm --filter=nc-gui run dev", "start:backend": "pnpm --filter=nocodb run start", "lint:staged:playwright": "cd ./tests/playwright; pnpm dlx lint-staged; cd -", diff --git a/packages/nc-gui/assets/img/views/calendar.png b/packages/nc-gui/assets/img/views/calendar.png new file mode 100644 index 0000000000..e8881efbc2 Binary files /dev/null and b/packages/nc-gui/assets/img/views/calendar.png differ diff --git a/packages/nc-gui/assets/img/views/form.png b/packages/nc-gui/assets/img/views/form.png new file mode 100644 index 0000000000..e33524b0c8 Binary files /dev/null and b/packages/nc-gui/assets/img/views/form.png differ diff --git a/packages/nc-gui/assets/img/views/gallery.png b/packages/nc-gui/assets/img/views/gallery.png new file mode 100644 index 0000000000..43a94f74d4 Binary files /dev/null and b/packages/nc-gui/assets/img/views/gallery.png differ diff --git a/packages/nc-gui/assets/img/views/grid.png b/packages/nc-gui/assets/img/views/grid.png new file mode 100644 index 0000000000..3b4285f196 Binary files /dev/null and b/packages/nc-gui/assets/img/views/grid.png differ diff --git a/packages/nc-gui/assets/img/views/kanban.png b/packages/nc-gui/assets/img/views/kanban.png new file mode 100644 index 0000000000..185098eaea Binary files /dev/null and b/packages/nc-gui/assets/img/views/kanban.png differ diff --git a/packages/nc-gui/assets/nc-icons/bell.svg b/packages/nc-gui/assets/nc-icons/bell.svg new file mode 100644 index 0000000000..de7f88412d --- /dev/null +++ b/packages/nc-gui/assets/nc-icons/bell.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/nc-gui/assets/nc-icons/check-circle.svg b/packages/nc-gui/assets/nc-icons/check-circle.svg new file mode 100644 index 0000000000..f1e9436075 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons/check-circle.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/nc-gui/assets/nc-icons/drag.svg b/packages/nc-gui/assets/nc-icons/drag.svg new file mode 100644 index 0000000000..c7d048f261 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons/drag.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/nc-gui/assets/nc-icons/key.svg b/packages/nc-gui/assets/nc-icons/key.svg new file mode 100644 index 0000000000..c48e718411 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons/key.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/nc-gui/assets/nc-icons/maximize-all.svg b/packages/nc-gui/assets/nc-icons/maximize-all.svg new file mode 100644 index 0000000000..990f6ab478 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons/maximize-all.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/nc-gui/assets/nc-icons/maximize.svg b/packages/nc-gui/assets/nc-icons/maximize.svg index 5ebeb1d312..54d389776b 100644 --- a/packages/nc-gui/assets/nc-icons/maximize.svg +++ b/packages/nc-gui/assets/nc-icons/maximize.svg @@ -1,6 +1,8 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/packages/nc-gui/assets/nc-icons/minimize-all.svg b/packages/nc-gui/assets/nc-icons/minimize-all.svg new file mode 100644 index 0000000000..5f648c170b --- /dev/null +++ b/packages/nc-gui/assets/nc-icons/minimize-all.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/nc-gui/assets/nc-icons/minimize.svg b/packages/nc-gui/assets/nc-icons/minimize.svg new file mode 100644 index 0000000000..1cc18a87e1 --- /dev/null +++ b/packages/nc-gui/assets/nc-icons/minimize.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/packages/nc-gui/assets/style.scss b/packages/nc-gui/assets/style.scss index 5555e60382..f1cc42ee0d 100644 --- a/packages/nc-gui/assets/style.scss +++ b/packages/nc-gui/assets/style.scss @@ -249,9 +249,26 @@ a { @apply !rounded-md; } } + +.nc-select-shadow { + &.ant-select { + &:not(.ant-select-disabled):not(:hover):not(.ant-select-focused) .ant-select-selector, + &:not(.ant-select-disabled):hover.ant-select-disabled .ant-select-selector { + @apply shadow-default; + } + + &:hover:not(.ant-select-focused):not(.ant-select-disabled) .ant-select-selector { + @apply border-gray-300 shadow-hover; + } + &.ant-select-disabled .ant-select-selector { + box-shadow: none; + } + } +} + // select dropdown border style .ant-select-dropdown { - @apply border-1 border-gray-200; + @apply border-1 border-gray-200 rounded-lg; .rc-virtual-list-scrollbar { @apply !w-1; diff --git a/packages/nc-gui/components/cell/Checkbox.vue b/packages/nc-gui/components/cell/Checkbox.vue index 834e4d98e4..569ea6248e 100644 --- a/packages/nc-gui/components/cell/Checkbox.vue +++ b/packages/nc-gui/components/cell/Checkbox.vue @@ -25,6 +25,8 @@ const isEditColumnMenu = inject(EditColumnInj, ref(false)) const isGallery = inject(IsGalleryInj, ref(false)) +const isKanban = inject(IsKanbanInj, ref(false)) + const readOnly = inject(ReadonlyInj) const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false)) @@ -110,11 +112,11 @@ useSelectedCellKeyupListener(active, (e) => {
{ type="number" class="nc-cell-field h-full border-none rounded-md py-1 outline-none focus:outline-none focus:ring-0" :class="isForm && !isEditColumn ? 'flex flex-1' : 'w-full'" - :placeholder="placeholder !== undefined ? placeholder : isEditColumn ? $t('labels.optional') : ''" + :placeholder="placeholder" :disabled="readOnly" @blur="onBlur" @keydown.enter="onKeydownEnter" diff --git a/packages/nc-gui/components/cell/DatePicker.vue b/packages/nc-gui/components/cell/DatePicker.vue index 878ab83887..23ce8ab77f 100644 --- a/packages/nc-gui/components/cell/DatePicker.vue +++ b/packages/nc-gui/components/cell/DatePicker.vue @@ -144,11 +144,10 @@ watch(editable, (nextValue) => { const placeholder = computed(() => { if ( ((isForm.value || isExpandedForm.value) && !isDateInvalid.value) || - (isGrid.value && !showNull.value && !isDateInvalid.value && !isSystemColumn(columnMeta.value) && active.value) + (isGrid.value && !showNull.value && !isDateInvalid.value && !isSystemColumn(columnMeta.value) && active.value) || + isEditColumn.value ) { return dateFormat.value - } else if (isEditColumn.value && (modelValue === '' || modelValue === null)) { - return t('labels.optional') } else if (modelValue === null && showNull.value) { return t('general.null').toUpperCase() } else if (isDateInvalid.value) { @@ -300,8 +299,9 @@ function handleSelectDate(value?: dayjs.Dayjs) { :overlay-class-name="`${randomClass} nc-picker-date ${open ? 'active' : ''} !min-w-[260px]`" >
@@ -354,7 +354,9 @@ function handleSelectDate(value?: dayjs.Dayjs) { diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 78cd8e3c1b..b7e98b446e 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -1,6 +1,6 @@ diff --git a/packages/nc-gui/components/cell/Decimal.vue b/packages/nc-gui/components/cell/Decimal.vue index 19c1c8ed18..92930c82fb 100644 --- a/packages/nc-gui/components/cell/Decimal.vue +++ b/packages/nc-gui/components/cell/Decimal.vue @@ -40,6 +40,8 @@ const displayValue = computed(() => { if (isNaN(Number(_vModel.value))) return null + if (meta.value.isLocaleString) return (+Number(_vModel.value).toFixed(meta.value.precision ?? 1)).toLocaleString() + return Number(_vModel.value).toFixed(meta.value.precision ?? 1) }) @@ -102,7 +104,7 @@ watch(isExpandedFormOpen, () => { class="nc-cell-field outline-none py-1 border-none rounded-md w-full h-full" type="number" :step="precision" - :placeholder="placeholder !== undefined ? placeholder : isEditColumn ? $t('labels.optional') : ''" + :placeholder="placeholder" style="letter-spacing: 0.06rem" @blur="editEnabled = false" @keydown.down.stop="onKeyDown" diff --git a/packages/nc-gui/components/cell/Duration.vue b/packages/nc-gui/components/cell/Duration.vue index de4140d2bc..3439a6b414 100644 --- a/packages/nc-gui/components/cell/Duration.vue +++ b/packages/nc-gui/components/cell/Duration.vue @@ -10,8 +10,6 @@ const { modelValue, showValidationError = true } = defineProps() const emit = defineEmits(['update:modelValue']) -const { t } = useI18n() - const { showNull } = useGlobal() const column = inject(ColumnInj) @@ -30,9 +28,7 @@ const isEdited = ref(false) const durationType = computed(() => parseProp(column?.value?.meta)?.duration || 0) -const durationPlaceholder = computed(() => - isEditColumn.value ? `(${t('labels.optional')})` : durationOptions[durationType.value].title, -) +const durationPlaceholder = computed(() => durationOptions[durationType.value].title) const localState = computed({ get: () => convertMS2Duration(modelValue, durationType.value), diff --git a/packages/nc-gui/components/cell/Email.vue b/packages/nc-gui/components/cell/Email.vue index 2c21e641f3..6af36304fa 100644 --- a/packages/nc-gui/components/cell/Email.vue +++ b/packages/nc-gui/components/cell/Email.vue @@ -81,7 +81,6 @@ watch( :ref="focus" v-model="vModel" class="nc-cell-field w-full outline-none py-1" - :placeholder="isEditColumn ? $t('labels.optional') : ''" @blur="editEnabled = false" @keydown.down.stop @keydown.left.stop @@ -98,7 +97,7 @@ watch( class="nc-cell-field outline-none px-1 border-none w-full h-full" type="number" step="0.1" - :placeholder="isEditColumn ? $t('labels.optional') : ''" @blur="editEnabled = false" @keydown.down.stop @keydown.left.stop diff --git a/packages/nc-gui/components/cell/GeoData.vue b/packages/nc-gui/components/cell/GeoData.vue index fec80eb34a..24394fae4c 100644 --- a/packages/nc-gui/components/cell/GeoData.vue +++ b/packages/nc-gui/components/cell/GeoData.vue @@ -151,25 +151,33 @@ const openInOSM = () => { v-if="isLoading" :class="{ 'animate-infinite animate-spin text-gray-500': isLoading }" /> - {{ $t('labels.currentLocation') }} + +
+ {{ $t('labels.currentLocation') }} +
+
- {{ $t('activity.map.openInOpenStreetMap') }} - {{ $t('activity.map.openInGoogleMaps') }} + +
+ {{ $t('activity.map.openInOpenStreetMap') }} +
+
+ +
+ {{ $t('activity.map.openInGoogleMaps') }} +
+
- {{ $t('general.cancel') }} - {{ $t('general.submit') }} + {{ $t('general.cancel') }} + {{ + $t('general.submit') + }}
@@ -179,7 +187,10 @@ const openInOSM = () => { diff --git a/packages/nc-gui/components/cell/MultiSelect.vue b/packages/nc-gui/components/cell/MultiSelect.vue index bfcf05b774..ec1aab31af 100644 --- a/packages/nc-gui/components/cell/MultiSelect.vue +++ b/packages/nc-gui/components/cell/MultiSelect.vue @@ -469,7 +469,6 @@ const onFocus = () => { v-model:value="vModel" mode="multiple" class="w-full overflow-hidden" - :placeholder="isEditColumn ? $t('labels.optional') : ''" :bordered="false" clear-icon :show-search="!isMobileMode" diff --git a/packages/nc-gui/components/cell/Percent.vue b/packages/nc-gui/components/cell/Percent.vue index cedb175863..76dabe449f 100644 --- a/packages/nc-gui/components/cell/Percent.vue +++ b/packages/nc-gui/components/cell/Percent.vue @@ -145,7 +145,7 @@ const onTabPress = (e: KeyboardEvent) => { v-model="vModel" class="nc-cell-field w-full !border-none !outline-none focus:ring-0 py-1" :type="inputType" - :placeholder="placeholder !== undefined ? placeholder : isEditColumn ? $t('labels.optional') : ''" + :placeholder="placeholder" @blur="onBlur" @focus="onFocus" @keydown.down.stop diff --git a/packages/nc-gui/components/cell/PhoneNumber.vue b/packages/nc-gui/components/cell/PhoneNumber.vue index be6c2309a2..b249487190 100644 --- a/packages/nc-gui/components/cell/PhoneNumber.vue +++ b/packages/nc-gui/components/cell/PhoneNumber.vue @@ -65,7 +65,6 @@ watch( :ref="focus" v-model="vModel" class="nc-cell-field w-full outline-none py-1" - :placeholder="isEditColumn ? $t('labels.optional') : ''" @blur="editEnabled = false" @keydown.down.stop @keydown.left.stop @@ -80,7 +79,7 @@ watch( +import type { UserFieldRecordType } from 'nocodb-sdk' + interface Props { - modelValue?: string | null + modelValue?: UserFieldRecordType[] | UserFieldRecordType | string | null } defineProps() diff --git a/packages/nc-gui/components/cell/RichText.vue b/packages/nc-gui/components/cell/RichText.vue index 1c6e2f4f7d..bc7d346a2d 100644 --- a/packages/nc-gui/components/cell/RichText.vue +++ b/packages/nc-gui/components/cell/RichText.vue @@ -40,16 +40,28 @@ const rowHeight = inject(RowHeightInj, ref(1 as const)) const readOnlyCell = inject(ReadonlyInj, ref(false)) +const isEditColumn = inject(EditColumnInj, ref(false)) + const isForm = inject(IsFormInj, ref(false)) const isGrid = inject(IsGridInj, ref(false)) const isSurveyForm = inject(IsSurveyFormInj, ref(false)) +const isGallery = inject(IsGalleryInj, ref(false)) + +const isKanban = inject(IsKanbanInj, ref(false)) + const isFocused = ref(false) const keys = useMagicKeys() +const localRowHeight = computed(() => { + if (readOnlyCell.value && !isExpandedFormOpen.value && (isGallery.value || isKanban.value)) return 6 + + return rowHeight.value +}) + const shouldShowLinkOption = computed(() => { return isFormField.value ? isFocused.value : true }) @@ -155,7 +167,7 @@ const editor = useEditor({ .turndown(editor.getHTML().replaceAll(/

<\/p>/g, '
')) .replaceAll(/\n\n
\n\n/g, '
\n\n') - vModel.value = isFormField.value && markdown === '
' ? '' : markdown + vModel.value = markdown === '
' ? '' : markdown }, editable: !props.readOnly, autofocus: props.autofocus, @@ -220,7 +232,7 @@ if (isFormField.value) { } onMounted(() => { - if (fullMode.value || isFormField.value || isForm.value) { + if (fullMode.value || isFormField.value || isForm.value || isEditColumn.value) { setEditorContent(vModel.value, true) if (fullMode.value || isSurveyForm.value) { @@ -320,8 +332,8 @@ onClickOutside(editorDom, (e) => { 'mt-2.5 flex-grow': fullMode, 'scrollbar-thin scrollbar-thumb-gray-200 scrollbar-track-transparent': !fullMode || (!fullMode && isExpandedFormOpen), 'flex-grow': isExpandedFormOpen, - [`!overflow-hidden nc-truncate nc-line-clamp-${rowHeightTruncateLines(rowHeight)}`]: - !fullMode && readOnly && rowHeight && !isExpandedFormOpen && !isForm, + [`!overflow-hidden nc-truncate nc-line-clamp-${rowHeightTruncateLines(localRowHeight)}`]: + !fullMode && readOnly && localRowHeight && !isExpandedFormOpen && !isForm, }" @keydown.alt.enter.stop @keydown.shift.enter.stop @@ -393,6 +405,17 @@ onClickOutside(editorDom, (e) => { } } } + &.allow-vertical-resize:not(.readonly) { + .ProseMirror { + @apply nc-scrollbar-thin; + + overflow-y: auto; + overflow-x: hidden; + resize: vertical; + min-width: 100%; + max-height: min(800px, calc(100vh - 200px)) !important; + } + } } .nc-rich-text-full { diff --git a/packages/nc-gui/components/cell/RichText/LinkOptions.vue b/packages/nc-gui/components/cell/RichText/LinkOptions.vue index 5c89009095..49c0f7ef96 100644 --- a/packages/nc-gui/components/cell/RichText/LinkOptions.vue +++ b/packages/nc-gui/components/cell/RichText/LinkOptions.vue @@ -11,6 +11,7 @@ const emits = defineEmits(['blur']) interface Props { editor: Editor isFormField?: boolean + isComment?: boolean } const { editor, isFormField } = toRefs(props) @@ -164,6 +165,9 @@ const openLink = () => { const onMountLinkOptions = (e) => { if (e?.popper?.style) { + if (props.isComment) { + e.popper.style.left = '-10%' + } e.popper.style.width = '95%' } } @@ -233,14 +237,6 @@ const tabIndex = computed(() => { -

-
-
diff --git a/packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue b/packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue index 7d48b3b472..7f9af40d3c 100644 --- a/packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue +++ b/packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue @@ -27,6 +27,8 @@ const props = withDefaults(defineProps(), { const { editor, embedMode, isFormField, hiddenOptions } = toRefs(props) +const isEditColumn = inject(EditColumnInj, ref(false)) + const cmdOrCtrlKey = computed(() => { return isMac() ? '⌘' : 'CTRL' }) @@ -108,6 +110,7 @@ const showDivider = (options: RichTextBubbleMenuOptions[]) => { 'flex bg-gray-100 px-1 py-1': !isFormField, 'embed-mode': embedMode, 'full-mode': !embedMode, + 'edit-column-mode': isEditColumn, }" > @@ -172,7 +175,7 @@ const showDivider = (options: RichTextBubbleMenuOptions[]) => { - + - + { v-else ref="aselect" v-model:value="vModel" - class="w-full overflow-hidden xs:min-h-12" + class="w-full overflow-hidden" :class="{ 'caret-transparent': !hasEditRoles }" - :placeholder="isEditColumn ? $t('labels.optional') : ''" :allow-clear="!column.rqd && editAllowed" :bordered="false" :open="isOpen && editAllowed" diff --git a/packages/nc-gui/components/cell/Text.vue b/packages/nc-gui/components/cell/Text.vue index d7b4b659d1..235b25689a 100644 --- a/packages/nc-gui/components/cell/Text.vue +++ b/packages/nc-gui/components/cell/Text.vue @@ -35,7 +35,6 @@ const focus: VNodeRef = (el) => :ref="focus" v-model="vModel" class="nc-cell-field h-full w-full outline-none py-1 bg-transparent" - :placeholder="isEditColumn ? $t('labels.optional') : ''" @blur="editEnabled = false" @keydown.down.stop @keydown.left.stop diff --git a/packages/nc-gui/components/cell/TextArea.vue b/packages/nc-gui/components/cell/TextArea.vue index 6b9ea05d72..ed45452691 100644 --- a/packages/nc-gui/components/cell/TextArea.vue +++ b/packages/nc-gui/components/cell/TextArea.vue @@ -21,6 +21,10 @@ const isForm = inject(IsFormInj, ref(false)) const isGrid = inject(IsGridInj, ref(false)) +const isGallery = inject(IsGalleryInj, ref(false)) + +const isKanban = inject(IsKanbanInj, ref(false)) + const readOnly = inject(ReadonlyInj, ref(false)) const { showNull } = useGlobal() @@ -60,6 +64,12 @@ const height = computed(() => { return rowHeight.value * 36 }) +const localRowHeight = computed(() => { + if (readOnly.value && !isExpandedFormOpen.value && (isGallery.value || isKanban.value)) return 6 + + return rowHeight.value +}) + const isVisible = ref(false) const inputWrapperRef = ref(null) @@ -199,16 +209,15 @@ watch(inputWrapperRef, () => { >
{ class="w-full cursor-pointer nc-readonly-rich-text-wrapper" :class="{ 'nc-readonly-rich-text-grid ': !isExpandedFormOpen && !isForm, - 'nc-readonly-rich-text-sort-height': rowHeight === 1 && !isExpandedFormOpen && !isForm, + 'nc-readonly-rich-text-sort-height': localRowHeight === 1 && !isExpandedFormOpen && !isForm, }" :style="{ - maxHeight: isForm ? undefined : isExpandedFormOpen ? `${height}px` : `${21 * rowHeightTruncateLines(rowHeight)}px`, - minHeight: isForm ? undefined : isExpandedFormOpen ? `${height}px` : `${21 * rowHeightTruncateLines(rowHeight)}px`, + maxHeight: isForm ? undefined : isExpandedFormOpen ? `${height}px` : `${21 * rowHeightTruncateLines(localRowHeight)}px`, + minHeight: isForm ? undefined : isExpandedFormOpen ? `${height}px` : `${21 * rowHeightTruncateLines(localRowHeight)}px`, }" @dblclick="onExpand" @keydown.enter="onExpand" @@ -246,8 +255,8 @@ watch(inputWrapperRef, () => { }" :style="{ minHeight: isForm ? '117px' : `${height}px`, + maxHeight: 'min(800px, calc(100vh - 200px))', }" - :placeholder="isEditColumn ? $t('labels.optional') : ''" :disabled="readOnly" @blur="editEnabled = false" @keydown.alt.enter.stop @@ -266,12 +275,12 @@ watch(inputWrapperRef, () => { @@ -281,7 +290,7 @@ watch(inputWrapperRef, () => { import dayjs from 'dayjs' -import { isSystemColumn, isValidTimeFormat } from 'nocodb-sdk' +import { isSystemColumn } from 'nocodb-sdk' interface Props { modelValue?: string | null | undefined @@ -140,11 +140,10 @@ watch(editable, (nextValue) => { const placeholder = computed(() => { if ( ((isForm.value || isExpandedForm.value) && !isTimeInvalid.value) || - (isGrid.value && !showNull.value && !isTimeInvalid.value && !isSystemColumn(column.value) && active.value) + (isGrid.value && !showNull.value && !isTimeInvalid.value && !isSystemColumn(column.value) && active.value) || + isEditColumn.value ) { - return 'HH:mm' - } else if (isEditColumn.value && (modelValue === '' || modelValue === null)) { - return t('labels.optional') + return parseProp(column.value.meta).is12hrFormat ? 'hh:mm AM' : 'HH:mm' } else if (modelValue === null && showNull.value) { return t('general.null').toUpperCase() } else if (isTimeInvalid.value) { @@ -212,6 +211,12 @@ const handleKeydown = (e: KeyboardEvent, _open?: boolean) => { default: if (!_open && /^[0-9a-z]$/i.test(e.key)) { open.value = true + const targetEl = e.target as HTMLInputElement + const value = targetEl.value + + nextTick(() => { + targetEl.value = value + }) } } } @@ -256,12 +261,18 @@ const handleUpdateValue = (e: Event) => { return } - if (targetValue.length > 5) { - targetValue = targetValue.slice(0, 5) - } + targetValue = parseProp(column.value.meta).is12hrFormat + ? targetValue + .trim() + .toUpperCase() + .replace(/(AM|PM)$/, ' $1') + .replace(/\s+/g, ' ') + : targetValue.trim() - if (isValidTimeFormat(targetValue, 'HH:mm')) { - tempDate.value = dayjs(`${dayjs().format('YYYY-MM-DD')} ${targetValue}`) + const parsedDate = dayjs(targetValue, parseProp(column.value.meta).is12hrFormat ? 'hh:mm A' : 'HH:mm') + + if (parsedDate.isValid()) { + tempDate.value = dayjs(`${dayjs().format('YYYY-MM-DD')} ${parsedDate.format('HH:mm')}`) } } @@ -284,6 +295,8 @@ function handleSelectTime(value?: dayjs.Dayjs) { open.value = false } + +const cellValue = computed(() => localState.value?.format(parseProp(column.value.meta).is12hrFormat ? 'hh:mm A' : 'HH:mm') ?? '') diff --git a/packages/nc-gui/components/cell/attachment/Image.vue b/packages/nc-gui/components/cell/attachment/Image.vue index 370256276d..58a6d6dcc7 100644 --- a/packages/nc-gui/components/cell/attachment/Image.vue +++ b/packages/nc-gui/components/cell/attachment/Image.vue @@ -14,7 +14,7 @@ const onError = () => index.value++