Browse Source

Merge pull request #7788 from nocodb/develop

pull/7789/head 0.204.4
github-actions[bot] 4 months ago committed by GitHub
parent
commit
71af871904
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 47
      packages/nc-gui/app.vue
  2. 10
      packages/nc-gui/assets/nc-icons/link.svg
  3. 147
      packages/nc-gui/components/cell/RichText.vue
  4. 248
      packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue
  5. 2
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  6. 2
      packages/nc-gui/components/dlg/TableDuplicate.vue
  7. 4
      packages/nc-gui/components/general/ColorPicker.vue
  8. 16
      packages/nc-gui/components/general/FormBanner.vue
  9. 12
      packages/nc-gui/components/general/ImageCropper.vue
  10. 146
      packages/nc-gui/components/nc/ErrorBoundary.vue
  11. 25
      packages/nc-gui/components/project/AllTables.vue
  12. 2
      packages/nc-gui/components/smartsheet/Cell.vue
  13. 1771
      packages/nc-gui/components/smartsheet/Form.vue
  14. 1
      packages/nc-gui/components/smartsheet/calendar/index.vue
  15. 76
      packages/nc-gui/components/smartsheet/form/LimitOptions.vue
  16. 273
      packages/nc-gui/composables/useCalendarViewStore.ts
  17. 8
      packages/nc-gui/composables/useExpandedFormStore.ts
  18. 71
      packages/nc-gui/composables/useSharedView.ts
  19. 250
      packages/nc-gui/composables/useViewGroupBy.ts
  20. 7
      packages/nc-gui/lang/ar.json
  21. 7
      packages/nc-gui/lang/bn_IN.json
  22. 7
      packages/nc-gui/lang/cs.json
  23. 7
      packages/nc-gui/lang/da.json
  24. 7
      packages/nc-gui/lang/de.json
  25. 9
      packages/nc-gui/lang/en.json
  26. 7
      packages/nc-gui/lang/es.json
  27. 7
      packages/nc-gui/lang/eu.json
  28. 7
      packages/nc-gui/lang/fa.json
  29. 7
      packages/nc-gui/lang/fi.json
  30. 7
      packages/nc-gui/lang/fr.json
  31. 7
      packages/nc-gui/lang/he.json
  32. 7
      packages/nc-gui/lang/hi.json
  33. 7
      packages/nc-gui/lang/hr.json
  34. 7
      packages/nc-gui/lang/id.json
  35. 7
      packages/nc-gui/lang/it.json
  36. 7
      packages/nc-gui/lang/ja.json
  37. 7
      packages/nc-gui/lang/ko.json
  38. 7
      packages/nc-gui/lang/lv.json
  39. 7
      packages/nc-gui/lang/nl.json
  40. 7
      packages/nc-gui/lang/no.json
  41. 7
      packages/nc-gui/lang/pl.json
  42. 7
      packages/nc-gui/lang/pt.json
  43. 7
      packages/nc-gui/lang/pt_BR.json
  44. 7
      packages/nc-gui/lang/ru.json
  45. 7
      packages/nc-gui/lang/sk.json
  46. 7
      packages/nc-gui/lang/sl.json
  47. 7
      packages/nc-gui/lang/sv.json
  48. 7
      packages/nc-gui/lang/th.json
  49. 7
      packages/nc-gui/lang/tr.json
  50. 7
      packages/nc-gui/lang/uk.json
  51. 7
      packages/nc-gui/lang/vi.json
  52. 7
      packages/nc-gui/lang/zh-Hans.json
  53. 7
      packages/nc-gui/lang/zh-Hant.json
  54. 10
      packages/nc-gui/lib/types.ts
  55. 9
      packages/nc-gui/package.json
  56. 37
      packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index.vue
  57. 59
      packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index/index.vue
  58. 55
      packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index/survey.vue
  59. 2
      packages/nc-gui/utils/iconUtils.ts
  60. 2
      packages/noco-docs/docs/130.automation/020.webhook/020.create-webhook.md
  61. BIN
      packages/noco-docs/static/img/v2-unannotated/automations/webhooks/create-webhook-2.png
  62. BIN
      packages/noco-docs/static/img/v2/automations/webhooks/create-webhook-2.png
  63. 2
      packages/nocodb/package.json
  64. 103
      packages/nocodb/src/controllers/calendars-datas.controller.ts
  65. 25
      packages/nocodb/src/controllers/data-alias.controller.ts
  66. 1
      packages/nocodb/src/controllers/data-table.controller.ts
  67. 15
      packages/nocodb/src/controllers/public-datas.controller.ts
  68. 12
      packages/nocodb/src/db/BaseModelSqlv2.ts
  69. 20
      packages/nocodb/src/helpers/formulaHelpers.ts
  70. 4
      packages/nocodb/src/models/BaseUser.ts
  71. 2
      packages/nocodb/src/models/CalendarViewColumn.ts
  72. 37
      packages/nocodb/src/models/Column.ts
  73. 20
      packages/nocodb/src/models/Filter.ts
  74. 108
      packages/nocodb/src/models/FormView.ts
  75. 2
      packages/nocodb/src/models/GalleryViewColumn.ts
  76. 4
      packages/nocodb/src/models/GridViewColumn.ts
  77. 8
      packages/nocodb/src/models/Model.ts
  78. 8
      packages/nocodb/src/models/Sort.ts
  79. 10
      packages/nocodb/src/models/View.ts
  80. 5
      packages/nocodb/src/modules/datas/datas.module.ts
  81. 302
      packages/nocodb/src/schema/swagger-v2.json
  82. 885
      packages/nocodb/src/schema/swagger.json
  83. 230
      packages/nocodb/src/services/calendar-datas.service.ts
  84. 121
      packages/nocodb/src/services/datas.service.ts
  85. 79
      packages/nocodb/src/services/public-datas.service.ts
  86. 45
      packages/nocodb/tests/unit/rest/tests/viewRow.test.ts
  87. 61
      pnpm-lock.yaml
  88. 4
      scripts/pkg-executable/package.json
  89. 32
      tests/playwright/pages/Dashboard/Form/index.ts
  90. 1
      tests/playwright/tests/db/views/viewForm.spec.ts

47
packages/nc-gui/app.vue

@ -1,6 +1,5 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { extractSdkResponseErrorMsg } from './utils'
import ErrorBoundary from './components/nc/ErrorBoundary.vue'
import { applyNonSelectable, computed, isEeUI, isMac, useCommandPalette, useRouter, useTheme } from '#imports'
import type { CommandPaletteType } from '~/lib'
@ -98,49 +97,18 @@ onMounted(() => {
refreshCommandPalette()
})
})
let errorCount = 0
const handleError = async (error, clearError) => {
console.error('UI ERROR', error.value)
// if error is api error, show toast message with error message
if (error.value?.response) {
message.warn(await extractSdkResponseErrorMsg(error.value))
} else {
// else show generic error message
message.warn('Something went wrong. Please reload the page if page is not functioning properly.')
}
clearError()
// if error count is more than 3 within 3 second, navigate to home
// since it's likely endless loop of errors due to some UI issue in certain page
errorCount++
if (errorCount > 3) {
router.push('/')
}
// reset error count after 1 second
setTimeout(() => {
errorCount = 0
}, 3000)
}
</script>
<template>
<a-config-provider>
<NuxtLayout :name="disableBaseLayout ? false : 'base'">
<NuxtErrorBoundary>
<ErrorBoundary>
<NuxtPage :key="key" :transition="false" />
<!-- on error, clear error and show toast message -->
<template #error="{ error, clearError }">
{{ handleError(error, clearError) }}
</template>
</NuxtErrorBoundary>
</ErrorBoundary>
</NuxtLayout>
</a-config-provider>
<NuxtErrorBoundary>
<ErrorBoundary>
<div>
<!-- Command Menu -->
<CmdK
@ -158,10 +126,5 @@ const handleError = async (error, clearError) => {
<!-- Documentation. Integrated NocoDB Docs directly inside the Product -->
<CmdJ />
</div>
<!-- on error, clear error and show toast message -->
<template #error="{ error, clearError }">
{{ handleError(error, clearError) }}
</template>
</NuxtErrorBoundary>
</ErrorBoundary>
</template>

10
packages/nc-gui/assets/nc-icons/link.svg

@ -1,4 +1,8 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.6665 9.16668C6.95281 9.54943 7.31808 9.86613 7.73754 10.0953C8.157 10.3245 8.62084 10.4608 9.0976 10.4949C9.57437 10.529 10.0529 10.4603 10.5007 10.2932C10.9486 10.1261 11.3552 9.86473 11.6932 9.52668L13.6932 7.52668C14.3004 6.89801 14.6363 6.056 14.6288 5.18201C14.6212 4.30802 14.2706 3.47198 13.6526 2.85395C13.0345 2.23592 12.1985 1.88536 11.3245 1.87777C10.4505 1.87017 9.60851 2.20615 8.97984 2.81335L7.83317 3.95335" stroke="#3366FF" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.33347 7.83332C9.04716 7.45057 8.68189 7.13387 8.26243 6.90469C7.84297 6.67552 7.37913 6.53924 6.90237 6.5051C6.4256 6.47095 5.94707 6.53974 5.49924 6.7068C5.0514 6.87386 4.64472 7.13527 4.3068 7.47332L2.3068 9.47332C1.69961 10.102 1.36363 10.944 1.37122 11.818C1.37881 12.692 1.72938 13.528 2.3474 14.146C2.96543 14.7641 3.80147 15.1146 4.67546 15.1222C5.54945 15.1298 6.39146 14.7938 7.02013 14.1867L8.16013 13.0467" stroke="#3366FF" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<path
d="M6.6665 9.16668C6.95281 9.54943 7.31808 9.86613 7.73754 10.0953C8.157 10.3245 8.62084 10.4608 9.0976 10.4949C9.57437 10.529 10.0529 10.4603 10.5007 10.2932C10.9486 10.1261 11.3552 9.86473 11.6932 9.52668L13.6932 7.52668C14.3004 6.89801 14.6363 6.056 14.6288 5.18201C14.6212 4.30802 14.2706 3.47198 13.6526 2.85395C13.0345 2.23592 12.1985 1.88536 11.3245 1.87777C10.4505 1.87017 9.60851 2.20615 8.97984 2.81335L7.83317 3.95335"
stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
<path
d="M9.33347 7.83332C9.04716 7.45057 8.68189 7.13387 8.26243 6.90469C7.84297 6.67552 7.37913 6.53924 6.90237 6.5051C6.4256 6.47095 5.94707 6.53974 5.49924 6.7068C5.0514 6.87386 4.64472 7.13527 4.3068 7.47332L2.3068 9.47332C1.69961 10.102 1.36363 10.944 1.37122 11.818C1.37881 12.692 1.72938 13.528 2.3474 14.146C2.96543 14.7641 3.80147 15.1146 4.67546 15.1222C5.54945 15.1298 6.39146 14.7938 7.02013 14.1867L8.16013 13.0467"
stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

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

@ -6,6 +6,7 @@ import TurndownService from 'turndown'
import { marked } from 'marked'
import { generateJSON } from '@tiptap/html'
import Underline from '@tiptap/extension-underline'
import Placeholder from '@tiptap/extension-placeholder'
import { TaskItem } from '@/helpers/dbTiptapExtensions/task-item'
import { Link } from '@/helpers/dbTiptapExtensions/links'
import { IsExpandedFormOpenInj, IsFormInj, ReadonlyInj, RowHeightInj } from '#imports'
@ -16,6 +17,10 @@ const props = defineProps<{
syncValueChange?: boolean
showMenu?: boolean
fullMode?: boolean
isFormField?: boolean
autofocus?: boolean
placeholder?: string
renderAsText?: boolean
}>()
const emits = defineEmits(['update:value'])
@ -28,6 +33,8 @@ const readOnlyCell = inject(ReadonlyInj, ref(false))
const isForm = inject(IsFormInj, ref(false))
const isFocused = ref(false)
const turndownService = new TurndownService({})
turndownService.addRule('lineBreak', {
@ -105,13 +112,19 @@ const editorDom = ref<HTMLElement | null>(null)
const vModel = useVModel(props, 'value', emits, { defaultValue: '' })
const tiptapExtensions = [
StarterKit,
StarterKit.configure({
heading: props.isFormField ? false : undefined,
}),
TaskList,
TaskItem.configure({
nested: true,
}),
Underline,
Link,
Placeholder.configure({
emptyEditorClass: 'is-editor-empty',
placeholder: props.placeholder,
}),
]
const editor = useEditor({
@ -121,9 +134,18 @@ const editor = useEditor({
.turndown(editor.getHTML().replaceAll(/<p><\/p>/g, '<br />'))
.replaceAll(/\n\n<br \/>\n\n/g, '<br>\n\n')
vModel.value = markdown
vModel.value = props.isFormField && markdown === '<br />' ? '' : markdown
},
editable: !props.readOnly,
autofocus: props.autofocus,
onFocus: () => {
isFocused.value = true
},
onBlur: (e) => {
if (!(e?.event?.relatedTarget as HTMLElement)?.closest('.bubble-menu, .nc-textarea-rich-editor')) {
isFocused.value = false
}
},
})
const setEditorContent = (contentMd: any, focusEndOfDoc?: boolean) => {
@ -153,21 +175,43 @@ const setEditorContent = (contentMd: any, focusEndOfDoc?: boolean) => {
}
if (props.syncValueChange) {
watch(vModel, () => {
watch([vModel, editor], () => {
setEditorContent(vModel.value)
})
}
if (props.isFormField) {
watch([props, editor], () => {
if (props.readOnly) {
editor.value?.setEditable(false)
} else {
editor.value?.setEditable(true)
}
})
}
watch(editorDom, () => {
if (!editorDom.value) return
setEditorContent(vModel.value, true)
if (props.isFormField) return
// Focus editor after editor is mounted
setTimeout(() => {
editor.value?.chain().focus().run()
}, 50)
})
useEventListener(
editorDom,
'focusout',
(e: FocusEvent) => {
if (!(e?.relatedTarget as HTMLElement)?.closest('.bubble-menu, .nc-textarea-rich-editor')) {
isFocused.value = false
}
},
true,
)
</script>
<template>
@ -177,34 +221,51 @@ watch(editorDom, () => {
'flex flex-col flex-grow nc-rich-text-full': fullMode,
'nc-rich-text-embed flex flex-col pl-1 w-full': !fullMode,
'readonly': readOnly,
'nc-form-rich-text-field !p-0': isFormField,
}"
:tabindex="readOnlyCell ? -1 : 0"
:tabindex="readOnlyCell || isFormField ? -1 : 0"
>
<div
v-if="showMenu && !readOnly"
class="absolute top-0 right-0.5 xs:hidden"
:class="{
'max-w-[calc(100%_-_198px)] flex justify-end rounded-tr-2xl overflow-hidden': fullMode,
}"
>
<div class="nc-longtext-scrollbar">
<CellRichTextSelectedBubbleMenu v-if="editor" :editor="editor" embed-mode />
</div>
<div v-if="renderAsText" class="truncate">
<span v-if="editor"> {{ editor?.getText() ?? '' }}</span>
</div>
<CellRichTextSelectedBubbleMenuPopup v-if="editor" :editor="editor" />
<CellRichTextLinkOptions v-if="editor" :editor="editor" />
<EditorContent
ref="editorDom"
:editor="editor"
class="flex flex-col nc-textarea-rich-editor w-full"
:class="{
'mt-2.5 flex-grow': fullMode,
'nc-scrollbar-md': !fullMode || (!fullMode && isExpandedFormOpen),
'flex-grow': isExpandedFormOpen,
[`!overflow-hidden children:line-clamp-${rowHeight}`]:
!fullMode && readOnly && rowHeight && !isExpandedFormOpen && !isForm,
}"
/>
<template v-else>
<div
v-if="showMenu && !readOnly && !isFormField"
class="absolute top-0 right-0.5 xs:hidden"
:class="{
'max-w-[calc(100%_-_198px)] flex justify-end rounded-tr-2xl overflow-hidden': fullMode,
}"
>
<div class="nc-longtext-scrollbar">
<CellRichTextSelectedBubbleMenu v-if="editor" :editor="editor" embed-mode :is-form-field="isFormField" />
</div>
</div>
<CellRichTextSelectedBubbleMenuPopup v-if="editor && !isFormField" :editor="editor" />
<CellRichTextLinkOptions v-if="editor" :editor="editor" />
<EditorContent
ref="editorDom"
:editor="editor"
class="flex flex-col nc-textarea-rich-editor w-full"
:class="{
'mt-2.5 flex-grow': fullMode,
'nc-scrollbar-md': !fullMode || (!fullMode && isExpandedFormOpen),
'flex-grow': isExpandedFormOpen,
[`!overflow-hidden children:line-clamp-${rowHeight}`]:
!fullMode && readOnly && rowHeight && !isExpandedFormOpen && !isForm,
}"
/>
<div v-if="isFormField && !readOnly">
<div
class="overflow-hidden"
:class="isFocused ? 'max-h-[50px]' : 'max-h-0'"
:style="{
transition: 'max-height 0.2s ease-in-out',
}"
>
<CellRichTextSelectedBubbleMenu v-if="editor" :editor="editor" embed-mode is-form-field />
</div>
</div>
</template>
</div>
</template>
@ -223,7 +284,28 @@ watch(editorDom, () => {
.nc-rich-text-embed {
.ProseMirror {
@apply !border-transparent max-h-full;
min-height: 8rem;
}
&:not(.nc-form-rich-text-field) {
.ProseMirror {
min-height: 8rem;
}
}
&.nc-form-rich-text-field {
.ProseMirror {
padding: 0;
}
&.readonly {
ul[data-type='taskList'] li input[type='checkbox'] {
background-color: #d5d5d9 !important;
&:not(:checked) {
@apply !border-gray-400;
}
&:focus {
box-shadow: none !important;
background-color: #d5d5d9 !important;
}
}
}
}
&.readonly {
.nc-textarea-rich-editor {
@ -256,6 +338,13 @@ watch(editorDom, () => {
}
.nc-textarea-rich-editor {
.tiptap p.is-editor-empty:first-child::before {
color: #6a7184;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
.ProseMirror {
@apply flex-grow pt-1 border-1 border-gray-200 rounded-lg;

248
packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue

@ -14,13 +14,12 @@ import MsFormatQuote from '~icons/material-symbols/format-quote'
interface Props {
editor: Editor
embedMode?: boolean
isFormField?: boolean
}
const props = defineProps<Props>()
const editor = computed(() => props.editor)
const embedMode = computed(() => props.embedMode)
const { editor, embedMode } = toRefs(props)
const cmdOrCtrlKey = computed(() => {
return isMac() ? '⌘' : 'CTRL'
@ -79,13 +78,15 @@ const onToggleLink = () => {
<template>
<div
class="bubble-menu flex flex-row gap-x-1 bg-gray-100 py-1 rounded-lg px-1"
class="bubble-menu flex-row gap-x-1 py-1 rounded-lg"
:class="{
'inline-flex !bg-transparent': isFormField,
'flex bg-gray-100 px-1': !isFormField,
'embed-mode': embedMode,
'full-mode': !embedMode,
}"
>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
@ -99,13 +100,14 @@ const onToggleLink = () => {
type="text"
:class="{ 'is-active': editor.isActive('bold') }"
:disabled="editor.isActive('codeBlock')"
:tabindex="isFormField ? -1 : 0"
@click="editor!.chain().focus().toggleBold().run()"
>
<MdiFormatBold />
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
@ -119,12 +121,13 @@ const onToggleLink = () => {
type="text"
:disabled="editor.isActive('codeBlock')"
:class="{ 'is-active': editor.isActive('italic') }"
:tabindex="isFormField ? -1 : 0"
@click=";(editor!.chain().focus() as any).toggleItalic().run()"
>
<MdiFormatItalic />
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
@ -139,12 +142,13 @@ const onToggleLink = () => {
type="text"
:class="{ 'is-active': editor.isActive('underline') }"
:disabled="editor.isActive('codeBlock')"
:tabindex="isFormField ? -1 : 0"
@click="editor!.chain().focus().toggleUnderline().run()"
>
<MdiFormatUnderline />
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
@ -158,155 +162,160 @@ const onToggleLink = () => {
type="text"
:class="{ 'is-active': editor.isActive('strike') }"
:disabled="editor.isActive('codeBlock')"
:tabindex="isFormField ? -1 : 0"
@click="editor!.chain().focus().toggleStrike().run()"
>
<MdiFormatStrikeThrough />
</NcButton>
</NcTooltip>
<NcTooltip v-if="embedMode">
<template #title> {{ $t('general.code') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('codeBlock') }"
@click="editor!.chain().focus().toggleCodeBlock().run()"
>
<MsCode />
</NcButton>
</NcTooltip>
<NcTooltip v-else :disabled="editor.isActive('codeBlock')">
<template #title> {{ $t('general.quote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('code') }"
:disabled="editor.isActive('codeBlock')"
@click="editor!.chain().focus().toggleCode().run()"
>
<MsFormatQuote />
</NcButton>
</NcTooltip>
<div class="divider"></div>
<template v-if="!isFormField">
<NcTooltip v-if="embedMode">
<template #title> {{ $t('general.code') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('codeBlock') }"
@click="editor!.chain().focus().toggleCodeBlock().run()"
>
<MsCode />
</NcButton>
</NcTooltip>
<NcTooltip v-else :disabled="editor.isActive('codeBlock')">
<template #title> {{ $t('general.quote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('code') }"
:disabled="editor.isActive('codeBlock')"
@click="editor!.chain().focus().toggleCode().run()"
>
<MsFormatQuote />
</NcButton>
</NcTooltip>
<div class="divider"></div>
<template v-if="embedMode">
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading1') }}
<template v-if="embedMode">
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading1') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 1</div>
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 1</div>
</div>
</template>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 1 }).run()"
>
<MsFormatH1 />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading2') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 2</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 2 }).run()"
>
<MsFormatH2 />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading3') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 3</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 3 }).run()"
>
<MsFormatH3 />
</NcButton>
</NcTooltip>
<div class="divider"></div>
</template>
<NcTooltip v-if="embedMode">
<template #title> {{ $t('labels.blockQuote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 1 }).run()"
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor!.chain().focus().toggleBlockquote().run()"
>
<MsFormatH1 />
<TablerBlockQuote class="-mt-0.25" />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading2') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 2</div>
</div>
</template>
<template #title> {{ $t('labels.bulletList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 2 }).run()"
:class="{ 'is-active': editor.isActive('bulletList') }"
@click="editor!.chain().focus().toggleBulletList().run()"
>
<MsFormatH2 />
<MdiFormatBulletList />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading3') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 3</div>
</div>
</template>
<template #title> {{ $t('labels.numberedList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 3 }).run()"
:class="{ 'is-active': editor.isActive('orderedList') }"
@click="editor!.chain().focus().toggleOrderedList().run()"
>
<MsFormatH3 />
<MdiFormatListNumber />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.taskList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('taskList') }"
@click="editor!.chain().focus().toggleTaskList().run()"
>
<MdiFormatListCheckbox />
</NcButton>
</NcTooltip>
<div class="divider"></div>
</template>
<NcTooltip v-if="embedMode">
<template #title> {{ $t('labels.blockQuote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor!.chain().focus().toggleBlockquote().run()"
>
<TablerBlockQuote class="-mt-0.25" />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.bulletList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('bulletList') }"
@click="editor!.chain().focus().toggleBulletList().run()"
>
<MdiFormatBulletList />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.numberedList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('orderedList') }"
@click="editor!.chain().focus().toggleOrderedList().run()"
>
<MdiFormatListNumber />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.taskList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('taskList') }"
@click="editor!.chain().focus().toggleTaskList().run()"
>
<MdiFormatListCheckbox />
</NcButton>
</NcTooltip>
<div class="divider"></div>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title> {{ $t('general.link') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('link') }"
:disabled="editor.isActive('codeBlock')"
:tabindex="isFormField ? -1 : 0"
@click="onToggleLink"
>
<div class="flex flex-row items-center px-0.5">
<GeneralIcon v-if="isFormField" icon="link2"></GeneralIcon>
<div v-else class="flex flex-row items-center px-0.5">
<MdiLink />
<div class="!text-xs !ml-1">{{ $t('general.link') }}</div>
</div>
@ -343,6 +352,9 @@ const onToggleLink = () => {
.bubble-menu.embed-mode {
@apply border-transparent !shadow-none;
}
.bubble-menu.form-field-mode {
@apply bg-transparent px-0;
}
.embed-mode.bubble-menu {
@apply !py-0 !my-0 !border-0;

2
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -284,6 +284,8 @@ const onProjectClick = async (base: NcProject, ignoreNavigation?: boolean, toggl
}
if (!isProjectPopulated) {
base.isLoading = false
const updatedProject = bases.value.get(base.id!)!
updatedProject.isLoading = false
}

2
packages/nc-gui/components/dlg/TableDuplicate.vue

@ -144,7 +144,7 @@ const isEaster = ref(false)
{{ $t('general.duplicate') }} {{ $t('objects.table') }}
</div>
<div class="mt-4">{{ $t('msg.warning.duplicateProject') }}</div>
<div class="mt-4">{{ $t('msg.warning.duplicateTable') }}</div>
<div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div>

4
packages/nc-gui/components/general/ColorPicker.vue

@ -117,6 +117,10 @@ watch(picked, (n, _o) => {
filter: brightness(90%);
-webkit-filter: brightness(90%);
}
.color-selector:focus.new-design {
outline: none;
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe;
}
.color-selector.selected.new-design {
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe;
}

16
packages/nc-gui/components/general/FormBanner.vue

@ -1,6 +1,8 @@
<script lang="ts" setup>
import type { AttachmentResType } from 'nocodb-sdk'
interface Props {
bannerImageUrl?: string | null
bannerImageUrl?: AttachmentResType
}
const { bannerImageUrl } = defineProps<Props>()
@ -9,7 +11,8 @@ const { getPossibleAttachmentSrc } = useAttachment()
<template>
<div
class="nc-form-banner-wrapper w-full mx-auto bg-white border-1 border-gray-200 rounded-2xl overflow-hidden"
class="nc-form-banner-wrapper w-full mx-auto rounded-2xl overflow-hidden"
:class="!bannerImageUrl ? 'shadow-sm' : ''"
:style="{ aspectRatio: 4 / 1 }"
>
<LazyCellAttachmentImage
@ -17,16 +20,15 @@ const { getPossibleAttachmentSrc } = useAttachment()
:srcs="getPossibleAttachmentSrc(parseProp(bannerImageUrl))"
class="nc-form-banner-image object-cover w-full"
/>
<!-- Todo: aspect ratio and cover image uploader and image cropper to crop image in fixed aspect ratio -->
<div v-else class="h-full flex items-stretch justify-between">
<div class="flex">
<div v-else class="h-full flex items-stretch justify-between bg-white">
<div class="flex -mt-1">
<img src="~assets/img/form-banner-left.png" alt="form-banner-left'" />
</div>
<div class="w-[91px] flex justify-center">
<img class="max-h-full self-center" src="~assets/img/icons/256x256.png" alt="form-banner-logo'" />
<img class="max-h-full self-center" src="~assets/img/icons/256x256.png" alt="form-banner-logo" />
</div>
<div class="flex justify-end">
<div class="flex justify-end -mb-1">
<img src="~assets/img/form-banner-right.png" alt="form-banner-left'" />
</div>
</div>

12
packages/nc-gui/components/general/ImageCropper.vue

@ -5,22 +5,21 @@ import 'vue-advanced-cropper/dist/theme.classic.css'
import type { AttachmentReqType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, useApi } from '#imports'
import type { ImageCropperConfig } from '~/lib'
interface Props {
imageConfig: {
src: string
type: string
name: string
}
cropperConfig: {
aspectRatio?: number
}
cropperConfig: ImageCropperConfig
uploadConfig?: {
path?: string
}
showCropper: boolean
}
const { imageConfig, cropperConfig, uploadConfig, ...props } = defineProps<Props>()
const emit = defineEmits(['update:showCropper', 'submit'])
const showCropper = useVModel(props, 'showCropper', emit)
@ -103,7 +102,10 @@ watch(showCropper, () => {
class="nc-cropper relative"
:src="imageConfig.src"
:auto-zoom="true"
:stencil-props="cropperConfig?.aspectRatio ? { aspectRatio: cropperConfig.aspectRatio } : {}"
:stencil-props="cropperConfig?.stencilProps || {}"
:min-height="cropperConfig?.minHeight"
:min-width="cropperConfig?.minWidth"
:image-restriction="cropperConfig?.imageRestriction"
/>
<div v-if="previewImage.src" class="result_preview">
<img :src="previewImage.src" alt="Preview Image" />

146
packages/nc-gui/components/nc/ErrorBoundary.vue

@ -0,0 +1,146 @@
<script lang="ts">
// modified version of default NuxtErrorBoundary component - https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/components/nuxt-error-boundary.ts
import { message } from 'ant-design-vue'
import { computed, onErrorCaptured, ref, useCopy, useNuxtApp } from '#imports'
export default {
emits: {
error(_error: unknown) {
return true
},
},
setup(_props, { emit }) {
const nuxtApp = useNuxtApp()
const error = ref()
const prevError = ref()
const errModal = computed(() => !!error.value)
const key = ref(0)
const isErrorExpanded = ref(false)
const { copy } = useCopy()
onErrorCaptured((err) => {
if (import.meta.client && (!nuxtApp.isHydrating || !nuxtApp.payload.serverRendered)) {
console.log('UI Error :', err)
emit('error', err)
error.value = err
return false
}
})
const copyError = async () => {
try {
if (error.value) await copy(`message: ${error.value.message}\n\n${error.value.stack}`)
message.info('Error message copied to clipboard.')
} catch (e) {
message.error('Something went wrong while copying to clipboard, please copy from browser console.')
}
}
const reload = () => {
prevError.value = error.value
error.value = null
key.value++
}
const navigateToHome = () => {
prevError.value = error.value
error.value = null
location.hash = '/'
location.reload()
}
return {
errModal,
error,
key,
isErrorExpanded,
prevError,
copyError,
reload,
navigateToHome,
}
},
}
</script>
<template>
<slot :key="key"></slot>
<slot name="error">
<NcModal v-model:visible="errModal" :class="{ active: errModal }" :centered="true" :closable="false" :footer="null">
<div class="w-full flex flex-col gap-1">
<h2 class="text-xl font-semibold">Oops! Something unexpected happened :/</h2>
<p class="mb-0">
<span
>Please report this error in our
<a href="https://discord.gg/8jX2GQn" target="_blank" rel="noopener noreferrer">Discord channel</a>. You can copy the
error message by clicking the "Copy" button below.</span
>
</p>
<span class="cursor-pointer" @click="isErrorExpanded = !isErrorExpanded"
>{{ isErrorExpanded ? 'Hide' : 'Show' }} details
<GeneralIcon
icon="arrowDown"
class="transition-transform transform duration-300"
:class="{
'rotate-180': isErrorExpanded,
}"
/></span>
<div
class="nc-error"
:class="{
active: isErrorExpanded,
}"
>
<div class="nc-left-vertical-bar"></div>
<div class="nc-error-content">
<span class="font-weight-bold">Message: {{ error.message }}</span>
<br />
<div class="text-gray-500 mt-2">{{ error.stack }}</div>
</div>
</div>
<div class="flex justify-end gap-2">
<NcButton size="small" type="secondary" @click="copyError">
<div class="flex items-center gap-1">
<GeneralIcon icon="copy" />
Copy Error
</div>
</NcButton>
<NcButton v-if="!prevError || error.message !== prevError.message" size="small" @click="reload">
<div class="flex items-center gap-1">
<GeneralIcon icon="reload" />
Reload
</div>
</NcButton>
<NcButton v-else size="small" @click="navigateToHome">
<div class="flex items-center gap-1">
<GeneralIcon icon="link" />
Home
</div>
</NcButton>
</div>
</div>
</NcModal>
</slot>
</template>
<style scoped lang="scss">
.nc-error {
@apply flex gap-2 mb-2 max-h-0;
white-space: pre;
transition: max-height 300ms linear;
&.active {
max-height: 250px;
}
.nc-left-vertical-bar {
@apply w-6px min-w-6px rounded min-h-full bg-gray-300;
}
.nc-error-content {
@apply min-w-0 overflow-auto pl-2 flex-shrink;
}
}
</style>

25
packages/nc-gui/components/project/AllTables.vue

@ -7,6 +7,8 @@ const { activeTables } = storeToRefs(useTablesStore())
const { openTable } = useTablesStore()
const { openedProject } = storeToRefs(useBases())
const { base } = useBase()
const isNewBaseModalOpen = ref(false)
const isDataSourceLimitReached = computed(() => Number(openedProject.value?.sources?.length) > 1)
@ -78,7 +80,12 @@ const onCreateBaseClick = () => {
<template>
<div class="nc-all-tables-view">
<div class="flex flex-row gap-x-6 pb-3 pt-6">
<div
class="flex flex-row gap-x-6 pb-3 pt-6"
:class="{
'pointer-events-none': base?.isLoading,
}"
>
<div
v-if="isUIAllowed('tableCreate')"
role="button"
@ -121,7 +128,21 @@ const onCreateBaseClick = () => {
</div>
</component>
</div>
<template v-if="activeTables.length">
<div
v-if="base?.isLoading"
class="flex items-center justify-center text-center"
:style="{
height: 'calc(100vh - var(--topbar-height) - 18rem)',
}"
>
<div>
<GeneralLoader size="xlarge" />
<div class="mt-2">
{{ $t('general.loading') }}
</div>
</div>
</div>
<template v-else-if="activeTables.length">
<div class="flex flex-row w-full text-gray-400 border-b-1 border-gray-50 py-3 px-2.5">
<div class="w-2/5">{{ $t('objects.table') }}</div>
<div class="w-1/5">{{ $t('general.source') }}</div>

2
packages/nc-gui/components/smartsheet/Cell.vue

@ -202,7 +202,7 @@ onUnmounted(() => {
{
'text-brand-500': isPrimary(column) && !props.virtual && !isForm && !isCalendar,
'nc-grid-numeric-cell-right': isGrid && isNumericField && !isEditColumnMenu && !isForm && !isExpandedFormOpen,
'h-10': isForm && !isSurveyForm && !isAttachment(column) && !props.virtual,
'h-10': isForm && !isSurveyForm && !isAttachment(column) && !isTextArea(column) && !isJSON(column) && !props.virtual,
'nc-grid-numeric-cell-left': (isForm && isNumericField && isExpandedFormOpen) || isEditColumnMenu,
'!min-h-30': isTextArea(column) && (isForm || isSurveyForm),
},

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

File diff suppressed because it is too large Load Diff

1
packages/nc-gui/components/smartsheet/calendar/index.vue

@ -86,7 +86,6 @@ const expandedFormRowState = ref<Record<string, any>>()
const expandRecord = (row: RowType, state?: Record<string, any>) => {
const rowId = extractPkFromRow(row.row, meta.value!.columns!)
if (rowId) {
router.push({
query: {

76
packages/nc-gui/components/smartsheet/form/LimitOptions.vue

@ -9,6 +9,7 @@ import { MetaInj, iconMap } from '#imports'
const props = defineProps<{
modelValue: FormFieldsLimitOptionsType[]
column: ColumnType
isRequired?: boolean
}>()
const emit = defineEmits(['update:modelValue'])
@ -121,6 +122,18 @@ async function onMove(_event: { moved: { newIndex: number; oldIndex: number; ele
vModel.value = [...vModel.value]
}
const showOrHideAll = (showAll: boolean) => {
if (props.isRequired && !showAll) {
return
}
vModel.value = vModel.value.map((o) => {
return {
...o,
show: showAll,
}
})
}
</script>
<template>
@ -147,13 +160,42 @@ async function onMove(_event: { moved: { newIndex: number; oldIndex: number; ele
</template>
</a-input>
</div>
<div v-if="vModel.length" class="flex items-stretch gap-2 pr-2 pl-3 py-1.5 rounded-t-lg border-1 border-b-0 border-gray-200">
<NcTooltip class="truncate max-w-full" :disabled="!isRequired">
<template #title> {{ $t('msg.info.preventHideAllOptions') }} </template>
<NcButton
type="secondary"
size="xxsmall"
class="!border-none !px-2 !text-xs !text-gray-500 !disabled:text-gray-300"
:disabled="isRequired || vModel.filter((o) => !o.show).length === vModel.length"
@click="showOrHideAll(false)"
>
Hide all
</NcButton>
</NcTooltip>
<div>
<NcButton
type="secondary"
size="xxsmall"
class="!border-none !px-2 !text-xs !text-gray-500 !disabled:text-gray-300"
:disabled="vModel.filter((o) => o.show).length === vModel.length"
@click="showOrHideAll(true)"
>
Show all
</NcButton>
</div>
</div>
<Draggable
v-if="vModel.length"
:model-value="vModel"
item-key="id"
handle=".nc-child-draggable-icon"
ghost-class="nc-form-field-limit-option-ghost"
class="rounded-lg border-1 border-gray-200 !max-h-[224px] overflow-y-auto nc-form-scrollbar"
class="rounded-b-lg border-1 border-gray-200 !max-h-[224px] overflow-y-auto nc-form-scrollbar"
@change="onMove($event)"
@start="drag = true"
@end="drag = false"
@ -175,19 +217,25 @@ async function onMove(_event: { moved: { newIndex: number; oldIndex: number; ele
>
<component :is="iconMap.drag" class="nc-child-draggable-icon flex-none cursor-move !h-4 !w-4 text-gray-600" />
<div
@click="
() => {
element.show = !element.show
vModel = [...vModel]
}
"
>
<component
:is="element.show ? iconMap.eye : iconMap.eyeSlash"
class="flex-none cursor-pointer !h-4 !w-4 text-gray-600"
/>
</div>
<NcTooltip :disabled="!isRequired || !(element.show && isRequired && vModel.filter((o) => o.show).length === 1)">
<template #title> {{ $t('msg.info.preventHideAllOptions') }} </template>
<div
class="!border-none !px-2"
@click="
() => {
if (element.show && isRequired && vModel.filter((o) => o.show).length === 1) return
element.show = !element.show
vModel = [...vModel]
}
"
>
<component
:is="element.show ? iconMap.eye : iconMap.eyeSlash"
class="flex-none cursor-pointer !h-4 !w-4 text-gray-600"
/>
</div>
</NcTooltip>
<a-tag v-if="column.uidt === UITypes.User" class="rounded-tag max-w-[calc(100%_-_70px)] !pl-0" color="'#ccc'">
<span

273
packages/nc-gui/composables/useCalendarViewStore.ts

@ -97,7 +97,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()
const { sharedView, fetchSharedViewData, fetchSharedViewActiveDate } = useSharedView()
const { sharedView, fetchSharedViewData, fetchSharedViewActiveDate, fetchSharedCalendarViewData } = useSharedView()
const calendarMetaData = ref<CalendarType>({})
@ -219,7 +219,6 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
}
fromDate = fromDate!.format('YYYY-MM-DD HH:mm:ssZ')
toDate = toDate!.format('YYYY-MM-DD HH:mm:ssZ')
prevDate = prevDate!.format('YYYY-MM-DD HH:mm:ssZ')
nextDate = nextDate!.format('YYYY-MM-DD HH:mm:ssZ')
@ -337,112 +336,6 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
}
}
const filterJSON = computed(() => {
if (!calendarRange.value) return []
const combinedFilters: any = {
is_group: true,
logical_op: 'and',
children: [],
}
let prevDate: string | null | dayjs.Dayjs = null
let fromDate: dayjs.Dayjs | null | string = null
let toDate: dayjs.Dayjs | null | string = null
let nextDate: string | null | dayjs.Dayjs = null
switch (activeCalendarView.value) {
case 'week':
fromDate = selectedDateRange.value.start.startOf('day')
toDate = selectedDateRange.value.end.endOf('day')
prevDate = selectedDateRange.value.start.subtract(1, 'day').endOf('day')
nextDate = selectedDateRange.value.end.add(1, 'day').startOf('day')
break
case 'month': {
const startOfMonth = selectedMonth.value.startOf('month')
const endOfMonth = selectedMonth.value.endOf('month')
const daysToDisplay = Math.max(endOfMonth.diff(startOfMonth, 'day') + 1, 35)
fromDate = startOfMonth.subtract((startOfMonth.day() + 7) % 7, 'day')
toDate = fromDate.add(daysToDisplay, 'day')
prevDate = fromDate.subtract(1, 'day').endOf('day')
nextDate = toDate.add(1, 'day').startOf('day')
break
}
case 'year':
fromDate = selectedDate.value.startOf('year')
toDate = selectedDate.value.endOf('year')
prevDate = fromDate.subtract(1, 'day').endOf('day')
nextDate = toDate.add(1, 'day').startOf('day')
break
case 'day':
fromDate = selectedDate.value.startOf('day')
toDate = selectedDate.value.endOf('day')
prevDate = selectedDate.value.subtract(1, 'day').endOf('day')
nextDate = selectedDate.value.add(1, 'day').startOf('day')
break
}
fromDate = fromDate!.format('YYYY-MM-DD HH:mm:ssZ')
toDate = toDate!.format('YYYY-MM-DD HH:mm:ssZ')
prevDate = prevDate!.format('YYYY-MM-DD HH:mm:ssZ')
nextDate = nextDate!.format('YYYY-MM-DD HH:mm:ssZ')
calendarRange.value.forEach((range) => {
const fromCol = range.fk_from_col
const toCol = range.fk_to_col
let rangeFilter: any = []
if (fromCol && toCol) {
rangeFilter = [
{
is_group: true,
logical_op: 'and',
children: [
{
fk_column_id: fromCol.id,
comparison_op: 'lt',
comparison_sub_op: 'exactDate',
value: nextDate,
},
{
fk_column_id: toCol.id,
comparison_op: 'gt',
comparison_sub_op: 'exactDate',
value: prevDate,
},
],
},
{
fk_column_id: fromCol.id,
comparison_op: 'eq',
logical_op: 'or',
comparison_sub_op: 'exactDate',
value: fromDate,
},
]
} else if (fromCol) {
rangeFilter = [
{
fk_column_id: fromCol.id,
comparison_op: 'lt',
comparison_sub_op: 'exactDate',
value: nextDate,
},
{
fk_column_id: fromCol.id,
comparison_op: 'gt',
comparison_sub_op: 'exactDate',
value: prevDate,
},
]
}
if (rangeFilter.length > 0) {
combinedFilters.children.push(rangeFilter)
}
})
return combinedFilters.children.length > 0 ? [combinedFilters] : []
})
const fetchActiveDates = async () => {
if (!base?.value?.id || !meta.value?.id || !viewMeta.value?.id || !calendarRange.value) return
let prevDate: dayjs.Dayjs | string | null = null
@ -459,85 +352,37 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
prevDate = fromDate.subtract(1, 'day').endOf('day')
nextDate = toDate.add(1, 'day').startOf('day')
} else if (activeCalendarView.value === 'year') {
fromDate = selectedDate.value.startOf('year')
prevDate = selectedDate.value.startOf('year').subtract(1, 'day').endOf('day')
nextDate = selectedDate.value.endOf('year').add(1, 'day').startOf('day')
}
prevDate = prevDate!.format('YYYY-MM-DD HH:mm:ssZ')
nextDate = nextDate!.format('YYYY-MM-DD HH:mm:ssZ')
fromDate = fromDate!.format('YYYY-MM-DD HH:mm:ssZ')
const activeDateFilter: Array<any> = []
calendarRange.value.forEach((range) => {
const fromCol = range.fk_from_col
const toCol = range.fk_to_col
let rangeFilter: any = []
if (fromCol && toCol) {
rangeFilter = [
{
is_group: true,
logical_op: 'and',
children: [
{
fk_column_id: fromCol.id,
comparison_op: 'lt',
comparison_sub_op: 'exactDate',
value: nextDate,
},
{
fk_column_id: toCol.id,
comparison_op: 'gt',
comparison_sub_op: 'exactDate',
value: prevDate,
},
],
},
{
fk_column_id: fromCol.id,
comparison_op: 'eq',
logical_op: 'or',
comparison_sub_op: 'exactDate',
value: fromDate,
},
]
} else if (fromCol) {
rangeFilter = [
{
fk_column_id: fromCol.id,
comparison_op: 'lt',
comparison_sub_op: 'exactDate',
value: nextDate,
},
{
fk_column_id: fromCol.id,
comparison_op: 'gt',
comparison_sub_op: 'exactDate',
value: prevDate,
},
]
}
activeDateFilter.push(rangeFilter)
})
if (!base?.value?.id || !meta.value?.id || !viewMeta.value?.id) return
try {
const res = !isPublic.value
? await api.dbViewRow.calendarCount('noco', base.value.id!, meta.value!.id!, viewMeta.value.id, {
? await api.dbCalendarViewRowCount.dbCalendarViewRowCount('noco', base.value.id!, meta.value!.id!, viewMeta.value.id, {
...queryParams.value,
...{},
...{},
...{ filterArrJson: JSON.stringify([...activeDateFilter]) },
from_date: prevDate,
to_date: nextDate,
})
: await fetchSharedViewActiveDate({
from_date: prevDate,
to_date: nextDate,
sortsArr: sorts.value,
filtersArr: activeDateFilter,
})
activeDates.value = res.map((dateObj: unknown) => dayjs(dateObj))
activeDates.value = res.dates.map((dateObj: unknown) => dayjs(dateObj))
if (res.count > 3000 && activeCalendarView.value !== 'year') {
message.warning(
'This current date range has more than 3000 records. Some records may not be displayed. To get complete records, contact support',
)
}
} catch (e) {
activeDates.value = []
message.error(`${t('msg.error.fetchingActiveDates')} ${await extractSdkResponseErrorMsg(e)}`)
@ -585,30 +430,78 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
}
async function loadCalendarData() {
if ((!base?.value?.id || !meta.value?.id || !viewMeta.value?.id || !filterJSON.value) && !isPublic?.value) return
if ((!base?.value?.id || !meta.value?.id || !viewMeta.value?.id) && !isPublic?.value) return
if (activeCalendarView.value === 'year') {
return
}
let prevDate: string | null | dayjs.Dayjs = null
let fromDate: dayjs.Dayjs | null | string = null
let toDate: dayjs.Dayjs | null | string = null
let nextDate: string | null | dayjs.Dayjs = null
switch (activeCalendarView.value) {
case 'week':
fromDate = selectedDateRange.value.start.startOf('day')
toDate = selectedDateRange.value.end.endOf('day')
prevDate = selectedDateRange.value.start.subtract(1, 'day').endOf('day')
nextDate = selectedDateRange.value.end.add(1, 'day').startOf('day')
break
case 'month': {
const startOfMonth = selectedMonth.value.startOf('month')
const endOfMonth = selectedMonth.value.endOf('month')
const daysToDisplay = Math.max(endOfMonth.diff(startOfMonth, 'day') + 1, 35)
fromDate = startOfMonth.subtract((startOfMonth.day() + 7) % 7, 'day')
toDate = fromDate.add(daysToDisplay, 'day')
prevDate = fromDate.subtract(1, 'day').endOf('day')
nextDate = toDate.add(1, 'day').startOf('day')
break
}
case 'year':
fromDate = selectedDate.value.startOf('year')
toDate = selectedDate.value.endOf('year')
prevDate = fromDate.subtract(1, 'day').endOf('day')
nextDate = toDate.add(1, 'day').startOf('day')
break
case 'day':
fromDate = selectedDate.value.startOf('day')
toDate = selectedDate.value.endOf('day')
prevDate = selectedDate.value.subtract(1, 'day').endOf('day')
nextDate = selectedDate.value.add(1, 'day').startOf('day')
break
}
prevDate = prevDate!.format('YYYY-MM-DD HH:mm:ssZ')
nextDate = nextDate!.format('YYYY-MM-DD HH:mm:ssZ')
try {
isCalendarDataLoading.value = true
const res = !isPublic.value
? await api.dbViewRow.list(
? await api.dbCalendarViewRow.list(
'noco',
base.value.id!,
meta.value!.id!,
viewMeta.value!.id!,
{
...queryParams.value,
...(isUIAllowed('filterSync')
? { filterArrJson: JSON.stringify([...filterJSON.value]) }
: { filterArrJson: JSON.stringify([nestedFilters.value, ...filterJSON.value]) }),
where: where?.value ?? '',
from_date: prevDate,
to_date: nextDate,
},
{
headers: {
'xc-ignore-pagination': true,
},
...queryParams.value,
...(isUIAllowed('filterSync') ? { filterArrJson: [] } : { filterArrJson: JSON.stringify([nestedFilters.value]) }),
where: where?.value ?? '',
filterArrJson: JSON.stringify([...nestedFilters.value]),
},
)
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: filterJSON.value })
: await fetchSharedCalendarViewData({
sortsArr: sorts.value,
from_date: prevDate,
to_date: nextDate,
filtersArr: nestedFilters.value,
where: where?.value ?? '',
})
formattedData.value = formatData(res!.list)
} catch (e) {
message.error(`${t('msg.error.fetchingCalendarData')} ${await extractSdkResponseErrorMsg(e)}`)
@ -782,17 +675,21 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
watch(selectedDate, async (value, oldValue) => {
if (activeCalendarView.value === 'month' || activeCalendarView.value === 'week') {
if (sideBarFilterOption.value === 'selectedDate') {
if (sideBarFilterOption.value === 'selectedDate' && showSideMenu.value) {
await loadSidebarData()
}
} else if (activeCalendarView.value === 'year') {
if (value.year() !== oldValue.year()) {
await Promise.all([loadCalendarData(), loadSidebarData(), await fetchActiveDates()])
} else if (sideBarFilterOption.value === 'selectedDate') {
} else if (sideBarFilterOption.value === 'selectedDate' && showSideMenu.value) {
await loadSidebarData()
}
} else {
await Promise.all([loadSidebarData(), loadCalendarData()])
if (showSideMenu.value) {
await Promise.all([loadSidebarData(), loadCalendarData()])
} else {
await Promise.all([loadCalendarData()])
}
}
if (activeCalendarView.value === 'year' && value.year() !== oldValue.year()) {
@ -801,7 +698,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
})
watch(selectedTime, async () => {
if (calDataType.value !== UITypes.Date) {
if (calDataType.value !== UITypes.Date && showSideMenu.value) {
await loadSidebarData()
}
})
@ -850,7 +747,15 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
}
}
sideBarFilterOption.value = activeCalendarView.value ?? 'allRecords'
await Promise.all([loadCalendarData(), loadSidebarData(), fetchActiveDates()])
if (activeCalendarView.value === 'year') {
await Promise.all([loadSidebarData(), fetchActiveDates()])
} else {
await Promise.all([loadCalendarData(), loadSidebarData(), fetchActiveDates()])
}
})
watch(showSideMenu, async (val) => {
if (val) await loadSidebarData()
})
watch(sideBarFilterOption, async () => {

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

@ -1,9 +1,11 @@
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { AuditType, ColumnType, TableType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue'
import dayjs from 'dayjs'
import {
IsPublicInj,
NOCO,
type Row,
computed,
extractPkFromRow,
extractSdkResponseErrorMsg,
@ -22,7 +24,6 @@ import {
useSharedView,
useUndoRedo,
} from '#imports'
import type { Row } from '#imports'
const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((meta: Ref<TableType>, _row: Ref<Row>) => {
const { $e, $state, $api } = useNuxtApp()
@ -31,6 +32,8 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
const { t } = useI18n()
const isPublic = inject(IsPublicInj, ref(false))
const commentsOnly = ref(false)
const commentsAndLogs = ref<any[]>([])
@ -301,6 +304,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
}
const loadRow = async (rowId?: string, onlyVirtual = false) => {
if (isPublic.value) return
let record = await $api.dbTableRow.read(
NOCO,
// todo: base_id missing on view type

71
packages/nc-gui/composables/useSharedView.ts

@ -110,21 +110,16 @@ export function useSharedView() {
}
}
const fetchSharedViewData = async (
param: {
sortsArr: SortType[]
filtersArr: FilterType[]
fields?: any[]
sort?: any[]
where?: string
/** Query params for nested data */
nested?: any
offset?: number
},
headers?: {
ignorePagination?: boolean
},
) => {
const fetchSharedViewData = async (param: {
sortsArr: SortType[]
filtersArr: FilterType[]
fields?: any[]
sort?: any[]
where?: string
/** Query params for nested data */
nested?: any
offset?: number
}) => {
if (!sharedView.value)
return {
list: [],
@ -148,13 +143,54 @@ export function useSharedView() {
{
headers: {
'xc-password': password.value,
'xc-ignore-pagination': headers?.ignorePagination ? 'true' : 'false',
},
},
)
}
const fetchSharedCalendarViewData = async (param: {
from_date: string
to_date: string
sortsArr: SortType[]
filtersArr: FilterType[]
fields?: any[]
sort?: any[]
where?: string
/** Query params for nested data */
nested?: any
offset?: number
}) => {
if (!sharedView.value)
return {
list: [],
pageInfo: {},
}
if (!param.offset) {
const page = paginationData.value.page || 1
const pageSize = paginationData.value.pageSize || appInfoDefaultLimit
param.offset = (page - 1) * pageSize
}
return await $api.dbCalendarViewRow.publicDataCalendarRowList(
sharedView.value.uuid!,
{
limit: sharedView.value?.type === ViewTypes.CALENDAR ? 3000 : undefined,
...param,
filterArrJson: JSON.stringify(param.filtersArr ?? nestedFilters.value),
sortArrJson: JSON.stringify(param.sortsArr ?? sorts.value),
} as any,
{
headers: {
'xc-password': password.value,
},
},
)
}
const fetchSharedViewActiveDate = async (param: {
from_date: string
to_date: string
sortsArr: SortType[]
filtersArr: FilterType[]
sort?: any[]
@ -166,7 +202,7 @@ export function useSharedView() {
pageInfo: {},
}
return await $api.public.calendarCount(
return await $api.public.dataCalendarRowCount(
sharedView.value.uuid!,
{
...param,
@ -234,6 +270,7 @@ export function useSharedView() {
nestedFilters,
fetchSharedViewData,
fetchSharedViewActiveDate,
fetchSharedCalendarViewData,
fetchSharedViewGroupedData,
paginationData,
sorts,

250
packages/nc-gui/composables/useViewGroupBy.ts

@ -1,5 +1,7 @@
import { type ColumnType, type SelectOptionsType, UITypes, type ViewType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { message } from 'ant-design-vue'
import { extractSdkResponseErrorMsg } from '../utils'
import { GROUP_BY_VARS, ref, storeToRefs, useApi, useBase, useViewColumnsOrThrow } from '#imports'
import type { Group, GroupNestedIn, Row } from '#imports'
@ -178,152 +180,160 @@ export const useViewGroupBy = (view: Ref<ViewType | undefined>, where?: Computed
}
async function loadGroups(params: any = {}, group?: Group) {
group = group || rootGroup.value
try {
group = group || rootGroup.value
if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id || !group) return
if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id || !group) return
if (groupBy.value.length === 0) {
group.children = []
return
}
if (groupBy.value.length === 0) {
group.children = []
return
}
if (group.nestedIn.length > groupBy.value.length) return
if (group.nestedIn.length > groupBy.value.length) return
if (group.nestedIn.length === 0) nextGroupColor.value = colors.value[0]
const groupby = groupBy.value[group.nestedIn.length]
if (group.nestedIn.length === 0) nextGroupColor.value = colors.value[0]
const groupby = groupBy.value[group.nestedIn.length]
const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value)
if (!groupby || !groupby.column.title) return
const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value)
if (!groupby || !groupby.column.title) return
if (isPublic.value && !sharedView.value?.uuid) {
return
}
if (isPublic.value && !sharedView.value?.uuid) {
return
}
const response = !isPublic.value
? await api.dbViewRow.groupBy('noco', base.value.id, view.value.fk_model_id, view.value.id, {
offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByGroupLimit.value),
limit: group.paginationData.pageSize ?? groupByGroupLimit.value,
...params,
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
where: `${nestedWhere}`,
sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`,
column_name: groupby.column.title,
} as any)
: await api.public.dataGroupBy(sharedView.value!.uuid!, {
offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByGroupLimit.value),
limit: group.paginationData.pageSize ?? groupByGroupLimit.value,
...params,
where: nestedWhere,
sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`,
column_name: groupby.column.title,
sortsArr: sorts.value,
filtersArr: nestedFilters.value,
})
const response = !isPublic.value
? await api.dbViewRow.groupBy('noco', base.value.id, view.value.fk_model_id, view.value.id, {
offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByGroupLimit.value),
limit: group.paginationData.pageSize ?? groupByGroupLimit.value,
...params,
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
where: `${nestedWhere}`,
sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`,
column_name: groupby.column.title,
} as any)
: await api.public.dataGroupBy(sharedView.value!.uuid!, {
offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByGroupLimit.value),
limit: group.paginationData.pageSize ?? groupByGroupLimit.value,
...params,
where: nestedWhere,
sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`,
column_name: groupby.column.title,
sortsArr: sorts.value,
filtersArr: nestedFilters.value,
})
const tempList: Group[] = response.list.reduce((acc: Group[], curr: Record<string, any>) => {
const keyExists = acc.find(
(a) => a.key === valueToTitle(curr[groupby.column.column_name!] ?? curr[groupby.column.title!], groupby.column),
)
if (keyExists) {
keyExists.count += +curr.count
keyExists.paginationData = { page: 1, pageSize: groupByGroupLimit.value, totalRows: keyExists.count }
return acc
}
if (groupby.column.title && groupby.column.uidt) {
acc.push({
key: valueToTitle(curr[groupby.column.title!], groupby.column),
column: groupby.column,
count: +curr.count,
color: findKeyColor(curr[groupby.column.title!], groupby.column),
nestedIn: [
...group!.nestedIn,
{
title: groupby.column.title,
column_name: groupby.column.title!,
key: valueToTitle(curr[groupby.column.title!], groupby.column),
column_uidt: groupby.column.uidt,
const tempList: Group[] = response.list.reduce((acc: Group[], curr: Record<string, any>) => {
const keyExists = acc.find(
(a) => a.key === valueToTitle(curr[groupby.column.column_name!] ?? curr[groupby.column.title!], groupby.column),
)
if (keyExists) {
keyExists.count += +curr.count
keyExists.paginationData = { page: 1, pageSize: groupByGroupLimit.value, totalRows: keyExists.count }
return acc
}
if (groupby.column.title && groupby.column.uidt) {
acc.push({
key: valueToTitle(curr[groupby.column.title!], groupby.column),
column: groupby.column,
count: +curr.count,
color: findKeyColor(curr[groupby.column.title!], groupby.column),
nestedIn: [
...group!.nestedIn,
{
title: groupby.column.title,
column_name: groupby.column.title!,
key: valueToTitle(curr[groupby.column.title!], groupby.column),
column_uidt: groupby.column.uidt,
},
],
paginationData: {
page: 1,
pageSize: group!.nestedIn.length < groupBy.value.length - 1 ? groupByGroupLimit.value : groupByRecordLimit.value,
totalRows: +curr.count,
},
],
paginationData: {
page: 1,
pageSize: group!.nestedIn.length < groupBy.value.length - 1 ? groupByGroupLimit.value : groupByRecordLimit.value,
totalRows: +curr.count,
},
nested: group!.nestedIn.length < groupBy.value.length - 1,
})
}
return acc
}, [])
nested: group!.nestedIn.length < groupBy.value.length - 1,
})
}
return acc
}, [])
if (!group.children) group.children = []
if (!group.children) group.children = []
for (const temp of tempList) {
const keyExists = group.children?.find((a) => a.key === temp.key)
if (keyExists) {
temp.paginationData = {
page: keyExists.paginationData.page || temp.paginationData.page,
pageSize: keyExists.paginationData.pageSize || temp.paginationData.pageSize,
totalRows: temp.count,
for (const temp of tempList) {
const keyExists = group.children?.find((a) => a.key === temp.key)
if (keyExists) {
temp.paginationData = {
page: keyExists.paginationData.page || temp.paginationData.page,
pageSize: keyExists.paginationData.pageSize || temp.paginationData.pageSize,
totalRows: temp.count,
}
temp.color = keyExists.color
// update group
Object.assign(keyExists, temp)
continue
}
temp.color = keyExists.color
// update group
Object.assign(keyExists, temp)
continue
group.children.push(temp)
}
group.children.push(temp)
}
// clear rest of the children
group.children = group.children.filter((c) => tempList.find((t) => t.key === c.key))
// clear rest of the children
group.children = group.children.filter((c) => tempList.find((t) => t.key === c.key))
if (group.count <= (group.paginationData.pageSize ?? groupByGroupLimit.value)) {
group.children.sort((a, b) => {
const orderA = tempList.findIndex((t) => t.key === a.key)
const orderB = tempList.findIndex((t) => t.key === b.key)
return orderA - orderB
})
}
if (group.count <= (group.paginationData.pageSize ?? groupByGroupLimit.value)) {
group.children.sort((a, b) => {
const orderA = tempList.findIndex((t) => t.key === a.key)
const orderB = tempList.findIndex((t) => t.key === b.key)
return orderA - orderB
})
}
group.paginationData = response.pageInfo
group.paginationData = response.pageInfo
// to cater the case like when querying with a non-zero offset
// the result page may point to the target page where the actual returned data don't display on
const expectedPage = Math.max(1, Math.ceil(group.paginationData.totalRows! / group.paginationData.pageSize!))
if (expectedPage < group.paginationData.page!) {
await groupWrapperChangePage(expectedPage, group)
// to cater the case like when querying with a non-zero offset
// the result page may point to the target page where the actual returned data don't display on
const expectedPage = Math.max(1, Math.ceil(group.paginationData.totalRows! / group.paginationData.pageSize!))
if (expectedPage < group.paginationData.page!) {
await groupWrapperChangePage(expectedPage, group)
}
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
async function loadGroupData(group: Group, force = false, params: any = {}) {
if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id) return
try {
if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id) return
if (group.children && !force) return
if (group.children && !force) return
if (!group.paginationData) {
group.paginationData = { page: 1, pageSize: groupByRecordLimit.value }
}
if (!group.paginationData) {
group.paginationData = { page: 1, pageSize: groupByRecordLimit.value }
}
const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value)
const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value)
const query = {
offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByRecordLimit.value),
limit: group.paginationData.pageSize ?? groupByRecordLimit.value,
where: `${nestedWhere}`,
}
const query = {
offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByRecordLimit.value),
limit: group.paginationData.pageSize ?? groupByRecordLimit.value,
where: `${nestedWhere}`,
}
const response = !isPublic.value
? await api.dbViewRow.list('noco', base.value.id, view.value.fk_model_id, view.value.id, {
...query,
...params,
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
} as any)
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value, ...query })
group.count = response.pageInfo.totalRows ?? 0
group.rows = formatData(response.list)
group.paginationData = response.pageInfo
const response = !isPublic.value
? await api.dbViewRow.list('noco', base.value.id, view.value.fk_model_id, view.value.id, {
...query,
...params,
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
} as any)
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value, ...query })
group.count = response.pageInfo.totalRows ?? 0
group.rows = formatData(response.list)
group.paginationData = response.pageInfo
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const loadGroupPage = async (group: Group, p: number) => {

7
packages/nc-gui/lang/ar.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "أدخل تسمية إدخال علامة",
"formHelpText": "أضف نص المساعدة",
"onlyCreator": "مرئية فقط للمنشيء",
"formTitle": "Add form Title",
"formDesc": "إضافة وصف النموذج",
"beforeEnablePwd": "تقييد الوصول بكلمة مرور",
"afterEnablePwd": "الوصول مقيد بكلمة مرور",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/bn_IN.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "ফরম ইনপট লল পরবশ করন",
"formHelpText": "কি সহয যত করন",
"onlyCreator": "কবল সরষর কযমন",
"formTitle": "Add form Title",
"formDesc": "ফরম বরণনগ করন",
"beforeEnablePwd": "একটিসওযড দিস সবদধ করন",
"afterEnablePwd": "অস পসওযড সবদধ",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/cs.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Zadejte vstupní štítek formuláře",
"formHelpText": "Přidání textu nápovědy",
"onlyCreator": "Viditelný pouze pro Stvořitele",
"formTitle": "Add form Title",
"formDesc": "Přidání popisu formuláře",
"beforeEnablePwd": "Omezení přístupu pomocí hesla",
"afterEnablePwd": "Přístup je omezen heslem",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/da.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Indtast formularindgangsmærke",
"formHelpText": "Tilføj nogle hjælpetekst",
"onlyCreator": "Kun synlig for skaberen",
"formTitle": "Add form Title",
"formDesc": "Tilføj form Beskrivelse.",
"beforeEnablePwd": "Begræns adgang med et kodeord",
"afterEnablePwd": "Adgang er adgangskode begrænset",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/de.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Formularbezeichnung eingeben",
"formHelpText": "Einen Hilfs-Text hinzufügen",
"onlyCreator": "Nur für den Ersteller sichtbar",
"formTitle": "Add form Title",
"formDesc": "Formularbeschreibung hinzufügen",
"beforeEnablePwd": "Den Zugriff mit einem Passwort einschränken",
"afterEnablePwd": "Zugriff ist Passwort-geschützt",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

9
packages/nc-gui/lang/en.json

@ -694,9 +694,9 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit ptions",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
"clearSelection": "Clear selection"
},
@ -1181,6 +1181,7 @@
"formInput": "Enter form input label",
"formHelpText": "Add some help text",
"onlyCreator": "Only visible to Creator",
"formTitle": "Add form Title",
"formDesc": "Add form description",
"beforeEnablePwd": "Restrict access with a password",
"afterEnablePwd": "Access is password restricted",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/es.json

@ -694,7 +694,7 @@
"appearanceSettings": "Ajustes de apariencia",
"backgroundColor": "Color de Fondo",
"hideNocodbBranding": "Ocultar marca NocoDB",
"showOnConditions": "Mostrar en condiciones",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Muestra el campo sólo cuando se cumplen las condiciones",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Ingrese la etiqueta de entrada del formulario",
"formHelpText": "Añade algo de texto de ayuda",
"onlyCreator": "Sólo visible por el creador",
"formTitle": "Add form Title",
"formDesc": "Añadir descripción de formulario",
"beforeEnablePwd": "Restringir acceso mediante contraseña",
"afterEnablePwd": "Restricción mediante contraseña",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/eu.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Enter form input label",
"formHelpText": "Add some help text",
"onlyCreator": "Only visible to Creator",
"formTitle": "Add form Title",
"formDesc": "Add form description",
"beforeEnablePwd": "Restrict access with a password",
"afterEnablePwd": "Access is password restricted",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/fa.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "برچسب ورودی فرم را وارد کنید",
"formHelpText": "مقداری متن راهنما اضافه کنید",
"onlyCreator": "فقط برای نقش ایجادکننده قابل مشاهده است",
"formTitle": "Add form Title",
"formDesc": "توضیحات فرم را اضافه کنید",
"beforeEnablePwd": "محدود کردن دسترسی با لزوم ورود کلمه عبور",
"afterEnablePwd": "دسترسی با لزوم ورود کلمه عبور محدود شده",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/fi.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Anna lomakkeen syöttömerkki",
"formHelpText": "Lisää joitain ohjeita",
"onlyCreator": "Näkyy vain Luojalle",
"formTitle": "Add form Title",
"formDesc": "Lisää muotokuvaus",
"beforeEnablePwd": "Rajoita pääsy salasanalla",
"afterEnablePwd": "Pääsy on salasana rajoitettu",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/fr.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Entrer le libellé du formulaire",
"formHelpText": "Ajouter un texte d'aide",
"onlyCreator": "Visible uniquement pour les créateurs",
"formTitle": "Add form Title",
"formDesc": "Ajouter une description au formulaire",
"beforeEnablePwd": "Restreindre l’accès à l’aide d’un mot de passe",
"afterEnablePwd": "L’accès est restreint par un mot de passe",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/he.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "הזן תווית קלט טופס",
"formHelpText": "הוסף טקסט עזרה",
"onlyCreator": "רק גלוי לבורא",
"formTitle": "Add form Title",
"formDesc": "הוסף טופס תיאור.",
"beforeEnablePwd": "הגבל גישה עם סיסמה",
"afterEnablePwd": "הגישה היא מוגבלת בסיסמה",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/hi.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "फम इनपट लबल दरज कर",
"formHelpText": "कछ सहयतठ ज",
"onlyCreator": "कवल नििई द",
"formTitle": "Add form Title",
"formDesc": "फम विवरण ज",
"beforeEnablePwd": "एक पसवरड कथ पहच करतिित कर",
"afterEnablePwd": "एकस पसवरड परतिित ह",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/hr.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Unesite oznaku ulazne obrasce",
"formHelpText": "Dodajte neki tekst pomoći",
"onlyCreator": "Samo vidljivo kreatoru",
"formTitle": "Add form Title",
"formDesc": "Dodajte opis obrasca",
"beforeEnablePwd": "Ograničite pristup s lozinkom",
"afterEnablePwd": "Pristup je ograničen lozinkom",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/id.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Masukkan label input formulir",
"formHelpText": "Tambahkan beberapa teks bantuan",
"onlyCreator": "Hanya terlihat oleh pencipta",
"formTitle": "Add form Title",
"formDesc": "Tambahkan Deskripsi Formulir ..",
"beforeEnablePwd": "Batasi akses dengan kata sandi",
"afterEnablePwd": "Akses dibatasi kata sandi",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/it.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Inserisci l'etichetta di input del modulo",
"formHelpText": "Aggiungi testo di aiuto",
"onlyCreator": "Solo visibile al creatore",
"formTitle": "Add form Title",
"formDesc": "Aggiungi descrizione del modulo",
"beforeEnablePwd": "Limita l'accesso con una password",
"afterEnablePwd": "L'accesso è protetto da una password",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/ja.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "フォーム入力ラベルを入力してください",
"formHelpText": "ヘルプテキストを追加してください",
"onlyCreator": "作成者だけが閲覧できます",
"formTitle": "Add form Title",
"formDesc": "フォームの説明を追加する",
"beforeEnablePwd": "パスワードでアクセスを制限",
"afterEnablePwd": "アクセスするにはパスワードが必要です",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/ko.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "양식 입력 레이블 입력",
"formHelpText": "도움말 텍스트 추가",
"onlyCreator": "작성자에게만 표시",
"formTitle": "Add form Title",
"formDesc": "양식 설명 추가",
"beforeEnablePwd": "비밀번호로 액세스 제한",
"afterEnablePwd": "엑세스 비밀번호 제한",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/lv.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Ievadiet formas ievades lauka birku",
"formHelpText": "Pievienot norādījumus",
"onlyCreator": "Redzams tikai autoram",
"formTitle": "Add form Title",
"formDesc": "Pievienot formas aprakstu",
"beforeEnablePwd": "Ierobežot piekļuvi ar paroli",
"afterEnablePwd": "Piekļuve ir ierobežota ar paroli",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/nl.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Voer formulier invoerlabel in",
"formHelpText": "Voeg wat hulptekst toe",
"onlyCreator": "Alleen zichtbaar voor Creators",
"formTitle": "Add form Title",
"formDesc": "Voeg een formulierbeschrijving toe",
"beforeEnablePwd": "Beperk de toegang met een wachtwoord",
"afterEnablePwd": "Toegang is wachtwoord beperkt",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/no.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Skriv inn skjema inngangsetikett",
"formHelpText": "Legg til litt hjelpetekst",
"onlyCreator": "Bare synlig for skaperen",
"formTitle": "Add form Title",
"formDesc": "Legg til form Beskrivelse",
"beforeEnablePwd": "Begrens tilgang med et passord",
"afterEnablePwd": "Tilgang er passordbegrenset",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/pl.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Wprowadź etykietę wejściową formularza",
"formHelpText": "Dodaj trochę pomocy",
"onlyCreator": "Tylko widoczne dla twórcy",
"formTitle": "Add form Title",
"formDesc": "Dodaj opis formularza",
"beforeEnablePwd": "Ogranicz dostęp do hasła",
"afterEnablePwd": "Dostęp jest ograniczony hasłem",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/pt.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Digite a etiqueta de entrada do formulário",
"formHelpText": "Adicione um texto de ajuda",
"onlyCreator": "Apenas visível para o Criador",
"formTitle": "Add form Title",
"formDesc": "Adicionar Formulário Descrição.",
"beforeEnablePwd": "Restringir acesso com uma palavra-passe",
"afterEnablePwd": "O acesso está protegido por uma palavra-passe",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/pt_BR.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Digite a etiqueta de entrada do formulário",
"formHelpText": "Adicione um texto de ajuda",
"onlyCreator": "Apenas visível para o Criador",
"formTitle": "Add form Title",
"formDesc": "Adicionar Formulário Descrição.",
"beforeEnablePwd": "Restringir acesso com uma senha",
"afterEnablePwd": "O acesso está protegido por uma senha",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/ru.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Введите форму ввода формы",
"formHelpText": "Добавьте текст подсказки",
"onlyCreator": "Видно только для создателя",
"formTitle": "Add form Title",
"formDesc": "Добавить описание формы",
"beforeEnablePwd": "Ограничить доступ с паролем",
"afterEnablePwd": "Доступ защищенный паролем",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/sk.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Zadajte vstupný štítok formulára",
"formHelpText": "Pridanie textu nápovedy",
"onlyCreator": "Viditeľné len pre Stvoriteľa",
"formTitle": "Add form Title",
"formDesc": "Pridanie popisu formulára",
"beforeEnablePwd": "Obmedzenie prístupu pomocou hesla",
"afterEnablePwd": "Prístup je obmedzený heslom",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/sl.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Vnesite nalepko vnosa obrazca",
"formHelpText": "Dodajte nekaj besedila pomoči",
"onlyCreator": "Viden samo ustvarjalcu",
"formTitle": "Add form Title",
"formDesc": "Dodajte Opis obrazca",
"beforeEnablePwd": "Omeji dostop z geslom",
"afterEnablePwd": "Za dostop je potrebno geslo",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/sv.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Ange formulärinmatningslabel",
"formHelpText": "Lägg till lite hjälptext",
"onlyCreator": "Bara synlig för skaparen",
"formTitle": "Add form Title",
"formDesc": "Lägg till formulär Beskrivning",
"beforeEnablePwd": "Begränsa åtkomsten med ett lösenord",
"afterEnablePwd": "Åtkomst är lösenordsbegränsad",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/th.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "ปอนปายชออนพตแบบฟอรม",
"formHelpText": "เพมขอความชวยเหลอบางอยาง",
"onlyCreator": "มองเหนเฉพาะกบผสรางเทานน",
"formTitle": "Add form Title",
"formDesc": "เพมคำอธบายแบบฟอรม",
"beforeEnablePwd": "จำกด การเขาถงดวยรหสผาน",
"afterEnablePwd": "การเขาถงคอรหสผานท จำกด",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/tr.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Form girdi etiketini girin",
"formHelpText": "Yardım metni ekleyin",
"onlyCreator": "Sadece Yaratıcı'ya görünür",
"formTitle": "Add form Title",
"formDesc": "Form açıklaması ekleyin",
"beforeEnablePwd": "Erişimi parola ile sınırlandırın",
"afterEnablePwd": "Erişim parola ile kısıtlanmış",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/uk.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Введіть назву форми для введення",
"formHelpText": "Додайте допоміжний текст",
"onlyCreator": "Видимий тільки для редактора",
"formTitle": "Add form Title",
"formDesc": "Додати опис форми",
"beforeEnablePwd": "Обмежити доступ за допомогою пароля",
"afterEnablePwd": "Доступ обмежено паролем",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/vi.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "Nhập nhãn đầu vào Mẫu",
"formHelpText": "Thêm một số văn bản trợ giúp",
"onlyCreator": "Chỉ có thể nhìn thấy để tạo",
"formTitle": "Add form Title",
"formDesc": "Thêm mô tả mẫu",
"beforeEnablePwd": "Hạn chế quyền truy cập bằng mật khẩu",
"afterEnablePwd": "Truy cập là mật khẩu bị hạn chế",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/zh-Hans.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "输入表单输入标签",
"formHelpText": "添加一些帮助文本",
"onlyCreator": "仅创始人可见",
"formTitle": "Add form Title",
"formDesc": "添加表单描述",
"beforeEnablePwd": "使用密码限制访问",
"afterEnablePwd": "访问受密码限制",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

7
packages/nc-gui/lang/zh-Hant.json

@ -694,7 +694,7 @@
"appearanceSettings": "Appearance Settings",
"backgroundColor": "Background Color",
"hideNocodbBranding": "Hide NocoDB Branding",
"showOnConditions": "Show on condtions",
"showOnConditions": "Show on conditions",
"showFieldOnConditionsMet": "Shows field only when conditions are met",
"limitOptions": "Limit options",
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
@ -1181,6 +1181,7 @@
"formInput": "輸入表單輸入標籤",
"formHelpText": "新增一些說明文字",
"onlyCreator": "僅建立者可見",
"formTitle": "Add form Title",
"formDesc": "新增表單描述",
"beforeEnablePwd": "使用密碼限制存取權限",
"afterEnablePwd": "存取受密碼限制",
@ -1305,8 +1306,10 @@
"groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column",
"groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column",
"upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}",
"thisFeatureIsOnlyAvailableInEnterpriseEdition": "This feature is only available in enterprise edition",
"yourCurrentRoleIs": "Your current role is",
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}"
"pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}",
"preventHideAllOptions": "You cannot hide all options if field is required"
},
"error": {
"fetchingCalendarData": "Error fetching calendar data",

10
packages/nc-gui/lib/types.ts

@ -207,6 +207,15 @@ interface FormFieldsLimitOptionsType {
show: boolean
}
interface ImageCropperConfig {
stencilProps?: {
aspectRatio?: number
}
minHeight?: number
minWidth?: number
imageRestriction?: 'fill-area' | 'fit-area' | 'stencil' | 'none'
}
export type {
User,
ProjectMetaInfo,
@ -236,4 +245,5 @@ export type {
CommandPaletteType,
CalendarRangeType,
FormFieldsLimitOptionsType,
ImageCropperConfig,
}

9
packages/nc-gui/package.json

@ -41,6 +41,7 @@
"@iconify/vue": "^4.1.1",
"@pinia/nuxt": "^0.5.1",
"@tiptap/extension-link": "2.2.4",
"@tiptap/extension-placeholder": "^2.2.4",
"@tiptap/extension-task-list": "2.2.4",
"@tiptap/extension-underline": "^2.2.4",
"@tiptap/html": "2.2.4",
@ -107,7 +108,7 @@
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@iconify-json/ant-design": "^1.1.15",
"@iconify-json/bi": "^1.1.23",
"@iconify-json/carbon": "^1.1.30",
"@iconify-json/carbon": "^1.1.31",
"@iconify-json/cil": "^1.1.8",
"@iconify-json/clarity": "^1.1.12",
"@iconify-json/eva": "^1.1.10",
@ -115,13 +116,13 @@
"@iconify-json/ion": "^1.1.15",
"@iconify-json/la": "^1.1.8",
"@iconify-json/logos": "^1.1.42",
"@iconify-json/lucide": "^1.1.170",
"@iconify-json/material-symbols": "^1.1.73",
"@iconify-json/lucide": "^1.1.171",
"@iconify-json/material-symbols": "^1.1.74",
"@iconify-json/mdi": "^1.1.64",
"@iconify-json/mi": "^1.1.8",
"@iconify-json/ph": "^1.1.11",
"@iconify-json/ri": "^1.1.20",
"@iconify-json/simple-icons": "^1.1.93",
"@iconify-json/simple-icons": "^1.1.94",
"@iconify-json/system-uicons": "^1.1.12",
"@iconify-json/tabler": "^1.1.106",
"@iconify-json/vscode-icons": "^1.1.33",

37
packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index.vue

@ -9,11 +9,6 @@ const route = useRoute()
const router = useRouter()
// For now dark theme is disabled
// const onClick = () => {
// isDark.value = !isDark.value
// }
onMounted(() => {
isDark.value = false
})
@ -39,16 +34,6 @@ router.afterEach((to) => shouldRedirect(to.name as string))
}"
>
<NuxtPage />
<!-- <div
class="color-transition flex items-center justify-center cursor-pointer absolute top-4 md:top-15 right-4 md:right-15 rounded-full p-2 bg-white dark:(bg-slate-600) shadow hover:(ring-1 ring-accent ring-opacity-100)"
@click="onClick"
>
<Transition name="slide-left" duration="250" mode="out-in">
<MaterialSymbolsDarkModeOutline v-if="isDark" />
<MaterialSymbolsLightModeOutline v-else />
</Transition>
</div> -->
</div>
</template>
@ -100,7 +85,7 @@ p {
@apply w-full;
&:not(.layout-list) {
@apply rounded-lg border-solid border-1 border-gray-200 focus-within:border-brand-500;
@apply rounded-lg border-solid border-1 border-gray-200 focus-within:border-brand-500 overflow-hidden;
& > div {
@apply !bg-transparent;
@ -153,32 +138,26 @@ p {
& > div {
@apply w-full;
}
:deep(textarea) {
@apply !p-2;
&:focus {
box-shadow: none !important;
}
textarea {
@apply px-3;
}
}
&:not(.nc-cell-longtext) {
@apply p-2;
}
textarea {
@apply px-4 py-2 rounded;
&:focus {
box-shadow: none !important;
}
}
&.nc-cell-json {
@apply h-auto;
& > div {
@apply w-full;
}
}
.ant-picker,
input.nc-cell-field {
@apply !py-0 !px-1;
}
}
}

59
packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index/index.vue

@ -79,47 +79,60 @@ const onDecode = async (scannedCodeValue: string) => {
</script>
<template>
<div class="h-full flex flex-col items-center">
<div class="h-full flex flex-col items-center w-full max-w-[max(33%,688px)] mx-auto">
<GeneralFormBanner
v-if="sharedFormView"
v-if="sharedFormView && !parseProp(sharedFormView?.meta).hide_banner"
:banner-image-url="sharedFormView.banner_image_url"
class="flex-none dark:border-none"
/>
<div
class="transition-all duration-300 ease-in relative flex flex-col justify-center gap-2 w-full max-w-[max(33%,688px)] mx-auto my-6 bg-white dark:bg-transparent rounded-3xl border-1 border-gray-200 px-4 py-8 lg:p-12 md:(p-8 dark:bg-slate-700)"
class="transition-all duration-300 ease-in relative flex flex-col justify-center gap-2 w-full my-6 bg-white dark:bg-transparent rounded-3xl border-1 border-gray-200 px-4 py-8 lg:p-12 md:(p-8 dark:bg-slate-700)"
>
<template v-if="sharedFormView">
<div class="mb-4">
<div>
<h1 class="text-2xl font-bold text-gray-900 mb-4">
{{ sharedFormView.heading }}
</h1>
<h2 v-if="sharedFormView.subheading" class="font-medium text-base text-gray-500 dark:text-slate-300 mb-4">
{{ sharedFormView.subheading }}
</h2>
<div v-if="sharedFormView.subheading">
<LazyCellRichText
:value="sharedFormView.subheading"
class="font-medium text-base text-gray-500 dark:text-slate-300 !h-auto mb-4 -ml-1"
is-form-field
read-only
sync-value-change
/>
</div>
</div>
<a-alert v-if="notFound" type="warning" class="my-4 text-center" message="Not found" />
<a-alert v-if="notFound" type="warning" class="!mt-2 !mb-4 text-center" message="Not found" />
<template v-else-if="submitted">
<div class="flex justify-center">
<div v-if="sharedFormView" class="w-full lg:w-[95%]">
<a-alert
type="success"
class="!my-4 text-center !rounded-lg"
outlined
:message="sharedFormView.success_msg || 'Successfully submitted form data'"
/>
<div v-if="sharedFormView" class="w-full">
<a-alert class="!mt-2 !mb-4 !py-4 text-left !rounded-lg" type="success" outlined>
<template #message>
<LazyCellRichText
v-if="sharedFormView?.success_msg?.trim()"
:value="sharedFormView?.success_msg"
class="!h-auto -ml-1"
is-form-field
read-only
sync-value-change
/>
<span v-else> {{ $t('msg.successfullySubmittedFormData') }} </span>
</template>
</a-alert>
<p v-if="sharedFormView.show_blank_form" class="text-xs text-slate-500 dark:text-slate-300 text-center my-4">
<p v-if="sharedFormView.show_blank_form" class="text-xs text-slate-500 dark:text-slate-300 my-4">
{{ $t('msg.newFormWillBeLoaded', { seconds: secondsRemain }) }}
</p>
<div v-if="sharedFormView.submit_another_form" class="text-center">
<div v-if="sharedFormView.submit_another_form" class="text-right">
<NcButton type="primary" size="medium" @click="submitted = false">
{{ $t('activity.submitAnotherForm') }}</NcButton
>
{{ $t('activity.submitAnotherForm') }}
</NcButton>
</div>
</div>
</div>
@ -157,7 +170,13 @@ const onDecode = async (scannedCodeValue: string) => {
<span v-if="isRequired(field, field.required)" class="text-red-500 text-base leading-[18px]">&nbsp;*</span>
</div>
<div v-if="field?.description" class="nc-form-column-description text-gray-500 text-sm">
{{ field?.description }}
<LazyCellRichText
:value="field?.description"
class="!h-auto -ml-1"
is-form-field
read-only
sync-value-change
/>
</div>
<div>

55
packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index/survey.vue

@ -239,20 +239,22 @@ onMounted(() => {
<div
v-if="sharedFormView"
style="height: max(40vh, 225px); min-height: 225px"
class="max-w-[max(33%,600px)] mx-auto flex flex-col justify-end"
class="w-full max-w-[max(33%,600px)] mx-auto flex flex-col justify-end"
>
<div class="px-4 md:px-0 flex flex-col justify-end">
<h1 class="text-2xl font-bold text-gray-900 self-center my-4" data-testid="nc-survey-form__heading">
<h1 class="text-2xl font-bold text-gray-900 self-center text-center my-4" data-testid="nc-survey-form__heading">
{{ sharedFormView.heading }}
</h1>
<h2
v-if="sharedFormView.subheading && sharedFormView.subheading !== ''"
class="font-medium text-base text-gray-500 dark:text-gray-300 self-center mb-4"
data-testid="nc-survey-form__sub-heading"
>
{{ sharedFormView?.subheading }}
</h2>
<div v-if="sharedFormView.subheading?.trim()" class="w-full">
<LazyCellRichText
:value="sharedFormView.subheading"
class="font-medium text-base text-gray-500 dark:text-slate-300 !h-auto mb-4 -ml-1"
is-form-field
read-only
sync-value-change
data-testid="nc-survey-form__sub-heading"
/>
</div>
</div>
</div>
@ -275,7 +277,7 @@ onMounted(() => {
class="nc-form-column-description text-gray-500 text-sm"
data-testid="nc-survey-form__field-description"
>
{{ field?.description }}
<LazyCellRichText :value="field?.description" class="!h-auto -ml-1" is-form-field read-only sync-value-change />
</div>
<LazySmartsheetDivDataCell v-if="field.title" class="relative nc-form-data-cell">
@ -374,20 +376,35 @@ onMounted(() => {
<Transition name="slide-left">
<div v-if="submitted" class="flex flex-col justify-center items-center text-center">
<a-alert
class="!my-4 !py-4 !rounded-lg text-left w-full"
type="success"
class="!my-4 !py-4 text-center !rounded-lg"
data-testid="nc-survey-form__success-msg"
outlined
:message="sharedFormView?.success_msg || $t('msg.info.thankYou')"
:description="sharedFormView?.success_msg ? undefined : $t('msg.info.submittedFormData')"
/>
<div v-if="sharedFormView" class="mt-3">
<p v-if="sharedFormView?.show_blank_form" class="text-xs text-slate-500 dark:text-slate-300 text-center my-4">
>
<template #message>
<LazyCellRichText
v-if="sharedFormView?.success_msg?.trim()"
:value="sharedFormView?.success_msg"
class="!h-auto -ml-1"
is-form-field
read-only
sync-value-change
/>
<span v-else>
{{ $t('msg.info.thankYou') }}
</span>
</template>
<template v-if="!sharedFormView?.success_msg?.trim()" #description>
{{ $t('msg.info.submittedFormData') }}
</template>
</a-alert>
<div v-if="sharedFormView" class="mt-3 w-full">
<p v-if="sharedFormView?.show_blank_form" class="text-xs text-slate-500 dark:text-slate-300 text-left my-4">
{{ $t('labels.newFormLoaded') }} {{ secondsRemain }} {{ $t('general.seconds') }}
</p>
<div v-if="sharedFormView?.submit_another_form" class="text-center">
<div v-if="sharedFormView?.submit_another_form" class="text-right">
<NcButton type="primary" size="medium" data-testid="nc-survey-form__btn-submit-another-form" @click="resetForm">
{{ $t('activity.submitAnotherForm') }}
</NcButton>

2
packages/nc-gui/utils/iconUtils.ts

@ -128,6 +128,7 @@ import NcItalic from '~icons/nc-icons/italic'
import NcBold from '~icons/nc-icons/bold'
import NcUnderline from '~icons/nc-icons/underline'
import NcCrop from '~icons/nc-icons/crop'
import NcLink from '~icons/nc-icons/link'
// keep it for reference
// todo: remove it after all icons are migrated
@ -326,6 +327,7 @@ export const iconMap = {
translate: h('span', { class: 'material-symbols' }, 'translate'),
preview: h('span', { class: 'material-symbols' }, 'visibility'),
link: h('span', { class: 'material-symbols' }, 'link'),
link2: NcLink,
returnKey: h('span', { class: 'material-symbols' }, 'keyboard_return'),
keyboard: h('span', { class: 'material-symbols' }, 'keyboard'),
accountPlus: h('span', { class: 'material-symbols' }, 'person_add'),

2
packages/noco-docs/docs/130.automation/020.webhook/020.create-webhook.md

@ -36,7 +36,7 @@ keywords: ['NocoDB webhook', 'create webhook']
6. Test webhook (with sample payload) to verify if parameter are configured appropriately (optional)
7. Save the webhook
![Configuring webhook](/img/v2/webhook/create-webhook-2.png)
![Configuring webhook](/img/v2/automations/webhooks/create-webhook-2.png)
### Webhook with conditions
In case of webhook with conditions, only records meeting the configured criteria will trigger webhook. For example, trigger webhook only when `Status` is `Complete`. You can also configure multiple conditions using `AND` or `OR` operators. For example, trigger webhook only when `Status` is `Complete` and `Priority` is `High`.

BIN
packages/noco-docs/static/img/v2-unannotated/automations/webhooks/create-webhook-2.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
packages/noco-docs/static/img/v2/automations/webhooks/create-webhook-2.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

2
packages/nocodb/package.json

@ -74,7 +74,7 @@
"ajv-formats": "^2.1.1",
"archiver": "^5.3.2",
"auto-bind": "^4.0.0",
"aws-kcl": "^2.2.4",
"aws-kcl": "^2.2.5",
"aws-sdk": "^2.1550.0",
"axios": "^1.6.7",
"bcryptjs": "^2.4.3",

103
packages/nocodb/src/controllers/calendars-datas.controller.ts

@ -0,0 +1,103 @@
import {
Controller,
Get,
Param,
Query,
Req,
Res,
UseGuards,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { GlobalGuard } from '~/guards/global/global.guard';
import { DataApiLimiterGuard } from '~/guards/data-api-limiter.guard';
import { CalendarDatasService } from '~/services/calendar-datas.service';
import { parseHrtimeToMilliSeconds } from '~/helpers';
import { Acl } from '~/middlewares/extract-ids/extract-ids.middleware';
@Controller()
@UseGuards(DataApiLimiterGuard, GlobalGuard)
export class CalendarDatasController {
constructor(private readonly calendarDatasService: CalendarDatasService) {}
@Get(['/api/v1/db/calendar-data/:orgs/:baseName/:tableName/views/:viewName'])
@Acl('dataList')
async dataList(
@Req() req: Request,
@Param('viewName') viewId: string,
@Query('from_date') fromDate: string,
@Query('to_date') toDate: string,
) {
return await this.calendarDatasService.getCalendarDataList({
viewId: viewId,
query: req.query,
from_date: fromDate,
to_date: toDate,
});
}
@Get([
'/api/v1/db/calendar-data/:orgs/:baseName/:tableName/views/:viewName/countByDate/',
])
@Acl('dataList')
async calendarDataCount(
@Req() req: Request,
@Res() res: Response,
@Param('baseName') baseName: string,
@Param('tableName') tableName: string,
@Param('viewName') viewName: string,
@Query('from_date') fromDate: string,
@Query('to_date') toDate: string,
) {
const startTime = process.hrtime();
const data = await this.calendarDatasService.getCalendarRecordCount({
query: req.query,
viewId: viewName,
from_date: fromDate,
to_date: toDate,
});
const elapsedSeconds = parseHrtimeToMilliSeconds(process.hrtime(startTime));
res.setHeader('xc-db-response', elapsedSeconds);
res.json(data);
}
@Get([
'/api/v1/db/public/calendar-view/:sharedViewUuid/countByDate',
'/api/v2/public/calendar-view/:sharedViewUuid/countByDate',
])
async countByDate(
@Req() req: Request,
@Param('sharedViewUuid') sharedViewUuid: string,
@Query('from_date') fromDate: string,
@Query('to_date') toDate: string,
) {
return await this.calendarDatasService.getPublicCalendarRecordCount({
query: req.query,
password: req.headers?.['xc-password'] as string,
sharedViewUuid,
from_date: fromDate,
to_date: toDate,
});
}
@Get([
'/api/v1/db/public/calendar-view/:sharedViewUuid',
'/api/v2/public/calendar-view/:sharedViewUuid',
])
async getPublicCalendarDataList(
@Req() req: Request,
@Param('sharedViewUuid') sharedViewUuid: string,
@Query('from_date') fromDate: string,
@Query('to_date') toDate: string,
) {
return await this.calendarDatasService.getPublicCalendarDataList({
query: req.query,
password: req.headers?.['xc-password'] as string,
sharedViewUuid,
from_date: fromDate,
to_date: toDate,
});
}
}

25
packages/nocodb/src/controllers/data-alias.controller.ts

@ -45,7 +45,6 @@ export class DataAliasController {
tableName: tableName,
viewName: viewName,
disableOptimization: opt === 'false',
ignorePagination: req.headers?.['xc-ignore-pagination'] === 'true',
});
const elapsedMilliSeconds = parseHrtimeToMilliSeconds(
process.hrtime(startTime),
@ -118,30 +117,6 @@ export class DataAliasController {
res.json(countResult);
}
@Get([
'/api/v1/db/data/:orgs/:baseName/:tableName/countByDate/',
'/api/v1/db/data/:orgs/:baseName/:tableName/views/:viewName/countByDate/',
])
@Acl('dataList')
async calendarDataCount(
@Req() req: Request,
@Res() res: Response,
@Param('baseName') baseName: string,
@Param('tableName') tableName: string,
@Param('viewName') viewName: string,
) {
const startTime = process.hrtime();
const data = await this.datasService.getCalendarRecordCount({
query: req.query,
viewId: viewName,
});
const elapsedSeconds = parseHrtimeToMilliSeconds(process.hrtime(startTime));
res.setHeader('xc-db-response', elapsedSeconds);
res.json(data);
}
@Post([
'/api/v1/db/data/:orgs/:baseName/:tableName',
'/api/v1/db/data/:orgs/:baseName/:tableName/views/:viewName',

1
packages/nocodb/src/controllers/data-table.controller.ts

@ -38,7 +38,6 @@ export class DataTableController {
query: req.query,
modelId: modelId,
viewId: viewId,
ignorePagination: req.headers?.['xc-ignore-pagination'] === 'true',
});
const elapsedSeconds = parseHrtimeToMilliSeconds(process.hrtime(startTime));
res.setHeader('xc-db-response', elapsedSeconds);

15
packages/nocodb/src/controllers/public-datas.controller.ts

@ -34,21 +34,6 @@ export class PublicDatasController {
return pagedResponse;
}
@Get([
'/api/v1/db/public/shared-view/:sharedViewUuid/countByDate',
'/api/v2/public/shared-view/:sharedViewUuid/countByDate',
])
async countByDate(
@Req() req: Request,
@Param('sharedViewUuid') sharedViewUuid: string,
) {
return await this.publicDatasService.getCalendarRecordCount({
query: req.query,
password: req.headers?.['xc-password'] as string,
sharedViewUuid,
});
}
@Get([
'/api/v1/db/public/shared-view/:sharedViewUuid/groupby',
'/api/v2/public/shared-view/:sharedViewUuid/groupby',

12
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -318,12 +318,14 @@ class BaseModelSqlv2 {
sortArr?: Sort[];
sort?: string | string[];
fieldsSet?: Set<string>;
calendarLimitOverride?: number;
} = {},
options: {
ignoreViewFilterAndSort?: boolean;
ignorePagination?: boolean;
validateFormula?: boolean;
throwErrorIfInvalidParams?: boolean;
calendarLimitOverride?: number;
} = {},
): Promise<any> {
const {
@ -331,6 +333,7 @@ class BaseModelSqlv2 {
ignorePagination = false,
validateFormula = false,
throwErrorIfInvalidParams = false,
calendarLimitOverride,
} = options;
const { where, fields, ...rest } = this._getListArgs(args as any);
@ -426,7 +429,14 @@ class BaseModelSqlv2 {
if (createdCol) qb.orderBy(createdCol.column_name);
}
if (!ignorePagination) applyPaginate(qb, rest);
// For calendar View, if calendarLimitOverride is provided, use it as limit for the query
if (!ignorePagination) {
if (!calendarLimitOverride) {
applyPaginate(qb, rest);
} else {
applyPaginate(qb, { ...rest, limit: calendarLimitOverride });
}
}
const proto = await this.getProto();
let data;

20
packages/nocodb/src/helpers/formulaHelpers.ts

@ -2,14 +2,18 @@ import jsep from 'jsep';
import { UITypes } from 'nocodb-sdk';
import type FormulaColumn from '../models/FormulaColumn';
import type { Column } from '~/models';
import Noco from '~/Noco';
export async function getFormulasReferredTheColumn({
column,
columns,
}: {
column: Column;
columns: Column[];
}): Promise<Column[]> {
export async function getFormulasReferredTheColumn(
{
column,
columns,
}: {
column: Column;
columns: Column[];
},
ncMeta = Noco.ncMeta,
): Promise<Column[]> {
const fn = (pt) => {
if (pt.type === 'CallExpression') {
return pt.arguments.some((arg) => fn(arg));
@ -25,7 +29,7 @@ export async function getFormulasReferredTheColumn({
const columns = await columnsPromise;
if (c.uidt !== UITypes.Formula) return columns;
const formula = await c.getColOptions<FormulaColumn>();
const formula = await c.getColOptions<FormulaColumn>(ncMeta);
if (fn(jsep(formula.formula))) {
columns.push(c);

4
packages/nocodb/src/models/BaseUser.ts

@ -262,8 +262,8 @@ export default class BaseUser {
// delete list cache to refresh list
await NocoCache.deepDel(
`${CacheScope.BASE_USER}:${baseId}:${userId}`,
CacheDelDirection.CHILD_TO_PARENT,
`${CacheScope.BASE_USER}:${baseId}:list`,
CacheDelDirection.PARENT_TO_CHILD,
);
return response;

2
packages/nocodb/src/models/CalendarViewColumn.ts

@ -93,7 +93,7 @@ export default class CalendarViewColumn {
{
const view = await View.get(column.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
return this.get(id, ncMeta).then(async (viewColumn) => {

37
packages/nocodb/src/models/Column.ts

@ -216,7 +216,7 @@ export default class Column<T = any> implements ColumnType {
ncMeta,
);
await View.clearSingleQueryCache(column.fk_model_id);
await View.clearSingleQueryCache(column.fk_model_id, null, ncMeta);
return col;
}
@ -914,7 +914,7 @@ export default class Column<T = any> implements ColumnType {
// on column delete, delete any optimised single query cache
{
await View.clearSingleQueryCache(col.fk_model_id);
await View.clearSingleQueryCache(col.fk_model_id, null, ncMeta);
}
}
@ -1074,11 +1074,16 @@ export default class Column<T = any> implements ColumnType {
column.column_order.view_id
) {
const viewColumn = (
await View.getColumns(column.column_order.view_id)
await View.getColumns(column.column_order.view_id, ncMeta)
).find((col) => col.fk_column_id === column.id);
await View.updateColumn(column.column_order.view_id, viewColumn.id, {
order: column.column_order.order,
});
await View.updateColumn(
column.column_order.view_id,
viewColumn.id,
{
order: column.column_order.order,
},
ncMeta,
);
}
// set meta
@ -1098,16 +1103,22 @@ export default class Column<T = any> implements ColumnType {
await this.insertColOption(column, colId, ncMeta);
// on column update, delete any optimised single query cache
await View.clearSingleQueryCache(oldCol.fk_model_id);
await View.clearSingleQueryCache(oldCol.fk_model_id, null, ncMeta);
const updatedColumn = await Column.get({ colId });
const updatedColumn = await Column.get({ colId }, ncMeta);
if (!skipFormulaInvalidate) {
// invalidate formula parsed-tree in which current column is used
// whenever a new request comes for that formula, it will be populated again
getFormulasReferredTheColumn({
column: updatedColumn,
columns: await Column.list({ fk_model_id: oldCol.fk_model_id }, ncMeta),
})
getFormulasReferredTheColumn(
{
column: updatedColumn,
columns: await Column.list(
{ fk_model_id: oldCol.fk_model_id },
ncMeta,
),
},
ncMeta,
)
.then(async (formulas) => {
for (const formula of formulas) {
await FormulaColumn.update(
@ -1146,7 +1157,7 @@ export default class Column<T = any> implements ColumnType {
const column = await Column.get({ colId }, ncMeta);
await View.clearSingleQueryCache(column.fk_model_id);
await View.clearSingleQueryCache(column.fk_model_id, null, ncMeta);
}
public getValidators(): any {

20
packages/nocodb/src/models/Filter.ts

@ -213,7 +213,7 @@ export default class Filter implements FilterType {
if (filter.fk_view_id) {
const view = await View.get(filter.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
}
@ -251,9 +251,11 @@ export default class Filter implements FilterType {
// if not a view filter then no need to delete
if (filter.fk_view_id) {
const view = await View.get(filter.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [
{ id: filter.fk_view_id },
]);
await View.clearSingleQueryCache(
view.fk_model_id,
[{ id: filter.fk_view_id }],
ncMeta,
);
}
}
@ -281,9 +283,11 @@ export default class Filter implements FilterType {
if (filter.fk_view_id) {
const view = await View.get(filter.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [
{ id: filter.fk_view_id },
]);
await View.clearSingleQueryCache(
view.fk_model_id,
[{ id: filter.fk_view_id }],
ncMeta,
);
}
}
}
@ -437,7 +441,7 @@ export default class Filter implements FilterType {
// on update delete any optimised single query cache
{
const view = await View.get(viewId, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
}

108
packages/nocodb/src/models/FormView.ts

@ -1,5 +1,10 @@
import type { MetaType } from 'nocodb-sdk';
import type { BoolType, FormType } from 'nocodb-sdk';
import type {
MetaType,
BoolType,
FormType,
AttachmentResType,
} from 'nocodb-sdk';
import { PresignedUrl } from '~/models';
import FormViewColumn from '~/models/FormViewColumn';
import View from '~/models/View';
import { extractProps } from '~/helpers/extractProps';
@ -9,7 +14,12 @@ import { deserializeJSON, serializeJSON } from '~/utils/serialize';
import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals';
import { prepareForDb, prepareForResponse } from '~/utils/modelUtils';
export default class FormView implements FormType {
type FormViewType = Omit<FormType, 'banner_image_url' | 'logo_url'> & {
banner_image_url?: AttachmentResType | string;
logo_url?: AttachmentResType | string;
};
export default class FormView implements FormViewType {
show: BoolType;
is_default: BoolType;
order: number;
@ -20,8 +30,8 @@ export default class FormView implements FormType {
redirect_url?: string;
redirect_after_secs?: string;
email?: string;
banner_image_url?: string;
logo_url?: string;
banner_image_url?: AttachmentResType | string;
logo_url?: AttachmentResType | string;
submit_another_form?: BoolType;
show_blank_form?: BoolType;
@ -46,11 +56,21 @@ export default class FormView implements FormType {
view = await ncMeta.metaGet2(null, null, MetaTable.FORM_VIEW, {
fk_view_id: viewId,
});
if (view) {
view.meta = deserializeJSON(view.meta);
await NocoCache.set(`${CacheScope.FORM_VIEW}:${viewId}`, view);
}
}
const convertedAttachment = await this.convertAttachmentType({
banner_image_url: view?.banner_image_url,
logo_url: view?.logo_url,
});
view.banner_image_url = convertedAttachment.banner_image_url || null;
view.logo_url = convertedAttachment.logo_url || null;
return view && new FormView(view);
}
@ -74,6 +94,17 @@ export default class FormView implements FormType {
if (insertObj.meta) {
insertObj.meta = serializeJSON(insertObj.meta);
}
if (insertObj?.logo_url) {
insertObj.logo_url = this.serializeAttachmentJSON(insertObj.logo_url);
}
if (insertObj?.banner_image_url) {
insertObj.banner_image_url = this.serializeAttachmentJSON(
insertObj.banner_image_url,
);
}
if (!(view.base_id && view.source_id)) {
const viewRef = await View.get(view.fk_view_id);
insertObj.base_id = viewRef.base_id;
@ -103,6 +134,16 @@ export default class FormView implements FormType {
'meta',
]);
if (updateObj?.logo_url) {
updateObj.logo_url = this.serializeAttachmentJSON(updateObj.logo_url);
}
if (updateObj?.banner_image_url) {
updateObj.banner_image_url = this.serializeAttachmentJSON(
updateObj.banner_image_url,
);
}
// update meta
const res = await ncMeta.metaUpdate(
null,
@ -131,4 +172,61 @@ export default class FormView implements FormType {
await form.getColumns(ncMeta);
return form;
}
static serializeAttachmentJSON(attachment): string | null {
if (attachment) {
return serializeJSON(
extractProps(deserializeJSON(attachment), [
'url',
'path',
'title',
'mimetype',
'size',
'icon',
]),
);
}
return attachment;
}
protected static async convertAttachmentType(
formAttachments: Record<string, any>,
) {
try {
if (formAttachments) {
const promises = [];
for (const key in formAttachments) {
if (
formAttachments[key] &&
typeof formAttachments[key] === 'string'
) {
formAttachments[key] = deserializeJSON(formAttachments[key]);
}
if (formAttachments[key]?.path) {
promises.push(
PresignedUrl.getSignedUrl({
path: formAttachments[key].path.replace(/^download\//, ''),
}).then((r) => (formAttachments[key].signedPath = r)),
);
} else if (formAttachments[key]?.url) {
if (formAttachments[key].url.includes('.amazonaws.com/')) {
const relativePath = decodeURI(
formAttachments[key].url.split('.amazonaws.com/')[1],
);
promises.push(
PresignedUrl.getSignedUrl({
path: relativePath,
s3: true,
}).then((r) => (formAttachments[key].signedUrl = r)),
);
}
}
}
await Promise.all(promises);
}
} catch {}
return formAttachments;
}
}

2
packages/nocodb/src/models/GalleryViewColumn.ts

@ -81,7 +81,7 @@ export default class GalleryViewColumn {
// on new view column, delete any optimised single query cache
{
const view = await View.get(column.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
return this.get(id, ncMeta).then(async (viewColumn) => {

4
packages/nocodb/src/models/GridViewColumn.ts

@ -114,7 +114,7 @@ export default class GridViewColumn implements GridColumnType {
// on new view column, delete any optimised single query cache
{
const view = await View.get(column.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
return this.get(id, ncMeta).then(async (viewColumn) => {
@ -159,7 +159,7 @@ export default class GridViewColumn implements GridColumnType {
{
const gridCol = await this.get(columnId, ncMeta);
const view = await View.get(gridCol.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
return res;

8
packages/nocodb/src/models/Model.ts

@ -677,7 +677,7 @@ export default class Model implements TableType {
]);
// clear all the cached query under this model
await View.clearSingleQueryCache(tableId);
await View.clearSingleQueryCache(tableId, null, ncMeta);
// clear all the cached query under related models
for (const col of await this.get(tableId).then((t) => t.getColumns())) {
@ -687,7 +687,11 @@ export default class Model implements TableType {
if (colOptions.fk_related_model_id === tableId) continue;
await View.clearSingleQueryCache(colOptions.fk_related_model_id);
await View.clearSingleQueryCache(
colOptions.fk_related_model_id,
null,
ncMeta,
);
}
return res;

8
packages/nocodb/src/models/Sort.ts

@ -37,7 +37,7 @@ export default class Sort {
// on delete, delete any optimised single query cache
{
const view = await View.get(viewId, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
}
@ -95,7 +95,7 @@ export default class Sort {
// on insert, delete any optimised single query cache
{
const view = await View.get(row.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
return this.get(row.id, ncMeta).then(async (sort) => {
@ -169,7 +169,7 @@ export default class Sort {
{
const sort = await this.get(sortId, ncMeta);
const view = await View.get(sort.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
return res;
@ -188,7 +188,7 @@ export default class Sort {
// on delete, delete any optimised single query cache
if (sort?.fk_view_id) {
const view = await View.get(sort.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
}
}

10
packages/nocodb/src/models/View.ts

@ -878,7 +878,7 @@ export default class View implements ViewType {
await NocoCache.update(`${cacheScope}:${colId}`, updateObj);
// on view column update, delete corresponding single query cache
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
return res;
}
@ -920,7 +920,7 @@ export default class View implements ViewType {
);
// on view column update, delete any optimised single query cache
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
return { ...existingCol, ...colData };
} else {
@ -1137,7 +1137,7 @@ export default class View implements ViewType {
}
// on update, delete any optimised single query cache
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
return view;
}
@ -1188,7 +1188,7 @@ export default class View implements ViewType {
]);
// on update, delete any optimised single query cache
await View.clearSingleQueryCache(view.fk_model_id, [view]);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);
await Model.getNonDefaultViewsCountAndReset(
{ modelId: view.fk_model_id },
@ -1476,6 +1476,8 @@ export default class View implements ViewType {
views?: { id?: string }[],
ncMeta = Noco.ncMeta,
) {
if (!Noco.isEE()) return;
// get all views of the model
let viewsList =
views || (await NocoCache.getList(CacheScope.VIEW, [modelId])).list;

5
packages/nocodb/src/modules/datas/datas.module.ts

@ -18,6 +18,8 @@ import { DataAliasNestedService } from '~/services/data-alias-nested.service';
import { OldDatasService } from '~/controllers/old-datas/old-datas.service';
import { PublicDatasExportService } from '~/services/public-datas-export.service';
import { PublicDatasService } from '~/services/public-datas.service';
import { CalendarDatasController } from '~/controllers/calendars-datas.controller';
import { CalendarDatasService } from '~/services/calendar-datas.service';
export const dataModuleMetadata = {
imports: [
@ -33,6 +35,7 @@ export const dataModuleMetadata = {
? [
DataTableController,
DatasController,
CalendarDatasController,
BulkDataAliasController,
DataAliasController,
DataAliasNestedController,
@ -48,6 +51,7 @@ export const dataModuleMetadata = {
DatasService,
BulkDataAliasService,
DataAliasNestedService,
CalendarDatasService,
OldDatasService,
PublicDatasService,
PublicDatasExportService,
@ -55,6 +59,7 @@ export const dataModuleMetadata = {
exports: [
DatasService,
BulkDataAliasService,
CalendarDatasService,
DataAliasNestedService,
OldDatasService,
PublicDatasService,

302
packages/nocodb/src/schema/swagger-v2.json

@ -5714,7 +5714,12 @@
"examples": {
"Example 1": {
"value": {
"banner_image_url": null,
"banner_image_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"email": "user@example.com",
"heading": "My Form",
"lock_type": "collaborative",
@ -5755,7 +5760,13 @@
"Example 1": {
"value": {
"source_id": "ds_g4ccx6e77h1dmi",
"banner_image_url": null,
"banner_image_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg",
"signedPath": "dltemp/lNoLbqB62Jdo5Rmp/1709308800000/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"columns": [
{
"id": "fvc_ugj9zo5bzocxtl",
@ -7273,7 +7284,7 @@
"description": "List Shared View Grouped Data"
}
},
"/api/v2/public/shared-view/{sharedViewUuid}/countByDate": {
"/api/v2/public/calendar-view/{sharedViewUuid}/countByDate": {
"parameters": [
{
"schema": {
@ -7350,6 +7361,180 @@
]
}
},
"/api/v2/public/calendar-view/{sharedViewUuid}/": {
"parameters": [
{
"schema": {
"type": "string",
"example": "24a6d0bb-e45d-4b1a-bfef-f492d870de9f"
},
"name": "sharedViewUuid",
"in": "path",
"required": true,
"description": "Shared View UUID"
},
{
"schema": {
"type": "string"
},
"in": "header",
"name": "xc-password",
"description": "Shared view password"
}
],
"get": {
"summary": "List Shared View Rows",
"operationId": "public-calendar-data-list",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SharedViewList"
},
"examples": {
"Example 1": {
"value": {
"list": [
{
"source_id": "ds_g4ccx6e77h1dmi",
"created_at": "2023-03-02 17:46:31",
"fk_model_id": "md_mhs9z4r2ak98x0",
"id": "vw_lg052cnc1c26kf",
"is_default": 1,
"lock_type": "collaborative",
"meta": {},
"order": 1,
"password": null,
"base_id": "p_xm3thidrblw4n7",
"show": 1,
"show_system_fields": null,
"title": "Sheet-1",
"type": 3,
"updated_at": "2023-03-02 17:46:31",
"uuid": null,
"view": {
"source_id": "ds_g4ccx6e77h1dmi",
"created_at": "2023-03-02 17:46:31",
"fk_view_id": "vw_lg052cnc1c26kf",
"meta": null,
"base_id": "p_xm3thidrblw4n7",
"row_height": null,
"updated_at": "2023-03-02 17:46:31",
"uuid": null
}
}
],
"pageInfo": {
"isFirstPage": true,
"isLastPage": true,
"page": 1,
"pageSize": 10,
"totalRows": 1
}
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"tags": [
"Public"
],
"parameters": [
{
"schema": {
"type": "array"
},
"in": "query",
"name": "fields",
"description": "Which fields to be shown"
},
{
"schema": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"in": "query",
"name": "sort",
"description": "The result will be sorted based on `sort` query"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "from_date",
"description": "From Date"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "to_date",
"description": "To Date"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where",
"description": "Extra filtering"
},
{
"schema": {
"type": "integer",
"minimum": 0
},
"in": "query",
"name": "offset",
"description": "Offset in rows"
},
{
"schema": {
"type": "integer",
"minimum": 1
},
"in": "query",
"name": "limit",
"description": "Limit in rows"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "sortArrJson",
"description": "Used for multiple sort queries"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "filterArrJson",
"description": "Used for multiple filter queries"
}
],
"description": "List all shared view rows"
},
},
"/api/v2/public/shared-view/{sharedViewUuid}/rows": {
"parameters": [
{
@ -11497,6 +11682,64 @@
"id": "6cr1iwhbyxncd"
}
},
"AttachmentRes": {
"description": "Model for Attachment Response",
"oneOf": [
{
"type": "object",
"x-examples": {
"Example 1": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg",
"signedPath": "dltemp/lNoLbqB62Jdo5Rmp/1709308800000/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"Example 2" :{
"mimetype": "image/jpeg",
"size": 146143,
"title": "2 be loved.jpeg",
"url": "https://some-s3-server.com/nc/uploads/2023/10/16/some-key/3niqHLngUKiU2Hupe8.jpeg",
"signedUrl": "https://some-s3-server.com/nc/uploads/2023/10/16/signed-url-misc-info"
}
},
"properties": {
"mimetype": {
"type": "string",
"description": "The mimetype of the attachment"
},
"path": {
"type": "string",
"description": "The attachment stored path"
},
"size": {
"type": "number",
"description": "The size of the attachment"
},
"title": {
"type": "string",
"description": "The title of the attachment used in UI"
},
"url": {
"type": "string",
"description": "The attachment stored url"
},
"signedPath": {
"type": "string",
"description": "Attachment signedPath will allow to access attachment directly"
},
"signedUrl": {
"type": "string",
"description": "Attachment signedUrl will allow to access attachment directly"
}
}
},
{
"type": "null"
}
],
"title": "Attachment Response Model"
},
"Audit": {
"description": "Model for Audit",
"examples": [
@ -12978,7 +13221,13 @@
"examples": [
{
"source_id": "ds_g4ccx6e77h1dmi",
"banner_image_url": null,
"banner_image_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg",
"signedPath": "dltemp/lNoLbqB62Jdo5Rmp/1709308800000/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"columns": [
{
"id": "fvc_ugj9zo5bzocxtl",
@ -13002,7 +13251,13 @@
"fk_model_id": "md_rsu68aqjsbyqtl",
"heading": "My Form",
"lock_type": "collaborative",
"logo_url": null,
"logo_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg",
"signedPath": "dltemp/lNoLbqB62Jdo5Rmp/1709308800000/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"meta": null,
"redirect_after_secs": null,
"redirect_url": null,
@ -13024,8 +13279,8 @@
}
},
"banner_image_url": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Banner Image URL. Not in use currently."
"$ref": "#/components/schemas/AttachmentRes",
"description": "Banner Image URL"
},
"columns": {
"type": "array",
@ -13067,8 +13322,8 @@
"example": "collaborative"
},
"logo_url": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Logo URL. Not in use currently."
"$ref": "#/components/schemas/AttachmentRes",
"description": "Logo URL."
},
"meta": {
"$ref": "#/components/schemas/Meta",
@ -13113,7 +13368,12 @@
"description": "Model for Form Update Request",
"examples": [
{
"banner_image_url": null,
"banner_image_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"email": "user@example.com",
"heading": "My Form",
"logo_url": null,
@ -13130,8 +13390,15 @@
"type": "object",
"properties": {
"banner_image_url": {
"$ref": "#/components/schemas/TextOrNull",
"description": "Banner Image URL. Not in use currently."
"oneOf": [
{
"$ref": "#/components/schemas/AttachmentReq"
},
{
"type" : "null"
}
],
"description": "Banner Image URL"
},
"email": {
"$ref": "#/components/schemas/StringOrNull",
@ -13144,8 +13411,15 @@
"type": "string"
},
"logo_url": {
"$ref": "#/components/schemas/TextOrNull",
"description": "Logo URL. Not in use currently."
"oneOf": [
{
"$ref": "#/components/schemas/AttachmentReq"
},
{
"type" : "null"
}
],
"description": "Logo URL."
},
"meta": {
"$ref": "#/components/schemas/Meta",

885
packages/nocodb/src/schema/swagger.json

@ -7426,7 +7426,12 @@
"examples": {
"Example 1": {
"value": {
"banner_image_url": null,
"banner_image_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"email": "user@example.com",
"heading": "My Form",
"lock_type": "collaborative",
@ -7467,7 +7472,13 @@
"Example 1": {
"value": {
"source_id": "ds_g4ccx6e77h1dmi",
"banner_image_url": null,
"banner_image_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg",
"signedPath": "dltemp/lNoLbqB62Jdo5Rmp/1709308800000/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"columns": [
{
"id": "fvc_ugj9zo5bzocxtl",
@ -9713,7 +9724,592 @@
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
}
}
},
"/api/v1/db/calendar-data/{orgs}/{baseName}/{tableName}/views/{viewName}": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "orgs",
"in": "path",
"required": true,
"description": "Organisation Name. Currently `noco` will be used."
},
{
"schema": {
"type": "string"
},
"name": "baseName",
"in": "path",
"required": true,
"description": "Base Name"
},
{
"schema": {
"type": "string"
},
"name": "tableName",
"in": "path",
"required": true,
"description": "Table Name"
},
{
"schema": {
"type": "string"
},
"name": "viewName",
"in": "path",
"required": true
}
],
"get": {
"summary": "List rows in Calendar View of a Table",
"operationId": "db-calendar-view-row-list",
"description": "List all rows in Calendar View of a Table",
"tags": [
"DB Calendar View Row"
],
"parameters": [
{
"schema": {
"type": "string"
},
"name": "from_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "string"
},
"name": "to_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "array"
},
"in": "query",
"name": "fields"
},
{
"schema": {
"type": "array"
},
"in": "query",
"name": "sort"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where"
},
{
"schema": {},
"in": "query",
"name": "nested",
"description": "Query params for nested data"
},
{
"schema": {
"type": "number"
},
"in": "query",
"name": "offset"
},
{
"$ref": "#/components/parameters/xc-auth"
}
]
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"list": {
"type": "array",
"x-stoplight": {
"id": "okd8utzm9xqet"
},
"description": "List of calendar view rows",
"items": {
"x-stoplight": {
"id": "j758lsjv53o4q"
},
"type": "object"
}
},
"pageInfo": {
"$ref": "#/components/schemas/Paginated",
"x-stoplight": {
"id": "hylgqzgm8yhye"
},
"description": "Paginated Info"
}
},
"required": [
"list",
"pageInfo"
]
},
"examples": {
"Example 1": {
"value": {
"list": [
{
"Id": 1,
"Title": "baz",
"SingleSelect": null,
"Sheet-1 List": [
{
"Id": 1,
"Title": "baz"
}
],
"LTAR": [
{
"Id": 1,
"Title": "baz"
}
]
},
{
"Id": 2,
"Title": "foo",
"SingleSelect": "a",
"Sheet-1 List": [
{
"Id": 2,
"Title": "foo"
}
],
"LTAR": [
{
"Id": 2,
"Title": "foo"
}
]
},
{
"Id": 3,
"Title": "bar",
"SingleSelect": "b",
"Sheet-1 List": [],
"LTAR": []
}
],
"pageInfo": {
"totalRows": 3,
"page": 1,
"pageSize": 25,
"isFirstPage": true,
"isLastPage": true
}
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
},
"/api/v1/db/calendar-data/{orgs}/{baseName}/{tableName}/views/{viewName}/countByDate/" : {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "orgs",
"in": "path",
"required": true,
"description": "Organisation Name. Currently `noco` will be used."
},
{
"schema": {
"type": "string"
},
"name": "baseName",
"in": "path",
"required": true,
"description": "Base Name"
},
{
"schema": {
"type": "string"
},
"name": "tableName",
"in": "path",
"required": true,
"description": "Table Name"
},
{
"schema": {
"type": "string"
},
"name": "viewName",
"in": "path",
"required": true
}
],
"get": {
"summary": "Count of Records in Dates in Calendar View",
"operationId": "db-calendar-view-row-count",
"description": "Get the count of table view rows grouped by the dates",
"tags": [
"DB Calendar View Row Count"
],
"parameters": [
{
"schema": {
"type": "string"
},
"name": "from_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "string"
},
"name": "to_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "array"
},
"in": "query",
"name": "sort"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where"
},
{
"schema": {
"type": "integer",
"minimum": 1
},
"in": "query",
"name": "limit"
},
{
"schema": {
"type": "integer",
"minimum": 0
},
"in": "query",
"name": "offset"
},
{
"$ref": "#/components/parameters/xc-auth"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
}
},
"/api/v1/db/public/calendar-view/{sharedViewUuid}": {
"parameters": [
{
"schema": {
"type": "string",
"example": "24a6d0bb-e45d-4b1a-bfef-f492d870de9f"
},
"name": "sharedViewUuid",
"in": "path",
"required": true,
"description": "Shared View UUID"
},
{
"schema": {
"type": "string"
},
"in": "header",
"name": "xc-password",
"description": "Shared view password"
}
],
"get": {
"summary": "List rows in Calendar View of a Table",
"operationId": "public-data-calendar-row-list",
"description": "List all rows in Calendar View of a Table",
"tags": [
"DB Calendar View Row"
],
"parameters": [
{
"schema": {
"type": "string"
},
"name": "from_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "string"
},
"name": "to_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "array"
},
"in": "query",
"name": "fields"
},
{
"schema": {
"type": "array"
},
"in": "query",
"name": "sort"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where"
},
{
"schema": {},
"in": "query",
"name": "nested",
"description": "Query params for nested data"
},
{
"schema": {
"type": "number"
},
"in": "query",
"name": "offset"
},
{
"$ref": "#/components/parameters/xc-auth"
}
]
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"list": {
"type": "array",
"x-stoplight": {
"id": "okd8utzm9xqet"
},
"description": "List of calendar view rows",
"items": {
"x-stoplight": {
"id": "j758lsjv53o4q"
},
"type": "object"
}
},
"pageInfo": {
"$ref": "#/components/schemas/Paginated",
"x-stoplight": {
"id": "hylgqzgm8yhye"
},
"description": "Paginated Info"
}
},
"required": [
"list",
"pageInfo"
]
},
"examples": {
"Example 1": {
"value": {
"list": [
{
"Id": 1,
"Title": "baz",
"SingleSelect": null,
"Sheet-1 List": [
{
"Id": 1,
"Title": "baz"
}
],
"LTAR": [
{
"Id": 1,
"Title": "baz"
}
]
},
{
"Id": 2,
"Title": "foo",
"SingleSelect": "a",
"Sheet-1 List": [
{
"Id": 2,
"Title": "foo"
}
],
"LTAR": [
{
"Id": 2,
"Title": "foo"
}
]
},
{
"Id": 3,
"Title": "bar",
"SingleSelect": "b",
"Sheet-1 List": [],
"LTAR": []
}
],
"pageInfo": {
"totalRows": 3,
"page": 1,
"pageSize": 25,
"isFirstPage": true,
"isLastPage": true
}
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
},
"/api/v1/db/public/calendar-view/{sharedViewUuid}/countByDate": {
"parameters": [
{
"schema": {
"type": "string",
"example": "24a6d0bb-e45d-4b1a-bfef-f492d870de9f"
},
"name": "sharedViewUuid",
"in": "path",
"required": true,
"description": "Shared View UUID"
},
{
"schema": {
"type": "string"
},
"in": "header",
"name": "xc-password",
"description": "Shared view password"
}
],
"get": {
"summary": "Count of Records in Dates in Calendar View",
"operationId": "public-data-calendar-row-count",
"parameters": [
{
"schema": {
"type": "string"
},
"name": "from_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "string"
},
"name": "to_date",
"in": "query",
"required": true
},
{
"schema": {
"type": "array"
},
"in": "query",
"name": "sort"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where"
},
{
"schema": {
"type": "integer",
"minimum": 1
},
"in": "query",
"name": "limit"
},
{
"schema": {
"type": "integer",
"minimum": 0
},
"in": "query",
"name": "offset"
},
{
"$ref": "#/components/parameters/xc-auth"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"tags": [
"Public"
]
}
},
"/api/v1/db/data/{orgs}/{baseName}/{tableName}/views/{viewName}": {
@ -10054,101 +10650,6 @@
}
}
},
"/api/v1/db/data/{orgs}/{baseName}/{tableName}/views/{viewName}/countByDate/" : {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "orgs",
"in": "path",
"required": true,
"description": "Organisation Name. Currently `noco` will be used."
},
{
"schema": {
"type": "string"
},
"name": "baseName",
"in": "path",
"required": true,
"description": "Base Name"
},
{
"schema": {
"type": "string"
},
"name": "tableName",
"in": "path",
"required": true,
"description": "Table Name"
},
{
"schema": {
"type": "string"
},
"name": "viewName",
"in": "path",
"required": true
}
],
"get": {
"summary": "Count of Records in Dates in Calendar View",
"operationId": "db-view-row-calendar-count",
"description": "Get the count of table view rows grouped by the dates",
"tags": [
"DB View Row"
],
"parameters": [
{
"schema": {
"type": "array"
},
"in": "query",
"name": "sort"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where"
},
{
"schema": {
"type": "integer",
"minimum": 1
},
"in": "query",
"name": "limit"
},
{
"schema": {
"type": "integer",
"minimum": 0
},
"in": "query",
"name": "offset"
},
{
"$ref": "#/components/parameters/xc-auth"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
}
},
"/api/v1/db/data/{orgs}/{baseName}/{tableName}/views/{viewName}/groupby": {
"parameters": [
{
@ -12118,83 +12619,6 @@
"description": "List Shared View Grouped Data"
}
},
"/api/v1/db/public/shared-view/{sharedViewUuid}/countByDate": {
"parameters": [
{
"schema": {
"type": "string",
"example": "24a6d0bb-e45d-4b1a-bfef-f492d870de9f"
},
"name": "sharedViewUuid",
"in": "path",
"required": true,
"description": "Shared View UUID"
},
{
"schema": {
"type": "string"
},
"in": "header",
"name": "xc-password",
"description": "Shared view password"
}
],
"get": {
"summary": "Count of Records in Dates in Calendar View",
"operationId": "public-calendar-count",
"parameters": [
{
"schema": {
"type": "array"
},
"in": "query",
"name": "sort"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where"
},
{
"schema": {
"type": "integer",
"minimum": 1
},
"in": "query",
"name": "limit"
},
{
"schema": {
"type": "integer",
"minimum": 0
},
"in": "query",
"name": "offset"
},
{
"$ref": "#/components/parameters/xc-auth"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"tags": [
"Public"
]
}
},
"/api/v1/db/public/shared-view/{sharedViewUuid}/rows": {
"parameters": [
{
@ -17093,6 +17517,64 @@
"id": "6cr1iwhbyxncd"
}
},
"AttachmentRes": {
"description": "Model for Attachment Response",
"oneOf": [
{
"type": "object",
"x-examples": {
"Example 1": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg",
"signedPath": "dltemp/lNoLbqB62Jdo5Rmp/1709308800000/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"Example 2" :{
"mimetype": "image/jpeg",
"size": 146143,
"title": "2 be loved.jpeg",
"url": "https://some-s3-server.com/nc/uploads/2023/10/16/some-key/3niqHLngUKiU2Hupe8.jpeg",
"signedUrl": "https://some-s3-server.com/nc/uploads/2023/10/16/signed-url-misc-info"
}
},
"properties": {
"mimetype": {
"type": "string",
"description": "The mimetype of the attachment"
},
"path": {
"type": "string",
"description": "The attachment stored path"
},
"size": {
"type": "number",
"description": "The size of the attachment"
},
"title": {
"type": "string",
"description": "The title of the attachment used in UI"
},
"url": {
"type": "string",
"description": "The attachment stored url"
},
"signedPath": {
"type": "string",
"description": "Attachment signedPath will allow to access attachment directly"
},
"signedUrl": {
"type": "string",
"description": "Attachment signedUrl will allow to access attachment directly"
}
}
},
{
"type": "null"
}
],
"title": "Attachment Response Model"
},
"FileReq": {
"description": "Model for File Request",
"type": "object",
@ -18620,7 +19102,13 @@
"examples": [
{
"source_id": "ds_g4ccx6e77h1dmi",
"banner_image_url": null,
"banner_image_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg",
"signedPath": "dltemp/lNoLbqB62Jdo5Rmp/1709308800000/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"columns": [
{
"id": "fvc_ugj9zo5bzocxtl",
@ -18644,7 +19132,13 @@
"fk_model_id": "md_rsu68aqjsbyqtl",
"heading": "My Form",
"lock_type": "collaborative",
"logo_url": null,
"logo_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg",
"signedPath": "dltemp/lNoLbqB62Jdo5Rmp/1709308800000/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"meta": null,
"redirect_after_secs": null,
"redirect_url": null,
@ -18666,8 +19160,8 @@
}
},
"banner_image_url": {
"$ref": "#/components/schemas/TextOrNull",
"description": "Banner Image URL. Not in use currently."
"$ref": "#/components/schemas/AttachmentRes",
"description": "Banner Image URL"
},
"columns": {
"type": "array",
@ -18709,8 +19203,8 @@
"example": "collaborative"
},
"logo_url": {
"$ref": "#/components/schemas/TextOrNull",
"description": "Logo URL. Not in use currently."
"$ref": "#/components/schemas/AttachmentRes",
"description": "Logo URL"
},
"meta": {
"$ref": "#/components/schemas/Meta",
@ -18755,7 +19249,12 @@
"description": "Model for Form Update Request",
"examples": [
{
"banner_image_url": null,
"banner_image_url": {
"mimetype": "image/jpg",
"size": 32903,
"title": "Random-Pictures-of-Conceptual-and-Creative-Ideas-02.jpg",
"path": "download/noco/pm0umqsip16i1u5/m8yn03dncqal6ri//iDL5ednaHz2j2Sa3Cl.jpg"
},
"email": "user@example.com",
"heading": "My Form",
"logo_url": null,
@ -18772,8 +19271,15 @@
"type": "object",
"properties": {
"banner_image_url": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Banner Image URL. Not in use currently."
"oneOf": [
{
"$ref": "#/components/schemas/AttachmentReq"
},
{
"type" : "null"
}
],
"description": "Banner Image URL"
},
"email": {
"$ref": "#/components/schemas/StringOrNull",
@ -18786,8 +19292,15 @@
"type": "string"
},
"logo_url": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Logo URL. Not in use currently."
"oneOf": [
{
"$ref": "#/components/schemas/AttachmentReq"
},
{
"type" : "null"
}
],
"description": "Logo URL"
},
"meta": {
"$ref": "#/components/schemas/Meta",

230
packages/nocodb/src/services/calendar-datas.service.ts

@ -0,0 +1,230 @@
import { Injectable, Logger } from '@nestjs/common';
import { ErrorMessages, ViewTypes } from 'nocodb-sdk';
import dayjs from 'dayjs';
import type { CalendarRangeType, FilterType } from 'nocodb-sdk';
import { CalendarRange, Model, View } from '~/models';
import { NcError } from '~/helpers/catchError';
import { DatasService } from '~/services/datas.service';
@Injectable()
export class CalendarDatasService {
protected logger = new Logger(CalendarDatasService.name);
constructor(protected datasService: DatasService) {}
async getCalendarDataList(param: {
viewId: string;
query: any;
from_date: string;
to_date: string;
}) {
const { viewId, query, from_date, to_date } = param;
if (!from_date || !to_date)
NcError.badRequest('from_date and to_date are required');
if (dayjs(to_date).diff(dayjs(from_date), 'days') > 42) {
NcError.badRequest('Date range should not exceed 42 days');
}
const view = await View.get(viewId);
if (!view) NcError.notFound('View not found');
if (view.type !== ViewTypes.CALENDAR)
NcError.badRequest('View is not a calendar view');
const calendarRange = await CalendarRange.read(view.id);
if (!calendarRange?.ranges?.length) NcError.badRequest('No ranges found');
const filterArr = await this.buildFilterArr({
viewId,
from_date,
to_date,
});
query.filterArr = [...(query.filterArr ? query.filterArr : []), filterArr];
const model = await Model.getByIdOrName({
id: view.fk_model_id,
});
return await this.datasService.dataList({
...param,
...query,
baseName: model.base_id,
tableName: model.id,
calendarLimitOverride: 3000, // TODO: make this configurable in env
});
}
async getPublicCalendarRecordCount(param: {
password: string;
query: any;
sharedViewUuid: string;
from_date: string;
to_date: string;
}) {
const { sharedViewUuid, password, query = {} } = param;
const view = await View.getByUUID(sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (view.type !== ViewTypes.CALENDAR) {
NcError.notFound('Not found');
}
if (view.password && view.password !== password) {
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
return this.getCalendarRecordCount({
viewId: view.id,
query,
from_date: param.from_date,
to_date: param.to_date,
});
}
async getPublicCalendarDataList(param: {
password: string;
query: any;
sharedViewUuid: string;
from_date: string;
to_date: string;
}) {
const { sharedViewUuid, password, query = {} } = param;
const view = await View.getByUUID(sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (view.type !== ViewTypes.CALENDAR) {
NcError.notFound('Not found');
}
if (view.password && view.password !== password) {
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
return this.getCalendarDataList({
viewId: view.id,
query,
from_date: param.from_date,
to_date: param.to_date,
});
}
async getCalendarRecordCount(param: {
viewId: string;
query: any;
from_date: string;
to_date: string;
}) {
const { viewId, query, from_date, to_date } = param;
if (!from_date || !to_date)
NcError.badRequest('from_date and to_date are required');
if (dayjs(to_date).diff(dayjs(from_date), 'days') > 395) {
NcError.badRequest('Date range should not exceed 395 days');
}
const view = await View.get(viewId);
if (!view) NcError.notFound('View not found');
if (view.type !== ViewTypes.CALENDAR)
NcError.badRequest('View is not a calendar view');
const { ranges } = await CalendarRange.read(view.id);
if (!ranges.length) NcError.badRequest('No ranges found');
const filterArr = await this.buildFilterArr({
viewId,
from_date,
to_date,
});
query.filterArr = [...(query.filterArr ? query.filterArr : []), filterArr];
const model = await Model.getByIdOrName({
id: view.fk_model_id,
});
const data = await this.datasService.dataList({
...param,
baseName: model.base_id,
tableName: model.id,
ignorePagination: true,
});
if (!data) NcError.notFound('Data not found');
const dates: Array<string> = [];
const columns = await model.getColumns();
ranges.forEach((range: CalendarRangeType) => {
const fromCol = columns.find(
(c) => c.id === range.fk_from_column_id,
)?.title;
data.list.forEach((date) => {
const fromDt = dayjs(date[fromCol]);
if (fromCol && fromDt.isValid()) {
dates.push(fromDt.format('YYYY-MM-DD HH:mm:ssZ'));
}
});
});
return {
count: dates.length,
dates: Array.from(new Set(dates)),
};
}
async buildFilterArr({
viewId,
from_date,
to_date,
}: {
viewId: string;
from_date: string;
to_date: string;
}) {
const calendarRange = await CalendarRange.read(viewId);
if (!calendarRange?.ranges?.length) NcError.badRequest('No ranges found');
const filterArr: FilterType = {
is_group: true,
logical_op: 'and',
children: [],
};
calendarRange.ranges.forEach((range: CalendarRange) => {
const fromColumn = range.fk_from_column_id;
let rangeFilter: any = [];
if (fromColumn) {
rangeFilter = [
{
fk_column_id: fromColumn,
comparison_op: 'lt',
comparison_sub_op: 'exactDate',
value: to_date as string,
},
{
fk_column_id: fromColumn,
comparison_op: 'gt',
comparison_sub_op: 'exactDate',
value: from_date as string,
},
];
}
if (rangeFilter.length > 0) filterArr.children.push(rangeFilter);
});
return filterArr;
}
}

121
packages/nocodb/src/services/datas.service.ts

@ -1,13 +1,12 @@
import { Injectable, Logger } from '@nestjs/common';
import { isSystemColumn, ViewTypes } from 'nocodb-sdk';
import { isSystemColumn } from 'nocodb-sdk';
import * as XLSX from 'xlsx';
import papaparse from 'papaparse';
import { nocoExecute } from 'nc-help';
import dayjs from 'dayjs';
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2';
import type { PathParams } from '~/modules/datas/helpers';
import { getDbRows, getViewAndModelByAliasOrId } from '~/modules/datas/helpers';
import { Base, CalendarRange, Column, Model, Source, View } from '~/models';
import { Base, Column, Model, Source, View } from '~/models';
import { NcBaseError, NcError } from '~/helpers/catchError';
import getAst from '~/helpers/getAst';
import { PagedResponseImpl } from '~/helpers/PagedResponse';
@ -24,6 +23,7 @@ export class DatasService {
query: any;
disableOptimization?: boolean;
ignorePagination?: boolean;
calendarLimitOverride?: number;
throwErrorIfInvalidParams?: boolean;
},
) {
@ -43,6 +43,7 @@ export class DatasService {
query: param.query,
throwErrorIfInvalidParams: true,
ignorePagination: param.ignorePagination,
calendarLimitOverride: param.calendarLimitOverride,
});
}
@ -152,6 +153,7 @@ export class DatasService {
throwErrorIfInvalidParams?: boolean;
ignoreViewFilterAndSort?: boolean;
ignorePagination?: boolean;
calendarLimitOverride?: number;
}) {
const { model, view, query = {}, ignoreViewFilterAndSort = false } = param;
@ -180,16 +182,6 @@ export class DatasService {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
let options = {};
if (view && view.type === ViewTypes.CALENDAR && param.ignorePagination) {
{
options = {
ignorePagination: true,
};
}
}
const [count, data] = await Promise.all([
baseModel.count(listArgs, false, param.throwErrorIfInvalidParams),
(async () => {
@ -200,7 +192,8 @@ export class DatasService {
await baseModel.list(listArgs, {
ignoreViewFilterAndSort,
throwErrorIfInvalidParams: param.throwErrorIfInvalidParams,
...options,
ignorePagination: param.ignorePagination,
calendarLimitOverride: param.calendarLimitOverride,
}),
{},
listArgs,
@ -221,67 +214,6 @@ export class DatasService {
});
}
async getCalendarRecordCount(param: { viewId: string; query: any }) {
const { viewId, query = {} } = param;
const view = await View.get(viewId);
if (!view) NcError.notFound('View not found');
if (view.type !== ViewTypes.CALENDAR)
NcError.badRequest('View is not a calendar view');
const calendarRange = await CalendarRange.read(view.id);
if (!calendarRange?.ranges?.length) NcError.badRequest('No ranges found');
const model = await Model.getByIdOrName({
id: view.fk_model_id,
});
const data = await this.getDataList({
model,
view,
query,
});
if (!data) NcError.notFound('Data not found');
const dates: Array<string> = [];
calendarRange.ranges.forEach((range: any) => {
data.list.forEach((date) => {
const from =
date[
model.columns.find((c) => c.id === range.fk_from_column_id).title
];
let to;
if (range.fk_to_column_id) {
to =
date[
model.columns.find((c) => c.id === range.fk_to_column_id).title
];
}
if (from && to) {
const fromDt = dayjs(from);
const toDt = dayjs(to);
let current = fromDt;
while (current.isSameOrBefore(toDt)) {
dates.push(current.format('YYYY-MM-DD HH:mm:ssZ'));
current = current.add(1, 'day');
}
} else if (from) {
dates.push(dayjs(from).format('YYYY-MM-DD HH:mm:ssZ'));
}
});
});
return dates;
}
async getFindOne(param: { model: Model; view: View; query: any }) {
const { model, view, query = {} } = param;
@ -1054,43 +986,4 @@ export class DatasService {
return column;
}
async getDataAggregateBy(param: {
viewId: string;
query?: any;
aggregateColumnName: string;
aggregateFunction: string;
groupByColumnName?: string;
ignoreFilters?: boolean;
sort?: {
column_name: string;
direction: 'asc' | 'desc';
};
}) {
const { viewId, query = {} } = param;
const view = await View.get(viewId);
const source = await Source.get(view.source_id);
const baseModel = await Model.getBaseModelSQL({
id: view.fk_model_id,
viewId: view?.id,
dbDriver: await NcConnectionMgrv2.get(source),
});
const data = await baseModel.groupByAndAggregate(
param.aggregateColumnName,
param.aggregateFunction,
{
groupByColumnName: param.groupByColumnName,
sortBy: param.sort,
...query,
},
);
return new PagedResponseImpl(data, {
...query,
});
}
}

79
packages/nocodb/src/services/public-datas.service.ts

@ -10,9 +10,8 @@ import {
import slash from 'slash';
import { nocoExecute } from 'nc-help';
import dayjs from 'dayjs';
import type { LinkToAnotherRecordColumn } from '~/models';
import { CalendarRange, Column, Model, Source, View } from '~/models';
import { Column, Model, Source, View } from '~/models';
import { NcError } from '~/helpers/catchError';
import getAst from '~/helpers/getAst';
import NcPluginMgrv2 from '~/helpers/NcPluginMgrv2';
@ -80,17 +79,10 @@ export class PublicDatasService {
let data = [];
let count = 0;
let option = {};
if (view && view.type === ViewTypes.CALENDAR) {
option = {
ignorePagination: true,
};
}
try {
data = await nocoExecute(
ast,
await baseModel.list(listArgs, option),
await baseModel.list(listArgs),
{},
listArgs,
);
@ -103,73 +95,6 @@ export class PublicDatasService {
return new PagedResponseImpl(data, { ...param.query, count });
}
async getCalendarRecordCount(param: {
sharedViewUuid: string;
password?: string;
query: any;
}) {
const { sharedViewUuid, password, query = {} } = param;
const view = await View.getByUUID(sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (view.password && view.password !== password) {
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
if (view.type !== ViewTypes.CALENDAR)
NcError.badRequest('View is not a calendar view');
const calendarRange = await CalendarRange.read(view.id);
if (!calendarRange?.ranges?.length)
NcError.notFound('Calendar ranges are required in a calendar view');
const model = await Model.getByIdOrName({
id: view.fk_model_id,
});
const columns = await model.getColumns();
const data: any = await this.dataList({
sharedViewUuid,
password,
query,
});
if (!data) NcError.notFound('Data not found');
const dates: Array<string> = [];
calendarRange.ranges.forEach((range: any) => {
data.list.forEach((date) => {
const from =
date[columns.find((c) => c.id === range.fk_from_column_id).title];
let to;
if (range.fk_to_column_id) {
to = date[columns.find((c) => c.id === range.fk_to_column_id).title];
}
if (from && to) {
const fromDt = dayjs(from);
const toDt = dayjs(to);
let current = fromDt;
while (current.isSameOrBefore(toDt)) {
dates.push(current.format('YYYY-MM-DD HH:mm:ssZ'));
current = current.add(1, 'day');
}
} else if (from) {
dates.push(dayjs(from).format('YYYY-MM-DD HH:mm:ssZ'));
}
});
});
return dates;
}
// todo: Handle the error case where view doesnt belong to model
async groupedDataList(param: {
sharedViewUuid: string;

45
packages/nocodb/tests/unit/rest/tests/viewRow.test.ts

@ -1554,6 +1554,39 @@ function viewRowTests() {
await testViewRowNotExists(ViewTypes.CALENDAR);
});
const testCalendarDataApi = async () => {
const table = rentalTable;
const calendar_range = {
fk_from_column_id: rentalColumns.find((c) => c.title === 'RentalDate').id,
};
const view = await createView(context, {
title: 'View',
table: table,
type: ViewTypes.CALENDAR,
range: calendar_range,
});
const response = await request(context.app)
.get(
`/api/v1/db/calendar-data/noco/${sakilaProject.id}/${table.id}/views/${view.id}`,
)
.query({
from_date: '2005-05-25',
to_date: '2005-05-26',
})
.set('xc-auth', context.token)
.expect(200);
if (response.body.list.length !== 137) {
throw new Error('Wrong calendar data');
}
};
it('Calendar data', async function () {
await testCalendarDataApi();
});
const testCountDatesByRange = async (viewType: ViewTypes) => {
let calendar_range = {};
let expectStatus = 400;
@ -1575,12 +1608,20 @@ function viewRowTests() {
const response = await request(context.app)
.get(
`/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}/countByDate/`,
`/api/v1/db/calendar-data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}/countByDate/`,
)
.query({
from_date: '2005-05-25',
to_date: '2005-05-26',
})
.set('xc-auth', context.token)
.expect(expectStatus);
if (expectStatus === 200 && response.body.length !== 25) {
if (
expectStatus === 200 &&
response.body.count !== 137 &&
response.body.dates.length !== 137
) {
throw new Error('Wrong count');
} else if (
expectStatus === 400 &&

61
pnpm-lock.yaml

@ -42,6 +42,9 @@ importers:
'@tiptap/extension-link':
specifier: 2.2.4
version: 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4)
'@tiptap/extension-placeholder':
specifier: ^2.2.4
version: 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4)
'@tiptap/extension-task-list':
specifier: 2.2.4
version: 2.2.4(@tiptap/core@2.2.4)
@ -236,8 +239,8 @@ importers:
specifier: ^1.1.23
version: 1.1.23
'@iconify-json/carbon':
specifier: ^1.1.30
version: 1.1.30
specifier: ^1.1.31
version: 1.1.31
'@iconify-json/cil':
specifier: ^1.1.8
version: 1.1.8
@ -260,11 +263,11 @@ importers:
specifier: ^1.1.42
version: 1.1.42
'@iconify-json/lucide':
specifier: ^1.1.170
version: 1.1.170
specifier: ^1.1.171
version: 1.1.171
'@iconify-json/material-symbols':
specifier: ^1.1.73
version: 1.1.73
specifier: ^1.1.74
version: 1.1.74
'@iconify-json/mdi':
specifier: ^1.1.64
version: 1.1.64
@ -278,8 +281,8 @@ importers:
specifier: ^1.1.20
version: 1.1.20
'@iconify-json/simple-icons':
specifier: ^1.1.93
version: 1.1.93
specifier: ^1.1.94
version: 1.1.94
'@iconify-json/system-uicons':
specifier: ^1.1.12
version: 1.1.12
@ -500,8 +503,8 @@ importers:
specifier: ^4.0.0
version: 4.0.0
aws-kcl:
specifier: ^2.2.4
version: 2.2.4
specifier: ^2.2.5
version: 2.2.5
aws-sdk:
specifier: ^2.1550.0
version: 2.1555.0
@ -889,7 +892,7 @@ importers:
version: 6.3.4
ts-jest:
specifier: 29.1.2
version: 29.1.2(@babel/core@7.23.7)(jest@29.7.0)(typescript@5.3.2)
version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.2)
ts-loader:
specifier: ^9.5.1
version: 9.5.1(typescript@5.3.2)(webpack@5.90.3)
@ -959,7 +962,7 @@ importers:
version: 5.0.5
ts-jest:
specifier: ^29.1.2
version: 29.1.2(@babel/core@7.23.7)(jest@29.7.0)(typescript@5.3.2)
version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.2)
tsc-alias:
specifier: ^1.8.8
version: 1.8.8
@ -4469,8 +4472,8 @@ packages:
'@iconify/types': 2.0.0
dev: true
/@iconify-json/carbon@1.1.30:
resolution: {integrity: sha512-tEvEmxCO0J0t0p2NT2IvJ+iiSNqqnabygSo/S8wkeh2Guhc4tQOgl9dxvDMpy6RqwtKRTKsaROPtnRfu/bl+Tg==}
/@iconify-json/carbon@1.1.31:
resolution: {integrity: sha512-CAvECFfiwGyZmlcuM2JLMRDEN3VsIEZv6lml7Xf+3giQ5oXloADm0b5wiVPFZmONKM5jXERmx+E7YSvAtFJIbw==}
dependencies:
'@iconify/types': 2.0.0
dev: true
@ -4517,14 +4520,14 @@ packages:
'@iconify/types': 2.0.0
dev: true
/@iconify-json/lucide@1.1.170:
resolution: {integrity: sha512-PRo/1IbIKE6O++XJjPUNM/+enA4sVOZLS8ykpy3ztPziNfmKgrgzx/1+fe6O6Zhy51C7RPQFkVuYPsCLiUiKFQ==}
/@iconify-json/lucide@1.1.171:
resolution: {integrity: sha512-qYOXqnmYBHwH6yGVHOyNgLQ2WFz3UrZpBou37jFe1OWtTKbu/8fDixZuv/rBEhyQcX1mw+dFY+dguYNlGjYAmQ==}
dependencies:
'@iconify/types': 2.0.0
dev: true
/@iconify-json/material-symbols@1.1.73:
resolution: {integrity: sha512-3ioEMQvxUR0eWg3jCxeydowS7xlqJzUHqeKJg29Z5HN15ZBYniQhHYrDCRpmFjopgwxox9OIsoonBiBSLV4EMA==}
/@iconify-json/material-symbols@1.1.74:
resolution: {integrity: sha512-cuQKvpGWrMNJq0i3ynO+V6yus0Smiupw92GW8Gq/4MHfYfbl1MrbVmafKQAd8RJVqiEXq4/F084OEcigf77UqQ==}
dependencies:
'@iconify/types': 2.0.0
dev: true
@ -4553,8 +4556,8 @@ packages:
'@iconify/types': 2.0.0
dev: true
/@iconify-json/simple-icons@1.1.93:
resolution: {integrity: sha512-rPsEIJlNZQJekCy5Qj6QHhrCJyCF0UAAql5PEz+B8iNvEn/BDyqq2zgpfxsU2KQn2NdCui8xnjZiuyTc5nwIqw==}
/@iconify-json/simple-icons@1.1.94:
resolution: {integrity: sha512-2UwwbEJeZ/aMpACG/dZoOhNszKFO+IjcRCbYB+lMqd+6fA5ewykRy63IP8//UdviazOPamGJ/XbNBJH/o1YFdQ==}
dependencies:
'@iconify/types': 2.0.0
dev: true
@ -8742,6 +8745,16 @@ packages:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4)
dev: false
/@tiptap/extension-placeholder@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4):
resolution: {integrity: sha512-UL4Fn9T33SoS7vdI3NnSxBJVeGUIgCIutgXZZ5J8CkcRoDIeS78z492z+6J+qGctHwTd0xUL5NzNJI82HfiTdg==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4)
'@tiptap/pm': 2.2.4
dev: false
/@tiptap/extension-strike@2.2.4(@tiptap/core@2.2.4):
resolution: {integrity: sha512-/a2EwQgA+PpG17V2tVRspcrIY0SN3blwcgM7lxdW4aucGkqSKnf7+91dkhQEwCZ//o8kv9mBCyRoCUcGy6S5Xg==}
peerDependencies:
@ -11571,8 +11584,8 @@ packages:
possible-typed-array-names: 1.0.0
dev: true
/aws-kcl@2.2.4:
resolution: {integrity: sha512-3xxJlhnaPSYuB3HjwRb4kvcGX/bFFaegjnVcuIHfnIv468CFmPXgj6qGl7pmrXQTBZb0POkMnJExxL5JPPYcgg==}
/aws-kcl@2.2.5:
resolution: {integrity: sha512-evYJbsagtfM0ScjoMjx2Mi30wFWftWfaFv/wRGWQL33dwSo261xF3cPxGIqdY6+31+7hFVmKnsH6ImnD/CbLgQ==}
engines: {node: '>= 0.8.0'}
hasBin: true
dependencies:
@ -25205,7 +25218,7 @@ packages:
engines: {node: '>=14.0.0'}
dev: false
/ts-jest@29.1.2(@babel/core@7.23.7)(jest@29.7.0)(typescript@5.3.2):
/ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.2):
resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==}
engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0}
hasBin: true
@ -25226,7 +25239,7 @@ packages:
esbuild:
optional: true
dependencies:
'@babel/core': 7.23.7
'@babel/core': 7.23.9
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@20.11.24)(ts-node@10.9.2)

4
scripts/pkg-executable/package.json

@ -30,10 +30,10 @@
"@nestjs/common": "^10.3.3",
"@nestjs/core": "^10.3.3",
"express": "^4.18.3",
"nocodb": "0.204.2"
"nocodb": "0.204.3"
},
"overrides": {
"sqlite3": "5.1.6"
"sqlite3": "5.1.7"
},
"devDependencies": {
"@mapbox/node-pre-gyp": "^1.0.11"

32
tests/playwright/pages/Dashboard/Form/index.ts

@ -10,8 +10,7 @@ export class FormPage extends BasePage {
readonly topbar: TopbarPage;
// todo: All the locator should be private
readonly addAllButton: Locator;
readonly removeAllButton: Locator;
readonly addOrRemoveAllButton: Locator;
readonly submitButton: Locator;
readonly showAnotherFormRadioButton: Locator;
@ -30,8 +29,10 @@ export class FormPage extends BasePage {
this.toolbar = new ToolbarPage(this);
this.topbar = new TopbarPage(this);
this.addAllButton = dashboard.get().locator('[data-testid="nc-form-show-all-fields"]').locator('.nc-switch');
this.removeAllButton = dashboard.get().locator('[data-testid="nc-form-show-all-fields"]').locator('.nc-switch');
this.addOrRemoveAllButton = dashboard
.get()
.locator('[data-testid="nc-form-show-all-fields"]')
.locator('.nc-switch');
this.submitButton = dashboard.get().locator('[data-testid="nc-form-submit"]');
this.showAnotherFormRadioButton = dashboard.get().locator('[data-testid="nc-form-checkbox-submit-another-form"]');
@ -40,8 +41,8 @@ export class FormPage extends BasePage {
.locator('[data-testid="nc-form-checkbox-show-blank-form"]');
this.emailMeRadioButton = dashboard.get().locator('[data-testid="nc-form-checkbox-send-email"]');
this.formHeading = dashboard.get().locator('[data-testid="nc-form-heading"]');
this.formSubHeading = dashboard.get().locator('[data-testid="nc-form-sub-heading"]');
this.afterSubmitMsg = dashboard.get().locator('[data-testid="nc-form-after-submit-msg"]');
this.formSubHeading = dashboard.get().locator('[data-testid="nc-form-sub-heading"] .tiptap.ProseMirror');
this.afterSubmitMsg = dashboard.get().locator('[data-testid="nc-form-after-submit-msg"] .tiptap.ProseMirror');
this.formFields = dashboard.get().locator('.nc-form-fields-list');
}
@ -67,7 +68,7 @@ export class FormPage extends BasePage {
}
getFormFieldsInputHelpText() {
return this.get().locator('textarea[data-testid="nc-form-input-help-text"]:visible');
return this.get().locator('[data-testid="nc-form-input-help-text"] .tiptap.ProseMirror:visible');
}
async verifyFormFieldLabel({ index, label }: { index: number; label: string }) {
@ -83,7 +84,7 @@ export class FormPage extends BasePage {
}
async verifyAfterSubmitMsg({ msg }: { msg: string }) {
expect((await this.afterSubmitMsg.inputValue()).includes(msg)).toBeTruthy();
expect((await this.afterSubmitMsg.textContent()).includes(msg)).toBeTruthy();
}
async verifyFormViewFieldsOrder({ fields }: { fields: string[] }) {
@ -136,11 +137,18 @@ export class FormPage extends BasePage {
}
async removeAllFields() {
await this.removeAllButton.click();
if (await this.addOrRemoveAllButton.isChecked()) {
await this.addOrRemoveAllButton.click();
} else {
await this.addOrRemoveAllButton.click();
await this.addOrRemoveAllButton.click();
}
}
async addAllFields() {
await this.addAllButton.click();
if (!(await this.addOrRemoveAllButton.isChecked())) {
await this.addOrRemoveAllButton.click();
}
}
async configureHeader(param: { subtitle: string; title: string }) {
@ -166,7 +174,7 @@ export class FormPage extends BasePage {
async verifyHeader(param: { subtitle: string; title: string }) {
await expect.poll(async () => await this.formHeading.inputValue()).toBe(param.title);
await expect.poll(async () => await this.formSubHeading.inputValue()).toBe(param.subtitle);
await expect.poll(async () => await this.formSubHeading.textContent()).toBe(param.subtitle);
}
async fillForm(param: { field: string; value: string }[]) {
@ -233,7 +241,7 @@ export class FormPage extends BasePage {
const fieldHelpText = this.get()
.locator(`.nc-form-drag-${field.replace(' ', '')}`)
.locator('div[data-testid="nc-form-input-help-text-label"]');
.locator('div[data-testid="nc-form-input-help-text-label"] .tiptap.ProseMirror');
await expect(fieldHelpText).toHaveText(helpText);
}

1
tests/playwright/tests/db/views/viewForm.spec.ts

@ -6,7 +6,6 @@ import { SharedFormPage } from '../../../pages/SharedForm';
import { Api, UITypes } from 'nocodb-sdk';
import { LoginPage } from '../../../pages/LoginPage';
import { getDefaultPwd } from '../../../tests/utils/general';
import { WorkspacePage } from '../../../pages/WorkspacePage';
import { enableQuickRun, isEE } from '../../../setup/db';
// todo: Move most of the ui actions to page object and await on the api response

Loading…
Cancel
Save