Browse Source

Merge branch 'develop' into nc-fix/title-len

pull/7092/head
աɨռɢӄաօռɢ 1 year ago
parent
commit
117451285e
  1. 12
      packages/nc-gui/components/cell/TextArea.vue
  2. 6
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  3. 21
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  4. 7
      packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
  5. 17
      packages/nc-gui/components/dashboard/settings/UIAcl.vue
  6. 2
      packages/nc-gui/components/erd/TableNode.vue
  7. 2
      packages/nc-gui/components/nc/Switch.vue
  8. 9
      packages/nc-gui/components/project/View.vue
  9. 1
      packages/nc-gui/components/smartsheet/Cell.vue
  10. 13
      packages/nc-gui/components/smartsheet/Toolbar.vue
  11. 8
      packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue
  12. 19
      packages/nc-gui/components/smartsheet/details/Fields.vue
  13. 12
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  14. 12
      packages/nc-gui/components/smartsheet/header/Cell.vue
  15. 11
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  16. 9
      packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue
  17. 10
      packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue
  18. 27
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  19. 6
      packages/nc-gui/components/smartsheet/toolbar/OpenedViewAction.vue
  20. 5
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  21. 2
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  22. 4
      packages/nc-gui/composables/useData.ts
  23. 4
      packages/nc-gui/composables/useViewData.ts
  24. 88
      packages/nc-gui/lang/es.json
  25. 364
      packages/nc-gui/lang/zh-Hans.json
  26. 50
      packages/nc-gui/package.json
  27. 2
      packages/nc-gui/pages/signin.vue
  28. 2
      packages/nc-lib-gui/package.json
  29. 378
      packages/noco-docs/docs/070.fields/040.field-types/060.formula/020.numeric-functions.md
  30. 228
      packages/noco-docs/docs/070.fields/040.field-types/060.formula/030.string-functions.md
  31. 101
      packages/noco-docs/docs/070.fields/040.field-types/060.formula/040.date-functions.md
  32. 76
      packages/noco-docs/docs/070.fields/040.field-types/060.formula/050.conditional-expressions.md
  33. 12
      packages/nocodb-sdk/package.json
  34. 2837
      packages/nocodb-sdk/pnpm-lock.yaml
  35. 6
      packages/nocodb/package.json
  36. 8
      packages/nocodb/src/controllers/data-table.controller.ts
  37. 42
      packages/nocodb/src/db/BaseModelSqlv2.ts
  38. 9
      packages/nocodb/src/db/sortV2.ts
  39. 2
      packages/nocodb/src/helpers/getAst.ts
  40. 8
      packages/nocodb/src/services/data-table.service.ts
  41. 1144
      pnpm-lock.yaml
  42. 2
      scripts/pkg-executable/package.json

12
packages/nc-gui/components/cell/TextArea.vue

@ -1,16 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { import {
ActiveCellInj, ColumnInj,
EditColumnInj, EditColumnInj,
EditModeInj, EditModeInj,
IsExpandedFormOpenInj, IsExpandedFormOpenInj,
IsFormInj,
ReadonlyInj, ReadonlyInj,
RowHeightInj, RowHeightInj,
computed,
iconMap, iconMap,
inject, inject,
onClickOutside,
ref,
useGlobal,
useVModel, useVModel,
watch,
} from '#imports' } from '#imports'
const props = defineProps<{ const props = defineProps<{
@ -61,9 +66,8 @@ const height = computed(() => {
const isVisible = ref(false) const isVisible = ref(false)
const inputWrapperRef = ref<HTMLElement | null>(null) const inputWrapperRef = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLTextAreaElement | null>(null)
const active = inject(ActiveCellInj, ref(false)) const inputRef = ref<HTMLTextAreaElement | null>(null)
const readOnly = inject(ReadonlyInj) const readOnly = inject(ReadonlyInj)

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

@ -424,15 +424,17 @@ function projectDelete() {
@keyup.esc="updateProjectTitle" @keyup.esc="updateProjectTitle"
@blur="updateProjectTitle" @blur="updateProjectTitle"
/> />
<span <NcTooltip
v-else v-else
class="nc-sidebar-node-title capitalize text-ellipsis overflow-hidden select-none" class="nc-sidebar-node-title capitalize text-ellipsis overflow-hidden select-none"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
:class="{ 'text-black font-semibold': activeProjectId === base.id && baseViewOpen }" :class="{ 'text-black font-semibold': activeProjectId === base.id && baseViewOpen }"
@click="onProjectClick(base)"
> >
<template #title>{{ base.title }}</template>
<span @click="onProjectClick(base)">
{{ base.title }} {{ base.title }}
</span> </span>
</NcTooltip>
<div :class="{ 'flex flex-grow h-full': !editMode }" @click="onProjectClick(base)"></div> <div :class="{ 'flex flex-grow h-full': !editMode }" @click="onProjectClick(base)"></div>
<NcDropdown v-if="!isSharedBase" v-model:visible="isOptionsOpen" :trigger="['click']"> <NcDropdown v-if="!isSharedBase" v-model:visible="isOptionsOpen" :trigger="['click']">

21
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -149,20 +149,15 @@ const isTableOpened = computed(() => {
:class="[`nc-base-tree-tbl nc-base-tree-tbl-${table.title}`]" :class="[`nc-base-tree-tbl nc-base-tree-tbl-${table.title}`]"
:data-active="openedTableId === table.id" :data-active="openedTableId === table.id"
> >
<GeneralTooltip <div
class="nc-tree-item-inner nc-sidebar-node pl-11 pr-0.75 mb-0.25 rounded-md h-7.1 w-full group cursor-pointer hover:bg-gray-200" v-e="['a:table:open']"
class="table-context flex items-center gap-1 h-full nc-tree-item-inner nc-sidebar-node pl-11 pr-0.75 mb-0.25 rounded-md h-7.1 w-full group cursor-pointer hover:bg-gray-200"
:class="{ :class="{
'hover:bg-gray-200': openedTableId !== table.id, 'hover:bg-gray-200': openedTableId !== table.id,
'pl-12 xs:(pl-14)': sourceIndex !== 0, 'pl-12 xs:(pl-14)': sourceIndex !== 0,
'pl-6.5': sourceIndex === 0, 'pl-6.5': sourceIndex === 0,
'!bg-primary-selected': isTableOpened, '!bg-primary-selected': isTableOpened,
}" }"
modifier-key="Alt"
>
<template #title>{{ table.table_name }}</template>
<div
v-e="['a:table:open']"
class="table-context flex items-center gap-1 h-full"
:data-testid="`nc-tbl-side-node-${table.title}`" :data-testid="`nc-tbl-side-node-${table.title}`"
@contextmenu="setMenuContext('table', table)" @contextmenu="setMenuContext('table', table)"
@click="onOpenTable" @click="onOpenTable"
@ -236,9 +231,9 @@ const isTableOpened = computed(() => {
</div> </div>
</div> </div>
</div> </div>
<NcTooltip class="nc-tbl-title nc-sidebar-node-title text-ellipsis w-full overflow-hidden select-none">
<template #title>{{ table.title }}</template>
<span <span
class="nc-tbl-title nc-sidebar-node-title text-ellipsis overflow-hidden select-none"
:class="{ :class="{
'text-black !font-medium': isTableOpened, 'text-black !font-medium': isTableOpened,
}" }"
@ -247,12 +242,12 @@ const isTableOpened = computed(() => {
> >
{{ table.title }} {{ table.title }}
</span> </span>
</NcTooltip>
<div class="flex flex-grow h-full"></div> <div class="flex flex-grow h-full"></div>
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
<div <div
v-if=" v-if="
!isSharedBase && !isSharedBase && (isUIAllowed('tableRename', { roles: baseRole }) || isUIAllowed('tableDelete', { roles: baseRole }))
(isUIAllowed('tableRename', { roles: baseRole }) || isUIAllowed('tableDelete', { roles: baseRole }))
" "
v-e="['c:table:option']" v-e="['c:table:option']"
> >
@ -313,7 +308,7 @@ const isTableOpened = computed(() => {
:table-id="table.id" :table-id="table.id"
:base-id="base.id" :base-id="base.id"
/> />
</GeneralTooltip>
<DashboardTreeViewViewsList v-if="isExpanded" :table-id="table.id" :base-id="base.id" /> <DashboardTreeViewViewsList v-if="isExpanded" :table-id="table.id" :base-id="base.id" />
</div> </div>
</template> </template>

7
packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue

@ -232,10 +232,9 @@ watch(isDropdownOpen, async () => {
@blur="onRename" @blur="onRename"
@keydown.stop="onKeyDown($event)" @keydown.stop="onKeyDown($event)"
/> />
<NcTooltip v-else class="nc-sidebar-node-title text-ellipsis overflow-hidden select-none w-full">
<template #title> {{ vModel.alias || vModel.title }}</template>
<div <div
v-else
class="nc-sidebar-node-title text-ellipsis overflow-hidden select-none w-full"
data-testid="sidebar-view-title" data-testid="sidebar-view-title"
:class="{ :class="{
'font-medium': activeView?.id === vModel.id, 'font-medium': activeView?.id === vModel.id,
@ -244,7 +243,7 @@ watch(isDropdownOpen, async () => {
> >
{{ vModel.alias || vModel.title }} {{ vModel.alias || vModel.title }}
</div> </div>
</NcTooltip>
<div class="flex-1" /> <div class="flex-1" />
<template v-if="!isEditing && !isLocked && isUIAllowed('viewCreateOrEdit')"> <template v-if="!isEditing && !isLocked && isUIAllowed('viewCreateOrEdit')">

17
packages/nc-gui/components/dashboard/settings/UIAcl.vue

@ -147,7 +147,10 @@ const toggleSelectAll = (role: Role) => {
<template> <template>
<div class="flex flex-row w-full items-center justify-center"> <div class="flex flex-row w-full items-center justify-center">
<div class="flex flex-col w-[900px]"> <div class="flex flex-col w-[900px]">
<span class="mb-4 first-letter:capital font-bold"> UI ACL : {{ base.title }} </span> <NcTooltip class="mb-4 first-letter:capital font-bold max-w-100 truncate">
<template #title>{{ base.title }}</template>
<span> UI ACL : {{ base.title }} </span>
</NcTooltip>
<div class="flex flex-row items-center w-full mb-4 gap-2 justify-between"> <div class="flex flex-row items-center w-full mb-4 gap-2 justify-between">
<a-input v-model:value="searchInput" :placeholder="$t('placeholder.searchModels')" class="nc-acl-search !w-[400px]"> <a-input v-model:value="searchInput" :placeholder="$t('placeholder.searchModels')" class="nc-acl-search !w-[400px]">
<template #prefix> <template #prefix>
@ -208,9 +211,10 @@ const toggleSelectAll = (role: Role) => {
<div class="min-w-5 flex items-center justify-center"> <div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="{ meta: record.table_meta, type: record.ptype }" class="text-gray-500" /> <GeneralTableIcon :meta="{ meta: record.table_meta, type: record.ptype }" class="text-gray-500" />
</div> </div>
<GeneralTruncateText> <NcTooltip class="overflow-ellipsis min-w-0 shrink-1 truncate">
<span class="overflow-ellipsis min-w-0 shrink-1">{{ record._ptn }}</span> <template #title>{{ record._ptn }}</template>
</GeneralTruncateText> <span>{{ record._ptn }}</span>
</NcTooltip>
</div> </div>
</div> </div>
@ -219,7 +223,10 @@ const toggleSelectAll = (role: Role) => {
<div class="min-w-5 flex items-center justify-center"> <div class="min-w-5 flex items-center justify-center">
<GeneralViewIcon :meta="record" class="text-gray-500"></GeneralViewIcon> <GeneralViewIcon :meta="record" class="text-gray-500"></GeneralViewIcon>
</div> </div>
<span class="overflow-ellipsis min-w-0 shrink-1">{{ record.title }}</span> <NcTooltip class="overflow-ellipsis min-w-0 shrink-1 truncate">
<template #title>{{ record.title }}</template>
<span>{{ record.title }}</span>
</NcTooltip>
</div> </div>
</div> </div>

2
packages/nc-gui/components/erd/TableNode.vue

@ -54,7 +54,7 @@ watch(
</template> </template>
<div <div
class="relative h-full flex flex-col justify-center bg-white min-w-16 min-h-8 rounded-lg nc-erd-table-node" class="relative h-full max-w-76 flex flex-col justify-center bg-white min-w-16 min-h-8 rounded-lg nc-erd-table-node"
:class="[ :class="[
`nc-erd-table-node-${table.table_name}`, `nc-erd-table-node-${table.table_name}`,
showSkeleton ? 'cursor-pointer items-center min-h-200px min-w-300px' : '', showSkeleton ? 'cursor-pointer items-center min-h-200px min-w-300px' : '',

2
packages/nc-gui/components/nc/Switch.vue

@ -12,7 +12,7 @@ const onChange = (e: boolean) => {
<template> <template>
<a-switch v-model:checked="checked" :disabled="props.disabled" class="nc-switch" size="small" @change="onChange"> </a-switch> <a-switch v-model:checked="checked" :disabled="props.disabled" class="nc-switch" size="small" @change="onChange"> </a-switch>
<span class="cursor-pointer pl-2" @click="checked = !checked"> <span v-if="$slots.default" class="cursor-pointer pl-2" @click="checked = !checked">
<slot /> <slot />
</span> </span>
</template> </template>

9
packages/nc-gui/components/project/View.vue

@ -68,16 +68,19 @@ watch(
<template> <template>
<div class="h-full nc-base-view"> <div class="h-full nc-base-view">
<div <div
class="flex flex-row pl-2 pr-2 border-b-1 border-gray-200 justify-between w-full" class="flex flex-row pl-2 pr-2 gap-1 border-b-1 border-gray-200 justify-between w-full"
:class="{ 'nc-table-toolbar-mobile': isMobileMode, 'h-[var(--topbar-height)]': !isMobileMode }" :class="{ 'nc-table-toolbar-mobile': isMobileMode, 'h-[var(--topbar-height)]': !isMobileMode }"
> >
<div class="flex flex-row items-center gap-x-3"> <div class="flex flex-row items-center gap-x-3">
<GeneralOpenLeftSidebarBtn /> <GeneralOpenLeftSidebarBtn />
<div class="flex flex-row items-center h-full gap-x-2.5"> <div class="flex flex-row items-center h-full gap-x-2.5">
<GeneralProjectIcon :type="openedProject?.type" /> <GeneralProjectIcon :type="openedProject?.type" />
<div class="flex font-medium text-sm capitalize"> <NcTooltip class="flex font-medium text-sm capitalize truncate max-w-150">
<template #title> {{ openedProject?.title }}</template>
<span class="truncate">
{{ openedProject?.title }} {{ openedProject?.title }}
</div> </span>
</NcTooltip>
</div> </div>
</div> </div>
<LazyGeneralShareProject /> <LazyGeneralShareProject />

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

@ -8,7 +8,6 @@ import {
EditModeInj, EditModeInj,
IsExpandedFormOpenInj, IsExpandedFormOpenInj,
IsFormInj, IsFormInj,
IsLockedInj,
IsPublicInj, IsPublicInj,
IsSurveyFormInj, IsSurveyFormInj,
NavigateDir, NavigateDir,

13
packages/nc-gui/components/smartsheet/Toolbar.vue

@ -1,5 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { IsPublicInj, inject, ref, useRoles, useSharedView, useSmartsheetStoreOrThrow, useViewsStore } from '#imports' import {
IsPublicInj,
inject,
ref,
storeToRefs,
useGlobal,
useSharedView,
useSmartsheetStoreOrThrow,
useViewsStore,
} from '#imports'
const { isGrid, isGallery, isKanban, isMap } = useSmartsheetStoreOrThrow() const { isGrid, isGallery, isKanban, isMap } = useSmartsheetStoreOrThrow()
@ -9,8 +18,6 @@ const { isViewsLoading } = storeToRefs(useViewsStore())
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()
const { isUIAllowed } = useRoles()
const { allowCSVDownload } = useSharedView() const { allowCSVDownload } = useSharedView()
</script> </script>

8
packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue

@ -78,12 +78,14 @@ const isLinks = computed(() => vModel.value.uidt === UITypes.Links)
@change="onDataTypeChange" @change="onDataTypeChange"
> >
<a-select-option v-for="table of refTables" :key="table.title" :value="table.id"> <a-select-option v-for="table of refTables" :key="table.title" :value="table.id">
<div class="flex items-center gap-2"> <div class="flex w-full items-center gap-2">
<div class="min-w-5 flex items-center justify-center"> <div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="table" class="text-gray-500" /> <GeneralTableIcon :meta="table" class="text-gray-500" />
</div> </div>
<NcTooltip class="flex-1 truncate">
<span class="overflow-ellipsis min-w-0 shrink-1">{{ table.title }}</span> <template #title>{{ table.title }}</template>
<span>{{ table.title }}</span>
</NcTooltip>
</div> </div>
</a-select-option> </a-select-option>
</a-select> </a-select>

19
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -769,8 +769,8 @@ const onFieldOptionUpdate = () => {
</NcTooltip> </NcTooltip>
</div> </div>
</div> </div>
<div class="flex flex-row rounded-lg border-1 border-gray-200"> <div class="flex flex-row rounded-lg border-1 overflow-clip border-gray-200">
<div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto w-full flex-grow-1 nc-fields-height"> <div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto flex-1 flex-grow-1 nc-fields-height">
<Draggable v-model="fields" :disabled="isLocked" item-key="id" @change="onMove($event)"> <Draggable v-model="fields" :disabled="isLocked" item-key="id" @change="onMove($event)">
<template #item="{ element: field }"> <template #item="{ element: field }">
<div <div
@ -814,14 +814,17 @@ const onFieldOptionUpdate = () => {
'text-brand-500': compareCols(field, activeField), 'text-brand-500': compareCols(field, activeField),
}" }"
/> />
<span <NcTooltip
:class="{ :class="{
'text-brand-500': compareCols(field, activeField), 'text-brand-500': compareCols(field, activeField),
}" }"
class="truncate max-w-64" class="truncate flex-1"
> >
<template #title> {{ fieldState(field)?.title || field.title }} </template>
<span>
{{ fieldState(field)?.title || field.title }} {{ fieldState(field)?.title || field.title }}
</span> </span>
</NcTooltip>
</div> </div>
<div class="flex items-center justify-end gap-1"> <div class="flex items-center justify-end gap-1">
<div class="flex items-center"> <div class="flex items-center">
@ -970,13 +973,17 @@ const onFieldOptionUpdate = () => {
'text-brand-500': compareCols(displayColumn, activeField), 'text-brand-500': compareCols(displayColumn, activeField),
}" }"
/> />
<span <NcTooltip
class="truncate flex-1"
:class="{ :class="{
'text-brand-500': compareCols(displayColumn, activeField), 'text-brand-500': compareCols(displayColumn, activeField),
}" }"
> >
<template #title> {{ fieldState(displayColumn)?.title || displayColumn.title }} </template>
<span>
{{ fieldState(displayColumn)?.title || displayColumn.title }} {{ fieldState(displayColumn)?.title || displayColumn.title }}
</span> </span>
</NcTooltip>
</div> </div>
<div class="flex items-center justify-end gap-1"> <div class="flex items-center justify-end gap-1">
<div class="flex items-center"> <div class="flex items-center">
@ -1072,7 +1079,7 @@ const onFieldOptionUpdate = () => {
</Draggable> </Draggable>
</div> </div>
<Transition v-if="!changingField" name="slide-fade"> <Transition v-if="!changingField" name="slide-fade">
<div class="border-gray-200 border-l-1 rounded-r-xl h-[calc(100vh-(var(--topbar-height)*3.85))]"> <div class="border-gray-200 border-l-1 nc-scrollbar-md nc-fields-height !overflow-y-auto">
<SmartsheetColumnEditOrAddProvider <SmartsheetColumnEditOrAddProvider
v-if="activeField" v-if="activeField"
class="p-4 w-[25rem]" class="p-4 w-[25rem]"

12
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -645,11 +645,11 @@ export default {
<template v-if="isLoading"> <template v-if="isLoading">
<div <div
v-if="isMobileMode" v-if="isMobileMode"
class="!h-8.5 !xs:h-12 !xs:bg-white !sm:mr-21 !w-60 mt-0.75 !rounded-lg !overflow-hidden" class="!h-8.5 !xs:h-12 !xs:bg-white sm:mr-21 w-60 mt-0.75 !rounded-lg !overflow-hidden"
></div> ></div>
<a-skeleton-input <a-skeleton-input
v-else v-else
class="!h-8.5 !xs:h-9.5 !xs:bg-white !sm:mr-21 !w-60 mt-0.75 !rounded-lg !overflow-hidden" class="!h-8.5 !xs:h-9.5 !xs:bg-white sm:mr-21 !w-60 mt-0.75 !rounded-lg !overflow-hidden"
active active
size="small" size="small"
/> />
@ -658,7 +658,7 @@ export default {
<SmartsheetDivDataCell <SmartsheetDivDataCell
v-if="col.title" v-if="col.title"
:ref="i ? null : (el: any) => (cellWrapperEl = el)" :ref="i ? null : (el: any) => (cellWrapperEl = el)"
class="bg-white rounded-lg !w-[20rem] !xs:w-full border-1 border-gray-200 overflow-hidden px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative" class="bg-white rounded-lg w-80 xs:w-full border-1 border-gray-200 overflow-hidden px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative"
:class="{ :class="{
'!bg-gray-50 !px-0 !select-text': isReadOnlyVirtualCell(col), '!bg-gray-50 !px-0 !select-text': isReadOnlyVirtualCell(col),
}" }"
@ -720,11 +720,11 @@ export default {
<template v-if="isLoading"> <template v-if="isLoading">
<div <div
v-if="isMobileMode" v-if="isMobileMode"
class="!h-8.5 !xs:h-9.5 !xs:bg-white !sm:mr-21 !w-60 mt-0.75 !rounded-lg !overflow-hidden" class="!h-8.5 !xs:h-9.5 !xs:bg-white sm:mr-21 w-60 mt-0.75 !rounded-lg !overflow-hidden"
></div> ></div>
<a-skeleton-input <a-skeleton-input
v-else v-else
class="!h-8.5 !xs:h-12 !xs:bg-white !sm:mr-21 !w-60 mt-0.75 !rounded-lg !overflow-hidden" class="!h-8.5 !xs:h-12 !xs:bg-white sm:mr-21 w-60 mt-0.75 !rounded-lg !overflow-hidden"
active active
size="small" size="small"
/> />
@ -733,7 +733,7 @@ export default {
<LazySmartsheetDivDataCell <LazySmartsheetDivDataCell
v-if="col.title" v-if="col.title"
:ref="i ? null : (el: any) => (cellWrapperEl = el)" :ref="i ? null : (el: any) => (cellWrapperEl = el)"
class="!bg-white rounded-lg !w-[20rem] border-1 overflow-hidden border-gray-200 px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative" class="bg-white rounded-lg w-80 border-1 overflow-hidden border-gray-200 px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative"
> >
<LazySmartsheetVirtualCell <LazySmartsheetVirtualCell
v-if="isVirtualCol(col)" v-if="isVirtualCol(col)"

12
packages/nc-gui/components/smartsheet/header/Cell.vue

@ -90,18 +90,22 @@ const onClick = (e: Event) => {
'self-start': isForm || isSurveyForm, 'self-start': isForm || isSurveyForm,
}" }"
/> />
<div <NcTooltip
v-if="column" v-if="column"
class="name pl-1"
:class="{ :class="{
'cursor-pointer pt-0.25': !isForm && isUIAllowed('fieldEdit') && !hideMenu && !isExpandedForm, 'cursor-pointer pt-0.25': !isForm && isUIAllowed('fieldEdit') && !hideMenu && !isExpandedForm,
'cursor-default': isForm || !isUIAllowed('fieldEdit') || hideMenu, 'cursor-default': isForm || !isUIAllowed('fieldEdit') || hideMenu,
'!truncate': !isForm, 'truncate': !isForm,
}" }"
:data-test-id="column.title" class="name pl-1"
placement="bottom"
> >
<template #title> {{ column.title }} </template>
<div :class="{ truncate: !isForm }" :data-test-id="column.title">
{{ column.title }} {{ column.title }}
</div> </div>
</NcTooltip>
<span v-if="(column.rqd && !column.cdf) || required" class="text-red-500">&nbsp;*</span> <span v-if="(column.rqd && !column.cdf) || required" class="text-red-500">&nbsp;*</span>

11
packages/nc-gui/components/smartsheet/header/VirtualCell.vue

@ -92,6 +92,7 @@ const tooltipMsg = computed(() => {
if (!column.value) { if (!column.value) {
return '' return ''
} }
if (isHm(column.value)) { if (isHm(column.value)) {
return `'${tableTile.value}' ${t('labels.hasMany')} '${relatedTableTitle.value}'` return `'${tableTile.value}' ${t('labels.hasMany')} '${relatedTableTitle.value}'`
} else if (isMm(column.value)) { } else if (isMm(column.value)) {
@ -110,7 +111,7 @@ const tooltipMsg = computed(() => {
} else if (isRollup(column.value)) { } else if (isRollup(column.value)) {
return `'${childColumn.value.title}' of '${relatedTableTitle.value}' (${childColumn.value.uidt})` return `'${childColumn.value.title}' of '${relatedTableTitle.value}' (${childColumn.value.uidt})`
} }
return '' return column?.value?.title || ''
}) })
const columnOrder = ref<Pick<ColumnReqType, 'column_order'> | null>(null) const columnOrder = ref<Pick<ColumnReqType, 'column_order'> | null>(null)
@ -153,14 +154,14 @@ const openDropDown = (e: Event) => {
> >
<LazySmartsheetHeaderVirtualCellIcon v-if="column && !props.hideIcon" /> <LazySmartsheetHeaderVirtualCellIcon v-if="column && !props.hideIcon" />
<a-tooltip placement="bottom"> <NcTooltip placement="bottom" class="truncate name pl-1">
<template v-if="!isForm && !isExpandedForm" #title> <template #title>
{{ tooltipMsg }} {{ tooltipMsg }}
</template> </template>
<span class="name truncate pl-1" :class="{ truncate: !isForm }" :data-test-id="column.title"> <span :data-test-id="column.title">
{{ column.title }} {{ column.title }}
</span> </span>
</a-tooltip> </NcTooltip>
<span v-if="isVirtualColRequired(column, meta?.columns || []) || required" class="text-red-500">&nbsp;*</span> <span v-if="isVirtualColRequired(column, meta?.columns || []) || required" class="text-red-500">&nbsp;*</span>

9
packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue

@ -91,7 +91,7 @@ const onArrowUp = () => {
<div class="flex pb-3 px-4 border-b-1 border-gray-100"> <div class="flex pb-3 px-4 border-b-1 border-gray-100">
<input ref="inputRef" v-model="search" class="w-full focus:outline-none" :placeholder="$t('msg.selectFieldToSort')" /> <input ref="inputRef" v-model="search" class="w-full focus:outline-none" :placeholder="$t('msg.selectFieldToSort')" />
</div> </div>
<div class="flex-col w-full max-h-100 nc-scrollbar-md !overflow-y-auto"> <div class="flex-col w-full max-h-100 max-w-76 nc-scrollbar-md !overflow-y-auto">
<div v-if="!options.length" class="flex text-gray-500 px-4 py-2.25">{{ $t('general.empty') }}</div> <div v-if="!options.length" class="flex text-gray-500 px-4 py-2.25">{{ $t('general.empty') }}</div>
<div <div
v-for="(option, index) in options" v-for="(option, index) in options"
@ -104,9 +104,12 @@ const onArrowUp = () => {
@click="onClick(option)" @click="onClick(option)"
> >
<SmartsheetHeaderIcon :column="option" /> <SmartsheetHeaderIcon :column="option" />
<div> <NcTooltip class="truncate">
<template #title> {{ option.title }}</template>
<span>
{{ option.title }} {{ option.title }}
</div> </span>
</NcTooltip>
</div> </div>
</div> </div>
</div> </div>

10
packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue

@ -82,13 +82,15 @@ if (!localValue.value && allowEmpty !== true) {
<a-select-option v-for="option in options" :key="option.value" :label="option.label" :value="option.value"> <a-select-option v-for="option in options" :key="option.value" :label="option.label" :value="option.value">
<div class="flex gap-2 items-center items-center h-full"> <div class="flex gap-2 items-center items-center h-full">
<component :is="option.icon" class="min-w-5 !mx-0" /> <component :is="option.icon" class="min-w-5 !mx-0" />
<NcTooltip
<div
class="min-w-0 text-ellipsis overflow-hidden select-none"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
class="max-w-[15rem] truncate select-none"
> >
<template #title> {{ option.label }}</template>
<span>
{{ option.label }} {{ option.label }}
</div> </span>
</NcTooltip>
</div> </div>
</a-select-option> </a-select-option>
</NcSelect> </NcSelect>

27
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -371,7 +371,7 @@ useMenuCloseOnEsc(open)
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-600 mr-1" /> <component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-600 mr-1" />
<div <div
v-e="['a:fields:show-hide']" v-e="['a:fields:show-hide']"
class="flex flex-row items-center justify-between w-full cursor-pointer ml-1" class="flex flex-row items-center w-full truncate cursor-pointer ml-1"
@click=" @click="
() => { () => {
field.show = !field.show field.show = !field.show
@ -379,15 +379,13 @@ useMenuCloseOnEsc(open)
} }
" "
> >
<div class="flex items-center -ml-0.75">
<component :is="getIcon(metaColumnById[field.fk_column_id])" /> <component :is="getIcon(metaColumnById[field.fk_column_id])" />
<NcTooltip :disabled="field.title.length < 30"> <NcTooltip :disabled="field.title.length < 30" class="flex-1 px-1 truncate">
<template #title> <template #title>
{{ field.title }} {{ field.title }}
</template> </template>
<span class="mx-0.65 break-all line-clamp-1">{{ field.title }}</span> <span>{{ field.title }}</span>
</NcTooltip> </NcTooltip>
</div>
<NcSwitch v-e="['a:fields:show-hide']" :checked="field.show" :disabled="field.isViewEssentialField" /> <NcSwitch v-e="['a:fields:show-hide']" :checked="field.show" :disabled="field.isViewEssentialField" />
</div> </div>
@ -399,7 +397,7 @@ useMenuCloseOnEsc(open)
<div <div
v-if="gridDisplayValueField && filteredFieldList[0].title.toLowerCase().includes(filterQuery.toLowerCase())" v-if="gridDisplayValueField && filteredFieldList[0].title.toLowerCase().includes(filterQuery.toLowerCase())"
:key="`pv-${gridDisplayValueField.id}`" :key="`pv-${gridDisplayValueField.id}`"
class="pl-7.5 pr-2.1 py-2 flex flex-row items-center border-1 border-gray-200" class="pl-7.4 pr-2 py-2 flex flex-row items-center border-1 border-gray-200"
:class="{ :class="{
'rounded-t-lg': filteredFieldList.length > 1, 'rounded-t-lg': filteredFieldList.length > 1,
'rounded-lg': filteredFieldList.length === 1, 'rounded-lg': filteredFieldList.length === 1,
@ -407,23 +405,14 @@ useMenuCloseOnEsc(open)
:data-testid="`nc-fields-menu-${gridDisplayValueField.title}`" :data-testid="`nc-fields-menu-${gridDisplayValueField.title}`"
@click.stop @click.stop
> >
<div class="flex flex-row items-center justify-between w-full">
<div class="flex items">
<a-tooltip placement="bottom">
<template #title>
<span class="text-sm">$t('title.displayValue') </span>
</template>
</a-tooltip>
<div class="flex items-center">
<component :is="getIcon(metaColumnById[filteredFieldList[0].fk_column_id as string])" /> <component :is="getIcon(metaColumnById[filteredFieldList[0].fk_column_id as string])" />
<NcTooltip :disabled="filteredFieldList?.[0]?.title?.length < 30" class="px-1 flex-1 truncate">
<template #title>{{ filteredFieldList[0].title }}</template>
<span>{{ filteredFieldList[0].title }}</span> <span>{{ filteredFieldList[0].title }}</span>
</div> </NcTooltip>
</div>
<NcSwitch v-e="['a:fields:show-hide']" :checked="true" :disabled="true" /> <NcSwitch v-e="['a:fields:show-hide']" :checked="true" :disabled="true" />
</div> </div>
</div>
</template> </template>
</Draggable> </Draggable>
</div> </div>

6
packages/nc-gui/components/smartsheet/toolbar/OpenedViewAction.vue

@ -173,7 +173,7 @@ function openDeleteDialog() {
> >
<div <div
v-e="['c:breadcrumb:view-actions']" v-e="['c:breadcrumb:view-actions']"
class="truncate nc-active-view-title !hover:(bg-gray-100 text-gray-800) ml-0.25 pl-1 pr-0.25 rounded-md py-1 cursor-pointer" class="truncate nc-active-view-title flex gap-0.5 items-center !hover:(bg-gray-100 text-gray-800) ml-1 pl-1 pr-0.25 rounded-md py-1 cursor-pointer"
:class="{ :class="{
'max-w-2/5': !isSharedBase && !isMobileMode && activeView?.is_default, 'max-w-2/5': !isSharedBase && !isMobileMode && activeView?.is_default,
'max-w-3/5': !isSharedBase && !isMobileMode && !activeView?.is_default, 'max-w-3/5': !isSharedBase && !isMobileMode && !activeView?.is_default,
@ -182,14 +182,16 @@ function openDeleteDialog() {
'text-gray-800 font-medium': !activeView?.is_default, 'text-gray-800 font-medium': !activeView?.is_default,
}" }"
> >
<NcTooltip class="truncate xs:pl-1.25 flex-1 text-inherit">
<template #title>{{ activeView?.is_default ? $t('title.defaultView') : activeView?.title }} </template>
<span <span
class="truncate xs:pl-1.25 text-inherit"
:class="{ :class="{
'max-w-28/100': !isMobileMode, 'max-w-28/100': !isMobileMode,
}" }"
> >
{{ activeView?.is_default ? $t('title.defaultView') : activeView?.title }} {{ activeView?.is_default ? $t('title.defaultView') : activeView?.title }}
</span> </span>
</NcTooltip>
<GeneralIcon icon="arrowDown" class="ml-1" /> <GeneralIcon icon="arrowDown" class="ml-1" />
</div> </div>
<template #overlay> <template #overlay>

5
packages/nc-gui/components/smartsheet/toolbar/SearchData.vue

@ -114,7 +114,10 @@ watch(columns, () => {
<a-select-option v-for="op of columns" :key="op.value" v-e="['c:search:field:select']" :value="op.value"> <a-select-option v-for="op of columns" :key="op.value" v-e="['c:search:field:select']" :value="op.value">
<div class="text-[0.75rem] flex items-center -ml-1 gap-2"> <div class="text-[0.75rem] flex items-center -ml-1 gap-2">
<SmartsheetHeaderIcon class="text-sm" :column="op.column" /> <SmartsheetHeaderIcon class="text-sm" :column="op.column" />
{{ op.label }} <NcTooltip class="truncate" placement="top">
<template #title>{{ op.label }}</template>
<span>{{ op.label }}</span>
</NcTooltip>
</div> </div>
</a-select-option> </a-select-option>
</a-select> </a-select>

2
packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue

@ -138,7 +138,7 @@ onMounted(() => {
<template v-for="(sort, i) of sorts" :key="i"> <template v-for="(sort, i) of sorts" :key="i">
<SmartsheetToolbarFieldListAutoCompleteDropdown <SmartsheetToolbarFieldListAutoCompleteDropdown
v-model="sort.fk_column_id" v-model="sort.fk_column_id"
class="flex caption nc-sort-field-select min-w-40 flex-grow" class="flex caption nc-sort-field-select w-44 flex-grow"
:columns="columns" :columns="columns"
is-sort is-sort
@click.stop @click.stop

4
packages/nc-gui/composables/useData.ts

@ -34,10 +34,6 @@ export function useData(args: {
}) { }) {
const { meta, viewMeta, formattedData, paginationData, callbacks } = args const { meta, viewMeta, formattedData, paginationData, callbacks } = args
if (!meta) {
throw new Error('Table meta is not available')
}
const { t } = useI18n() const { t } = useI18n()
const { getMeta, metas } = useMetas() const { getMeta, metas } = useMetas()

4
packages/nc-gui/composables/useViewData.ts

@ -42,10 +42,6 @@ export function useViewData(
const meta = computed(() => _meta.value || activeTable.value) const meta = computed(() => _meta.value || activeTable.value)
const metaId = computed(() => _meta.value?.id || activeTableId.value) const metaId = computed(() => _meta.value?.id || activeTableId.value)
if (!meta.value) {
throw new Error('Table meta is not available')
}
const { t } = useI18n() const { t } = useI18n()
const optimisedQuery = useState('optimisedQuery', () => true) const optimisedQuery = useState('optimisedQuery', () => true)

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

@ -1,45 +1,45 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "Crear nueva interfaz",
"connect_data_sources": "Connect data sources", "connect_data_sources": "Conectar bases de datos",
"alert": "Alert", "alert": "Alerta",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "No se ha conectado ninguna base de datos. Conecte las bases de datos para crear interfaces. Omita este paso y luego añada bases de datos desde la página de inicio de la base.",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Seleccione las bases de datos que desea vincular a esta interfaz.",
"create_interface": "Create interface", "create_interface": "Crear interfaz",
"project_name": "Base Name", "project_name": "Nombre de la base",
"connect": "Connect", "connect": "Conectar",
"buttonActionTypes": { "buttonActionTypes": {
"open_external_url": "Open external link", "open_external_url": "Abrir enlace externo",
"delete_record": "Delete record", "delete_record": "Borrar fila",
"update_record": "Update record", "update_record": "Actualizar fila",
"open_layout": "Open layout" "open_layout": "Abrir diagrama"
}, },
"widgets": { "widgets": {
"static_text": "Text", "static_text": "Texto",
"chart": "Chart", "chart": "Gráfico",
"table": "Table", "table": "Tabla",
"image": "Image", "image": "Imagen",
"map": "Map", "map": "Mapa",
"button": "Button", "button": "Botón",
"number": "Number", "number": "Número",
"bar_chart": "Bar Chart", "bar_chart": "Gráfico de barras",
"line_chart": "Line Chart", "line_chart": "Gráfico de Líneas",
"area_chart": "Area Chart", "area_chart": "Gráfico de área",
"pie_chart": "Pie Chart", "pie_chart": "Gráfico de tarta",
"donut_chart": "Donut Chart", "donut_chart": "Gráfico de dona",
"scatter_plot": "Scatter Plot", "scatter_plot": "Gráfico de dispersión",
"bubble_chart": "Bubble Chart", "bubble_chart": "Gráfico de burbujas",
"radar_chart": "Radar Chart", "radar_chart": "Gráfico de radar",
"polar_area_chart": "Polar Area Chart", "polar_area_chart": "Gráfico de área polar",
"radial_bar_chart": "Radial Bar Chart", "radial_bar_chart": "Gráfico de barra radial",
"heatmap_chart": "Heatmap Chart", "heatmap_chart": "Mapa de calor",
"treemap_chart": "Treemap Chart", "treemap_chart": "Gráfico de árbol",
"box_plot_chart": "Box Plot Chart", "box_plot_chart": "Gráfico de cajas",
"candlestick_chart": "Candlestick Chart" "candlestick_chart": "Gráfico de velas"
} }
}, },
"general": { "general": {
"quit": "Quit", "quit": "Salir",
"home": "Inicio", "home": "Inicio",
"load": "Cargar", "load": "Cargar",
"open": "Abrir", "open": "Abrir",
@ -47,24 +47,24 @@
"yes": "Sí", "yes": "Sí",
"no": "No", "no": "No",
"ok": "Ok", "ok": "Ok",
"back": "Back", "back": "Volver",
"and": "Y", "and": "Y",
"or": "O", "or": "O",
"add": "Agregar", "add": "Agregar",
"edit": "Editar", "edit": "Editar",
"link": "Link", "link": "Enlace",
"links": "Links", "links": "Enlaces",
"remove": "Eliminar", "remove": "Eliminar",
"import": "Import", "import": "Importar",
"logout": "Log Out", "logout": "Cerrar sesión",
"empty": "Empty", "empty": "Empty",
"changeIcon": "Change Icon", "changeIcon": "Cambiar icono",
"save": "Salvar", "save": "Salvar",
"available": "Available", "available": "Disponible",
"abort": "Abort", "abort": "Cancelar",
"saving": "Saving", "saving": "Guardando",
"cancel": "Cancelar", "cancel": "Cancelar",
"null": "Null", "null": "Nulo",
"escape": "Escape", "escape": "Escape",
"hex": "Hex", "hex": "Hex",
"clear": "Clear", "clear": "Clear",

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

@ -1,27 +1,27 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "创建新界面",
"connect_data_sources": "Connect data sources", "connect_data_sources": "连接数据源",
"alert": "Alert", "alert": "警告",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "尚未连接数据库,连接数据库以构建界面。跳过此步骤,稍后从数据库主页添加数据库。",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "选择要链接到此界面的数据库。",
"create_interface": "Create interface", "create_interface": "创建界面",
"project_name": "Base Name", "project_name": "项目名",
"connect": "Connect", "connect": "连接",
"buttonActionTypes": { "buttonActionTypes": {
"open_external_url": "Open external link", "open_external_url": "打开外部链接",
"delete_record": "Delete record", "delete_record": "删除行",
"update_record": "Update record", "update_record": "更新记录",
"open_layout": "Open layout" "open_layout": "Open layout"
}, },
"widgets": { "widgets": {
"static_text": "Text", "static_text": "文本",
"chart": "Chart", "chart": "图表",
"table": "Table", "table": "表格",
"image": "Image", "image": "图片",
"map": "Map", "map": "地图",
"button": "Button", "button": "按钮",
"number": "Number", "number": "数字",
"bar_chart": "Bar Chart", "bar_chart": "Bar Chart",
"line_chart": "Line Chart", "line_chart": "Line Chart",
"area_chart": "Area Chart", "area_chart": "Area Chart",
@ -39,7 +39,7 @@
} }
}, },
"general": { "general": {
"quit": "Quit", "quit": "退出",
"home": "首页", "home": "首页",
"load": "加载", "load": "加载",
"open": "打开", "open": "打开",
@ -47,16 +47,16 @@
"yes": "是", "yes": "是",
"no": "否", "no": "否",
"ok": "行", "ok": "行",
"back": "Back", "back": "返回",
"and": "与", "and": "与",
"or": "或", "or": "或",
"add": "添加", "add": "添加",
"edit": "编辑", "edit": "编辑",
"link": "Link", "link": "链接",
"links": "Links", "links": "链接",
"remove": "移除", "remove": "移除",
"import": "Import", "import": "导入",
"logout": "Log Out", "logout": "退出登录",
"empty": "Empty", "empty": "Empty",
"changeIcon": "Change Icon", "changeIcon": "Change Icon",
"save": "保存", "save": "保存",
@ -69,7 +69,7 @@
"hex": "Hex", "hex": "Hex",
"clear": "Clear", "clear": "Clear",
"slack": "Slack", "slack": "Slack",
"comment": "Comment", "comment": "评论",
"microsoftTeams": "Microsoft Teams", "microsoftTeams": "Microsoft Teams",
"discord": "Discord", "discord": "Discord",
"matterMost": "Mattermost", "matterMost": "Mattermost",
@ -78,12 +78,12 @@
"quote": "Quote", "quote": "Quote",
"submit": "提交", "submit": "提交",
"create": "创建", "create": "创建",
"createEntity": "Create {entity}", "createEntity": "新建{entity}",
"creating": "Creating", "creating": "Creating",
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "跳过",
"code": "Code", "code": "代码",
"duplicate": "复制", "duplicate": "复制",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -107,7 +107,7 @@
"deprecated": "Deprecated", "deprecated": "Deprecated",
"showAll": "显示全部", "showAll": "显示全部",
"hideAll": "隐藏全部", "hideAll": "隐藏全部",
"notFound": "Not found", "notFound": "未找到",
"showMore": "显示更多", "showMore": "显示更多",
"showOptions": "显示选项", "showOptions": "显示选项",
"hideOptions": "隐藏选项", "hideOptions": "隐藏选项",
@ -127,8 +127,8 @@
"upload": "上传", "upload": "上传",
"download": "下载", "download": "下载",
"default": "默认", "default": "默认",
"base": "Source", "base": "",
"datasource": "Data Source", "datasource": "数据源",
"more": "更多的", "more": "更多的",
"less": "较少的", "less": "较少的",
"event": "事件", "event": "事件",
@ -160,39 +160,39 @@
"hideField": "隐藏字段", "hideField": "隐藏字段",
"sortAsc": "升序", "sortAsc": "升序",
"sortDesc": "降序", "sortDesc": "降序",
"move": "Move", "move": "移动",
"geoDataField": "地理数据字段", "geoDataField": "地理数据字段",
"type": "Type", "type": "类型",
"name": "Name", "name": "名称",
"changes": "Changes", "changes": "更新",
"new": "New", "new": "新的",
"old": "Old", "old": "旧的",
"data": "Data", "data": "数据",
"source": "Source", "source": "",
"destination": "Destination", "destination": "Destination",
"active": "Active", "active": "Active",
"inactive": "Inactive", "inactive": "Inactive",
"linked": "linked", "linked": "linked",
"finish": "Finish", "finish": "Finish",
"min": "Min", "min": "最小",
"max": "Max", "max": "最大",
"avg": "Avg", "avg": "平均值",
"sum": "Sum", "sum": "求和",
"count": "Count", "count": "总计",
"countDistinct": "Count Distinct", "countDistinct": "Count Distinct",
"sumDistinct": "Sum Distinct", "sumDistinct": "Sum Distinct",
"avgDistinct": "Avg Distinct", "avgDistinct": "Avg Distinct",
"join": "Join", "join": "Join",
"options": "Options", "options": "选项",
"primaryValue": "Primary Value", "primaryValue": "Primary Value",
"useSurveyMode": "Use Survey Mode", "useSurveyMode": "Use Survey Mode",
"shift": "Shift", "shift": "Shift",
"enter": "Enter", "enter": "回车键",
"seconds": "Seconds" "seconds": ""
}, },
"objects": { "objects": {
"workspace": "Workspace", "workspace": "工作区",
"workspaces": "Workspaces", "workspaces": "工作区",
"project": "项目", "project": "项目",
"projects": "项目", "projects": "项目",
"table": "表格", "table": "表格",
@ -209,7 +209,7 @@
"webhooks": "Webhooks.", "webhooks": "Webhooks.",
"view": "视图", "view": "视图",
"views": "视图", "views": "视图",
"sidebar": "Sidebar", "sidebar": "侧边栏",
"viewType": { "viewType": {
"grid": "表格", "grid": "表格",
"gallery": "画廊", "gallery": "画廊",
@ -222,7 +222,7 @@
"users": "用户", "users": "用户",
"role": "角色", "role": "角色",
"roles": "角色", "roles": "角色",
"developer": "Developer", "developer": "开发人员",
"roleType": { "roleType": {
"owner": "所有者", "owner": "所有者",
"creator": "创造者", "creator": "创造者",
@ -240,9 +240,9 @@
"short": "Short", "short": "Short",
"medium": "Medium", "medium": "Medium",
"tall": "Tall", "tall": "Tall",
"extra": "Extra" "extra": "备注"
}, },
"externalDb": "External Database" "externalDb": "外部数据库"
}, },
"datatype": { "datatype": {
"ID": "ID", "ID": "ID",
@ -297,8 +297,8 @@
"isNotNull": "有值(Not Null)" "isNotNull": "有值(Not Null)"
}, },
"title": { "title": {
"docs": "Docs", "docs": "文档",
"forum": "Forum", "forum": "论坛",
"parameter": "Parameter", "parameter": "Parameter",
"headers": "Headers", "headers": "Headers",
"parameterName": "Parameter Name", "parameterName": "Parameter Name",
@ -311,39 +311,39 @@
"inDesktop": "in Desktop", "inDesktop": "in Desktop",
"rowData": "Record data", "rowData": "Record data",
"creator": "Creator", "creator": "Creator",
"qrCode": "QR Code", "qrCode": "二维码",
"termsOfService": "Terms of Service", "termsOfService": "服务条款",
"updateSelectedRows": "Update Selected Records", "updateSelectedRows": "更新选定记录",
"noFiltersAdded": "No filters added", "noFiltersAdded": "未添加过滤器",
"editCards": "Edit Cards", "editCards": "编辑卡片",
"noFieldsFound": "No fields found", "noFieldsFound": "无可用字段",
"displayValue": "Display Value", "displayValue": "显示值",
"expand": "Expand", "expand": "Expand",
"hideAll": "Hide all", "hideAll": "隐藏全部",
"hideSystemFields": "Hide system fields", "hideSystemFields": "隐藏系统字段",
"removeFile": "Remove File", "removeFile": "删除文件",
"hasMany": "Has Many", "hasMany": "单对多",
"manyToMany": "Many to Many", "manyToMany": "多对多",
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"downloadFile": "Download File", "downloadFile": "下载文件",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
"renamingWs": "Renaming Workspace", "renamingWs": "Renaming Workspace",
"renameWs": "Rename Workspace", "renameWs": "Rename Workspace",
"deleteWs": "Delete Workspace", "deleteWs": "Delete Workspace",
"deletingWs": "Deleting Workspace", "deletingWs": "Deleting Workspace",
"copyAuthToken": "Copy Auth Token", "copyAuthToken": "复制 Auth 令牌",
"copiedAuthToken": "Copied Auth Token", "copiedAuthToken": "Copied Auth Token",
"copyInviteToken": "Copy Invite Token", "copyInviteToken": "Copy Invite Token",
"showSidebar": "Show Sidebar", "showSidebar": "Show Sidebar",
"hideSidebar": "Hide Sidebar", "hideSidebar": "Hide Sidebar",
"creatingTable": "Creating Table", "creatingTable": "Creating Table",
"erdView": "ERD 视图", "erdView": "ERD 视图",
"newBase": "New Data Source", "newBase": "新建数据源",
"newProj": "创建新项目", "newProj": "创建新项目",
"createBase": "Create Base", "createBase": "新建项目",
"myProject": "我的项目", "myProject": "我的项目",
"formTitle": "表单标题", "formTitle": "表单标题",
"collaborative": "Collaborative", "collaborative": "Collaborative",
@ -353,7 +353,7 @@
"teamAndAuth": "团队和认证", "teamAndAuth": "团队和认证",
"rolesUserMgmt": "角色和用户管理", "rolesUserMgmt": "角色和用户管理",
"userMgmt": "用户管理", "userMgmt": "用户管理",
"apiTokens": "API Tokens", "apiTokens": "API 令牌",
"apiTokenMgmt": "API Tokens 管理", "apiTokenMgmt": "API Tokens 管理",
"rolesMgmt": "角色管理", "rolesMgmt": "角色管理",
"projMeta": "项目基础信息", "projMeta": "项目基础信息",
@ -380,11 +380,11 @@
"swaggerDocumentation": "Swagger 文档", "swaggerDocumentation": "Swagger 文档",
"quickImportFrom": "快速导入自", "quickImportFrom": "快速导入自",
"quickImport": "快速导入", "quickImport": "快速导入",
"quickImportAirtable": "Quick Import - Airtable", "quickImportAirtable": "快速导入 - Airtable",
"quickImportCSV": "Quick Import - CSV", "quickImportCSV": "快速导入 - CSV",
"quickImportExcel": "Quick Import - Excel", "quickImportExcel": "快速导入 - Excel",
"quickImportJSON": "Quick Import - JSON", "quickImportJSON": "快速导入 - JSON",
"jsonEditor": "JSON Editor", "jsonEditor": "JSON编辑器",
"comingSoon": "Coming Soon", "comingSoon": "Coming Soon",
"advancedSettings": "高级设置", "advancedSettings": "高级设置",
"codeSnippet": "代码片段", "codeSnippet": "代码片段",
@ -392,17 +392,17 @@
"generateRandomName": "随机名称", "generateRandomName": "随机名称",
"findRowByScanningCode": "通过扫描二维码或条码查找行", "findRowByScanningCode": "通过扫描二维码或条码查找行",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "添加新令牌(Token)",
"accountSettings": "Account Settings", "accountSettings": "账户设置",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "密码重置",
"tokens": "Tokens", "tokens": "令牌",
"userManagement": "User Management", "userManagement": "User Management",
"accountManagement": "Account management", "accountManagement": "Account management",
"licence": "Licence", "licence": "许可证",
"allowAllMimeTypes": "Allow All Mime Types", "allowAllMimeTypes": "Allow All Mime Types",
"defaultView": "Default View", "defaultView": "默认视图",
"relations": "Relations", "relations": "Relations",
"switchLanguage": "Switch Language", "switchLanguage": "切换语言",
"renameFile": "Rename File", "renameFile": "Rename File",
"links": { "links": {
"noAction": "No Action", "noAction": "No Action",
@ -413,30 +413,30 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1", "heading1": "一级标题",
"heading2": "Heading 2", "heading2": "二级标题",
"heading3": "Heading 3", "heading3": "三级标题",
"bold": "Bold", "bold": "粗体",
"italic": "Italic", "italic": "斜体",
"underline": "Underline", "underline": "下划线",
"strike": "Strike", "strike": "删除线",
"taskList": "Task List", "taskList": "任务列表",
"bulletList": "Bullet List", "bulletList": "无序列表",
"numberedList": "Numbered List", "numberedList": "有序列表",
"downloadData": "Download Data", "downloadData": "下载数据",
"blockQuote": "Block Quote", "blockQuote": "Block Quote",
"noToken": "No Token", "noToken": "无令牌",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "每个用户只能使用一个令牌",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "文件{filename} 已附加",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "查看 ID:{viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
"body": "Body", "body": "Body",
"commaSeparatedMobileNumber": "Comma separated Mobile #", "commaSeparatedMobileNumber": "Comma separated Mobile #",
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "图标",
"max": "Max", "max": "最大",
"enableRichText": "Enable Rich Text", "enableRichText": "启用富文本",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -446,33 +446,33 @@
"examples": "Examples", "examples": "Examples",
"durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).", "durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).",
"addHeader": "Add Header", "addHeader": "Add Header",
"enterDefaultUrlOptional": "Enter default URL (Optional)", "enterDefaultUrlOptional": "输入默认 URL(可选)",
"negative": "Negative", "negative": "Negative",
"discard": "Discard", "discard": "Discard",
"default": "Default", "default": "Default",
"defaultNumberPercent": "Default Number (%)", "defaultNumberPercent": "Default Number (%)",
"durationFormat": "Duration Format", "durationFormat": "Duration Format",
"dateFormat": "Date Format", "dateFormat": "日期格式",
"timeFormat": "Time Format", "timeFormat": "Time Format",
"singularLabel": "Singular Label", "singularLabel": "Singular Label",
"pluralLabel": "Plural Label", "pluralLabel": "Plural Label",
"optional": "(Optional)", "optional": "(可选)",
"clickToMake": "Click to make", "clickToMake": "点击创建",
"visibleForRole": "visible for role:", "visibleForRole": "visible for role:",
"inUI": "in UI Dashboard", "inUI": "in UI Dashboard",
"projectSettings": "Base Settings", "projectSettings": "基础设置",
"clickToHide": "Click to hide", "clickToHide": "点击隐藏",
"clickToDownload": "Click to download", "clickToDownload": "点击下载",
"forRole": "for role", "forRole": "for role",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "点击复制视图ID",
"viewMode": "View Mode", "viewMode": "视图模式",
"searchUsers": "Search Users", "searchUsers": "搜索用户",
"superAdmin": "Super Admin", "superAdmin": "超级管理员",
"allTables": "All Tables", "allTables": "所有表格",
"members": "Members", "members": "Members",
"dataSources": "Data Sources", "dataSources": "数据源",
"connectDataSource": "Connect a Data Source", "connectDataSource": "Connect a Data Source",
"searchProjects": "Search Bases", "searchProjects": "搜索项目",
"createdBy": "创建自", "createdBy": "创建自",
"viewingAttachmentsOf": "Viewing Attachments of", "viewingAttachmentsOf": "Viewing Attachments of",
"readOnly": "Readonly", "readOnly": "Readonly",
@ -491,8 +491,8 @@
"createView": "Create a View", "createView": "Create a View",
"creatingView": "Creating View", "creatingView": "Creating View",
"duplicateView": "Duplicate View", "duplicateView": "Duplicate View",
"duplicateGridView": "Duplicate Grid View", "duplicateGridView": "复制视图",
"createGridView": "Create Grid View", "createGridView": "新建视图",
"duplicateGalleryView": "Duplicate Gallery View", "duplicateGalleryView": "Duplicate Gallery View",
"createGalleryView": "Create Gallery View", "createGalleryView": "Create Gallery View",
"duplicateFormView": "Duplicate Form View", "duplicateFormView": "Duplicate Form View",
@ -528,12 +528,12 @@
"where": "在哪里", "where": "在哪里",
"cache": "缓存", "cache": "缓存",
"chat": "聊天", "chat": "聊天",
"showOrHide": "Show or Hide", "showOrHide": "显示或隐藏",
"airtable": "Airtable", "airtable": "Airtable",
"csv": "CSV", "csv": "CSV",
"csvFile": "CSV File", "csvFile": "CSV文件",
"json": "JSON", "json": "JSON",
"jsonFile": "JSON File", "jsonFile": "JSON 文件",
"excel": "Excel", "excel": "Excel",
"microsoftExcel": "Microsoft Excel", "microsoftExcel": "Microsoft Excel",
"email": "电子邮件", "email": "电子邮件",
@ -544,7 +544,7 @@
"created": "创建了", "created": "创建了",
"sqlOutput": "输出 SQL", "sqlOutput": "输出 SQL",
"addOption": "添加选项", "addOption": "添加选项",
"interfaceColor": "Interface Color", "interfaceColor": "界面颜色",
"qrCodeValueColumn": "二维码要显示的数据", "qrCodeValueColumn": "二维码要显示的数据",
"barcodeValueColumn": "条形码要显示的数据", "barcodeValueColumn": "条形码要显示的数据",
"barcodeFormat": "条形码码制", "barcodeFormat": "条形码码制",
@ -575,14 +575,14 @@
"followNocodb": "关注 NocoDB", "followNocodb": "关注 NocoDB",
"communityTranslated": "(Community Translated)" "communityTranslated": "(Community Translated)"
}, },
"twitter": "Twitter", "twitter": "推特(Twitter",
"docReference": "参考文档", "docReference": "参考文档",
"selectUserRole": "选择用户角色", "selectUserRole": "选择用户角色",
"childTable": "子表", "childTable": "子表",
"childColumn": "子列", "childColumn": "子列",
"childField": "Child field", "childField": "Child field",
"linkToAnotherRecord": "关联到其他表", "linkToAnotherRecord": "关联到其他表",
"links": "Links", "links": "链接",
"onUpdate": "更新时", "onUpdate": "更新时",
"onDelete": "删除时", "onDelete": "删除时",
"account": "帐户", "account": "帐户",
@ -593,7 +593,7 @@
"requestDataSource": "请求您需要的数据源?", "requestDataSource": "请求您需要的数据源?",
"apiKey": "API 密钥", "apiKey": "API 密钥",
"personalAccessToken": "Personal Access Token", "personalAccessToken": "Personal Access Token",
"sharedBaseUrl": "Shared Base URL", "sharedBaseUrl": "分享项目链接",
"importData": "导入数据", "importData": "导入数据",
"importSecondaryViews": "导入次要视图", "importSecondaryViews": "导入次要视图",
"importRollupColumns": "导入聚合列", "importRollupColumns": "导入聚合列",
@ -620,15 +620,15 @@
"subscribeNewsletter": "订阅我们的每周新闻", "subscribeNewsletter": "订阅我们的每周新闻",
"signUpWithProvider": "使用 {provider} 注册", "signUpWithProvider": "使用 {provider} 注册",
"signInWithProvider": "使用 {provider} 登录", "signInWithProvider": "使用 {provider} 登录",
"agreeToTos": "注册即表您同意服务条款", "agreeToTos": "注册即表您同意服务条款",
"welcomeToNc": "欢迎来到NocoDB!", "welcomeToNc": "欢迎来到NocoDB!",
"inviteOnlySignup": "只能通过邀请链接注册账户", "inviteOnlySignup": "只能通过邀请链接注册账户",
"nextRow": "下一行", "nextRow": "下一行",
"prevRow": "上一行", "prevRow": "上一行",
"addRowGrid": "Manually add data in grid view", "addRowGrid": "Manually add data in grid view",
"addRowForm": "Enter record data through a form", "addRowForm": "Enter record data through a form",
"noAccess": "No access", "noAccess": "无权访问",
"restApis": "Rest APIs", "restApis": "重置API",
"apis": "APIs", "apis": "APIs",
"includeData": "Include Data", "includeData": "Include Data",
"includeView": "Include View", "includeView": "Include View",
@ -649,37 +649,37 @@
"attachFile": "Attach File", "attachFile": "Attach File",
"viewAttachment": "View Attachments", "viewAttachment": "View Attachments",
"attachmentDrop": "Click or drop a file into cell", "attachmentDrop": "Click or drop a file into cell",
"addFiles": "Add File(s)", "addFiles": "添加文件(一个或多个)",
"hideInUI": "Hide in UI", "hideInUI": "Hide in UI",
"addBase": "Add Base", "addBase": "添加项目",
"addParameter": "Add Parameter", "addParameter": "Add Parameter",
"submitAnotherForm": "Submit Another Form", "submitAnotherForm": "Submit Another Form",
"dragAndDropFieldsHereToAdd": "Drag and drop fields here to add", "dragAndDropFieldsHereToAdd": "Drag and drop fields here to add",
"editSource": "Edit Data Source", "editSource": "Edit Data Source",
"enterText": "Enter text", "enterText": "输入文本",
"okEditBase": "Ok & Edit Base", "okEditBase": "Ok & Edit Base",
"showInUI": "Show in UI", "showInUI": "Show in UI",
"outOfSync": "Out of sync", "outOfSync": "Out of sync",
"newSource": "New Data Source", "newSource": "新建数据源",
"newWebhook": "New Webhook", "newWebhook": "新建 Webhook",
"enablePublicAccess": "Enable Public Access", "enablePublicAccess": "启用公共访问",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?", "doYouWantToSaveTheChanges": "您要保存这些更改吗?",
"editingAccess": "Editing access", "editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing", "enabledPublicViewing": "启用公开查看",
"restrictAccessWithPassword": "Restrict access with password", "restrictAccessWithPassword": "使用密码限制访问",
"manageProjectAccess": "Manage Base Access", "manageProjectAccess": "管理项目访问",
"allowDownload": "Allow Download", "allowDownload": "允许下载",
"surveyMode": "Survey Mode", "surveyMode": "Survey Mode",
"rtlOrientation": "RTL Orientation", "rtlOrientation": "RTL Orientation",
"useTheme": "Use Theme", "useTheme": "使用主题",
"copyLink": "Copy Link", "copyLink": "复制链接",
"copiedLink": "Link Copied", "copiedLink": "Link Copied",
"copyInviteLink": "Copy invite link", "copyInviteLink": "Copy invite link",
"copiedInviteLink": "Copied invite link", "copiedInviteLink": "Copied invite link",
"copyUrl": "复制链接", "copyUrl": "复制链接",
"moreColors": "More Colors", "moreColors": "更多颜色",
"moveProject": "Move Base", "moveProject": "Move Base",
"createProject": "建项目", "createProject": "建项目",
"importProject": "导入项目", "importProject": "导入项目",
"searchProject": "搜索项目", "searchProject": "搜索项目",
"editProject": "编辑项目", "editProject": "编辑项目",
@ -689,7 +689,7 @@
"deleteProject": "删除项目", "deleteProject": "删除项目",
"refreshProject": "刷新项目", "refreshProject": "刷新项目",
"saveProject": "保存项目", "saveProject": "保存项目",
"saveAndQuit": "Save & Quit", "saveAndQuit": "保存并退出",
"deleteKanbanStack": "是否删除此类别?", "deleteKanbanStack": "是否删除此类别?",
"createProjectExtended": { "createProjectExtended": {
"extDB": "新建 <br>从外部数据库", "extDB": "新建 <br>从外部数据库",
@ -715,10 +715,10 @@
"filter": "筛选", "filter": "筛选",
"addFilter": "添加过滤器", "addFilter": "添加过滤器",
"share": "分享", "share": "分享",
"groupBy": "Group By", "groupBy": "分组",
"addSubGroup": "Add subgroup", "addSubGroup": "添加子分组",
"shareBase": { "shareBase": {
"label": "Share base", "label": "分享项目",
"disable": "停止分享项目", "disable": "停止分享项目",
"enable": "任何有链接的人", "enable": "任何有链接的人",
"link": "分享项目链接" "link": "分享项目链接"
@ -729,7 +729,7 @@
"inviteUser": "邀请用户", "inviteUser": "邀请用户",
"inviteToken": "邀请令牌", "inviteToken": "邀请令牌",
"linkedRecords": "Linked Records", "linkedRecords": "Linked Records",
"addNewLink": "Add New Link", "addNewLink": "添加新的链接",
"newUser": "创建用户", "newUser": "创建用户",
"editUser": "编辑用户", "editUser": "编辑用户",
"deleteUser": "从项目中踢出用户", "deleteUser": "从项目中踢出用户",
@ -782,7 +782,7 @@
"ListView": "视图列表", "ListView": "视图列表",
"copyView": "复制视图", "copyView": "复制视图",
"renameView": "重命名视图", "renameView": "重命名视图",
"uploadData": "Upload Data", "uploadData": "上传数据",
"deleteView": "删除视图", "deleteView": "删除视图",
"createGrid": "创建网格视图", "createGrid": "创建网格视图",
"createGallery": "创建画廊视图", "createGallery": "创建画廊视图",
@ -815,7 +815,7 @@
"addFilterGroup": "添加筛选器组", "addFilterGroup": "添加筛选器组",
"linkRecord": "关联到其他记录", "linkRecord": "关联到其他记录",
"addNewRecord": "新增记录", "addNewRecord": "新增记录",
"newRecord": "New record", "newRecord": "新增记录",
"tableNameCreateNewRecord": "{tableName}: Create new record", "tableNameCreateNewRecord": "{tableName}: Create new record",
"gotSavedLinkedSuccessfully": "{tableName} '{recordTitle}' got saved & linked successfully", "gotSavedLinkedSuccessfully": "{tableName} '{recordTitle}' got saved & linked successfully",
"recordCreatedLinked": "Record Created & Linked", "recordCreatedLinked": "Record Created & Linked",
@ -823,15 +823,15 @@
"toggleCommentsDraw": "切换评论视图", "toggleCommentsDraw": "切换评论视图",
"expandRecord": "展开记录", "expandRecord": "展开记录",
"deleteRecord": "删除记录", "deleteRecord": "删除记录",
"fullWidth": "Full width", "fullWidth": "使用宽屏",
"exitFullWidth": "Exit full width", "exitFullWidth": "退出宽屏",
"markAllAsRead": "Mark all as read", "markAllAsRead": "Mark all as read",
"column": { "column": {
"delete": "Delete Field", "delete": "删除字段",
"addNumber": "Add Number Field", "addNumber": "添加数字字段",
"addSingleLineText": "Add SingleLineText Field", "addSingleLineText": "添加单行文本字段",
"addLongText": "Add LongText Field", "addLongText": "添加长文本字段",
"addOther": "Add Other Field" "addOther": "添加其它字段"
}, },
"erd": { "erd": {
"showColumns": "显示列", "showColumns": "显示列",
@ -854,7 +854,7 @@
"openInOpenStreetMap": "OSM" "openInOpenStreetMap": "OSM"
}, },
"toggleMobileMode": "切换移动模式", "toggleMobileMode": "切换移动模式",
"startCommenting": "Start commenting!" "startCommenting": "开始评论"
}, },
"tooltip": { "tooltip": {
"reachedSourceLimit": "Limited to only one data source for the moment", "reachedSourceLimit": "Limited to only one data source for the moment",
@ -891,13 +891,13 @@
"selectTeamsChannels": "Select Microsoft Teams channels", "selectTeamsChannels": "Select Microsoft Teams channels",
"selectDiscordChannels": "Select Discord channels", "selectDiscordChannels": "Select Discord channels",
"selectMattermostChannels": "Select Mattermost channels", "selectMattermostChannels": "Select Mattermost channels",
"webhookTitle": "Webhook Title", "webhookTitle": "Webhook 标题",
"barcodeColumn": "Select a field for the Barcode value", "barcodeColumn": "Select a field for the Barcode value",
"notFoundContent": "No valid field Type can be found.", "notFoundContent": "No valid field Type can be found.",
"selectBarcodeFormat": "Select a Barcode format", "selectBarcodeFormat": "Select a Barcode format",
"projName": "输入项目名称", "projName": "输入项目名称",
"selectGroupField": "Select a Grouping Field", "selectGroupField": "选择分组字段",
"selectGroupFieldNotFound": "No Single Select Field can be found. Please create one first.", "selectGroupFieldNotFound": "没有可用的单选字段,请先创建。",
"selectGeoField": "Select a GeoData Field", "selectGeoField": "Select a GeoData Field",
"selectGeoFieldNotFound": "No GeoData Field can be found. Please create one first.", "selectGeoFieldNotFound": "No GeoData Field can be found. Please create one first.",
"password": { "password": {
@ -928,14 +928,14 @@
"decimal6": "1.000000", "decimal6": "1.000000",
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "",
"key": "Key" "key": ""
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "点击复制字段ID",
"enterPassword": "Enter password", "enterPassword": "输入密码",
"bySigningUp": "By signing up, you agree to the", "bySigningUp": "注册即表示您同意服务条款",
"subscribeToOurWeeklyNewsletter": "Subscribe to our weekly newsletter", "subscribeToOurWeeklyNewsletter": "订阅我们的每周的新闻",
"verifyingPassword": "Verifying Password", "verifyingPassword": "Verifying Password",
"thisSharedViewIsProtected": "This shared view is protected", "thisSharedViewIsProtected": "This shared view is protected",
"successfullySubmittedFormData": "Successfully submitted form data", "successfullySubmittedFormData": "Successfully submitted form data",
@ -1048,7 +1048,7 @@
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked", "disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "项目已迁移。请重试。",
"pasteNotSupported": "处于活动状态的单元格不能粘贴内容", "pasteNotSupported": "处于活动状态的单元格不能粘贴内容",
"roles": { "roles": {
"orgCreator": "创始人可以创建新项目,访问受邀项目。", "orgCreator": "创始人可以创建新项目,访问受邀项目。",
@ -1203,29 +1203,29 @@
"userConfirmation": "I understand that this action is irreversible", "userConfirmation": "I understand that this action is irreversible",
"pageNotFound": "Page Not Found", "pageNotFound": "Page Not Found",
"makeLineBreak": "to make a line break", "makeLineBreak": "to make a line break",
"goToPrevious": "Go to previous", "goToPrevious": "转到上一个",
"goToNext": "Go to next", "goToNext": "转到下一个",
"thankYou": "Thank you!", "thankYou": "谢谢你!",
"submittedFormData": "You have successfully submitted the form data." "submittedFormData": "您已成功提交表单数据。"
}, },
"error": { "error": {
"nameRequired": "Name Required", "nameRequired": "Name Required",
"nameMinLength": "Name must be at least 2 characters long", "nameMinLength": "Name must be at least 2 characters long",
"nameMaxLength": "Name must be at most 60 characters long", "nameMaxLength": "名称长度不得超过 60 个字符",
"viewNameRequired": "View name is required", "viewNameRequired": "View name is required",
"nameMaxLength256": "Name must be at most 256 characters long", "nameMaxLength256": "名称长度不得超过 256 个字符",
"viewNameUnique": "View name should be unique", "viewNameUnique": "视图名称应该是唯一的",
"searchProject": "搜索: {search} 没有发现匹配的结果", "searchProject": "搜索: {search} 没有发现匹配的结果",
"invalidChar": "文件夹路径中的字符无效。", "invalidChar": "文件夹路径中的字符无效。",
"invalidDbCredentials": "无效的数据库凭据。", "invalidDbCredentials": "无效的数据库凭据。",
"unableToConnectToDb": "无法连接到数据库,请检查您的数据库是否已启动。", "unableToConnectToDb": "无法连接到数据库,请检查您的数据库是否已启动。",
"invalidYear": "Invalid year", "invalidYear": "年份无效",
"userDoesntHaveSufficientPermission": "用户不存在或无权创建模式。", "userDoesntHaveSufficientPermission": "用户不存在或无权创建模式。",
"dbConnectionStatus": "数据库参数无效", "dbConnectionStatus": "数据库参数无效",
"dbConnectionFailed": "连接失败:", "dbConnectionFailed": "连接失败:",
"nullFilterExists": "Null filter exists. Please remove them", "nullFilterExists": "存在空过滤器。请删除它们",
"signUpRules": { "signUpRules": {
"emailRequired": "Email is required", "emailRequired": "电子邮件为必填",
"emailInvalid": "邮箱格式无效", "emailInvalid": "邮箱格式无效",
"passwdRequired": "密码必填", "passwdRequired": "密码必填",
"passwdLength": "您的密码必须至少为8个字符", "passwdLength": "您的密码必须至少为8个字符",
@ -1236,8 +1236,8 @@
"atLeastOneNumber": "一个数字", "atLeastOneNumber": "一个数字",
"atLeastOneSpecialChar": "一个特殊字符", "atLeastOneSpecialChar": "一个特殊字符",
"allowedSpecialCharList": "允许的特殊字符列表", "allowedSpecialCharList": "允许的特殊字符列表",
"invalidEmails": "Invalid emails", "invalidEmails": "邮箱格式错误",
"invalidEmail": "Invalid Email" "invalidEmail": "无效的电子邮件"
}, },
"invalidURL": "无效的 URL", "invalidURL": "无效的 URL",
"invalidEmail": "无效的电子邮件", "invalidEmail": "无效的电子邮件",
@ -1302,7 +1302,7 @@
"futureRelease": "即将推出!" "futureRelease": "即将推出!"
}, },
"success": { "success": {
"licenseKeyUpdated": "License Key Updated", "licenseKeyUpdated": "更新许可证密钥",
"columnDuplicated": "此列的副本创建成功", "columnDuplicated": "此列的副本创建成功",
"rowDuplicatedWithoutSavedYet": "该行已复制(未保存)", "rowDuplicatedWithoutSavedYet": "该行已复制(未保存)",
"updatedUIACL": "已成功更新表的 UI ACL", "updatedUIACL": "已成功更新表的 UI ACL",

50
packages/nc-gui/package.json

@ -73,7 +73,7 @@
"rfdc": "^1.3.0", "rfdc": "^1.3.0",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"socket.io-client": "^4.7.2", "socket.io-client": "^4.7.2",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.1",
"splitpanes": "^3.1.5", "splitpanes": "^3.1.5",
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
@ -93,10 +93,10 @@
"xlsx": "^0.18.5", "xlsx": "^0.18.5",
"@tiptap/extension-link": "2.0.4", "@tiptap/extension-link": "2.0.4",
"@tiptap/extension-task-list": "2.0.4", "@tiptap/extension-task-list": "2.0.4",
"@tiptap/extension-underline": "^2.1.12", "@tiptap/extension-underline": "^2.1.13",
"@tiptap/html": "2.0.4", "@tiptap/html": "2.0.4",
"@tiptap/pm": "^2.1.12", "@tiptap/pm": "^2.1.13",
"@tiptap/starter-kit": "^2.1.12", "@tiptap/starter-kit": "^2.1.13",
"marked": "^4.3.0", "marked": "^4.3.0",
"turndown": "^7.1.2", "turndown": "^7.1.2",
"@tiptap/vue-3": "2.0.4" "@tiptap/vue-3": "2.0.4"
@ -104,34 +104,34 @@
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.26.3", "@antfu/eslint-config": "^0.26.3",
"@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@iconify-json/ant-design": "^1.1.10", "@iconify-json/ant-design": "^1.1.12",
"@iconify-json/bi": "^1.1.21", "@iconify-json/bi": "^1.1.21",
"@iconify-json/carbon": "^1.1.21", "@iconify-json/carbon": "^1.1.23",
"@iconify-json/cil": "^1.1.5", "@iconify-json/cil": "^1.1.7",
"@iconify-json/clarity": "^1.1.9", "@iconify-json/clarity": "^1.1.11",
"@iconify-json/eva": "^1.1.7", "@iconify-json/eva": "^1.1.9",
"@iconify-json/ic": "^1.1.14", "@iconify-json/ic": "^1.1.16",
"@iconify-json/ion": "^1.1.12", "@iconify-json/ion": "^1.1.14",
"@iconify-json/la": "^1.1.5", "@iconify-json/la": "^1.1.7",
"@iconify-json/logos": "^1.1.38", "@iconify-json/logos": "^1.1.40",
"@iconify-json/lucide": "^1.1.141", "@iconify-json/lucide": "^1.1.144",
"@iconify-json/material-symbols": "^1.1.63", "@iconify-json/material-symbols": "^1.1.65",
"@iconify-json/mdi": "^1.1.55", "@iconify-json/mdi": "^1.1.57",
"@iconify-json/mi": "^1.1.5", "@iconify-json/mi": "^1.1.7",
"@iconify-json/ph": "^1.1.6", "@iconify-json/ph": "^1.1.8",
"@iconify-json/ri": "^1.1.12", "@iconify-json/ri": "^1.1.15",
"@iconify-json/simple-icons": "^1.1.79", "@iconify-json/simple-icons": "^1.1.81",
"@iconify-json/system-uicons": "^1.1.9", "@iconify-json/system-uicons": "^1.1.11",
"@iconify-json/tabler": "^1.1.98", "@iconify-json/tabler": "^1.1.100",
"@iconify-json/vscode-icons": "^1.1.29", "@iconify-json/vscode-icons": "^1.1.31",
"@intlify/unplugin-vue-i18n": "^0.12.3", "@intlify/unplugin-vue-i18n": "^0.12.3",
"@nuxt/image-edge": "1.1.0-28346300.6030589", "@nuxt/image-edge": "1.1.0-28355789.b3279fe",
"@types/d3-scale": "^4.0.8", "@types/d3-scale": "^4.0.8",
"@types/dagre": "^0.7.52", "@types/dagre": "^0.7.52",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@types/leaflet.markercluster": "^1.5.4", "@types/leaflet.markercluster": "^1.5.4",
"@types/papaparse": "^5.3.13", "@types/papaparse": "^5.3.14",
"@types/parse-github-url": "^1.0.3", "@types/parse-github-url": "^1.0.3",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/showdown": "^2.0.6", "@types/showdown": "^2.0.6",

2
packages/nc-gui/pages/signin.vue

@ -184,7 +184,7 @@ function navigateForgotPassword() {
</a> </a>
</div> </div>
<div class="text-end prose-sm" v-if="!appInfo.inviteOnlySignup"> <div v-if="!appInfo.inviteOnlySignup" class="text-end prose-sm">
{{ $t('msg.info.signUp.dontHaveAccount') }} {{ $t('msg.info.signUp.dontHaveAccount') }}
<nuxt-link @click="navigateSignUp">{{ $t('general.signUp') }}</nuxt-link> <nuxt-link @click="navigateSignUp">{{ $t('general.signUp') }}</nuxt-link>
</div> </div>

2
packages/nc-lib-gui/package.json

@ -1,6 +1,6 @@
{ {
"name": "nc-lib-gui", "name": "nc-lib-gui",
"version": "0.202.8", "version": "0.202.9",
"description": "NocoDB GUI", "description": "NocoDB GUI",
"author": { "author": {
"name": "NocoDB", "name": "NocoDB",

378
packages/noco-docs/docs/070.fields/040.field-types/060.formula/020.numeric-functions.md

@ -5,34 +5,360 @@ tags: ['Fields', 'Field types', 'Formula']
keywords: ['Fields', 'Field types', 'Formula', 'Create formula field', 'Numeric functions'] keywords: ['Fields', 'Field types', 'Formula', 'Create formula field', 'Numeric functions']
--- ---
This cheat sheet provides a quick reference guide for various mathematical functions commonly used in data analysis and programming. Each function is accompanied by its syntax, a sample usage, and a brief description.
### Numeric functions ------------
| Name | Syntax | Sample | Output |
|-------------|----------------------------|----------------------------------------|-------------------------------------------------------------------------------------------------------------|
| **ABS** | `ABS(value)` | `ABS({field})` | Absolute value of the input parameter |
| **ADD** | `ADD(value1,[value2,...])` | `ADD({field1}, {field2})` | Sum of input parameters |
| **AVG** | `AVG(value1,[value2,...])` | `AVG({field1}, {field2})` | Average of input parameters |
| **CEILING** | `CEILING(value)` | `CEILING({field})` | Rounded next largest integer value of input parameter |
| **EXP** | `EXP(value)` | `EXP({field})` | Exponential value of input parameter (`e^x`) |
| **FLOOR** | `FLOOR(value)` | `FLOOR({field})` | Rounded largest integer less than or equal to input parameter |
| **INT** | `INT(value)` | `INT({field})` | Integer value of input parameter |
| **LOG** | `LOG([base], value)` | `LOG(10, {field})` | Logarithm of input parameter to the base (default = e) specified |
| **MAX** | `MAX(value1,[value2,...])` | `MAX({field1}, {field2}, {field3})` | Maximum value amongst input parameters |
| **MIN** | `MIN(value1,[value2,...])` | `MIN({field1}, {field2}, {field3})` | Minimum value amongst input parameters |
| **MOD** | `MOD(value1, value2)` | `MOD({field}, 2)` | Remainder after integer division of input parameters |
| **POWER** | `POWER(base, exponent)` | `POWER({field}, 3)` | `base` to the `exponent` power, as in `base ^ exponent` |
| **ROUND** | `ROUND(value, precision)` | `ROUND({field}, 3)` | Round input `value` to decimal place specified by `precision` (Nearest integer if `precision` not provided) |
| **SQRT** | `SQRT(value)` | `SQRT({field})` | Square root of the input parameter |
| **COUNT** | `COUNT(value1,[value2,...])`| `COUNT({field1},{field2},{field3})` | Count the number of arguments that are numbers |
| **COUNTA** | `COUNTA(value1,[value2,...])`| `COUNTA({field1},{field2},{field3})`| Counts the number of non-empty arguments |
| **COUNTALL** | `COUNTALL(value1,[value2,...])`|`COUNTALL({field1},{field2},{field3})`| Counts the number of arguments |
| **EVEN** | `EVEN(value)` | `EVEN({field})` | Returns the nearest even integer that is greater than or equal to the specified value |
| **ODD** | `ODD(value)` | `ODD({field})` | Returns the nearest odd integer that is greater than or equal to the specified value |
| **VALUE** | `VALUE(value)` | `VALUE({field})` | Extract the numeric value from a string, if `%` or `-` is present, it will handle it accordingly and return the numeric value|
| **ROUNDDOWN** | `ROUNDDOWN(value, [precision])` | `ROUNDDOWN({field}, 2)` | Round down the value after the decimal point to the number of decimal places given by "precision"(default is 0) |
| **ROUNDUP** | `ROUNDUP(value, [precision])` | `ROUNDUP({field}, 2)` | Round up the value after the decimal point to the number of decimal places given by "precision"(default is 0) |
## ABS
The ABS function returns the distance of the number from zero on the number line, ensuring that the result is non-negative.
#### Syntax
```plaintext
ABS(number)
```
#### Sample
```plaintext
ABS(10.35) => 10.35
ABS(-15) => 15
```
------------
## ADD
The ADD function computes the total of multiple numbers provided as arguments.
#### Syntax
```plaintext
ADD(number1, [number2, ...])
```
#### Sample
```plaintext
ADD(5, 7) => 12
ADD(-10, 15, 20) => 25
```
------------
## AVG
The AVG function calculates the mean of a set of numerical values.
#### Syntax
```plaintext
AVG(number1, [number2, ...])
```
#### Sample
```plaintext
AVG(10, 20, 30) => 20
AVG(-5, 5) => 0
```
------------
## CEILING
The CEILING function rounds a number up to the nearest integer greater than or equal to the input.
#### Syntax
```plaintext
CEILING(number)
```
#### Sample
```plaintext
CEILING(8.75) => 9
CEILING(-15.25) => -15
```
------------
## COUNT
The COUNT function calculates the number of numeric arguments provided.
#### Syntax
```plaintext
COUNT(number1, [number2, ...])
```
#### Sample
```plaintext
COUNT(1, 2, "abc", 3) => 3
COUNT(-5, 0, "$abc", 5) => 3
```
------------
## COUNTA
The COUNTA function counts the number of non-empty arguments provided.
#### Syntax
```plaintext
COUNTA(value1, [value2, ...])
```
#### Sample
```plaintext
COUNTA(1, "", "text") => 2
COUNTA("one", "two", "three") => 3
```
------------
## COUNTALL
The COUNTALL function calculates the total number of arguments, both numeric and non-numeric.
#### Syntax
```plaintext
COUNTALL(value1, [value2, ...])
```
#### Sample
```plaintext
COUNTALL(1, "", "text") => 3
COUNTALL("one", "two", "three") => 3
```
------------
## EVEN
The EVEN function rounds positive values up to the nearest even number and negative values down to the nearest even number.
#### Syntax
```plaintext
EVEN(number)
```
#### Sample
```plaintext
EVEN(7) => 8
EVEN(-5) => -6
```
------------
## EXP
The EXP function returns 'e' raised to the power of a given number.
#### Syntax
```plaintext
EXP(number)
```
#### Sample
```plaintext
EXP(2) => 7.38905609893065
EXP(-1) => 0.36787944117144233
```
------------
## FLOOR
The FLOOR function rounds a number down to the nearest integer.
#### Syntax
```plaintext
FLOOR(number)
```
#### Sample
```plaintext
FLOOR(8.75) => 8
FLOOR(-15.25) => -16
```
------------
## INT
The INT function truncates the decimal part, returning the integer portion of a number.
#### Syntax
```plaintext
INT(number)
```
#### Sample
```plaintext
INT(8.75) => 8
INT(-15.25) => -15
```
------------
## LOG
The LOG function computes the logarithm of a number to a specified base. (default = e).
#### Syntax
```plaintext
LOG([base], number)
```
#### Sample
```plaintext
LOG(10, 100) => 2
LOG(2, 8) => 3
```
------------
## MAX
The MAX function identifies the highest value from a set of numbers.
#### Syntax
```plaintext
MAX(number1, [number2, ...])
```
#### Sample
```plaintext
MAX(5, 10, 3) => 10
MAX(-10, -5, -20) => -5
```
------------
## MIN
The MIN function identifies the lowest value from a set of numbers.
#### Syntax
```plaintext
MIN(number1, [number2, ...])
```
#### Sample
```plaintext
MIN(5, 10, 3) => 3
MIN(-10, -5, -20) => -20
```
------------
## MOD
The MOD function calculates the remainder when dividing (integer division) one number by another.
#### Syntax
```plaintext
MOD(number1, number2)
```
#### Sample
```plaintext
MOD(10, 3) => 1
MOD(-15, 4) => -3
```
------------
## ODD
The ODD function rounds positive values up to the nearest odd number and negative values down to the nearest odd number.
#### Syntax
```plaintext
ODD(number)
```
#### Sample
```plaintext
ODD(6) => 7
ODD(-5.5) => -7
```
------------
## POWER
The POWER function raises a given base to a specified exponent.
#### Syntax
```plaintext
POWER(base, exponent)
```
#### Sample
```plaintext
POWER(2, 3) => 8
POWER(10, -2) => 0.01
```
------------
## ROUND
The ROUND function is used to round a number to a specified number of decimal places (precision). Default value for precision is 0.
#### Syntax
```plaintext
ROUND(number, [precision])
```
#### Sample
```plaintext
ROUND(8.765, 2) => 8.77
ROUND(-15.123, 1) => -15.1
```
------------
## ROUNDDOWN
The ROUNDDOWN function rounds a number down to a specified number of decimal places (precision). Default value for precision is 0.
#### Syntax
```plaintext
ROUNDDOWN(number, [precision])
```
#### Sample
```plaintext
ROUNDDOWN(8.765, 2) => 8.76
ROUNDDOWN(-15.123, 1) => -15.2
```
------------
## ROUNDUP
The ROUNDUP function rounds a number up to a specified number of decimal places (precision). Default value for precision is 0.
#### Syntax
```plaintext
ROUNDUP(number, [precision])
```
#### Sample
```plaintext
ROUNDUP(8.765, 2) => 8.77
ROUNDUP(-15.123, 1) => -15.1
```
------------
## SQRT
The SQRT function calculates the square root of a given number.
#### Syntax
```plaintext
SQRT(number)
```
#### Sample
```plaintext
SQRT(25) => 5
SQRT(2) => 1.4142135623730951
```
------------
## VALUE
The VALUE function is used to extract the numeric value from a string (after handling `%` or `-` accordingly).
#### Syntax
```plaintext
VALUE(text)
```
#### Sample
```plaintext
VALUE("123$") => 123
VALUE("USD -45.67") => -45.67
```
------------
## Related Articles ## Related Articles
- [Numeric and Logical Operators](015.operators.md) - [Numeric and Logical Operators](015.operators.md)

228
packages/noco-docs/docs/070.fields/040.field-types/060.formula/030.string-functions.md

@ -5,28 +5,216 @@ tags: ['Fields', 'Field types', 'Formula']
keywords: ['Fields', 'Field types', 'Formula', 'Create formula field', 'String functions'] keywords: ['Fields', 'Field types', 'Formula', 'Create formula field', 'String functions']
--- ---
This cheat sheet provides a quick reference guide for various string based functions commonly used in data analysis and programming. Each function is accompanied by its syntax, a sample usage, and a brief description.
### String functions ## CONCAT
The CONCAT function concatenates one or more strings into a single string.
| Name | Syntax | Sample | Output |
|-------------|----------------------------------|-------------------------------------|---------------------------------------------------------------------------|
| **CONCAT** | `CONCAT(str1, [str2,...])` | `CONCAT({field1}, ' ', {field2})` | Concatenated string of input parameters |
| **LEFT** | `LEFT(str1, n)` | `LEFT({field}, 3)` | `n` characters from the beginning of input parameter |
| **LEN** | `LEN(str)` | `LEN({field})` | Input parameter character length |
| **LOWER** | `LOWER(str)` | `LOWER({field})` | Lower case converted string of input parameter |
| **MID** | `MID(str, position, [count])` | `MID({field}, 3, 2)` | Alias for `SUBSTR` |
| **REPEAT** | `REPEAT(str, count)` | `REPEAT({field}, 2)` | Specified copies of the input parameter string concatenated together |
| **REPLACE** | `REPLACE(str, srchStr, rplcStr)` | `REPLACE({field}, 'int', 'num')` | String, after replacing all occurrences of `srchStr` with `rplcStr` |
| **RIGHT** | `RIGHT(str, n)` | `RIGHT({field}, 3)` | `n` characters from the end of input parameter |
| **SEARCH** | `SEARCH(str, srchStr)` | `SEARCH({field}, 'str')` | Index of `srchStr` specified if found, 0 otherwise |
| **SUBSTR** | `SUBTR(str, position, [count])` | `SUBSTR({field}, 3, 2)` | Substring of length 'count' of input string, from the postition specified |
| **TRIM** | `TRIM(str)` | `TRIM({field})` | Remove trailing and leading whitespaces from input parameter |
| **UPPER** | `UPPER(str)` | `UPPER({field})` | Upper case converted string of input parameter |
| **URL** | `URL(str)` | `URL({field})` | Convert to a hyperlink if it is a valid URL |
| **REGEX_MATCH** | `REGEX_MATCH(str, pattern)` | `REGEX_MATCH({field}, 'a.*')` | Returns 1 if the input text matches a regular expression or 0 if it does not |
| **REGEX_EXTRACT** | `REGEX_EXTRACT(str, pattern)` | `REGEX_MATCH({field}, 'a.*')` | Returns the first match of a regular expression in a string |
| **REGEX_REPLACE** | `REGEX_REPLACE(str, pattern, replacer)` | `REGEX_MATCH({field}, 'a.*', '---')` | Replaces all matches of a regular expression in a string with a replacement string |
#### Syntax
```plaintext
CONCAT(text, [text,...])
```
#### Sample
```plaintext
CONCAT('John', ' ', 'Doe') => 'John Doe'
```
## LEFT
The LEFT function retrieves the first 'n' characters specified from the beginning of the input string.
#### Syntax
```plaintext
LEFT(text, count)
```
#### Sample
```plaintext
LEFT('123-456-7890', 3) => '123'
```
## LEN
The LEN function calculates and returns the total number of characters present in the provided string.
#### Syntax
```plaintext
LEN(text)
```
#### Sample
```plaintext
LEN('Product Description') => 19
```
## LOWER
The LOWER function transforms all characters in the input string to lowercase
#### Syntax
```plaintext
LOWER(text)
```
#### Sample
```plaintext
LOWER('User INPUT') => 'user input'
```
## MID
The MID function retrieves a substring from the input string starting at the specified position and extending for the specified count of characters.
#### Syntax
```plaintext
MID(text, position, [count])
```
#### Sample
```plaintext
MID('This is a sentence', 5, 3) => 'is '
```
## REGEX_EXTRACT
The REGEX_EXTRACT function searches the input string for the first occurrence of the specified regular expression pattern and returns the matched substring.
#### Syntax
```plaintext
REGEX_EXTRACT(text, pattern)
```
#### Sample
```plaintext
REGEX_EXTRACT('Error: Something went wrong', 'Error: (.*)') => 'Something went wrong'
```
## REGEX_MATCH
The REGEX_MATCH function evaluates whether the input string matches the specified regular expression pattern, returning 1 if there is a match and 0 if there is no match.
#### Syntax
```plaintext
REGEX_MATCH(text, pattern)
```
#### Sample
```plaintext
REGEX_MATCH('123-45-6789', '\d{3}-\d{2}-\d{4}') => 1
```
## REGEX_REPLACE
The REGEX_REPLACE function identifies all occurrences of the specified regular expression pattern in the input string and substitutes them with the provided replacement string.
#### Syntax
```plaintext
REGEX_REPLACE(text, pattern, replacer)
```
#### Sample
```plaintext
REGEX_REPLACE('Replace all bugs', 'bug', 'feature') => 'Replace all features'
```
## REPEAT
The REPEAT function duplicates the provided string the specified number of times, facilitating the creation of repeated patterns or sequences.
#### Syntax
```plaintext
REPEAT(text, count)
```
#### Sample
```plaintext
REPEAT('😃', 3) => '😃😃😃'
```
## REPLACE
The REPLACE function identifies all instances of a particular substring within the given string and substitutes them with another specified substring.
#### Syntax
```plaintext
REPLACE(text, srchStr, rplcStr)
```
#### Sample
```plaintext
REPLACE('Replace old text', 'old', 'new') => 'Replace new text'
```
## RIGHT
The RIGHT function retrieves the last 'n' characters from the end of the input string, allowing you to extract a substring starting from the right.
#### Syntax
```plaintext
RIGHT(text, n)
```
#### Sample
```plaintext
RIGHT('file_name.txt', 3) => 'txt'
```
## SEARCH
The SEARCH function identifies the position of the specified substring within the input string, returning the index if found, and 0 otherwise.
#### Syntax
```plaintext
SEARCH(text, srchStr)
```
#### Sample
```plaintext
SEARCH('user@example.com', '@') => 5
```
## SUBSTR
The SUBSTR function extracts a substring from the input string, starting at the specified position and optionally extending for the specified count of characters.
#### Syntax
```plaintext
SUBSTR(text, position, [count])
```
#### Sample
```plaintext
SUBSTR('Extract this text', 9, 4) => 'this'
```
## TRIM
The TRIM function eliminates any leading or trailing whitespaces from the input string.
#### Syntax
```plaintext
TRIM(text)
```
#### Sample
```plaintext
TRIM(' Trim this ') => 'Trim this'
```
## UPPER
The UPPER function transforms all characters in the input string to uppercase.
#### Syntax
```plaintext
UPPER(text)
```
#### Sample
```plaintext
UPPER('title') => 'TITLE'
```
## URL
The URL function checks if the input string is a valid URL and converts it into a hyperlink
#### Syntax
```plaintext
URL(text)
```
#### Sample
```plaintext
URL('https://www.example.com') => a clickable link for https://www.example.com
```
## Related Articles ## Related Articles

101
packages/noco-docs/docs/070.fields/040.field-types/060.formula/040.date-functions.md

@ -5,22 +5,95 @@ tags: ['Fields', 'Field types', 'Formula', 'Date & Time']
keywords: ['Fields', 'Field types', 'Formula', 'Date & Time', 'Create formula field', 'Date functions'] keywords: ['Fields', 'Field types', 'Formula', 'Date & Time', 'Create formula field', 'Date functions']
--- ---
This cheat sheet provides a quick reference guide for various date functions commonly used in data analysis and programming. Each function is accompanied by its syntax, a sample usage, and a brief description.
| Name | Syntax | Sample | Output | Remark | ## DATETIME_DIFF
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| The DATETIME_DIFF function calculates the difference between two dates in various units.
| **NOW** | `NOW()` | `NOW()` | 2022-05-19 17:20:43 | Returns the current time and day |
| | `IF(NOW() < {DATE_COL}`, "true", "false")` | `IF(NOW() < date, "true", "false")` | If current date is less than `{DATE_COL}`, it returns true. Otherwise, it returns false. | DateTime fields and negative values are supported. | #### Syntax
| **DATEADD** | `DATEADD(date \| datetime, value, ["day" \| "week" \| "month" \| "year"])` | `DATEADD(date, 1, 'day')` | Supposing `{DATE_COL}` is 2022-03-14. The result is 2022-03-15. | DateTime fields and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -1, 'day')` | ```plaintext
| | | `DATEADD(date, 1, 'week')` | Supposing `{DATE_COL}` is 2022-03-14 03:14. The result is 2022-03-21 03:14. | DateTime fields and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -1, 'week')` | DATETIME_DIFF(date1, date2, ["milliseconds" | "ms" | "seconds" | "s" | "minutes" | "m" | "hours" | "h" | "days" | "d" | "weeks" | "w" | "months" | "M" | "quarters" | "Q" | "years" | "y"])
| | | `DATEADD(date, 1, 'month')` | Supposing `{DATE_COL}` is 2022-03-14 03:14. The result is 2022-04-14 03:14. | DateTime fields and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -1, 'month')` | ```
| | | `DATEADD(date, 1, 'year')` | Supposing `{DATE_COL}` is 2022-03-14 03:14. The result is 2023-03-14 03:14. | DateTime fields and negative values are supported. Example: `DATEADD(DATE_TIME_COL, -1, 'year')` |
| | | `IF(NOW() < DATEADD(date,10,'day'), "true", "false")` | If the current date is less than `{DATE_COL}` plus 10 days, it returns true. Otherwise, it returns false. | DateTime fields and negative values are supported. | #### Sample
| | | `IF(NOW() < DATEADD(date,10,'day'), "true", "false")` | If the current date is less than `{DATE_COL}` plus 10 days, it returns true. Otherwise, it returns false. | DateTime fields and negative values are supported. | ```plaintext
| **DATETIME_DIFF** | `DATETIME_DIFF(date, date, ["milliseconds" \| "ms" \| "seconds" \| "s" \| "minutes" \| "m" \| "hours" \| "h" \| "days" \| "d" \| "weeks" \| "w" \| "months" \| "M" \| "quarters" \| "Q" \| "years" \| "y"])` | `DATETIME_DIFF("2022/10/14", "2022/10/15", "second")` | Supposing `{DATE_COL_1}` is 2017-08-25 and `{DATE_COL_2}` is 2011-08-25. The result is 86400. | Compares two dates and returns the difference in the unit specified. Positive integers indicate the second date being in the past compared to the first and vice versa for negative ones. | DATETIME_DIFF("2022/10/14", "2022/10/15", "seconds") => -86400
| | | `WEEKDAY(NOW(), "sunday")` | If today is Monday, it returns 1 | Get the week day of NOW() with the first day set as sunday | ```
| **WEEKDAY** | `WEEKDAY(date, [startDayOfWeek])` | `WEEKDAY(NOW())` | If today is Monday, it returns 0 | Returns the day of the week as an integer between 0 and 6 inclusive starting from Monday by default. You can optionally change the start day of the week by specifying in the second argument |
| | | `WEEKDAY(NOW(), "sunday")` | If today is Monday, it returns 1 | Get the week day of NOW() with the first day set as sunday | #### Remark
This function compares two dates and returns the difference in the specified unit. Positive integers indicate that second date is in the past compared to first, and vice versa for negative values.
---
## DATEADD
The DATEADD function adds a specified value to a date or datetime.
#### Syntax
```plaintext
DATEADD(date | datetime, value, ["day" | "week" | "month" | "year"])
```
#### Sample
```plaintext
DATEADD('2022-03-14', 1, 'day') => 2022-03-15
DATEADD('2022-03-14', 1, 'week') => 2022-03-21
DATEADD('2022-03-14', 1, 'month') => 2022-04-14
DATEADD('2022-03-14', 1, 'year') => 2023-03-14
```
#### Conditional Example
```plaintext
IF(NOW() < DATEADD(date, 10, 'day'), "true", "false") => If the current date is less than the specified date plus 10 days, it returns true. Otherwise, it returns false.
```
#### Remark
This function supports date and datetime fields and can handle negative values.
---
## NOW
The NOW function returns the current time and day.
#### Syntax
```plaintext
NOW()
```
#### Sample
```plaintext
NOW() => 2022-05-19 17:20:43 (current date & time)
```
#### Conditional Example
```plaintext
IF(NOW() < date, "true", "false") => If the current date is less than the specified date, it returns true. Otherwise, it returns false.
```
#### Remark
This function provides the current time and day, supporting datetime fields and negative values.
---
## WEEKDAY
The WEEKDAY function returns the day of the week as an integer.
#### Syntax
```plaintext
WEEKDAY(date, [startDayOfWeek])
```
#### Sample
```plaintext
WEEKDAY(NOW()) => If today is Monday, it returns 0.
WEEKDAY(NOW(), "sunday") => If today is Monday, it returns 1.
```
#### Remark
Returns the day of the week as an integer between 0 and 6 (inclusive), with Monday as the default start day. The start day of the week can be optionally changed by specifying it as the second argument.
---
## Related Articles ## Related Articles
- [Numeric and Logical Operators](015.operators.md) - [Numeric and Logical Operators](015.operators.md)

76
packages/noco-docs/docs/070.fields/040.field-types/060.formula/050.conditional-expressions.md

@ -1,19 +1,79 @@
--- ---
title: 'Conditional expressions' title: 'Conditional Expressions'
description: 'This article explains various conditional expressions that can be used in formula fields.' description: 'This article explains various conditional expressions that can be used in formula fields.'
tags: ['Fields', 'Field types', 'Formula'] tags: ['Fields', 'Field types', 'Formula']
keywords: ['Fields', 'Field types', 'Formula', 'Create formula field', 'Conditional expressions'] keywords: ['Fields', 'Field types', 'Formula', 'Create formula field', 'Conditional expressions']
--- ---
This cheat sheet provides a quick reference guide for various conditional expressions commonly used in data analysis and programming. Each expression is accompanied by its syntax, a sample usage, and a brief description.
### Conditional expressions ## IF
The IF function in programming and spreadsheet formulas provides a way to perform conditional operations. It evaluates a condition and returns a value if the condition is `TRUE`, or another value if the condition is `FALSE`.
| Name | Syntax | Sample | Output | #### Syntax
|------------|------------------------------------------------|------------------------------------------------|-------------------------------------------------------------| ```markdown
| **IF** | `IF(expr, successCase, elseCase)` | `IF({field} > 1, Value1, Value2)` | successCase if `expr` evaluates to TRUE, elseCase otherwise | IF(expr, successCase, elseCase)
| **SWITCH** | `SWITCH(expr, [pattern, value, ..., default])` | `SWITCH({field}, 1, 'One', 2, 'Two', '--')` | Switch case value based on `expr` output | ```
| **AND** | `AND(expr1, [expr2,...])` | `AND({field} > 2, {field} < 10)` | TRUE if all `expr` evaluate to TRUE |
| **OR** | `OR(expr1, [expr2,...])` | `OR({field} > 2, {field} < 10)` | TRUE if at least one `expr` evaluates to TRUE | #### Sample
```markdown
IF({field} > 1, Value1, Value2)
Output
- `Value1` if `{field} > 1` evaluates to TRUE
- `Value2` otherwise
```
## SWITCH
The SWITCH function is a versatile tool for handling multiple cases. It evaluates the given expression (expr) against a series of patterns and returns the corresponding value of the first matching pattern. If none match, it returns the default value.
#### Syntax
```markdown
SWITCH(expr, [pattern, value, ..., default])
```
#### Sample
```markdown
SWITCH({field}, 1, 'One', 2, 'Two', '--')
Output
Switch case value based on the output of `{field}`:
- `'One'` if `{field} = 1`
- `'Two'` if `{field} = 2`
- `'--'` for the default case
```
## AND
The AND function is a logical operator that returns TRUE only if all its conditions are true.
#### Syntax
```markdown
AND(expr1, [expr2,...])
```
#### Sample
```markdown
AND({field} > 2, {field} < 10)
Output
TRUE if both `{field} > 2` and `{field} < 10` evaluate to TRUE
```
## OR
The OR function is another logical operator, returning TRUE if at least one of its conditions is true.
#### Syntax
```markdown
OR(expr1, [expr2,...])
```
#### Sample
```markdown
OR({field} > 2, {field} < 10)
Output
TRUE if at least one of the conditions `{field} > 2` or `{field} < 10` evaluates to TRUE
```
:::tip :::tip
Logical operators, along with Numerical operators can be used to build conditional `expressions`. Logical operators, along with Numerical operators can be used to build conditional `expressions`.

12
packages/nocodb-sdk/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.202.8", "version": "0.202.9",
"description": "NocoDB SDK", "description": "NocoDB SDK",
"main": "build/main/index.js", "main": "build/main/index.js",
"typings": "build/main/index.d.ts", "typings": "build/main/index.d.ts",
@ -42,14 +42,14 @@
"jsep": "^1.3.8" "jsep": "^1.3.8"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.1.0", "@typescript-eslint/parser": "^6.13.1",
"cspell": "^4.2.8", "cspell": "^4.2.8",
"eslint": "^8.33.0", "eslint": "^8.54.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.10.0",
"eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-functional": "^5.0.8", "eslint-plugin-functional": "^5.0.8",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.29.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^2.8.8", "prettier": "^2.8.8",

2837
packages/nocodb-sdk/pnpm-lock.yaml

File diff suppressed because it is too large Load Diff

6
packages/nocodb/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb", "name": "nocodb",
"version": "0.202.8", "version": "0.202.9",
"description": "NocoDB Backend", "description": "NocoDB Backend",
"main": "dist/bundle.js", "main": "dist/bundle.js",
"author": { "author": {
@ -132,7 +132,7 @@
"mysql2": "^3.6.5", "mysql2": "^3.6.5",
"nanoid": "^3.1.20", "nanoid": "^3.1.20",
"nc-help": "0.3.1", "nc-help": "0.3.1",
"nc-lib-gui": "0.202.8", "nc-lib-gui": "0.202.9",
"nc-plugin": "^0.1.3", "nc-plugin": "^0.1.3",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nestjs-kafka": "^1.0.6", "nestjs-kafka": "^1.0.6",
@ -165,7 +165,7 @@
"socket.io": "^4.4.1", "socket.io": "^4.4.1",
"sql-query-identifier": "^2.5.0", "sql-query-identifier": "^2.5.0",
"sqlite3": "^5.1.6", "sqlite3": "^5.1.6",
"tedious": "^16.6.0", "tedious": "^16.6.1",
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
"twilio": "^3.55.1", "twilio": "^3.55.1",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",

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

@ -153,7 +153,13 @@ export class DataTableController {
@Param('columnId') columnId: string, @Param('columnId') columnId: string,
@Param('rowId') rowId: string, @Param('rowId') rowId: string,
@Body() @Body()
refRowIds: string | string[] | number | number[] | Record<string, any>, refRowIds:
| string
| string[]
| number
| number[]
| Record<string, any>
| Record<string, any>[],
) { ) {
return await this.dataTableService.nestedLink({ return await this.dataTableService.nestedLink({
modelId, modelId,

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

@ -4869,7 +4869,9 @@ class BaseModelSqlv2 {
); );
NcError.unprocessableEntity( NcError.unprocessableEntity(
`Child record with id [${missingIds.join(', ')}] not found`, `Child record with id [${extractIdsString(
missingIds,
)}] not found`,
); );
} }
@ -4878,8 +4880,11 @@ class BaseModelSqlv2 {
.filter((childRow) => !childRow[vChildCol.column_name]) .filter((childRow) => !childRow[vChildCol.column_name])
// generate insert data for new links // generate insert data for new links
.map((childRow) => ({ .map((childRow) => ({
[vParentCol.column_name]: childRow[parentColumn.column_name], [vParentCol.column_name]:
[vChildCol.column_name]: row[childColumn.column_name], childRow[parentColumn.title] ??
childRow[parentColumn.column_name],
[vChildCol.column_name]:
row[childColumn.title] ?? row[childColumn.column_name],
})); }));
// if no new links, return true // if no new links, return true
@ -4930,7 +4935,9 @@ class BaseModelSqlv2 {
); );
NcError.unprocessableEntity( NcError.unprocessableEntity(
`Child record with id [${missingIds.join(', ')}] not found`, `Child record with id [${extractIdsString(
missingIds,
)}] not found`,
); );
} }
} }
@ -4980,7 +4987,10 @@ class BaseModelSqlv2 {
if (!childRow) { if (!childRow) {
NcError.unprocessableEntity( NcError.unprocessableEntity(
`Child record with id [${childIds[0]}] not found`, `Child record with id [${extractIdsString(
childIds,
true,
)}] not found`,
); );
} }
} }
@ -5106,7 +5116,9 @@ class BaseModelSqlv2 {
); );
NcError.unprocessableEntity( NcError.unprocessableEntity(
`Child record with id [${missingIds.join(', ')}] not found`, `Child record with id [${extractIdsString(
missingIds,
)}] not found`,
); );
} }
} }
@ -5154,7 +5166,9 @@ class BaseModelSqlv2 {
); );
NcError.unprocessableEntity( NcError.unprocessableEntity(
`Child record with id [${missingIds.join(', ')}] not found`, `Child record with id [${extractIdsString(
missingIds,
)}] not found`,
); );
} }
} }
@ -5208,7 +5222,10 @@ class BaseModelSqlv2 {
if (!childRow) { if (!childRow) {
NcError.unprocessableEntity( NcError.unprocessableEntity(
`Child record with id [${childIds[0]}] not found`, `Child record with id [${extractIdsString(
childIds,
true,
)}] not found`,
); );
} }
} }
@ -5674,4 +5691,13 @@ export function getListArgs(
return obj; return obj;
} }
function extractIdsString(
childIds: (string | number | Record<string, any>)[],
isBt = false,
) {
return (isBt ? childIds.slice(0, 1) : childIds)
.map((r) => (typeof r === 'object' ? JSON.stringify(r) : r))
.join(', ');
}
export { BaseModelSqlv2 }; export { BaseModelSqlv2 };

9
packages/nocodb/src/db/sortV2.ts

@ -1,12 +1,7 @@
import { RelationTypes, UITypes } from 'nocodb-sdk'; import { UITypes } from 'nocodb-sdk';
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2'; import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2';
import type { Knex } from 'knex'; import type { Knex } from 'knex';
import type { import type { FormulaColumn, RollupColumn } from '~/models';
FormulaColumn,
LinkToAnotherRecordColumn,
LookupColumn,
RollupColumn,
} from '~/models';
import { NcError } from '~/helpers/catchError'; import { NcError } from '~/helpers/catchError';
import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2'; import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2';
import genRollupSelectv2 from '~/db/genRollupSelectv2'; import genRollupSelectv2 from '~/db/genRollupSelectv2';

2
packages/nocodb/src/helpers/getAst.ts

@ -68,7 +68,7 @@ const getAst = async ({
fields = Array.isArray(fields) ? fields : fields.split(','); fields = Array.isArray(fields) ? fields : fields.split(',');
if (throwErrorIfInvalidParams) { if (throwErrorIfInvalidParams) {
const colAliasMap = await model.getColAliasMapping(); const colAliasMap = await model.getColAliasMapping();
const aliasColMap = await model.getAliasColMapping(); const aliasColMap = await model.getAliasColObjMap();
const invalidFields = fields.filter( const invalidFields = fields.filter(
(f) => !colAliasMap[f] && !aliasColMap[f], (f) => !colAliasMap[f] && !aliasColMap[f],
); );

8
packages/nocodb/src/services/data-table.service.ts

@ -371,7 +371,13 @@ export class DataTableService {
modelId: string; modelId: string;
columnId: string; columnId: string;
query: any; query: any;
refRowIds: string | string[] | number | number[] | Record<string, any>; refRowIds:
| string
| string[]
| number
| number[]
| Record<string, any>
| Record<string, any>[];
rowId: string; rowId: string;
}) { }) {
this.validateIds(param.refRowIds); this.validateIds(param.refRowIds);

1144
pnpm-lock.yaml

File diff suppressed because it is too large Load Diff

2
scripts/pkg-executable/package.json

@ -28,7 +28,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"express": "^4.17.3", "express": "^4.17.3",
"nocodb": "0.202.8", "nocodb": "0.202.9",
"@nestjs/common": "^10.2.10", "@nestjs/common": "^10.2.10",
"@nestjs/core": "^10.2.10" "@nestjs/core": "^10.2.10"
} }

Loading…
Cancel
Save