Browse Source

Merge branch 'new-docs' into patch-1

pull/8078/head
Anbarasu 8 months ago committed by GitHub
parent
commit
4fb8521483
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .github/workflows/sync-to-develop.yml
  2. 230
      packages/nc-gui/components/cell/DatePicker.vue
  3. 253
      packages/nc-gui/components/cell/DateTimePicker.vue
  4. 148
      packages/nc-gui/components/cell/TimePicker.vue
  5. 14
      packages/nc-gui/components/cell/Url.vue
  6. 155
      packages/nc-gui/components/cell/YearPicker.vue
  7. 100
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  8. 128
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  9. 3
      packages/nc-gui/components/dlg/TableRename.vue
  10. 9
      packages/nc-gui/components/project/View.vue
  11. 14
      packages/nc-gui/components/smartsheet/Cell.vue
  12. 2
      packages/nc-gui/components/smartsheet/Topbar.vue
  13. 2
      packages/nc-gui/components/smartsheet/grid/Table.vue
  14. 2
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  15. 2
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  16. 6
      packages/nc-gui/components/smartsheet/topbar/SelectMode.vue
  17. 6
      packages/nc-gui/components/virtual-cell/components/UnLinkedItems.vue
  18. 7
      packages/nc-gui/composables/useLTARStore.ts
  19. 52
      packages/nc-gui/composables/useSharedFormViewStore.ts
  20. 44
      packages/nc-gui/helpers/parsers/parserHelpers.ts
  21. 2
      packages/nc-gui/lang/ar.json
  22. 2
      packages/nc-gui/lang/bn_IN.json
  23. 2
      packages/nc-gui/lang/cs.json
  24. 2
      packages/nc-gui/lang/da.json
  25. 2
      packages/nc-gui/lang/de.json
  26. 2
      packages/nc-gui/lang/en.json
  27. 2
      packages/nc-gui/lang/es.json
  28. 2
      packages/nc-gui/lang/eu.json
  29. 2
      packages/nc-gui/lang/fa.json
  30. 2
      packages/nc-gui/lang/fi.json
  31. 2
      packages/nc-gui/lang/fr.json
  32. 2
      packages/nc-gui/lang/he.json
  33. 2
      packages/nc-gui/lang/hi.json
  34. 2
      packages/nc-gui/lang/hr.json
  35. 2
      packages/nc-gui/lang/id.json
  36. 2
      packages/nc-gui/lang/it.json
  37. 2
      packages/nc-gui/lang/ja.json
  38. 2
      packages/nc-gui/lang/ko.json
  39. 2
      packages/nc-gui/lang/lv.json
  40. 2
      packages/nc-gui/lang/nl.json
  41. 2
      packages/nc-gui/lang/no.json
  42. 12
      packages/nc-gui/lang/pl.json
  43. 2
      packages/nc-gui/lang/pt.json
  44. 2
      packages/nc-gui/lang/pt_BR.json
  45. 58
      packages/nc-gui/lang/ru.json
  46. 2
      packages/nc-gui/lang/sk.json
  47. 2
      packages/nc-gui/lang/sl.json
  48. 2
      packages/nc-gui/lang/sv.json
  49. 2
      packages/nc-gui/lang/th.json
  50. 2
      packages/nc-gui/lang/tr.json
  51. 2
      packages/nc-gui/lang/uk.json
  52. 2
      packages/nc-gui/lang/vi.json
  53. 2
      packages/nc-gui/lang/zh-Hans.json
  54. 2
      packages/nc-gui/lang/zh-Hant.json
  55. 28
      packages/nc-gui/package.json
  56. 11
      packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index/survey.vue
  57. 15
      packages/nc-gui/store/tables.ts
  58. 42
      packages/nc-gui/store/views.ts
  59. 2
      packages/nc-lib-gui/package.json
  60. 2
      packages/nocodb-sdk/package.json
  61. 8
      packages/nocodb/package.json
  62. 7
      packages/nocodb/src/models/CalendarViewColumn.ts
  63. 85
      packages/nocodb/src/models/Column.ts
  64. 4
      packages/nocodb/src/models/FormViewColumn.ts
  65. 7
      packages/nocodb/src/models/GalleryViewColumn.ts
  66. 2
      packages/nocodb/src/models/GridViewColumn.ts
  67. 4
      packages/nocodb/src/models/KanbanViewColumn.ts
  68. 4
      packages/nocodb/src/models/MapViewColumn.ts
  69. 20
      packages/nocodb/src/models/Model.ts
  70. 21
      packages/nocodb/src/models/UserRefreshToken.ts
  71. 3
      packages/nocodb/src/models/View.ts
  72. 4
      packages/nocodb/src/services/public-metas.service.ts
  73. 16
      packages/nocodb/src/services/users/users.service.ts
  74. 4
      packages/nocodb/src/services/views.service.ts
  75. 378
      pnpm-lock.yaml
  76. 458
      scripts/docs/fr/020.getting-started/050.self-hosted/fr-010.installation.md
  77. 76
      scripts/docs/fr/020.getting-started/050.self-hosted/fr-020.environment-variables.md
  78. 118
      scripts/docs/fr/020.getting-started/fr-040.keyboard-shortcuts.md
  79. 39
      scripts/docs/fr/030.workspaces/fr-020.create-workspace.md
  80. 62
      scripts/docs/fr/030.workspaces/fr-030.workspace-collaboration.md
  81. 39
      scripts/docs/fr/040.bases/fr-020.create-base.md
  82. 108
      scripts/docs/fr/040.bases/fr-040.import-base-from-airtable.md
  83. 47
      scripts/docs/fr/060.table-operations/fr-020.field-operations.md
  84. 50
      scripts/docs/fr/060.table-operations/fr-050.group-by.md
  85. 35
      scripts/docs/fr/060.table-operations/fr-060.row-height.md
  86. 33
      scripts/docs/fr/070.fields/040.field-types/010.text-based/fr-010.single-line-text.md
  87. 36
      scripts/docs/fr/070.fields/040.field-types/010.text-based/fr-040.phonenumber.md
  88. 39
      scripts/docs/fr/070.fields/040.field-types/010.text-based/fr-050.url.md
  89. 63
      scripts/docs/fr/070.fields/040.field-types/030.select-based/fr-010.single-select.md
  90. 21
      scripts/docs/fr/070.fields/040.field-types/050.custom-types/fr-080.json.md
  91. 272
      scripts/docs/fr/070.fields/040.field-types/060.formula/fr-030.string-functions.md
  92. 121
      scripts/docs/fr/070.fields/040.field-types/060.formula/fr-040.date-functions.md
  93. 114
      scripts/docs/fr/070.fields/040.field-types/060.formula/fr-050.conditional-expressions.md
  94. 96
      scripts/docs/fr/070.fields/040.field-types/070.date-time-based/fr-010.date-time.md
  95. 34
      scripts/docs/fr/070.fields/040.field-types/070.date-time-based/fr-050.created-time.md
  96. 34
      scripts/docs/fr/070.fields/040.field-types/080.user-based/fr-010.user.md
  97. 35
      scripts/docs/fr/070.fields/040.field-types/080.user-based/fr-020.created-by.md
  98. 36
      scripts/docs/fr/070.fields/040.field-types/080.user-based/fr-030.last-modified-by.md
  99. 40
      scripts/docs/fr/070.fields/fr-030.display-value.md
  100. 42
      scripts/docs/fr/090.views/040.view-types/fr-020.gallery.md
  101. Some files were not shown because too many files have changed in this diff Show More

5
.github/workflows/sync-to-develop.yml

@ -13,6 +13,10 @@ jobs:
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18.19.1 node-version: 18.19.1
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
@ -39,6 +43,7 @@ jobs:
git config user.name 'github-actions[bot]' git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com' git config user.email 'github-actions[bot]@users.noreply.github.com'
revertSDK=true node scripts/upgradeNocodbSdk.js revertSDK=true node scripts/upgradeNocodbSdk.js
pnpm bootstrap
git add . git add .
git diff-index --quiet HEAD || git commit -m "chore: update sdk path" git diff-index --quiet HEAD || git commit -m "chore: update sdk path"
git push origin $BRANCH_NAME git push origin $BRANCH_NAME

230
packages/nc-gui/components/cell/DatePicker.vue

@ -1,17 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { isDateMonthFormat } from 'nocodb-sdk' import { isDateMonthFormat, isSystemColumn } from 'nocodb-sdk'
import { import {
ActiveCellInj, ActiveCellInj,
CellClickHookInj, CellClickHookInj,
ColumnInj, ColumnInj,
EditColumnInj, EditColumnInj,
EditModeInj, EditModeInj,
IsSurveyFormInj,
ReadonlyInj, ReadonlyInj,
computed, computed,
inject, inject,
isDrawerOrModalExist,
onClickOutside, onClickOutside,
onMounted, onMounted,
onUnmounted, onUnmounted,
@ -19,7 +17,6 @@ import {
ref, ref,
useGlobal, useGlobal,
useI18n, useI18n,
useSelectedCellKeyupListener,
watch, watch,
} from '#imports' } from '#imports'
@ -34,7 +31,7 @@ const emit = defineEmits(['update:modelValue'])
const { t } = useI18n() const { t } = useI18n()
const { showNull } = useGlobal() const { showNull, isMobileMode } = useGlobal()
const columnMeta = inject(ColumnInj, null)! const columnMeta = inject(ColumnInj, null)!
@ -46,19 +43,27 @@ const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false)) const editable = inject(EditModeInj, ref(false))
const isGrid = inject(IsGridInj, ref(false))
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false)) const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
const isDateInvalid = ref(false) const isDateInvalid = ref(false)
const datePickerRef = ref<HTMLInputElement>()
const dateFormat = computed(() => parseProp(columnMeta?.value?.meta)?.date_format ?? 'YYYY-MM-DD') const dateFormat = computed(() => parseProp(columnMeta?.value?.meta)?.date_format ?? 'YYYY-MM-DD')
const picker = computed(() => (isDateMonthFormat(dateFormat.value) ? 'month' : '')) const picker = computed(() => (isDateMonthFormat(dateFormat.value) ? 'month' : ''))
const isClearedInputMode = ref<boolean>(false)
const open = ref<boolean>(false)
const localState = computed({ const localState = computed({
get() { get() {
if (!modelValue) { if (!modelValue || isClearedInputMode.value) {
return undefined return undefined
} }
@ -72,6 +77,8 @@ const localState = computed({
return dayjs(/^\d+$/.test(modelValue) ? +modelValue : modelValue, format) return dayjs(/^\d+$/.test(modelValue) ? +modelValue : modelValue, format)
}, },
set(val?: dayjs.Dayjs) { set(val?: dayjs.Dayjs) {
isClearedInputMode.value = false
if (!val) { if (!val) {
emit('update:modelValue', null) emit('update:modelValue', null)
return return
@ -85,25 +92,56 @@ const localState = computed({
if (val.isValid()) { if (val.isValid()) {
emit('update:modelValue', val?.format('YYYY-MM-DD')) emit('update:modelValue', val?.format('YYYY-MM-DD'))
} }
open.value = false
}, },
}) })
const open = ref<boolean>(false)
const randomClass = `picker_${Math.floor(Math.random() * 99999)}` const randomClass = `picker_${Math.floor(Math.random() * 99999)}`
onClickOutside(datePickerRef, (e) => {
if ((e.target as HTMLElement)?.closest(`.${randomClass}`)) return
datePickerRef.value?.blur?.()
open.value = false
})
const onBlur = (e) => {
if ((e?.relatedTarget as HTMLElement)?.closest(`.${randomClass}`)) return
open.value = false
}
watch( watch(
open, open,
(next) => { (next) => {
if (next) { if (next) {
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) editable.value = true
datePickerRef.value?.focus?.()
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, (e) => {
if ((e?.target as HTMLElement)?.closest(`.nc-${randomClass}`)) {
return
}
open.value = false
})
} else { } else {
editable.value = false isClearedInputMode.value = false
} }
}, },
{ flush: 'post' }, { flush: 'post' },
) )
watch(editable, (nextValue) => {
if (isGrid.value && nextValue && !open.value) {
open.value = true
}
})
const placeholder = computed(() => { const placeholder = computed(() => {
if (isForm.value && !isDateInvalid.value) { if (
((isForm.value || isExpandedForm.value) && !isDateInvalid.value) ||
(isGrid.value && !showNull.value && !isDateInvalid.value && !isSystemColumn(columnMeta.value) && active.value)
) {
return dateFormat.value return dateFormat.value
} else if (isEditColumn.value && (modelValue === '' || modelValue === null)) { } else if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return t('labels.optional') return t('labels.optional')
@ -116,113 +154,18 @@ const placeholder = computed(() => {
} }
}) })
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) {
case 'Enter':
e.stopPropagation()
// skip if drawer / modal is active
if (isDrawerOrModalExist()) {
return
}
if (!open.value) {
// open date picker
open.value = true
} else {
// select the current day
const el = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected') as HTMLButtonElement
if (el) {
el.click()
open.value = false
}
}
break
case 'Escape':
// skip if drawer / modal is active
if (isDrawerOrModalExist()) {
return
}
if (open.value) {
e.stopPropagation()
open.value = false
}
break
case 'ArrowLeft':
if (!localState.value) {
;(document.querySelector('.nc-picker-date.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click()
} else {
const prevEl = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected')
?.previousElementSibling as HTMLButtonElement
if (prevEl) {
prevEl.click()
} else {
// get the last td from previous tr
const prevRowLastEl = document
.querySelector('.nc-picker-date.active .ant-picker-cell-selected')
?.closest('tr')
?.previousElementSibling?.querySelector('td:last-child') as HTMLButtonElement
if (prevRowLastEl) {
prevRowLastEl.click()
} else {
// go to the previous month
;(document.querySelector('.nc-picker-date.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click()
}
}
}
break
case 'ArrowRight':
if (!localState.value) {
;(document.querySelector('.nc-picker-date.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click()
} else {
const nextEl = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected')
?.nextElementSibling as HTMLButtonElement
if (nextEl) {
nextEl.click()
} else {
// get the last td from previous tr
const nextRowFirstEl = document
.querySelector('.nc-picker-date.active .ant-picker-cell-selected')
?.closest('tr')
?.nextElementSibling?.querySelector('td:first-child') as HTMLButtonElement
if (nextRowFirstEl) {
nextRowFirstEl.click()
} else {
// go to the next month
;(document.querySelector('.nc-picker-date.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click()
}
}
}
break
case 'ArrowUp':
if (!localState.value)
(document.querySelector('.nc-picker-date.active .ant-picker-header-super-prev-btn') as HTMLButtonElement)?.click()
break
case 'ArrowDown':
if (!localState.value)
(document.querySelector('.nc-picker-date.active .ant-picker-header-super-next-btn') as HTMLButtonElement)?.click()
break
case ';':
localState.value = dayjs(new Date())
break
}
})
const isOpen = computed(() => { const isOpen = computed(() => {
if (readOnly.value) return false if (readOnly.value) return false
return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
}) })
// use the default date picker open sync only to close the picker
const updateOpen = (next: boolean) => {
if (open.value && !next) {
open.value = false
}
}
const cellClickHook = inject(CellClickHookInj, null) const cellClickHook = inject(CellClickHookInj, null)
const cellClickHandler = () => { const cellClickHandler = () => {
open.value = (active.value || editable.value) && !open.value if (readOnly.value || open.value) return
open.value = active.value || editable.value
} }
onMounted(() => { onMounted(() => {
@ -241,43 +184,88 @@ const clickHandler = () => {
} }
const handleKeydown = (e: KeyboardEvent) => { const handleKeydown = (e: KeyboardEvent) => {
if (e.key !== 'Enter') {
e.stopPropagation()
}
switch (e.key) { switch (e.key) {
case ' ': case 'Enter':
if (isSurveyForm.value) { open.value = !open.value
open.value = !open.value if (!open.value) {
editable.value = false
if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) {
datePickerRef.value?.blur?.()
}
} }
break return
case 'Escape':
if (open.value) {
open.value = false
editable.value = false
if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) {
datePickerRef.value?.blur?.()
}
} else {
editable.value = false
case 'Enter': datePickerRef.value?.blur?.()
if (!isSurveyForm.value) { }
open.value = !open.value
return
default:
if (!open.value && /^[0-9a-z]$/i.test(e.key)) {
open.value = true
} }
break
} }
} }
useEventListener(document, 'keydown', (e: KeyboardEvent) => {
// To prevent event listener on non active cell
if (!active.value) return
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey || !isGrid.value || isExpandedForm.value || isEditColumn.value) return
switch (e.key) {
case ';':
localState.value = dayjs(new Date())
e.preventDefault()
break
default:
if (!isOpen.value && datePickerRef.value && /^[0-9a-z]$/i.test(e.key)) {
isClearedInputMode.value = true
datePickerRef.value.focus()
editable.value = true
open.value = true
}
}
})
</script> </script>
<template> <template>
<a-date-picker <a-date-picker
ref="datePickerRef"
v-model:value="localState" v-model:value="localState"
:disabled="readOnly" :disabled="readOnly"
:picker="picker" :picker="picker"
:tabindex="0" :tabindex="0"
:bordered="false" :bordered="false"
class="nc-cell-field !w-full !py-1 !border-none !text-current" class="nc-cell-field !w-full !py-1 !border-none !text-current"
:class="{ 'nc-null': modelValue === null && showNull }" :class="[`nc-${randomClass}`, { 'nc-null': modelValue === null && showNull }]"
:format="dateFormat" :format="dateFormat"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !isEditColumn"
:input-read-only="true" :input-read-only="!!isMobileMode"
:dropdown-class-name="`${randomClass} nc-picker-date children:border-1 children:border-gray-200 ${open ? 'active' : ''} `" :dropdown-class-name="`${randomClass} nc-picker-date children:border-1 children:border-gray-200 ${open ? 'active' : ''} `"
:open="isOpen" :open="isOpen"
@blur="onBlur"
@click="clickHandler" @click="clickHandler"
@update:open="updateOpen"
@keydown="handleKeydown" @keydown="handleKeydown"
@mouseup.stop
@mousedown.stop
> >
<template #suffixIcon></template> <template #suffixIcon></template>
</a-date-picker> </a-date-picker>
<div v-if="!editable && isGrid" class="absolute inset-0 z-90 cursor-pointer"></div>
</template> </template>
<style scoped> <style scoped>

253
packages/nc-gui/components/cell/DateTimePicker.vue

@ -7,14 +7,11 @@ import {
ColumnInj, ColumnInj,
EditColumnInj, EditColumnInj,
IsFormInj, IsFormInj,
IsSurveyFormInj,
ReadonlyInj, ReadonlyInj,
inject, inject,
isDrawerOrModalExist,
parseProp, parseProp,
ref, ref,
useBase, useBase,
useSelectedCellKeyupListener,
watch, watch,
} from '#imports' } from '#imports'
@ -29,7 +26,7 @@ const emit = defineEmits(['update:modelValue'])
const { isMssql, isXcdbBase } = useBase() const { isMssql, isXcdbBase } = useBase()
const { showNull } = useGlobal() const { showNull, isMobileMode } = useGlobal()
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
@ -37,18 +34,22 @@ const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false)) const editable = inject(EditModeInj, ref(false))
const isForm = inject(IsFormInj, ref(false)) const isGrid = inject(IsGridInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const { t } = useI18n() const { t } = useI18n()
const isEditColumn = inject(EditColumnInj, ref(false)) const isEditColumn = inject(EditColumnInj, ref(false))
const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
const isDateInvalid = ref(false) const isDateInvalid = ref(false)
const datePickerRef = ref<HTMLInputElement>()
const dateTimeFormat = computed(() => { const dateTimeFormat = computed(() => {
const dateFormat = parseProp(column?.value?.meta)?.date_format ?? dateFormats[0] const dateFormat = parseProp(column?.value?.meta)?.date_format ?? dateFormats[0]
const timeFormat = parseProp(column?.value?.meta)?.time_format ?? timeFormats[0] const timeFormat = parseProp(column?.value?.meta)?.time_format ?? timeFormats[0]
@ -57,14 +58,13 @@ const dateTimeFormat = computed(() => {
let localModelValue = modelValue ? dayjs(modelValue).utc().local() : undefined let localModelValue = modelValue ? dayjs(modelValue).utc().local() : undefined
const tempLocalValue = ref<dayjs.Dayjs>() const isClearedInputMode = ref<boolean>(false)
const open = ref(false)
const localState = computed({ const localState = computed({
get() { get() {
if (!modelValue && tempLocalValue.value) { if (!modelValue || isClearedInputMode.value) {
return tempLocalValue.value
}
if (!modelValue) {
return undefined return undefined
} }
@ -114,8 +114,10 @@ const localState = computed({
return dayjs(modelValue).utc().local() return dayjs(modelValue).utc().local()
}, },
set(val?: dayjs.Dayjs) { set(val?: dayjs.Dayjs) {
isClearedInputMode.value = false
if (!val) { if (!val) {
emit('update:modelValue', null) emit('update:modelValue', null)
return return
} }
@ -128,8 +130,6 @@ const localState = computed({
}, },
}) })
const open = ref(false)
const isOpen = computed(() => { const isOpen = computed(() => {
if (readOnly.value) return false if (readOnly.value) return false
@ -137,27 +137,44 @@ const isOpen = computed(() => {
}) })
const randomClass = `picker_${Math.floor(Math.random() * 99999)}` const randomClass = `picker_${Math.floor(Math.random() * 99999)}`
onClickOutside(datePickerRef, (e) => {
if ((e.target as HTMLElement)?.closest(`.${randomClass}`)) return
datePickerRef.value?.blur?.()
open.value = false
})
const onBlur = (e) => {
if ((e?.relatedTarget as HTMLElement)?.closest(`.${randomClass}`)) return
open.value = false
}
watch( watch(
open, open,
(next) => { (next) => {
if (next) { if (next) {
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) editable.value = true
datePickerRef.value?.focus?.()
if (!modelValue) { onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, (e) => {
tempLocalValue.value = dayjs(new Date()).utc().local() if ((e?.target as HTMLElement)?.closest(`.nc-${randomClass}`)) {
} else { return
tempLocalValue.value = undefined }
} open.value = false
})
} else { } else {
editable.value = false isClearedInputMode.value = false
tempLocalValue.value = undefined
} }
}, },
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => { const placeholder = computed(() => {
if (isForm.value && !isDateInvalid.value) { if (
((isForm.value || isExpandedForm.value) && !isDateInvalid.value) ||
(isGrid.value && !showNull.value && !isDateInvalid.value && !isSystemColumn(column.value) && active.value)
) {
return dateTimeFormat.value return dateTimeFormat.value
} else if (isEditColumn.value && (modelValue === '' || modelValue === null)) { } else if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return t('labels.optional') return t('labels.optional')
@ -170,105 +187,18 @@ const placeholder = computed(() => {
} }
}) })
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) {
case 'Enter':
e.stopPropagation()
// skip if drawer / modal is active
if (isDrawerOrModalExist()) {
return
}
if (!open.value) {
// open date picker
open.value = true
} else {
// click Ok button to save the currently selected date
;(document.querySelector('.nc-picker-datetime.active .ant-picker-ok button') as HTMLButtonElement)?.click()
}
break
case 'Escape':
// skip if drawer / modal is active
if (isDrawerOrModalExist()) {
return
}
if (open.value) {
e.stopPropagation()
open.value = false
}
break
case 'ArrowLeft':
if (!localState.value) {
;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click()
} else {
const prevEl = document.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected')
?.previousElementSibling as HTMLButtonElement
if (prevEl) {
prevEl.click()
} else {
// get the last td from previous tr
const prevRowLastEl = document
.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected')
?.closest('tr')
?.previousElementSibling?.querySelector('td:last-child') as HTMLButtonElement
if (prevRowLastEl) {
prevRowLastEl.click()
} else {
// go to the previous month
;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click()
}
}
}
break
case 'ArrowRight':
if (!localState.value) {
;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click()
} else {
const nextEl = document.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected')
?.nextElementSibling as HTMLButtonElement
if (nextEl) {
nextEl.click()
} else {
// get the last td from previous tr
const nextRowFirstEl = document
.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected')
?.closest('tr')
?.nextElementSibling?.querySelector('td:first-child') as HTMLButtonElement
if (nextRowFirstEl) {
nextRowFirstEl.click()
} else {
// go to the next month
;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click()
}
}
}
break
case 'ArrowUp':
if (!localState.value)
(document.querySelector('.nc-picker-datetime.active .ant-picker-header-super-prev-btn') as HTMLButtonElement)?.click()
break
case 'ArrowDown':
if (!localState.value)
(document.querySelector('.nc-picker-datetime.active .ant-picker-header-super-next-btn') as HTMLButtonElement)?.click()
break
case ';':
localState.value = dayjs(new Date())
break
}
})
const cellClickHook = inject(CellClickHookInj, null) const cellClickHook = inject(CellClickHookInj, null)
const cellClickHandler = () => { const cellClickHandler = () => {
if (readOnly.value) return if (readOnly.value || open.value) return
open.value = (active.value || editable.value) && !open.value open.value = active.value || editable.value
} }
function okHandler(val: dayjs.Dayjs | string) { function okHandler(val: dayjs.Dayjs | string) {
isClearedInputMode.value = false
if (!val) { if (!val) {
emit('update:modelValue', null) emit('update:modelValue', null)
return } else if (dayjs(val).isValid()) {
}
if (dayjs(val).isValid()) {
// setting localModelValue to cater NOW function in date picker // setting localModelValue to cater NOW function in date picker
localModelValue = dayjs(val) localModelValue = dayjs(val)
// send the payload in UTC format // send the payload in UTC format
@ -276,7 +206,12 @@ function okHandler(val: dayjs.Dayjs | string) {
} }
open.value = !open.value open.value = !open.value
if (!open.value && isGrid.value && !isExpandedForm.value && !isEditColumn.value) {
datePickerRef.value?.blur?.()
editable.value = false
}
} }
onMounted(() => { onMounted(() => {
cellClickHook?.on(cellClickHandler) cellClickHook?.on(cellClickHandler)
}) })
@ -284,7 +219,14 @@ onUnmounted(() => {
cellClickHook?.on(cellClickHandler) cellClickHook?.on(cellClickHandler)
}) })
const clickHandler = () => { const clickHandler = (e) => {
if ((e.target as HTMLElement).closest(`.nc-${randomClass} .ant-picker-clear`)) {
e.stopPropagation()
emit('update:modelValue', null)
open.value = false
return
}
if (cellClickHook) { if (cellClickHook) {
return return
} }
@ -296,42 +238,99 @@ const isColDisabled = computed(() => {
}) })
const handleKeydown = (e: KeyboardEvent) => { const handleKeydown = (e: KeyboardEvent) => {
if (e.key !== 'Enter') {
e.stopPropagation()
}
switch (e.key) { switch (e.key) {
case ' ': case 'Enter':
if (isSurveyForm.value) { if (isOpen.value) {
open.value = !open.value return okHandler((e.target as HTMLInputElement).value)
} else {
open.value = true
} }
break return
case 'Escape':
if (open.value) {
open.value = false
editable.value = false
if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) {
datePickerRef.value?.blur?.()
}
} else {
editable.value = false
case 'Enter': datePickerRef.value?.blur?.()
if (!isSurveyForm.value) { }
open.value = !open.value
return
case 'Tab':
open.value = false
if (isGrid.value) {
editable.value = false
datePickerRef.value?.blur()
}
return
default:
if (!open.value && /^[0-9a-z]$/i.test(e.key)) {
open.value = true
} }
break
} }
} }
useEventListener(document, 'keydown', (e: KeyboardEvent) => {
// To prevent event listener on non active cell
if (!active.value) return
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey || !isGrid.value || isExpandedForm.value || isEditColumn.value) return
switch (e.key) {
case ';':
localState.value = dayjs(new Date())
e.preventDefault()
break
default:
if (!isOpen.value && datePickerRef.value && /^[0-9a-z]$/i.test(e.key)) {
isClearedInputMode.value = true
datePickerRef.value.focus()
editable.value = true
open.value = true
}
}
})
watch(editable, (nextValue) => {
if (isGrid.value && nextValue && !open.value) {
open.value = true
}
})
</script> </script>
<template> <template>
<a-date-picker <a-date-picker
ref="datePickerRef"
:value="localState" :value="localState"
:disabled="isColDisabled" :disabled="isColDisabled"
:show-time="true" :show-time="true"
:bordered="false" :bordered="false"
class="nc-cell-field !w-full !py-1 !border-none !text-current" class="nc-cell-field nc-cell-picker-datetime !w-full !py-1 !border-none !text-current"
:class="{ 'nc-null': modelValue === null && showNull }" :class="[`nc-${randomClass}`, { 'nc-null': modelValue === null && showNull }]"
:format="dateTimeFormat" :format="dateTimeFormat"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!isColDisabled && !isEditColumn"
:input-read-only="true" :input-read-only="!!isMobileMode"
:dropdown-class-name="`${randomClass} nc-picker-datetime children:border-1 children:border-gray-200 ${open ? 'active' : ''}`" :dropdown-class-name="`${randomClass} nc-picker-datetime children:border-1 children:border-gray-200 ${open ? 'active' : ''}`"
:open="isOpen" :open="isOpen"
@blur="onBlur"
@click="clickHandler" @click="clickHandler"
@ok="okHandler" @ok="okHandler"
@keydown="handleKeydown" @keydown="handleKeydown"
@mouseup.stop
@mousedown.stop
> >
<template #suffixIcon></template> <template #suffixIcon></template>
</a-date-picker> </a-date-picker>
<div v-if="!editable && isGrid" class="absolute inset-0 z-90 cursor-pointer"></div>
</template> </template>
<style scoped> <style scoped>

148
packages/nc-gui/components/cell/TimePicker.vue

@ -1,17 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import { isSystemColumn } from 'nocodb-sdk'
ActiveCellInj, import { ActiveCellInj, EditColumnInj, IsFormInj, ReadonlyInj, inject, onClickOutside, useBase, watch } from '#imports'
EditColumnInj,
IsFormInj,
IsSurveyFormInj,
ReadonlyInj,
inject,
onClickOutside,
useBase,
useSelectedCellKeyupListener,
watch,
} from '#imports'
interface Props { interface Props {
modelValue?: string | null | undefined modelValue?: string | null | undefined
@ -24,7 +14,7 @@ const emit = defineEmits(['update:modelValue'])
const { isMysql } = useBase() const { isMysql } = useBase()
const { showNull } = useGlobal() const { showNull, isMobileMode } = useGlobal()
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
@ -34,21 +24,29 @@ const editable = inject(EditModeInj, ref(false))
const isEditColumn = inject(EditColumnInj, ref(false)) const isEditColumn = inject(EditColumnInj, ref(false))
const isGrid = inject(IsGridInj, ref(false))
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false)) const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
const dateFormat = isMysql(column.value.source_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'
const isTimeInvalid = ref(false) const isTimeInvalid = ref(false)
const dateFormat = isMysql(column.value.source_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ' const datePickerRef = ref<HTMLInputElement>()
const isClearedInputMode = ref<boolean>(false)
const { t } = useI18n() const { t } = useI18n()
const open = ref(false)
const localState = computed({ const localState = computed({
get() { get() {
if (!modelValue) { if (!modelValue || isClearedInputMode.value) {
return undefined return undefined
} }
let dateTime = dayjs(modelValue) let dateTime = dayjs(modelValue)
@ -67,6 +65,7 @@ const localState = computed({
return dateTime return dateTime
}, },
set(val?: dayjs.Dayjs) { set(val?: dayjs.Dayjs) {
isClearedInputMode.value = false
if (!val) { if (!val) {
emit('update:modelValue', null) emit('update:modelValue', null)
return return
@ -80,23 +79,51 @@ const localState = computed({
}, },
}) })
const open = ref(false)
const randomClass = `picker_${Math.floor(Math.random() * 99999)}` const randomClass = `picker_${Math.floor(Math.random() * 99999)}`
onClickOutside(datePickerRef, (e) => {
if ((e.target as HTMLElement)?.closest(`.${randomClass}`)) return
datePickerRef.value?.blur?.()
open.value = false
})
const onBlur = (e) => {
if ((e?.relatedTarget as HTMLElement)?.closest(`.${randomClass}`)) return
open.value = false
}
watch( watch(
open, open,
(next) => { (next) => {
if (next) { if (next) {
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) editable.value = true
datePickerRef.value?.focus?.()
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, (e) => {
if ((e?.target as HTMLElement)?.closest(`.nc-${randomClass}`)) {
return
}
open.value = false
})
} else { } else {
editable.value = false isClearedInputMode.value = false
} }
}, },
{ flush: 'post' }, { flush: 'post' },
) )
watch(editable, (nextValue) => {
if (isGrid.value && nextValue && !open.value) {
open.value = true
}
})
const placeholder = computed(() => { const placeholder = computed(() => {
if (isForm.value && !isTimeInvalid.value) { if (
((isForm.value || isExpandedForm.value) && !isTimeInvalid.value) ||
(isGrid.value && !showNull.value && !isTimeInvalid.value && !isSystemColumn(column.value) && active.value)
) {
return 'HH:mm' return 'HH:mm'
} else if (isEditColumn.value && (modelValue === '' || modelValue === null)) { } else if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return t('labels.optional') return t('labels.optional')
@ -115,40 +142,71 @@ const isOpen = computed(() => {
return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
}) })
const handleKeydown = (e: KeyboardEvent) => { const clickHandler = () => {
switch (e.key) { if (readOnly.value || open.value) return
case ' ': open.value = active.value || editable.value
if (isSurveyForm.value) { }
open.value = !open.value
}
break
case 'Enter': const handleKeydown = (e: KeyboardEvent) => {
if (!isSurveyForm.value) { if (e.key !== 'Enter') {
open.value = !open.value e.stopPropagation()
}
break
} }
}
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
e.stopPropagation() open.value = !open.value
open.value = true if (!open.value) {
break editable.value = false
if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) {
datePickerRef.value?.blur?.()
}
}
return
case 'Escape': case 'Escape':
if (open.value) { if (open.value) {
e.stopPropagation()
open.value = false open.value = false
editable.value = false
if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) {
datePickerRef.value?.blur?.()
}
} else {
editable.value = false
datePickerRef.value?.blur?.()
}
return
default:
if (!open.value && /^[0-9a-z]$/i.test(e.key)) {
open.value = true
} }
}
}
useEventListener(document, 'keydown', (e: KeyboardEvent) => {
// To prevent event listener on non active cell
if (!active.value) return
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey || !isGrid.value || isExpandedForm.value || isEditColumn.value) return
switch (e.key) {
case ';':
localState.value = dayjs(new Date())
e.preventDefault()
break break
default:
if (!isOpen.value && datePickerRef.value && /^[0-9a-z]$/i.test(e.key)) {
isClearedInputMode.value = true
datePickerRef.value.focus()
editable.value = true
open.value = true
}
} }
}) })
</script> </script>
<template> <template>
<a-time-picker <a-time-picker
ref="datePickerRef"
v-model:value="localState" v-model:value="localState"
:tabindex="0" :tabindex="0"
:disabled="readOnly" :disabled="readOnly"
@ -157,18 +215,22 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
use12-hours use12-hours
format="HH:mm" format="HH:mm"
class="nc-cell-field !w-full !py-1 !border-none !text-current" class="nc-cell-field !w-full !py-1 !border-none !text-current"
:class="{ 'nc-null': modelValue === null && showNull }" :class="[`nc-${randomClass}`, { 'nc-null': modelValue === null && showNull }]"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !isPk && !isEditColumn"
:input-read-only="true" :input-read-only="!!isMobileMode"
:open="isOpen" :open="isOpen"
:popup-class-name="`${randomClass} nc-picker-time children:border-1 children:border-gray-200 ${open ? 'active' : ''}`" :popup-class-name="`${randomClass} nc-picker-time children:border-1 children:border-gray-200 ${open ? 'active' : ''}`"
@blur="onBlur"
@keydown="handleKeydown" @keydown="handleKeydown"
@click="open = (active || editable) && !open" @click="clickHandler"
@ok="open = !open" @ok="open = !open"
@mouseup.stop
@mousedown.stop
> >
<template #suffixIcon></template> <template #suffixIcon></template>
</a-time-picker> </a-time-picker>
<div v-if="!editable && isGrid" class="absolute inset-0 z-90 cursor-pointer"></div>
</template> </template>
<style scoped> <style scoped>

14
packages/nc-gui/components/cell/Url.vue

@ -47,6 +47,8 @@ const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const isForm = inject(IsFormInj)! const isForm = inject(IsFormInj)!
const trim = (val: string) => val?.trim?.()
// Used in the logic of when to display error since we are not storing the url if it's not valid // Used in the logic of when to display error since we are not storing the url if it's not valid
const localState = ref(value) const localState = ref(value)
@ -54,21 +56,21 @@ const vModel = computed({
get: () => value, get: () => value,
set: (val) => { set: (val) => {
localState.value = val localState.value = val
if (!parseProp(column.value.meta)?.validate || (val && isValidURL(val)) || !val || isForm.value) { if (!parseProp(column.value.meta)?.validate || (val && isValidURL(trim(val))) || !val || isForm.value) {
emit('update:modelValue', val) emit('update:modelValue', val)
} }
}, },
}) })
const isValid = computed(() => value && isValidURL(value)) const isValid = computed(() => value && isValidURL(trim(value)))
const url = computed(() => { const url = computed(() => {
if (!value || !isValidURL(value)) return '' if (!value || !isValidURL(trim(value))) return ''
/** add url scheme if missing */ /** add url scheme if missing */
if (/^https?:\/\//.test(value)) return value if (/^https?:\/\//.test(trim(value))) return trim(value)
return `https://${value}` return `https://${trim(value)}`
}) })
const { cellUrlOptions } = useCellUrlConfig(url) const { cellUrlOptions } = useCellUrlConfig(url)
@ -84,7 +86,7 @@ watch(
parseProp(column.value.meta)?.validate && parseProp(column.value.meta)?.validate &&
!editEnabled.value && !editEnabled.value &&
localState.value && localState.value &&
!isValidURL(localState.value) !isValidURL(trim(localState.value))
) { ) {
message.error(t('msg.error.invalidURL')) message.error(t('msg.error.invalidURL'))
localState.value = undefined localState.value = undefined

155
packages/nc-gui/components/cell/YearPicker.vue

@ -1,18 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import { isSystemColumn } from 'nocodb-sdk'
ActiveCellInj, import { ActiveCellInj, EditColumnInj, IsFormInj, ReadonlyInj, computed, inject, onClickOutside, ref, watch } from '#imports'
EditColumnInj,
IsFormInj,
IsSurveyFormInj,
ReadonlyInj,
computed,
inject,
onClickOutside,
ref,
useSelectedCellKeyupListener,
watch,
} from '#imports'
interface Props { interface Props {
modelValue?: number | string | null modelValue?: number | string | null
@ -23,7 +12,9 @@ const { modelValue, isPk = false } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { showNull } = useGlobal() const { showNull, isMobileMode } = useGlobal()
const column = inject(ColumnInj, null)!
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
@ -33,17 +24,25 @@ const editable = inject(EditModeInj, ref(false))
const isEditColumn = inject(EditColumnInj, ref(false)) const isEditColumn = inject(EditColumnInj, ref(false))
const isGrid = inject(IsGridInj, ref(false))
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false)) const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
const isYearInvalid = ref(false) const isYearInvalid = ref(false)
const datePickerRef = ref<HTMLInputElement>()
const isClearedInputMode = ref<boolean>(false)
const { t } = useI18n() const { t } = useI18n()
const open = ref<boolean>(false)
const localState = computed({ const localState = computed({
get() { get() {
if (!modelValue) { if (!modelValue || isClearedInputMode.value) {
return undefined return undefined
} }
@ -56,6 +55,8 @@ const localState = computed({
return yearDate return yearDate
}, },
set(val?: dayjs.Dayjs) { set(val?: dayjs.Dayjs) {
isClearedInputMode.value = false
if (!val) { if (!val) {
emit('update:modelValue', null) emit('update:modelValue', null)
return return
@ -64,26 +65,56 @@ const localState = computed({
if (val?.isValid()) { if (val?.isValid()) {
emit('update:modelValue', val.format('YYYY')) emit('update:modelValue', val.format('YYYY'))
} }
open.value = false
}, },
}) })
const open = ref<boolean>(false)
const randomClass = `picker_${Math.floor(Math.random() * 99999)}` const randomClass = `picker_${Math.floor(Math.random() * 99999)}`
onClickOutside(datePickerRef, (e) => {
if ((e.target as HTMLElement)?.closest(`.${randomClass}`)) return
datePickerRef.value?.blur?.()
open.value = false
})
const onBlur = (e) => {
if ((e?.relatedTarget as HTMLElement)?.closest(`.${randomClass}`)) return
open.value = false
}
watch( watch(
open, open,
(next) => { (next) => {
if (next) { if (next) {
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) editable.value = true
datePickerRef.value?.focus?.()
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, (e) => {
if ((e?.target as HTMLElement)?.closest(`.nc-${randomClass}`)) {
return
}
open.value = false
})
} else { } else {
editable.value = false isClearedInputMode.value = false
} }
}, },
{ flush: 'post' }, { flush: 'post' },
) )
watch(editable, (nextValue) => {
if (isGrid.value && nextValue && !open.value) {
open.value = true
}
})
const placeholder = computed(() => { const placeholder = computed(() => {
if (isForm.value && !isYearInvalid.value) { if (
((isForm.value || isExpandedForm.value) && !isYearInvalid.value) ||
(isGrid.value && !showNull.value && !isYearInvalid.value && !isSystemColumn(column.value) && active.value)
) {
return 'YYYY' return 'YYYY'
} else if (isEditColumn.value && (modelValue === '' || modelValue === null)) { } else if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return t('labels.optional') return t('labels.optional')
@ -98,62 +129,96 @@ const placeholder = computed(() => {
const isOpen = computed(() => { const isOpen = computed(() => {
if (readOnly.value) return false if (readOnly.value) return false
return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
}) })
const clickHandler = () => {
if (readOnly.value || open.value) return
open.value = active.value || editable.value
}
const handleKeydown = (e: KeyboardEvent) => { const handleKeydown = (e: KeyboardEvent) => {
if (e.key !== 'Enter') {
e.stopPropagation()
}
switch (e.key) { switch (e.key) {
case ' ': case 'Enter':
if (isSurveyForm.value) { open.value = !open.value
open.value = !open.value if (!open.value) {
editable.value = false
if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) {
datePickerRef.value?.blur?.()
}
} }
break
case 'Enter': return
if (!isSurveyForm.value) { case 'Escape':
open.value = !open.value if (open.value) {
open.value = false
editable.value = false
if (isGrid.value && !isExpandedForm.value && !isEditColumn.value) {
datePickerRef.value?.blur?.()
}
} else {
editable.value = false
datePickerRef.value?.blur?.()
}
return
default:
if (!open.value && /^[0-9a-z]$/i.test(e.key)) {
open.value = true
} }
break
} }
} }
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useEventListener(document, 'keydown', (e: KeyboardEvent) => {
// To prevent event listener on non active cell
if (!active.value) return
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey || !isGrid.value || isExpandedForm.value || isEditColumn.value) return
switch (e.key) { switch (e.key) {
case 'Enter': case ';':
e.stopPropagation() localState.value = dayjs(new Date())
open.value = true e.preventDefault()
break break
case 'Escape': default:
if (open.value) { if (!isOpen.value && datePickerRef.value && /^[0-9a-z]$/i.test(e.key)) {
e.stopPropagation() isClearedInputMode.value = true
open.value = false datePickerRef.value.focus()
editable.value = true
open.value = true
} }
break
} }
}) })
</script> </script>
<template> <template>
<a-date-picker <a-date-picker
ref="datePickerRef"
v-model:value="localState" v-model:value="localState"
:disabled="readOnly"
:tabindex="0" :tabindex="0"
picker="year" picker="year"
:bordered="false" :bordered="false"
class="nc-cell-field !w-full !py-1 !border-none !text-current" class="nc-cell-field !w-full !py-1 !border-none !text-current"
:class="{ 'nc-null': modelValue === null && showNull }" :class="[`nc-${randomClass}`, { 'nc-null': modelValue === null && showNull }]"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="(!readOnly && !localState && !isPk) || isEditColumn" :allow-clear="!readOnly && !isPk"
:input-read-only="true" :input-read-only="!!isMobileMode"
:open="isOpen" :open="isOpen"
:dropdown-class-name="`${randomClass} nc-picker-year children:border-1 children:border-gray-200 ${open ? 'active' : ''}`" :dropdown-class-name="`${randomClass} nc-picker-year children:border-1 children:border-gray-200 ${open ? 'active' : ''}`"
@blur="onBlur"
@keydown="handleKeydown" @keydown="handleKeydown"
@click="open = (active || editable) && !open" @click="clickHandler"
@change="open = (active || editable) && !open" @mouseup.stop
@ok="open = !open" @mousedown.stop
> >
<template #suffixIcon></template> <template #suffixIcon></template>
</a-date-picker> </a-date-picker>
<div v-if="!editable && isGrid" class="absolute inset-0 z-90 cursor-pointer"></div>
</template> </template>
<style scoped> <style scoped>

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

@ -4,7 +4,6 @@ import { message } from 'ant-design-vue'
import { stringifyRolesObj } from 'nocodb-sdk' import { stringifyRolesObj } from 'nocodb-sdk'
import type { BaseType, SourceType, TableType } from 'nocodb-sdk' import type { BaseType, SourceType, TableType } from 'nocodb-sdk'
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue'
import { useTitle } from '@vueuse/core'
import { import {
NcProjectType, NcProjectType,
ProjectInj, ProjectInj,
@ -109,6 +108,8 @@ const keys = ref<Record<string, number>>({})
const isTableDeleteDialogVisible = ref(false) const isTableDeleteDialogVisible = ref(false)
const isProjectDeleteDialogVisible = ref(false) const isProjectDeleteDialogVisible = ref(false)
const { refreshViewTabTitle } = useViewsStore()
// If only base is open, i.e in case of docs, base view is open and not the page view // If only base is open, i.e in case of docs, base view is open and not the page view
const baseViewOpen = computed(() => { const baseViewOpen = computed(() => {
const routeNameSplit = String(route.value?.name).split('baseId-index-index') const routeNameSplit = String(route.value?.name).split('baseId-index-index')
@ -144,7 +145,7 @@ const updateProjectTitle = async () => {
$e('a:base:rename') $e('a:base:rename')
useTitle(`${base.value?.title}`) refreshViewTabTitle?.()
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
@ -389,6 +390,29 @@ const projectDelete = () => {
isProjectDeleteDialogVisible.value = true isProjectDeleteDialogVisible.value = true
$e('c:project:delete') $e('c:project:delete')
} }
// Tracks if the table ID has been successfully copied to the clipboard
const isTableIdCopied = ref(false)
let tableIdCopiedTimeout: NodeJS.Timeout
const onTableIdCopy = async () => {
if (tableIdCopiedTimeout) {
clearTimeout(tableIdCopiedTimeout)
}
try {
await copy(contextMenuTarget.value.id)
isTableIdCopied.value = true
tableIdCopiedTimeout = setTimeout(() => {
isTableIdCopied.value = false
clearTimeout(tableIdCopiedTimeout)
}, 5000)
} catch (e: any) {
message.error(e.message)
}
}
</script> </script>
<template> <template>
@ -743,35 +767,63 @@ const projectDelete = () => {
</div> </div>
</div> </div>
<template v-if="!isSharedBase" #overlay> <template v-if="!isSharedBase" #overlay>
<NcMenu class="!py-0 rounded text-sm"> <NcMenu
class="!py-0 rounded text-sm"
:class="{
'!min-w-70': contextMenuTarget.type === 'table',
}"
>
<template v-if="contextMenuTarget.type === 'base' && base.type === 'database'"></template> <template v-if="contextMenuTarget.type === 'base' && base.type === 'database'"></template>
<template v-else-if="contextMenuTarget.type === 'source'"></template> <template v-else-if="contextMenuTarget.type === 'source'"></template>
<template v-else-if="contextMenuTarget.type === 'table'"> <template v-else-if="contextMenuTarget.type === 'table'">
<NcMenuItem v-if="isUIAllowed('tableRename')" @click="openRenameTableDialog(contextMenuTarget.value, true)"> <NcTooltip>
<div v-e="['c:table:rename']" class="nc-base-option-item flex gap-2 items-center"> <template #title> {{ $t('labels.clickToCopyTableID') }} </template>
<GeneralIcon icon="rename" class="text-gray-700" /> <div
{{ $t('general.rename') }} class="flex items-center justify-between p-2 mx-1.5 rounded-md cursor-pointer hover:bg-gray-100 group"
@click.stop="onTableIdCopy"
>
<div class="flex text-xs font-bold text-gray-500 ml-1">
{{
$t('labels.tableIdColon', {
tableId: contextMenuTarget.value?.id,
})
}}
</div>
<NcButton class="!group-hover:bg-gray-100" size="xsmall" type="secondary">
<GeneralIcon v-if="isTableIdCopied" class="max-h-4 min-w-4" icon="check" />
<GeneralIcon v-else class="max-h-4 min-w-4" else icon="copy" />
</NcButton>
</div> </div>
</NcMenuItem> </NcTooltip>
<NcMenuItem <template v-if="isUIAllowed('tableRename') || isUIAllowed('tableDelete')">
v-if="isUIAllowed('tableDuplicate') && (contextMenuBase?.is_meta || contextMenuBase?.is_local)" <NcDivider />
@click="duplicateTable(contextMenuTarget.value)" <NcMenuItem v-if="isUIAllowed('tableRename')" @click="openRenameTableDialog(contextMenuTarget.value, true)">
> <div v-e="['c:table:rename']" class="nc-base-option-item flex gap-2 items-center">
<div v-e="['c:table:duplicate']" class="nc-base-option-item flex gap-2 items-center"> <GeneralIcon icon="rename" class="text-gray-700" />
<GeneralIcon icon="duplicate" class="text-gray-700" /> {{ $t('general.rename') }} {{ $t('objects.table') }}
{{ $t('general.duplicate') }} </div>
</div> </NcMenuItem>
</NcMenuItem>
<NcDivider /> <NcMenuItem
<NcMenuItem v-if="isUIAllowed('tableDelete')" class="!hover:bg-red-50" @click="tableDelete"> v-if="isUIAllowed('tableDuplicate') && (contextMenuBase?.is_meta || contextMenuBase?.is_local)"
<div class="nc-base-option-item flex gap-2 items-center text-red-600"> @click="duplicateTable(contextMenuTarget.value)"
<GeneralIcon icon="delete" /> >
{{ $t('general.delete') }} <div v-e="['c:table:duplicate']" class="nc-base-option-item flex gap-2 items-center">
</div> <GeneralIcon icon="duplicate" class="text-gray-700" />
</NcMenuItem> {{ $t('general.duplicate') }} {{ $t('objects.table') }}
</div>
</NcMenuItem>
<NcDivider />
<NcMenuItem v-if="isUIAllowed('table-delete')" class="!hover:bg-red-50" @click="tableDelete">
<div class="nc-base-option-item flex gap-2 items-center text-red-600">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }} {{ $t('objects.table') }}
</div>
</NcMenuItem>
</template>
</template> </template>
</NcMenu> </NcMenu>
</template> </template>

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

@ -4,7 +4,7 @@ import { toRef } from '@vue/reactivity'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { ProjectRoleInj, TreeViewInj, useMagicKeys, useNuxtApp, useRoles, useTabs } from '#imports' import { ProjectRoleInj, TreeViewInj, useCopy, useMagicKeys, useNuxtApp, useRoles, useTabs } from '#imports'
import type { SidebarTableNode } from '~/lib' import type { SidebarTableNode } from '~/lib'
const props = withDefaults( const props = withDefaults(
@ -41,6 +41,8 @@ useTableNew({
const { meta: metaKey, control } = useMagicKeys() const { meta: metaKey, control } = useMagicKeys()
const { copy } = useCopy()
const baseRole = inject(ProjectRoleInj) const baseRole = inject(ProjectRoleInj)
provide(SidebarTableInj, table) provide(SidebarTableInj, table)
@ -90,6 +92,9 @@ const canUserEditEmote = computed(() => {
const isExpanded = ref(false) const isExpanded = ref(false)
const isLoading = ref(false) const isLoading = ref(false)
// Tracks if the table ID has been successfully copied to the clipboard
const isTableIdCopied = ref(false)
const onExpand = async () => { const onExpand = async () => {
if (isExpanded.value) { if (isExpanded.value) {
isExpanded.value = false isExpanded.value = false
@ -127,6 +132,25 @@ const onOpenTable = async () => {
isExpanded.value = true isExpanded.value = true
} }
} }
let tableIdCopiedTimeout: NodeJS.Timeout
const onTableIdCopy = async () => {
if (tableIdCopiedTimeout) {
clearTimeout(tableIdCopiedTimeout)
}
try {
await copy(table.value!.id!)
isTableIdCopied.value = true
tableIdCopiedTimeout = setTimeout(() => {
isTableIdCopied.value = false
clearTimeout(tableIdCopiedTimeout)
}, 5000)
} catch (e: any) {
message.error(e.message)
}
}
watch( watch(
() => activeView.value?.id, () => activeView.value?.id,
@ -276,12 +300,7 @@ watch(openedTableId, () => {
</NcTooltip> </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-e="['c:table:option']">
v-if="
!isSharedBase && (isUIAllowed('tableRename', { roles: baseRole }) || isUIAllowed('tableDelete', { roles: baseRole }))
"
v-e="['c:table:option']"
>
<NcDropdown :trigger="['click']" class="nc-sidebar-node-btn" @click.stop> <NcDropdown :trigger="['click']" class="nc-sidebar-node-btn" @click.stop>
<MdiDotsHorizontal <MdiDotsHorizontal
data-testid="nc-sidebar-table-context-menu" data-testid="nc-sidebar-table-context-menu"
@ -289,44 +308,73 @@ watch(openedTableId, () => {
/> />
<template #overlay> <template #overlay>
<NcMenu> <NcMenu class="!min-w-70" :data-testid="`sidebar-table-context-menu-list-${table.title}`">
<NcMenuItem <NcTooltip>
v-if="isUIAllowed('tableRename', { roles: baseRole })" <template #title> {{ $t('labels.clickToCopyTableID') }} </template>
:data-testid="`sidebar-table-rename-${table.title}`" <div
@click="openRenameTableDialog(table, base.sources[sourceIndex].id)" class="flex items-center justify-between p-2 mx-1.5 rounded-md cursor-pointer hover:bg-gray-100 group"
> @click.stop="onTableIdCopy"
<div v-e="['c:table:rename']" class="flex gap-2 items-center"> >
<GeneralIcon icon="rename" class="text-gray-700" /> <div class="flex text-xs font-bold text-gray-500 ml-1">
{{ $t('general.rename') }} {{
$t('labels.tableIdColon', {
tableId: table?.id,
})
}}
</div>
<NcButton class="!group-hover:bg-gray-100" size="xsmall" type="secondary">
<GeneralIcon v-if="isTableIdCopied" class="max-h-4 min-w-4" icon="check" />
<GeneralIcon v-else class="max-h-4 min-w-4" else icon="copy" />
</NcButton>
</div> </div>
</NcMenuItem> </NcTooltip>
<NcMenuItem <template
v-if=" v-if="
isUIAllowed('tableDuplicate') && !isSharedBase &&
base.sources?.[sourceIndex] && (isUIAllowed('tableRename', { roles: baseRole }) || isUIAllowed('tableDelete', { roles: baseRole }))
(base.sources[sourceIndex].is_meta || base.sources[sourceIndex].is_local)
" "
:data-testid="`sidebar-table-duplicate-${table.title}`"
@click="duplicateTable(table)"
> >
<div v-e="['c:table:duplicate']" class="flex gap-2 items-center"> <NcDivider />
<GeneralIcon icon="duplicate" class="text-gray-700" /> <NcMenuItem
{{ $t('general.duplicate') }} v-if="isUIAllowed('tableRename', { roles: baseRole })"
</div> :data-testid="`sidebar-table-rename-${table.title}`"
</NcMenuItem> @click="openRenameTableDialog(table, base.sources[sourceIndex].id)"
>
<NcMenuItem <div v-e="['c:table:rename']" class="flex gap-2 items-center">
v-if="isUIAllowed('tableDelete', { roles: baseRole })" <GeneralIcon icon="rename" class="text-gray-700" />
:data-testid="`sidebar-table-delete-${table.title}`" {{ $t('general.rename') }} {{ $t('objects.table') }}
class="!text-red-500 !hover:bg-red-50" </div>
@click="isTableDeleteDialogVisible = true" </NcMenuItem>
>
<div v-e="['c:table:delete']" class="flex gap-2 items-center"> <NcMenuItem
<GeneralIcon icon="delete" /> v-if="
{{ $t('general.delete') }} isUIAllowed('tableDuplicate') &&
</div> base.sources?.[sourceIndex] &&
</NcMenuItem> (base.sources[sourceIndex].is_meta || base.sources[sourceIndex].is_local)
"
:data-testid="`sidebar-table-duplicate-${table.title}`"
@click="duplicateTable(table)"
>
<div v-e="['c:table:duplicate']" class="flex gap-2 items-center">
<GeneralIcon icon="duplicate" class="text-gray-700" />
{{ $t('general.duplicate') }} {{ $t('objects.table') }}
</div>
</NcMenuItem>
<NcDivider />
<NcMenuItem
v-if="isUIAllowed('tableDelete', { roles: baseRole })"
:data-testid="`sidebar-table-delete-${table.title}`"
class="!text-red-500 !hover:bg-red-50"
@click="isTableDeleteDialogVisible = true"
>
<div v-e="['c:table:delete']" class="flex gap-2 items-center">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }} {{ $t('objects.table') }}
</div>
</NcMenuItem>
</template>
</NcMenu> </NcMenu>
</template> </template>
</NcDropdown> </NcDropdown>

3
packages/nc-gui/components/dlg/TableRename.vue

@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import type { ComponentPublicInstance } from '@vue/runtime-core' import type { ComponentPublicInstance } from '@vue/runtime-core'
import { useTitle } from '@vueuse/core'
import { import {
Form, Form,
computed, computed,
@ -180,8 +179,6 @@ const renameTable = async (undo = false, disableTitleDiffCheck?: boolean | undef
$e('a:table:rename') $e('a:table:rename')
useTitle(`${base.value?.title}: ${newMeta?.title}`)
dialogShow.value = false dialogShow.value = false
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))

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

@ -6,7 +6,7 @@ import { isEeUI } from '#imports'
const basesStore = useBases() const basesStore = useBases()
const { openedProject, activeProjectId, basesUser } = storeToRefs(basesStore) const { openedProject, activeProjectId, basesUser } = storeToRefs(basesStore)
const { activeTables } = storeToRefs(useTablesStore()) const { activeTables, activeTable } = storeToRefs(useTablesStore())
const { activeWorkspace, workspaceUserCount } = storeToRefs(useWorkspace()) const { activeWorkspace, workspaceUserCount } = storeToRefs(useWorkspace())
const { navigateToProjectPage } = useBase() const { navigateToProjectPage } = useBase()
@ -66,10 +66,15 @@ watch(projectPageTab, () => {
}) })
watch( watch(
() => openedProject.value?.title, () => [openedProject.value?.id, openedProject.value?.title],
() => { () => {
if (activeTable.value?.title) return
useTitle(`${openedProject.value?.title ?? activeWorkspace.value?.title ?? 'NocoDB'}`) useTitle(`${openedProject.value?.title ?? activeWorkspace.value?.title ?? 'NocoDB'}`)
}, },
{
immediate: true,
},
) )
</script> </script>

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

@ -196,7 +196,13 @@ onUnmounted(() => {
{ {
'text-brand-500': isPrimary(column) && !props.virtual && !isForm && !isCalendar, 'text-brand-500': isPrimary(column) && !props.virtual && !isForm && !isCalendar,
'nc-grid-numeric-cell-right': 'nc-grid-numeric-cell-right':
isGrid && isNumericField && !isEditColumnMenu && !isForm && !isExpandedFormOpen && !isRating(column), isGrid &&
isNumericField &&
!isEditColumnMenu &&
!isForm &&
!isExpandedFormOpen &&
!isRating(column) &&
!isYear(column, abstractType),
'h-10': !isEditColumnMenu && isForm && !isAttachment(column) && !isTextArea(column) && !isJSON(column) && !props.virtual, 'h-10': !isEditColumnMenu && isForm && !isAttachment(column) && !isTextArea(column) && !isJSON(column) && !props.virtual,
'nc-grid-numeric-cell-left': (isForm && isNumericField && isExpandedFormOpen) || isEditColumnMenu, 'nc-grid-numeric-cell-left': (isForm && isNumericField && isExpandedFormOpen) || isEditColumnMenu,
'!min-h-30': isTextArea(column) && (isForm || isSurveyForm), '!min-h-30': isTextArea(column) && (isForm || isSurveyForm),
@ -271,7 +277,9 @@ onUnmounted(() => {
} }
} }
.nc-cell .nc-cell-field { .nc-cell {
@apply px-0; :deep(.nc-cell-field) {
@apply px-0;
}
} }
</style> </style>

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

@ -29,7 +29,7 @@ const isSharedBase = computed(() => route.value.params.typeOrId === 'base')
<GeneralOpenLeftSidebarBtn /> <GeneralOpenLeftSidebarBtn />
<LazySmartsheetToolbarViewInfo v-if="!isPublic" /> <LazySmartsheetToolbarViewInfo v-if="!isPublic" />
<div v-if="!isSharedBase && !isMobileMode" class="w-47.5"> <div v-if="!isSharedBase && !isMobileMode">
<SmartsheetTopbarSelectMode /> <SmartsheetTopbarSelectMode />
</div> </div>
<div class="flex-1" /> <div class="flex-1" />

2
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -585,7 +585,7 @@ const {
// ignore navigating if picker(Date, Time, DateTime, Year) // ignore navigating if picker(Date, Time, DateTime, Year)
// or single/multi select options is open // or single/multi select options is open
const activePickerOrDropdownEl = document.querySelector( const activePickerOrDropdownEl = document.querySelector(
'.nc-picker-datetime.active,.nc-dropdown-single-select-cell.active,.nc-dropdown-multi-select-cell.active,.nc-picker-date.active,.nc-picker-year.active,.nc-picker-time.active', '.nc-dropdown-single-select-cell.active,.nc-dropdown-multi-select-cell.active',
) )
if (activePickerOrDropdownEl) { if (activePickerOrDropdownEl) {
e.preventDefault() e.preventDefault()

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

@ -81,7 +81,7 @@ const {
activeView, activeView,
parentId?.value, parentId?.value,
computed(() => autoSave.value), computed(() => autoSave.value),
() => reloadDataHook.trigger({ shouldShowLoading: showLoading.value }), () => reloadDataHook.trigger({ shouldShowLoading: showLoading.value, offset: 0 }),
modelValue.value || nestedFilters.value, modelValue.value || nestedFilters.value,
!modelValue.value, !modelValue.value,
webHook.value, webHook.value,

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

@ -153,7 +153,7 @@ const onDelete = async () => {
> >
<NcTooltip> <NcTooltip>
<template #title> {{ $t('labels.clickToCopyViewID') }} </template> <template #title> {{ $t('labels.clickToCopyViewID') }} </template>
<div class="flex items-center justify-between py-2 px-3 cursor-pointer hover:bg-gray-100 group" @click="onViewIdCopy"> <div class="flex items-center justify-between p-2 mx-1.5 rounded-md cursor-pointer hover:bg-gray-100 group" @click="onViewIdCopy">
<div class="flex text-xs font-bold text-gray-500 ml-1"> <div class="flex text-xs font-bold text-gray-500 ml-1">
{{ {{
$t('labels.viewIdColon', { $t('labels.viewIdColon', {

6
packages/nc-gui/components/smartsheet/topbar/SelectMode.vue

@ -62,9 +62,9 @@ const onClickDetails = () => {
} }
.tab .tab-title { .tab .tab-title {
@apply min-w-0; @apply min-w-0;
word-break: 'keep-all'; word-break: keep-all;
white-space: 'nowrap'; white-space: nowrap;
display: 'inline'; display: inline;
line-height: 0.95; line-height: 0.95;
} }

6
packages/nc-gui/components/virtual-cell/components/UnLinkedItems.vue

@ -51,7 +51,7 @@ const {
unlink, unlink,
row, row,
headerDisplayValue, headerDisplayValue,
resetChildrenExcludedOffsetCount resetChildrenExcludedOffsetCount,
} = useLTARStoreOrThrow() } = useLTARStoreOrThrow()
const { addLTARRef, isNew, removeLTARRef, state: rowState } = useSmartsheetRowStoreOrThrow() const { addLTARRef, isNew, removeLTARRef, state: rowState } = useSmartsheetRowStoreOrThrow()
@ -102,7 +102,7 @@ watch(
} }
loadChildrenExcludedList(rowState.value) loadChildrenExcludedList(rowState.value)
} }
if(!nextVal){ if (!nextVal) {
resetChildrenExcludedOffsetCount() resetChildrenExcludedOffsetCount()
} }
}, },
@ -262,7 +262,7 @@ onUnmounted(() => {
}) })
const onFilterChange = () => { const onFilterChange = () => {
childrenExcludedListPagination.page = 1; childrenExcludedListPagination.page = 1
resetChildrenExcludedOffsetCount() resetChildrenExcludedOffsetCount()
} }
</script> </script>

7
packages/nc-gui/composables/useLTARStore.ts

@ -191,7 +191,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
const loadChildrenExcludedList = async (activeState?: any) => { const loadChildrenExcludedList = async (activeState?: any) => {
if (activeState) newRowState.state = activeState if (activeState) newRowState.state = activeState
try { try {
let offset = childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1) - childrenExcludedOffsetCount.value let offset =
childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1) - childrenExcludedOffsetCount.value
if (offset < 0) { if (offset < 0) {
offset = 0 offset = 0
@ -550,8 +551,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
}) })
}) })
const resetChildrenExcludedOffsetCount = () =>{ const resetChildrenExcludedOffsetCount = () => {
childrenExcludedOffsetCount.value = 0; childrenExcludedOffsetCount.value = 0
} }
const resetChildrenListOffsetCount = () => { const resetChildrenListOffsetCount = () => {

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

@ -14,6 +14,7 @@ import type {
} from 'nocodb-sdk' } from 'nocodb-sdk'
import { RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk' import { RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { isString } from '@vue/shared' import { isString } from '@vue/shared'
import { useTitle } from '@vueuse/core'
import { filterNullOrUndefinedObjectProperties } from '~/helpers/parsers/parserHelpers' import { filterNullOrUndefinedObjectProperties } from '~/helpers/parsers/parserHelpers'
import { import {
NcErrorType, NcErrorType,
@ -132,25 +133,27 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
{} as Record<string, FormColumnType>, {} as Record<string, FormColumnType>,
) )
columns.value = viewMeta.model?.columns?.map((c) => { columns.value = (viewMeta.model?.columns || [])
if ( .filter((c) => fieldById[c.id])
!isSystemColumn(c) && .map((c) => {
!isVirtualCol(c) && if (
!isAttachment(c) && !isSystemColumn(c) &&
c.uidt !== UITypes.SpecificDBType && !isVirtualCol(c) &&
c?.title && !isAttachment(c) &&
c?.cdf && c.uidt !== UITypes.SpecificDBType &&
!/^\w+\(\)|CURRENT_TIMESTAMP$/.test(c.cdf) c?.title &&
) { c?.cdf &&
formState.value[c.title] = typeof c.cdf === 'string' ? c.cdf.replace(/^'|'$/g, '') : c.cdf !/^\w+\(\)|CURRENT_TIMESTAMP$/.test(c.cdf)
} ) {
formState.value[c.title] = typeof c.cdf === 'string' ? c.cdf.replace(/^'|'$/g, '') : c.cdf
}
return { return {
...c, ...c,
meta: { ...parseProp(fieldById[c.id].meta), ...parseProp(c.meta) }, meta: { ...parseProp(fieldById[c.id].meta), ...parseProp(c.meta) },
description: fieldById[c.id].description, description: fieldById[c.id].description,
} }
}) })
const _sharedViewMeta = (viewMeta as any).meta const _sharedViewMeta = (viewMeta as any).meta
sharedViewMeta.value = isString(_sharedViewMeta) ? JSON.parse(_sharedViewMeta) : _sharedViewMeta sharedViewMeta.value = isString(_sharedViewMeta) ? JSON.parse(_sharedViewMeta) : _sharedViewMeta
@ -188,6 +191,9 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
if (password.value && password.value !== '') { if (password.value && password.value !== '') {
passwordError.value = error.message passwordError.value = error.message
} }
} else if (error.error === NcErrorType.UNKNOWN_ERROR) {
console.error('Error occurred while loading shared form view', e)
message.error('Error occurred while loading shared form view')
} }
} }
} }
@ -541,6 +547,16 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
if (next !== prev && passwordError.value) passwordError.value = null if (next !== prev && passwordError.value) passwordError.value = null
}) })
watch(
() => sharedFormView.value?.heading,
() => {
useTitle(`${sharedFormView.value?.heading ?? 'NocoDB'}`)
},
{
flush: 'post',
},
)
return { return {
sharedView, sharedView,
sharedFormView, sharedFormView,

44
packages/nc-gui/helpers/parsers/parserHelpers.ts

@ -235,3 +235,47 @@ export const extractNextDefaultName = (namesData: string[], defaultName: string,
? `${defaultName}${splitOperator}${extractedSortedNumbers[extractedSortedNumbers.length - 1] + 1}` ? `${defaultName}${splitOperator}${extractedSortedNumbers[extractedSortedNumbers.length - 1] + 1}`
: `${defaultName}${splitOperator}1` : `${defaultName}${splitOperator}1`
} }
export const getFormattedViewTabTitle = ({
viewName,
tableName,
baseName,
isDefaultView = false,
charLimit = 20,
isSharedView = false,
}: {
viewName: string
tableName: string
baseName: string
isDefaultView?: boolean
charLimit?: number
isSharedView?: boolean
}) => {
if (isSharedView) {
return viewName || 'NocoDB'
}
let title = `${viewName} | ${tableName} | ${baseName}`
if (isDefaultView) {
charLimit = 30
title = `${tableName} | ${baseName}`
}
if (title.length <= 60) {
return title
}
// Function to truncate text and add ellipsis if needed
const truncateText = (text: string) => {
return text.length > charLimit ? `${text.substring(0, charLimit - 3)}...` : text
}
if (isDefaultView) {
title = `${truncateText(tableName)} | ${truncateText(baseName)}`
} else {
title = `${truncateText(viewName)} | ${truncateText(tableName)} | ${truncateText(baseName)}`
}
return title
}

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "ID Pohledu: {viewId}", "viewIdColon": "ID Pohledu: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Kliknutím zkopírujete ID pohledu", "clickToCopyViewID": "Kliknutím zkopírujete ID pohledu",
"viewMode": "Režim zobrazení", "viewMode": "Režim zobrazení",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "Kein Token", "noToken": "Kein Token",
"tokenLimit": "Nur ein Token pro Benutzer ist erlaubt", "tokenLimit": "Nur ein Token pro Benutzer ist erlaubt",
"duplicateAttachment": "Datei mit dem Namen {filename} ist bereits angehängt", "duplicateAttachment": "Datei mit dem Namen {filename} ist bereits angehängt",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "Anzeige-ID: {viewId}", "viewIdColon": "Anzeige-ID: {viewId}",
"toAddress": "Empfängeradresse", "toAddress": "Empfängeradresse",
"subject": "Betreff", "subject": "Betreff",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Benutzer durchsuchen", "searchUsers": "Benutzer durchsuchen",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "Sin Token", "noToken": "Sin Token",
"tokenLimit": "Sólo se permite un token por usuario", "tokenLimit": "Sólo se permite un token por usuario",
"duplicateAttachment": "Archivo con el nombre {filename} ya adjuntado", "duplicateAttachment": "Archivo con el nombre {filename} ya adjuntado",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "A Dirección", "toAddress": "A Dirección",
"subject": "Asunto", "subject": "Asunto",
@ -506,6 +507,7 @@
"clickToHide": "Clic para ocultar", "clickToHide": "Clic para ocultar",
"clickToDownload": "Clic para descargar", "clickToDownload": "Clic para descargar",
"forRole": "para el rol", "forRole": "para el rol",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Clic para copiar View ID", "clickToCopyViewID": "Clic para copiar View ID",
"viewMode": "Modo solo lectura", "viewMode": "Modo solo lectura",
"searchUsers": "Buscar usuarios", "searchUsers": "Buscar usuarios",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "Aucun jeton", "noToken": "Aucun jeton",
"tokenLimit": "Un seul jeton par utilisateur est autorisé", "tokenLimit": "Un seul jeton par utilisateur est autorisé",
"duplicateAttachment": "Un fichier avec le nom {filename} est déjà attaché", "duplicateAttachment": "Un fichier avec le nom {filename} est déjà attaché",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "ID DE VUE : {viewId}", "viewIdColon": "ID DE VUE : {viewId}",
"toAddress": "Vers l'adresse", "toAddress": "Vers l'adresse",
"subject": "Objet", "subject": "Objet",
@ -506,6 +507,7 @@
"clickToHide": "Cliquez pour masquer", "clickToHide": "Cliquez pour masquer",
"clickToDownload": "Cliquez pour télécharger", "clickToDownload": "Cliquez pour télécharger",
"forRole": "pour le rôle", "forRole": "pour le rôle",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Cliquer pour copier l'ID de la vue", "clickToCopyViewID": "Cliquer pour copier l'ID de la vue",
"viewMode": "Mode d'affichage", "viewMode": "Mode d'affichage",
"searchUsers": "Rechercher des utilisateurs", "searchUsers": "Rechercher des utilisateurs",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "Nessun token", "noToken": "Nessun token",
"tokenLimit": "È consentito un solo token per utente", "tokenLimit": "È consentito un solo token per utente",
"duplicateAttachment": "Il file con il nome {filename} è già allegato", "duplicateAttachment": "Il file con il nome {filename} è già allegato",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "ID VISTA: {viewId}", "viewIdColon": "ID VISTA: {viewId}",
"toAddress": "Indirizzo", "toAddress": "Indirizzo",
"subject": "Oggetto", "subject": "Oggetto",
@ -506,6 +507,7 @@
"clickToHide": "Clicca per nascondere", "clickToHide": "Clicca per nascondere",
"clickToDownload": "Clicca per scaricare", "clickToDownload": "Clicca per scaricare",
"forRole": "per il ruolo", "forRole": "per il ruolo",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Clicca per copiare l'ID della vista", "clickToCopyViewID": "Clicca per copiare l'ID della vista",
"viewMode": "Modalità Visualizzazione", "viewMode": "Modalità Visualizzazione",
"searchUsers": "Ricerca Utenti", "searchUsers": "Ricerca Utenti",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "토큰 없음", "noToken": "토큰 없음",
"tokenLimit": "사용자당 하나의 토큰만 허용됩니다", "tokenLimit": "사용자당 하나의 토큰만 허용됩니다",
"duplicateAttachment": "이름이 {filename}인 파일이 이미 연결됨", "duplicateAttachment": "이름이 {filename}인 파일이 이미 연결됨",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "받는 사람", "toAddress": "받는 사람",
"subject": "제목", "subject": "제목",
@ -506,6 +507,7 @@
"clickToHide": "클릭하여 숨김", "clickToHide": "클릭하여 숨김",
"clickToDownload": "클릭하여 다운로드", "clickToDownload": "클릭하여 다운로드",
"forRole": "역할", "forRole": "역할",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "클릭하여 ID 복사", "clickToCopyViewID": "클릭하여 ID 복사",
"viewMode": "보기 모드", "viewMode": "보기 모드",
"searchUsers": "사용자 검색", "searchUsers": "사용자 검색",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -199,8 +199,8 @@
"dropdown": "Rozwijane menu", "dropdown": "Rozwijane menu",
"list": "Lista", "list": "Lista",
"apply": "Zastosuj", "apply": "Zastosuj",
"text": "Text", "text": "Tekst",
"appearance": "Appearance" "appearance": "Wygląd"
}, },
"objects": { "objects": {
"day": "Dzień", "day": "Dzień",
@ -468,6 +468,7 @@
"noToken": "Brak tokenu", "noToken": "Brak tokenu",
"tokenLimit": "Dozwolony tylko jeden token na użytkownika", "tokenLimit": "Dozwolony tylko jeden token na użytkownika",
"duplicateAttachment": "Plik o nazwie {filename} jest już załączony", "duplicateAttachment": "Plik o nazwie {filename} jest już załączony",
"tableIdColon": "ID TABELI: {tableId}",
"viewIdColon": "ID WIDOKU: {viewId}", "viewIdColon": "ID WIDOKU: {viewId}",
"toAddress": "Adresat", "toAddress": "Adresat",
"subject": "Temat", "subject": "Temat",
@ -506,6 +507,7 @@
"clickToHide": "Kliknij, aby ukryć", "clickToHide": "Kliknij, aby ukryć",
"clickToDownload": "Kliknij, aby pobrać", "clickToDownload": "Kliknij, aby pobrać",
"forRole": "dla roli", "forRole": "dla roli",
"clickToCopyTableID": "Kliknij, aby skopiować identyfikator tabeli",
"clickToCopyViewID": "Kliknij, aby skopiować ID widoku", "clickToCopyViewID": "Kliknij, aby skopiować ID widoku",
"viewMode": "Tryb widoku", "viewMode": "Tryb widoku",
"searchUsers": "Szukaj użytkowników", "searchUsers": "Szukaj użytkowników",
@ -701,7 +703,7 @@
"hideNocodbBranding": "Ukryj branding NocoDB", "hideNocodbBranding": "Ukryj branding NocoDB",
"showOnConditions": "Pokaż na warunkach", "showOnConditions": "Pokaż na warunkach",
"showFieldOnConditionsMet": "Pokazuje pole tylko, gdy spełnione są warunki", "showFieldOnConditionsMet": "Pokazuje pole tylko, gdy spełnione są warunki",
"limitOptions": "Limit options", "limitOptions": "Ogranicz opcje",
"limitOptionsSubtext": "Ogranicz opcje widoczne dla użytkowników, wybierając dostępne opcje", "limitOptionsSubtext": "Ogranicz opcje widoczne dla użytkowników, wybierając dostępne opcje",
"clearSelection": "Wyczyść wybór" "clearSelection": "Wyczyść wybór"
}, },
@ -974,7 +976,7 @@
"clientKey": "Wybierz plik .key.", "clientKey": "Wybierz plik .key.",
"clientCert": "Wybierz plik .Cert.", "clientCert": "Wybierz plik .Cert.",
"clientCA": "Wybierz plik CA.", "clientCA": "Wybierz plik CA.",
"changeIconColour": "Change icon colour", "changeIconColour": "Zmiana koloru ikony",
"preFillFormInfo": "Generuj URL formularza udostępniania z wstępnie wypełnionymi danymi pola. Aby uzyskać link z wstępnie wypełnionymi danymi, upewnij się, że wypełniłeś wymagane pola w kreatorze widoku formularza.", "preFillFormInfo": "Generuj URL formularza udostępniania z wstępnie wypełnionymi danymi pola. Aby uzyskać link z wstępnie wypełnionymi danymi, upewnij się, że wypełniłeś wymagane pola w kreatorze widoku formularza.",
"surveyFormInfo": "Tryb formularza z jednym polem na stronę" "surveyFormInfo": "Tryb formularza z jednym polem na stronę"
}, },
@ -1124,7 +1126,7 @@
"selectFieldToGroup": "Wybierz pole do grupowania", "selectFieldToGroup": "Wybierz pole do grupowania",
"thereAreNoRecordsInTable": "Nie ma rekordów w tabeli", "thereAreNoRecordsInTable": "Nie ma rekordów w tabeli",
"createWebhookMsg1": "Rozpocznij pracę z webhookami!", "createWebhookMsg1": "Rozpocznij pracę z webhookami!",
"createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data", "createWebhookMsg2": "Zasil automatyzację. Otrzymuj powiadomienia jak tylko pojawią się zmiany w Twoich danych",
"areYouSureUWantTo": "Czy na pewno chcesz usunąć następujące", "areYouSureUWantTo": "Czy na pewno chcesz usunąć następujące",
"areYouSureUWantToDeleteLabel": "Czy na pewno chcesz {deleteLabel} następujące", "areYouSureUWantToDeleteLabel": "Czy na pewno chcesz {deleteLabel} następujące",
"idColumnRequired": "Pole ID jest wymagane, możesz to później zmienić, jeśli będzie to konieczne.", "idColumnRequired": "Pole ID jest wymagane, możesz to później zmienić, jeśli będzie to konieczne.",

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

@ -468,6 +468,7 @@
"noToken": "Nenhum Token", "noToken": "Nenhum Token",
"tokenLimit": "Só é permitido um token por utilizador", "tokenLimit": "Só é permitido um token por utilizador",
"duplicateAttachment": "Arquivo com o nome {filename} já está anexado", "duplicateAttachment": "Arquivo com o nome {filename} já está anexado",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "ID DE VISUALIZAÇÃO: {viewId}", "viewIdColon": "ID DE VISUALIZAÇÃO: {viewId}",
"toAddress": "Para o endereço", "toAddress": "Para o endereço",
"subject": "Assunto", "subject": "Assunto",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -157,8 +157,8 @@
"groupingField": "Поле группировки", "groupingField": "Поле группировки",
"insertAfter": "Вставить после", "insertAfter": "Вставить после",
"insertBefore": "Вставить перед", "insertBefore": "Вставить перед",
"insertAbove": "Insert above", "insertAbove": "Вставить выше",
"insertBelow": "Insert below", "insertBelow": "Вставить ниже",
"hideField": "Скрыть поле", "hideField": "Скрыть поле",
"sortAsc": "По Возрастанию", "sortAsc": "По Возрастанию",
"sortDesc": "По убыванию", "sortDesc": "По убыванию",
@ -192,21 +192,21 @@
"enter": "Вход", "enter": "Вход",
"seconds": "Секунды", "seconds": "Секунды",
"paste": "Вставить", "paste": "Вставить",
"restore": "Restore", "restore": "Восстановить",
"replace": "Replace", "replace": "Заменить",
"banner": "Banner", "banner": "Баннер",
"logo": "Logo", "logo": "Логотип",
"dropdown": "Dropdown", "dropdown": "Выпадающий список",
"list": "List", "list": "Список",
"apply": "Apply", "apply": "Применить",
"text": "Text", "text": "Текст",
"appearance": "Appearance" "appearance": "Внешний вид"
}, },
"objects": { "objects": {
"day": "Day", "day": "День",
"week": "Week", "week": "Неделя",
"month": "Month", "month": "Месяц",
"year": "Year", "year": "Год",
"workspace": "Рабочее пространство", "workspace": "Рабочее пространство",
"workspaces": "Рабочие пространства", "workspaces": "Рабочие пространства",
"project": "Проект", "project": "Проект",
@ -313,7 +313,7 @@
"isNotNull": "не равно Null" "isNotNull": "не равно Null"
}, },
"title": { "title": {
"sso": "Authentication (SSO)", "sso": "Аутентификация (SSO)",
"docs": "Документация", "docs": "Документация",
"forum": "Форум", "forum": "Форум",
"parameter": "Параметр", "parameter": "Параметр",
@ -341,7 +341,7 @@
"removeFile": "Удалить файл", "removeFile": "Удалить файл",
"hasMany": "Имеет много", "hasMany": "Имеет много",
"manyToMany": "Многие ко многим", "manyToMany": "Многие ко многим",
"oneToOne": "One to One", "oneToOne": "Один к одному",
"virtualRelation": "Виртуальные отношения", "virtualRelation": "Виртуальные отношения",
"linkMore": "Ссылка Подробнее", "linkMore": "Ссылка Подробнее",
"linkMoreRecords": "Связать больше записей", "linkMoreRecords": "Связать больше записей",
@ -436,9 +436,9 @@
"surveyFormSubmitConfirmMsg": "Are you sure you want to submit this form?" "surveyFormSubmitConfirmMsg": "Are you sure you want to submit this form?"
}, },
"labels": { "labels": {
"selectYear": "Select Year", "selectYear": "Выберите год",
"save": "Save", "save": "Сохранить",
"cancel": "Cancel", "cancel": "Отмена",
"metadataUrl": "Metadata URL", "metadataUrl": "Metadata URL",
"audience-entityId": "Audience/ Entity ID", "audience-entityId": "Audience/ Entity ID",
"redirectUrl": "Redirect URL", "redirectUrl": "Redirect URL",
@ -446,12 +446,12 @@
"saml": "Security Assertion Markup Language (SAML)", "saml": "Security Assertion Markup Language (SAML)",
"newProvider": "New Provider", "newProvider": "New Provider",
"generalSettings": "General Settings", "generalSettings": "General Settings",
"ssoSettings": "SSO Settings", "ssoSettings": "Настройки SSO",
"organizeBy": "Organize by", "organizeBy": "Organize by",
"previous": "Previous", "previous": "Предыдущий",
"nextMonth": "Next Month", "nextMonth": "Следующий месяц",
"previousMonth": "Previous Month", "previousMonth": "Предыдущий месяц",
"next": "Next", "next": "Следующий",
"organiseBy": "Organise by", "organiseBy": "Organise by",
"heading1": "Заголовок 1", "heading1": "Заголовок 1",
"heading2": "Заголовок 2", "heading2": "Заголовок 2",
@ -468,6 +468,7 @@
"noToken": "Нет токена", "noToken": "Нет токена",
"tokenLimit": "Для каждого пользователя разрешен только один токен", "tokenLimit": "Для каждого пользователя разрешен только один токен",
"duplicateAttachment": "Файл с именем {filename} уже прикреплен", "duplicateAttachment": "Файл с именем {filename} уже прикреплен",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "Адрес", "toAddress": "Адрес",
"subject": "Тема", "subject": "Тема",
@ -506,6 +507,7 @@
"clickToHide": "Нажмите, чтобы скрыть", "clickToHide": "Нажмите, чтобы скрыть",
"clickToDownload": "Нажмите, чтобы загрузить", "clickToDownload": "Нажмите, чтобы загрузить",
"forRole": "для роли", "forRole": "для роли",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Нажмите, чтобы скопировать ID вида", "clickToCopyViewID": "Нажмите, чтобы скопировать ID вида",
"viewMode": "Режим просмотра", "viewMode": "Режим просмотра",
"searchUsers": "Поиск пользователей", "searchUsers": "Поиск пользователей",
@ -573,7 +575,7 @@
"where": "Где", "where": "Где",
"cache": "Кэш", "cache": "Кэш",
"chat": "Чат", "chat": "Чат",
"showOrHide": "Show or Hide", "showOrHide": "Показать / Скрыть",
"airtable": "Airtable", "airtable": "Airtable",
"csv": "CSV", "csv": "CSV",
"csvFile": "Файл CSV", "csvFile": "Файл CSV",
@ -589,7 +591,7 @@
"created": "Созданный", "created": "Созданный",
"sqlOutput": "Вывод SQL", "sqlOutput": "Вывод SQL",
"addOption": "Добавить настройку", "addOption": "Добавить настройку",
"interfaceColor": "Interface Color", "interfaceColor": "Цвет интерфейса",
"qrCodeValueColumn": "Столбец с QR-кодом", "qrCodeValueColumn": "Столбец с QR-кодом",
"barcodeValueColumn": "Колонка со значением штрих-кода", "barcodeValueColumn": "Колонка со значением штрих-кода",
"barcodeFormat": "Формат штрих-кода", "barcodeFormat": "Формат штрих-кода",
@ -618,7 +620,7 @@
"joinCommunity": "Сообщество NocoDB", "joinCommunity": "Сообщество NocoDB",
"joinReddit": "Присоединиться /r/NocoDB", "joinReddit": "Присоединиться /r/NocoDB",
"followNocodb": "Следите за NocoDB", "followNocodb": "Следите за NocoDB",
"communityTranslated": "(Community Translated)" "communityTranslated": "(Перевод сообщества)"
}, },
"twitter": "Twitter", "twitter": "Twitter",
"docReference": "Ссылка на документ", "docReference": "Ссылка на документ",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Ämne", "subject": "Ämne",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

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

@ -468,6 +468,7 @@
"noToken": "无令牌", "noToken": "无令牌",
"tokenLimit": "每个用户只能使用一个令牌", "tokenLimit": "每个用户只能使用一个令牌",
"duplicateAttachment": "文件{filename} 已附加", "duplicateAttachment": "文件{filename} 已附加",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "查看 ID:{viewId}", "viewIdColon": "查看 ID:{viewId}",
"toAddress": "目的地址", "toAddress": "目的地址",
"subject": "主题", "subject": "主题",
@ -506,6 +507,7 @@
"clickToHide": "点击隐藏", "clickToHide": "点击隐藏",
"clickToDownload": "点击下载", "clickToDownload": "点击下载",
"forRole": "为角色", "forRole": "为角色",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "点击复制视图ID", "clickToCopyViewID": "点击复制视图ID",
"viewMode": "视图模式", "viewMode": "视图模式",
"searchUsers": "搜索用户", "searchUsers": "搜索用户",

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

@ -468,6 +468,7 @@
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"tableIdColon": "TABLE ID: {tableId}",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
@ -506,6 +507,7 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Search Users",

28
packages/nc-gui/package.json

@ -41,14 +41,14 @@
"@iconify/vue": "^4.1.1", "@iconify/vue": "^4.1.1",
"@nuxt/image": "^1.3.0", "@nuxt/image": "^1.3.0",
"@pinia/nuxt": "^0.5.1", "@pinia/nuxt": "^0.5.1",
"@tiptap/extension-link": "2.2.4", "@tiptap/extension-link": "2.2.6",
"@tiptap/extension-placeholder": "^2.2.4", "@tiptap/extension-placeholder": "^2.2.6",
"@tiptap/extension-task-list": "2.2.4", "@tiptap/extension-task-list": "2.2.6",
"@tiptap/extension-underline": "^2.2.4", "@tiptap/extension-underline": "^2.2.6",
"@tiptap/html": "2.2.4", "@tiptap/html": "2.2.6",
"@tiptap/pm": "^2.2.4", "@tiptap/pm": "^2.2.6",
"@tiptap/starter-kit": "^2.2.4", "@tiptap/starter-kit": "^2.2.6",
"@tiptap/vue-3": "2.2.4", "@tiptap/vue-3": "2.2.6",
"@vue-flow/additional-components": "^1.3.3", "@vue-flow/additional-components": "^1.3.3",
"@vue-flow/core": "^1.30.1", "@vue-flow/core": "^1.30.1",
"@vuelidate/core": "^2.0.3", "@vuelidate/core": "^2.0.3",
@ -117,13 +117,13 @@
"@iconify-json/ion": "^1.1.17", "@iconify-json/ion": "^1.1.17",
"@iconify-json/la": "^1.1.8", "@iconify-json/la": "^1.1.8",
"@iconify-json/logos": "^1.1.42", "@iconify-json/logos": "^1.1.42",
"@iconify-json/lucide": "^1.1.178", "@iconify-json/lucide": "^1.1.180",
"@iconify-json/material-symbols": "^1.1.76", "@iconify-json/material-symbols": "^1.1.77",
"@iconify-json/mdi": "^1.1.64", "@iconify-json/mdi": "^1.1.66",
"@iconify-json/mi": "^1.1.8", "@iconify-json/mi": "^1.1.8",
"@iconify-json/ph": "^1.1.11", "@iconify-json/ph": "^1.1.12",
"@iconify-json/ri": "^1.1.20", "@iconify-json/ri": "^1.1.20",
"@iconify-json/simple-icons": "^1.1.97", "@iconify-json/simple-icons": "^1.1.99",
"@iconify-json/system-uicons": "^1.1.12", "@iconify-json/system-uicons": "^1.1.12",
"@iconify-json/tabler": "^1.1.109", "@iconify-json/tabler": "^1.1.109",
"@iconify-json/vscode-icons": "^1.1.33", "@iconify-json/vscode-icons": "^1.1.33",
@ -132,7 +132,7 @@
"@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.9",
"@types/leaflet.markercluster": "^1.5.4", "@types/leaflet.markercluster": "^1.5.4",
"@types/papaparse": "^5.3.14", "@types/papaparse": "^5.3.14",
"@types/parse-github-url": "^1.0.3", "@types/parse-github-url": "^1.0.3",

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

@ -226,13 +226,17 @@ const showSubmitConfirmModal = async () => {
} }
onKeyStroke(['ArrowLeft', 'ArrowDown'], () => { onKeyStroke(['ArrowLeft', 'ArrowDown'], () => {
if (isMobileMode.value) return
goPrevious(AnimationTarget.ArrowLeft) goPrevious(AnimationTarget.ArrowLeft)
}) })
onKeyStroke(['ArrowRight', 'ArrowUp'], () => { onKeyStroke(['ArrowRight', 'ArrowUp'], () => {
if (isMobileMode.value) return
goNext(AnimationTarget.ArrowRight) goNext(AnimationTarget.ArrowRight)
}) })
onKeyStroke(['Enter'], async (e) => { onKeyStroke(['Enter'], async (e) => {
if (submitted.value) return if (isMobileMode.value || submitted.value) return
if (!isStarted.value && !submitted.value) { if (!isStarted.value && !submitted.value) {
onStart() onStart()
@ -441,7 +445,10 @@ onMounted(() => {
</div> </div>
</template> </template>
<div v-if="field.uidt === UITypes.LongText" class="text-sm text-gray-500 flex flex-wrap items-center"> <div
v-if="field.uidt === UITypes.LongText"
class="hidden text-sm text-gray-500 md:flex flex-wrap items-center"
>
{{ $t('general.shift') }} <MdiAppleKeyboardShift class="mx-1 text-primary" /> + {{ $t('general.enter') }} {{ $t('general.shift') }} <MdiAppleKeyboardShift class="mx-1 text-primary" /> + {{ $t('general.enter') }}
<MaterialSymbolsKeyboardReturn class="mx-1 text-primary" /> {{ $t('msg.info.makeLineBreak') }} <MaterialSymbolsKeyboardReturn class="mx-1 text-primary" /> {{ $t('msg.info.makeLineBreak') }}
</div> </div>

15
packages/nc-gui/store/tables.ts

@ -1,6 +1,5 @@
import { acceptHMRUpdate, defineStore } from 'pinia' import { acceptHMRUpdate, defineStore } from 'pinia'
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import { useTitle } from '@vueuse/core'
import type { SidebarTableNode } from '~/lib' import type { SidebarTableNode } from '~/lib'
export const useTablesStore = defineStore('tablesStore', () => { export const useTablesStore = defineStore('tablesStore', () => {
@ -49,20 +48,6 @@ export const useTablesStore = defineStore('tablesStore', () => {
return activeTables.value.find((t) => t.id === activeTableId.value) return activeTables.value.find((t) => t.id === activeTableId.value)
}) })
watch(
() => activeTable.value?.title,
(title) => {
if (basesStore.openedProject?.type !== 'database') return
if (!title) {
useTitle(basesStore.openedProject?.title)
return
}
useTitle(`${basesStore.openedProject?.title}: ${title}`)
},
)
const loadProjectTables = async (baseId: string, force = false) => { const loadProjectTables = async (baseId: string, force = false) => {
if (!force && baseTables.value.get(baseId)) { if (!force && baseTables.value.get(baseId)) {
return return

42
packages/nc-gui/store/views.ts

@ -1,7 +1,9 @@
import type { FilterType, SortType, ViewType, ViewTypes } from 'nocodb-sdk' import type { FilterType, SortType, ViewType, ViewTypes } from 'nocodb-sdk'
import { acceptHMRUpdate, defineStore } from 'pinia' import { acceptHMRUpdate, defineStore } from 'pinia'
import { useTitle } from '@vueuse/core'
import type { ViewPageType } from '~/lib' import type { ViewPageType } from '~/lib'
import { navigateToBlankTargetOpenOption, useMagicKeys } from '#imports' import { navigateToBlankTargetOpenOption, useMagicKeys } from '#imports'
import { getFormattedViewTabTitle } from '~/helpers/parsers/parserHelpers'
export const useViewsStore = defineStore('viewsStore', () => { export const useViewsStore = defineStore('viewsStore', () => {
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
@ -23,6 +25,7 @@ export const useViewsStore = defineStore('viewsStore', () => {
const route = router.currentRoute const route = router.currentRoute
const bases = useBases() const bases = useBases()
const { openedProject } = storeToRefs(bases)
const tablesStore = useTablesStore() const tablesStore = useTablesStore()
@ -121,6 +124,8 @@ export const useViewsStore = defineStore('viewsStore', () => {
const preFillFormSearchParams = ref('') const preFillFormSearchParams = ref('')
const refreshViewTabTitle = createEventHook<void>()
const loadViews = async ({ const loadViews = async ({
tableId, tableId,
ignoreLoading, ignoreLoading,
@ -343,6 +348,42 @@ export const useViewsStore = defineStore('viewsStore', () => {
] ]
}) })
const updateTabTitle = () => {
if (!activeView.value || !activeView.value.base_id) {
if (openedProject.value?.title) {
useTitle(openedProject.value?.title)
}
return
}
const tableName = tablesStore.baseTables
.get(activeView.value.base_id)
?.find((t) => t.id === activeView.value.fk_model_id)?.title
const baseName = bases.basesList.find((p) => p.id === activeView.value.base_id)?.title
useTitle(
getFormattedViewTabTitle({
viewName: activeView.value.title,
tableName: tableName || '',
baseName: baseName || '',
isDefaultView: !!activeView.value.is_default,
isSharedView: !!sharedView.value?.id,
}),
)
}
refreshViewTabTitle.on(() => {
updateTabTitle()
})
watch(
() => [activeView.value?.title, activeView.value?.id],
() => {
refreshViewTabTitle.trigger()
},
)
return { return {
isLockedView, isLockedView,
isViewsLoading, isViewsLoading,
@ -365,6 +406,7 @@ export const useViewsStore = defineStore('viewsStore', () => {
activeNestedFilters, activeNestedFilters,
isActiveViewLocked, isActiveViewLocked,
preFillFormSearchParams, preFillFormSearchParams,
refreshViewTabTitle: refreshViewTabTitle.trigger,
} }
}) })

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

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

2
packages/nocodb-sdk/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.204.9", "version": "0.205.0",
"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",

8
packages/nocodb/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb", "name": "nocodb",
"version": "0.204.9", "version": "0.205.0",
"description": "NocoDB Backend", "description": "NocoDB Backend",
"main": "dist/bundle.js", "main": "dist/bundle.js",
"author": { "author": {
@ -132,7 +132,7 @@
"mysql2": "^3.9.3", "mysql2": "^3.9.3",
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"nc-help": "0.3.1", "nc-help": "0.3.1",
"nc-lib-gui": "0.204.9", "nc-lib-gui": "0.205.1",
"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",
@ -153,7 +153,7 @@
"passport-http": "^0.3.0", "passport-http": "^0.3.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"pg": "^8.11.4", "pg": "^8.11.5",
"redlock": "^5.0.0-beta.2", "redlock": "^5.0.0-beta.2",
"reflect-metadata": "^0.2.1", "reflect-metadata": "^0.2.1",
"request-filtering-agent": "^1.1.2", "request-filtering-agent": "^1.1.2",
@ -229,4 +229,4 @@
"coverageDirectory": "../coverage", "coverageDirectory": "../coverage",
"testEnvironment": "node" "testEnvironment": "node"
} }
} }

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

@ -79,18 +79,13 @@ export default class CalendarViewColumn {
insertObj.source_id = viewRef.source_id; insertObj.source_id = viewRef.source_id;
} }
const { id, fk_column_id } = await ncMeta.metaInsert2( const { id } = await ncMeta.metaInsert2(
null, null,
null, null,
MetaTable.CALENDAR_VIEW_COLUMNS, MetaTable.CALENDAR_VIEW_COLUMNS,
insertObj, insertObj,
); );
await NocoCache.set(
`${CacheScope.CALENDAR_VIEW_COLUMN}:${fk_column_id}`,
id,
);
{ {
const view = await View.get(column.fk_view_id, ncMeta); const view = await View.get(column.fk_view_id, ncMeta);
await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta); await View.clearSingleQueryCache(view.fk_model_id, [view], ncMeta);

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

@ -823,64 +823,33 @@ export default class Column<T = any> implements ColumnType {
); );
} }
// Grid View Columns // Delete from all view columns
await ncMeta.metaDelete(null, null, MetaTable.GRID_VIEW_COLUMNS, { const viewColumnTables = [
fk_column_id: col.id, MetaTable.GRID_VIEW_COLUMNS,
}); MetaTable.FORM_VIEW_COLUMNS,
const gridViewColumnId = await NocoCache.get( MetaTable.KANBAN_VIEW_COLUMNS,
`${CacheScope.GRID_VIEW_COLUMN}:${col.id}`, MetaTable.GALLERY_VIEW_COLUMNS,
CacheGetType.TYPE_STRING, ];
); const viewColumnCacheScope = [
if (gridViewColumnId) { CacheScope.GRID_VIEW_COLUMN,
await NocoCache.deepDel( CacheScope.FORM_VIEW_COLUMN,
`${CacheScope.GRID_VIEW_COLUMN}:${gridViewColumnId}`, CacheScope.KANBAN_VIEW_COLUMN,
CacheDelDirection.CHILD_TO_PARENT, CacheScope.GALLERY_VIEW_COLUMN,
); ];
}
for (let i = 0; i < viewColumnTables.length; i++) {
// Form View Columns const table = viewColumnTables[i];
await ncMeta.metaDelete(null, null, MetaTable.FORM_VIEW_COLUMNS, { const cacheScope = viewColumnCacheScope[i];
fk_column_id: col.id, const viewColumns = await ncMeta.metaList2(null, null, table, {
}); condition: { fk_column_id: id },
const formViewColumnId = await NocoCache.get( });
`${CacheScope.FORM_VIEW_COLUMN}:${col.id}`, await ncMeta.metaDelete(null, null, table, { fk_column_id: id });
CacheGetType.TYPE_STRING, for (const viewColumn of viewColumns) {
); await NocoCache.deepDel(
if (formViewColumnId) { `${cacheScope}:${viewColumn.id}`,
await NocoCache.deepDel( CacheDelDirection.CHILD_TO_PARENT,
`${CacheScope.FORM_VIEW_COLUMN}:${formViewColumnId}`, );
CacheDelDirection.CHILD_TO_PARENT, }
);
}
// Kanban View Columns
await ncMeta.metaDelete(null, null, MetaTable.KANBAN_VIEW_COLUMNS, {
fk_column_id: col.id,
});
const kanbanViewColumnId = await NocoCache.get(
`${CacheScope.KANBAN_VIEW_COLUMN}:${col.id}`,
CacheGetType.TYPE_STRING,
);
if (kanbanViewColumnId) {
await NocoCache.deepDel(
`${CacheScope.KANBAN_VIEW_COLUMN}:${kanbanViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT,
);
}
// Gallery View Column
await ncMeta.metaDelete(null, null, MetaTable.GALLERY_VIEW_COLUMNS, {
fk_column_id: col.id,
});
const galleryViewColumnId = await NocoCache.get(
`${CacheScope.GALLERY_VIEW_COLUMN}:${col.id}`,
CacheGetType.TYPE_STRING,
);
if (galleryViewColumnId) {
await NocoCache.deepDel(
`${CacheScope.GALLERY_VIEW_COLUMN}:${galleryViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT,
);
} }
// Get LTAR columns in which current column is referenced as foreign key // Get LTAR columns in which current column is referenced as foreign key

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

@ -92,15 +92,13 @@ export default class FormViewColumn implements FormColumnType {
insertObj.source_id = viewRef.source_id; insertObj.source_id = viewRef.source_id;
} }
const { id, fk_column_id } = await ncMeta.metaInsert2( const { id } = await ncMeta.metaInsert2(
null, null,
null, null,
MetaTable.FORM_VIEW_COLUMNS, MetaTable.FORM_VIEW_COLUMNS,
insertObj, insertObj,
); );
await NocoCache.set(`${CacheScope.FORM_VIEW_COLUMN}:${fk_column_id}`, id);
return this.get(id, ncMeta).then(async (viewColumn) => { return this.get(id, ncMeta).then(async (viewColumn) => {
await NocoCache.appendToList( await NocoCache.appendToList(
CacheScope.FORM_VIEW_COLUMN, CacheScope.FORM_VIEW_COLUMN,

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

@ -66,18 +66,13 @@ export default class GalleryViewColumn {
insertObj.source_id = viewRef.source_id; insertObj.source_id = viewRef.source_id;
} }
const { id, fk_column_id } = await ncMeta.metaInsert2( const { id } = await ncMeta.metaInsert2(
null, null,
null, null,
MetaTable.GALLERY_VIEW_COLUMNS, MetaTable.GALLERY_VIEW_COLUMNS,
insertObj, insertObj,
); );
await NocoCache.set(
`${CacheScope.GALLERY_VIEW_COLUMN}:${fk_column_id}`,
id,
);
// on new view column, delete any optimised single query cache // on new view column, delete any optimised single query cache
{ {
const view = await View.get(column.fk_view_id, ncMeta); const view = await View.get(column.fk_view_id, ncMeta);

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

@ -107,8 +107,6 @@ export default class GridViewColumn implements GridColumnType {
insertObj, insertObj,
); );
await NocoCache.set(`${CacheScope.GRID_VIEW_COLUMN}:${fk_column_id}`, id);
await View.fixPVColumnForView(column.fk_view_id, ncMeta); await View.fixPVColumnForView(column.fk_view_id, ncMeta);
// on new view column, delete any optimised single query cache // on new view column, delete any optimised single query cache

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

@ -63,15 +63,13 @@ export default class KanbanViewColumn implements KanbanColumnType {
insertObj.source_id = viewRef.source_id; insertObj.source_id = viewRef.source_id;
} }
const { id, fk_column_id } = await ncMeta.metaInsert2( const { id } = await ncMeta.metaInsert2(
null, null,
null, null,
MetaTable.KANBAN_VIEW_COLUMNS, MetaTable.KANBAN_VIEW_COLUMNS,
insertObj, insertObj,
); );
await NocoCache.set(`${CacheScope.KANBAN_VIEW_COLUMN}:${fk_column_id}`, id);
return this.get(id, ncMeta).then(async (kanbanViewColumn) => { return this.get(id, ncMeta).then(async (kanbanViewColumn) => {
await NocoCache.appendToList( await NocoCache.appendToList(
CacheScope.KANBAN_VIEW_COLUMN, CacheScope.KANBAN_VIEW_COLUMN,

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

@ -57,15 +57,13 @@ export default class MapViewColumn {
insertObj.source_id = viewRef.source_id; insertObj.source_id = viewRef.source_id;
} }
const { id, fk_column_id } = await ncMeta.metaInsert2( const { id } = await ncMeta.metaInsert2(
null, null,
null, null,
MetaTable.MAP_VIEW_COLUMNS, MetaTable.MAP_VIEW_COLUMNS,
insertObj, insertObj,
); );
await NocoCache.set(`${CacheScope.MAP_VIEW_COLUMN}:${fk_column_id}`, id);
return this.get(id, ncMeta).then(async (viewCol) => { return this.get(id, ncMeta).then(async (viewCol) => {
await NocoCache.appendToList( await NocoCache.appendToList(
CacheScope.MAP_VIEW_COLUMN, CacheScope.MAP_VIEW_COLUMN,

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

@ -9,7 +9,7 @@ import dayjs from 'dayjs';
import type { BoolType, TableReqType, TableType } from 'nocodb-sdk'; import type { BoolType, TableReqType, TableType } from 'nocodb-sdk';
import type { XKnex } from '~/db/CustomKnex'; import type { XKnex } from '~/db/CustomKnex';
import type { LinkToAnotherRecordColumn } from '~/models/index'; import type { LinksColumn, LinkToAnotherRecordColumn } from '~/models/index';
import Hook from '~/models/Hook'; import Hook from '~/models/Hook';
import Audit from '~/models/Audit'; import Audit from '~/models/Audit';
import View from '~/models/View'; import View from '~/models/View';
@ -817,6 +817,24 @@ export default class Model implements TableType {
} }
} }
// use set to avoid duplicate
const relatedModelIds = new Set<string>();
// clear all single query cache of related views
for (const col of model.columns) {
if (!isLinksOrLTAR(col)) continue;
const colOptions = await col.getColOptions<
LinkToAnotherRecordColumn | LinksColumn
>();
relatedModelIds.add(colOptions?.fk_related_model_id);
}
await Promise.all(
Array.from(relatedModelIds).map(async (modelId: string) => {
await View.clearSingleQueryCache(modelId, null, ncMeta);
}),
);
return true; return true;
} }

21
packages/nocodb/src/models/UserRefreshToken.ts

@ -1,9 +1,18 @@
import process from 'process';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Noco from '~/Noco'; import Noco from '~/Noco';
import { extractProps } from '~/helpers/extractProps'; import { extractProps } from '~/helpers/extractProps';
import { MetaTable } from '~/utils/globals'; import { MetaTable } from '~/utils/globals';
import { parseMetaProp, stringifyMetaProp } from '~/utils/modelUtils'; import { parseMetaProp, stringifyMetaProp } from '~/utils/modelUtils';
const NC_REFRESH_TOKEN_EXP_IN_DAYS =
parseInt(process.env.NC_REFRESH_TOKEN_EXP_IN_DAYS, 10) || 90;
// throw error if user provided invalid value
if (NC_REFRESH_TOKEN_EXP_IN_DAYS <= 0) {
throw new Error('NC_REFRESH_TOKEN_EXP_IN_DAYS must be a positive number');
}
export default class UserRefreshToken { export default class UserRefreshToken {
fk_user_id: string; fk_user_id: string;
token: string; token: string;
@ -39,9 +48,11 @@ export default class UserRefreshToken {
'meta', 'meta',
]); ]);
// set default expiry as 90 days if missing // set expiry based on the env or default value
if (!('expires_at' in insertObj)) { if (!('expires_at' in insertObj)) {
insertObj.expires_at = dayjs().add(90, 'day').toDate(); insertObj.expires_at = dayjs()
.add(NC_REFRESH_TOKEN_EXP_IN_DAYS, 'day')
.toDate();
} }
if ('meta' in insertObj) { if ('meta' in insertObj) {
@ -68,11 +79,11 @@ export default class UserRefreshToken {
null, null,
MetaTable.USER_REFRESH_TOKENS, MetaTable.USER_REFRESH_TOKENS,
{ {
token: oldToken, token: newToken,
expires_at: dayjs().add(90, 'day').toDate(), expires_at: dayjs().add(NC_REFRESH_TOKEN_EXP_IN_DAYS, 'day').toDate(),
}, },
{ {
token: newToken, token: oldToken,
}, },
); );
} }

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

@ -1898,6 +1898,9 @@ export default class View implements ViewType {
'fk_parent_id', 'fk_parent_id',
'is_group', 'is_group',
'logical_op', 'logical_op',
'base_id',
'source_id',
'order',
]), ]),
fk_view_id: view_id, fk_view_id: view_id,
id: generatedId, id: generatedId,

4
packages/nocodb/src/services/public-metas.service.ts

@ -41,6 +41,10 @@ export class PublicMetasService {
view.model.columns = view.columns view.model.columns = view.columns
.filter((c) => { .filter((c) => {
const column = view.model.columnsById[c.fk_column_id]; const column = view.model.columnsById[c.fk_column_id];
// Check if column exists to prevent processing non-existent columns
if (!column) return false;
return ( return (
c.show || c.show ||
(column.rqd && !column.cdf && !column.ai) || (column.rqd && !column.cdf && !column.ai) ||

16
packages/nocodb/src/services/users/users.service.ts

@ -370,9 +370,9 @@ export class UsersService {
NcError.badRequest(`Missing refresh token`); NcError.badRequest(`Missing refresh token`);
} }
const user = await User.getByRefreshToken( const oldRefreshToken = param.req.cookies.refresh_token;
param.req.cookies.refresh_token,
); const user = await User.getByRefreshToken(oldRefreshToken);
if (!user) { if (!user) {
NcError.badRequest(`Invalid refresh token`); NcError.badRequest(`Invalid refresh token`);
@ -380,10 +380,12 @@ export class UsersService {
const refreshToken = randomTokenString(); const refreshToken = randomTokenString();
await UserRefreshToken.insert({ try {
token: refreshToken, await UserRefreshToken.updateOldToken(oldRefreshToken, refreshToken);
fk_user_id: user.id, } catch (error) {
}); console.error('Failed to update old refresh token:', error);
NcError.internalServerError('Failed to update refresh token');
}
setTokenCookie(param.res, refreshToken); setTokenCookie(param.res, refreshToken);

4
packages/nocodb/src/services/views.service.ts

@ -76,6 +76,10 @@ export class ViewsService {
}) { }) {
const model = await Model.get(param.tableId); const model = await Model.get(param.tableId);
if (!model) {
NcError.tableNotFound(param.tableId);
}
const viewList = await xcVisibilityMetaGet({ const viewList = await xcVisibilityMetaGet({
baseId: model.base_id, baseId: model.base_id,
models: [model], models: [model],

378
pnpm-lock.yaml

@ -52,29 +52,29 @@ importers:
specifier: ^0.5.1 specifier: ^0.5.1
version: 0.5.1(vue@3.3.13) version: 0.5.1(vue@3.3.13)
'@tiptap/extension-link': '@tiptap/extension-link':
specifier: 2.2.4 specifier: 2.2.6
version: 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) version: 2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6)
'@tiptap/extension-placeholder': '@tiptap/extension-placeholder':
specifier: ^2.2.4 specifier: ^2.2.6
version: 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) version: 2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6)
'@tiptap/extension-task-list': '@tiptap/extension-task-list':
specifier: 2.2.4 specifier: 2.2.6
version: 2.2.4(@tiptap/core@2.2.4) version: 2.2.6(@tiptap/core@2.2.5)
'@tiptap/extension-underline': '@tiptap/extension-underline':
specifier: ^2.2.4 specifier: ^2.2.6
version: 2.2.4(@tiptap/core@2.2.4) version: 2.2.6(@tiptap/core@2.2.5)
'@tiptap/html': '@tiptap/html':
specifier: 2.2.4 specifier: 2.2.6
version: 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) version: 2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6)
'@tiptap/pm': '@tiptap/pm':
specifier: ^2.2.4 specifier: ^2.2.6
version: 2.2.4 version: 2.2.6
'@tiptap/starter-kit': '@tiptap/starter-kit':
specifier: ^2.2.4 specifier: ^2.2.6
version: 2.2.4(@tiptap/pm@2.2.4) version: 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/vue-3': '@tiptap/vue-3':
specifier: 2.2.4 specifier: 2.2.6
version: 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4)(vue@3.3.13) version: 2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6)(vue@3.3.13)
'@vue-flow/additional-components': '@vue-flow/additional-components':
specifier: ^1.3.3 specifier: ^1.3.3
version: 1.3.3(@vue-flow/core@1.31.0)(vue@3.3.13) version: 1.3.3(@vue-flow/core@1.31.0)(vue@3.3.13)
@ -275,26 +275,26 @@ importers:
specifier: ^1.1.42 specifier: ^1.1.42
version: 1.1.42 version: 1.1.42
'@iconify-json/lucide': '@iconify-json/lucide':
specifier: ^1.1.178 specifier: ^1.1.180
version: 1.1.178 version: 1.1.180
'@iconify-json/material-symbols': '@iconify-json/material-symbols':
specifier: ^1.1.76 specifier: ^1.1.77
version: 1.1.76 version: 1.1.77
'@iconify-json/mdi': '@iconify-json/mdi':
specifier: ^1.1.64 specifier: ^1.1.66
version: 1.1.64 version: 1.1.66
'@iconify-json/mi': '@iconify-json/mi':
specifier: ^1.1.8 specifier: ^1.1.8
version: 1.1.8 version: 1.1.8
'@iconify-json/ph': '@iconify-json/ph':
specifier: ^1.1.11 specifier: ^1.1.12
version: 1.1.11 version: 1.1.12
'@iconify-json/ri': '@iconify-json/ri':
specifier: ^1.1.20 specifier: ^1.1.20
version: 1.1.20 version: 1.1.20
'@iconify-json/simple-icons': '@iconify-json/simple-icons':
specifier: ^1.1.97 specifier: ^1.1.99
version: 1.1.97 version: 1.1.99
'@iconify-json/system-uicons': '@iconify-json/system-uicons':
specifier: ^1.1.12 specifier: ^1.1.12
version: 1.1.12 version: 1.1.12
@ -317,8 +317,8 @@ importers:
specifier: ^2.0.7 specifier: ^2.0.7
version: 2.0.7 version: 2.0.7
'@types/leaflet': '@types/leaflet':
specifier: ^1.9.8 specifier: ^1.9.9
version: 1.9.8 version: 1.9.9
'@types/leaflet.markercluster': '@types/leaflet.markercluster':
specifier: ^1.5.4 specifier: ^1.5.4
version: 1.5.4 version: 1.5.4
@ -645,7 +645,7 @@ importers:
version: 2.2.4 version: 2.2.4
knex: knex:
specifier: 2.4.2 specifier: 2.4.2
version: 2.4.2(mysql2@3.9.3)(pg@8.11.4)(sqlite3@5.1.7)(tedious@16.6.1) version: 2.4.2(mysql2@3.9.3)(pg@8.11.5)(sqlite3@5.1.7)(tedious@16.6.1)
list-github-dir-content: list-github-dir-content:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
@ -686,8 +686,8 @@ importers:
specifier: 0.3.1 specifier: 0.3.1
version: 0.3.1(asn1.js@5.4.1)(debug@4.3.4)(knex@2.4.2) version: 0.3.1(asn1.js@5.4.1)(debug@4.3.4)(knex@2.4.2)
nc-lib-gui: nc-lib-gui:
specifier: 0.204.9 specifier: 0.205.1
version: 0.204.9 version: 0.205.1
nc-plugin: nc-plugin:
specifier: ^0.1.3 specifier: ^0.1.3
version: 0.1.3 version: 0.1.3
@ -749,8 +749,8 @@ importers:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0 version: 1.0.0
pg: pg:
specifier: ^8.11.4 specifier: ^8.11.5
version: 8.11.4 version: 8.11.5
redlock: redlock:
specifier: ^5.0.0-beta.2 specifier: ^5.0.0-beta.2
version: 5.0.0-beta.2 version: 5.0.0-beta.2
@ -4339,20 +4339,20 @@ packages:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/lucide@1.1.178: /@iconify-json/lucide@1.1.180:
resolution: {integrity: sha512-IW5Z3ytfOPtcAlIgeMCGNrbPD/wfOBOyUAXEUgNhGTWxJICtbeHQkmL4mEoJmScSkjpZvyevxOC9kfdJG8VBtg==} resolution: {integrity: sha512-QqDgX6lWmabgHSLPpc5bxiqoykNaXj2YTXZLmTqrhJj1wnDXQ5Dw4Gj7LR3mVtdQVx5KOSAkb6pmXxDB1ON24w==}
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/material-symbols@1.1.76: /@iconify-json/material-symbols@1.1.77:
resolution: {integrity: sha512-U3S0n2eENEZmAK+b+zvNBPxHySm7eo9bFQaKODlbrFyjNfaZVwpQMs7pZ3ZmeX9YLp0CtV8XqzmRb2YdhLJIkQ==} resolution: {integrity: sha512-b+jAkZKxBrGXXV76psToCGYVzu2IzyM8fxgiLOc6uPLB3R4vhrQGKT1J3wH9JnURShwQenEVPVNWetr5sj0Trg==}
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/mdi@1.1.64: /@iconify-json/mdi@1.1.66:
resolution: {integrity: sha512-zGeo5TjhNFAY6FmSDBLAzDO811t77r6v/mDi7CAL9w5eXqKez6bIjk8R9AL/RHIeq44ALP4Ozr4lMqFTkHr7ug==} resolution: {integrity: sha512-7KPF2RVUUWav/hXCM8Ti/smqu3cmgePJpiX9CSkldiL+80+eBRBeKlc4vPOc9jhAItlqIU1vKsbKoPP0JIfgbg==}
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
@ -4363,8 +4363,8 @@ packages:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/ph@1.1.11: /@iconify-json/ph@1.1.12:
resolution: {integrity: sha512-izLvLZrU7WM03zNwNWNrAySv3i45hM3D3K6USjCGBSQMUG0HB8zxwrMf4O0leDrganmuQBH7Z0DWTgyH1ET/fg==} resolution: {integrity: sha512-m+rXTW084YaQQHT+F8TxdkCoAh+i/5MWRoSuPmxCWPlxwMAaLT/QfyVsbEiV95HM5806U/jKpBV6F1b7Pmr3Vg==}
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
@ -4375,8 +4375,8 @@ packages:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/simple-icons@1.1.97: /@iconify-json/simple-icons@1.1.99:
resolution: {integrity: sha512-/jTwrcAM2Gh8FtgEn8mHolaoY1iNbRgH3AE8OXu7JULi4Tdp31u7bmCSIaNDTLdBrXjcDpw0y98Dm20TbauBCg==} resolution: {integrity: sha512-/csVihX/ztkl2j4Vk4JoEJv8DdQHrZafzC5QHe21Y+ztuVglvavjb4yWEn9KQHTitLH2fIMnP8+RSTxu5kdmtw==}
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
@ -8370,240 +8370,248 @@ packages:
resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==} resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==}
dev: false dev: false
/@tiptap/core@2.2.4(@tiptap/pm@2.2.4): /@tiptap/core@2.2.5(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-cRrI8IlLIhCE1hacBQzXIC8dsRvGq6a4lYWQK/BaHuZg21CG7szp3Vd8Ix+ra1f5v0xPOT+Hy+QFNQooRMKMCw==} resolution: {integrity: sha512-BgF6tiXhwph0Ig16ZNppMmg30t22Z/yjZ+KnS8EALdJere3ACNw8THz9q/8iQqBWjjBOHUvhrQOgER4BJxfmGA==}
peerDependencies:
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/pm': 2.2.6
dev: false
/@tiptap/core@2.2.6(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-v7S7RhQhTXQo9KSk2jM/jJlTd3clU2FsJA3Omjz7GbgYtPSy67qSiaTbH/tWq12GzDHbKymx+oQnKmyx+yPucA==}
peerDependencies: peerDependencies:
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
dev: false dev: false
/@tiptap/extension-blockquote@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-blockquote@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-FrfPnn0VgVrUwWLwja1afX99JGLp6PE9ThVcmri+tLwUZQvTTVcCvHoCdOakav3/nge1+aV4iE3tQdyq1tWI9Q==} resolution: {integrity: sha512-Qoq4Tl4wyEGfuBrMFth5hWP1SroJtgDYPnyzAZeLiGzF3Yxtu7FFqjGtD1/Bos9ftnFVCAj+nIXnuKsM1YUaGg==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-bold@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-bold@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-v3tTLc8YESFZPOGj5ByFr8VbmQ/PTo49T1vsK50VubxIN/5r9cXlKH8kb3dZlZxCxJa3FrXNO/M8rdGBSWQvSg==} resolution: {integrity: sha512-PI/jNH7rmi6hBvWy/z+3KUTYqeaDXBUjidM74gWP6OLV28HTJ5SkIPCriYe4u2j2Wc/nk3gPxs4/hPOAu/YiXA==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-bubble-menu@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-bubble-menu@2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-Nx1fS9jcFlhxaTDYlnayz2UulhK6CMaePc36+7PQIVI+u20RhgTCRNr25zKNemvsiM0RPZZVUjlHkxC0l5as1Q==} resolution: {integrity: sha512-nRWxbgkInhdGUL+e6iISgALcWh8A1PxeVB66w7yYZHS/WoZO0DXdXYT/BWb/XmEJ8r6B4c9SDZRklCiXT8dSXw==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.5(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
tippy.js: 6.3.7 tippy.js: 6.3.7
dev: false dev: false
/@tiptap/extension-bullet-list@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-bullet-list@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-z/MPmW8bhRougMuorl6MAQBXeK4rhlP+jBWlNwT+CT8h5IkXqPnDbM1sZeagp2nYfVV6Yc4RWpzimqHHtGnYTA==} resolution: {integrity: sha512-bSrmYlWfj/bXXoBMVB+gCTlsficVVzWi1jcAjAn+qNAENkhampmlFIUG4DiKGYtn18ZoTbyLgQGDMCO3SBdeDQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-code-block@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-code-block@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-h6WV9TmaBEZmvqe1ezMR83DhCPUap6P2mSR5pwVk0WVq6rvZjfgU0iF3EetBJOeDgPlz7cNe2NMDfVb1nGTM/g==} resolution: {integrity: sha512-834gVybNyI4nY6NINqnOosFPa4WKylMQTraEY2KhUH2XU1mh0Ni7EgyK10dfZvOUj90OjaxZtXkyZrZ89RTxog==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
dev: false dev: false
/@tiptap/extension-code@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-code@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-JB4SJ2mUU/9qXFUf+K5K9szvovnN9AIcCb0f0UlcVBuddKHSqCl3wO3QJgYt44BfQTLMNuyzr+zVqfFd6BNt/g==} resolution: {integrity: sha512-UGsSFvVWrWWWQFU4atk+b/qeewTLadOZG/BHZXQDloyP5eJ1SkgUVy9nv3y2cT8QWRbvF6sxkV+SdFoWnvaG3Q==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-document@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-document@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-z+05xGK0OFoXV1GL+/8bzcZuWMdMA3+EKwk5c+iziG60VZcvGTF7jBRsZidlu9Oaj0cDwWHCeeo6L9SgSh6i2A==} resolution: {integrity: sha512-yT9m5Oo9U/xAypcylaLiDE8qmVd3SCZSc8s5lqyC1OW+psb1oC0d14+TgKetO2s8K2wAbW2DxYG3yoxWffGYsQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-dropcursor@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-dropcursor@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-IHwkEKmqpqXyJi16h7871NrcIqeyN7I6XRE2qdqi+MhGigVWI8nWHoYbjRKa7K/1uhs5zeRYyDlq5EuZyL6mgA==} resolution: {integrity: sha512-mCeIbbfe4rl8CuxVQvT7iYSKGVX/ls1LOwALwlHJz5Uw5l3VknAJdjEmHt6hNFdHu162JivL02Il0QYQ8BZwvA==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
dev: false dev: false
/@tiptap/extension-floating-menu@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-floating-menu@2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-U25l7PEzOmlAPugNRl8t8lqyhQZS6W/+3f92+FdwW9qXju3i62iX/3OGCC3Gv+vybmQ4fbZmMjvl+VDfenNi3A==} resolution: {integrity: sha512-6ONKC6Dx8zCc5YffXpnQ9FxGRoUp5Jm9mOO3losgiDFhdJqaO7SCk1ziOiD7enoWqIc2shZh8ADnqttCfnFVFQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.5(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
tippy.js: 6.3.7 tippy.js: 6.3.7
dev: false dev: false
/@tiptap/extension-gapcursor@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-gapcursor@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-Y6htT/RDSqkQ1UwG2Ia+rNVRvxrKPOs3RbqKHPaWr3vbFWwhHyKhMCvi/FqfI3d5pViVHOZQ7jhb5hT/a0BmNw==} resolution: {integrity: sha512-HDYu+FmL9V+khsiT5904Dy2qG6KrAvnXEjZk1+vVul0TabnQvl2rqHjTxmev3P1rOYTgePmaWXazxAWFIvbMBQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
dev: false dev: false
/@tiptap/extension-hard-break@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-hard-break@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-FPvS57GcqHIeLbPKGJa3gnH30Xw+YB1PXXnAWG2MpnMtc2Vtj1l5xaYYBZB+ADdXLAlU0YMbKhFLQO4+pg1Isg==} resolution: {integrity: sha512-gwavC76sn26XQLyDaDtf28KIcbhMYPP+C5pkbRvAhVSckQB3Ebz3GRttVbm/jp+Uifp3bmoQEzISGCONEdKQoQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-heading@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-heading@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-gkq7Ns2FcrOCRq7Q+VRYt5saMt2R9g4REAtWy/jEevJ5UV5vA2AiGnYDmxwAkHutoYU0sAUkjqx37wE0wpamNw==} resolution: {integrity: sha512-XOmY+uezm42xSO1ero2bRBMdQxWytpxLJS+2shK0QogZ3sDplnfWfP5KV9Z2juXjTdPgPWG0ZaHzIIaLquEcfA==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-history@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-history@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-FDM32XYF5NU4mzh+fJ8w2CyUqv0l2Nl15sd6fOhQkVxSj8t57z+DUXc9ZR3zkH+1RAagYJo/2Gu3e99KpMr0tg==} resolution: {integrity: sha512-c2Aeozc+pHcpqghLjXRX/tGU/C+Gp6hApUWPXdhZw5Y/ARj6ZRwx2/ym2K8MOrJ36/W7gc7Xyxd9ZbG7m7pcjA==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
dev: false dev: false
/@tiptap/extension-horizontal-rule@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-horizontal-rule@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-iCRHjFQQHApWg3R4fkKkJQhWEOdu1Fdc4YEAukdOXPSg3fg36IwjvsMXjt9SYBtVZ+iio3rORCZGXyMvgCH9uw==} resolution: {integrity: sha512-zyLU+Xlk8y3yBCblE8pFwqAP2Rju1csyAu45hi3NCJ6HDGQGdjy8oh+Xa8y2kTPxRNMZARxqB+vCiEoW3YZn2A==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
dev: false dev: false
/@tiptap/extension-italic@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-italic@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-qIhGNvWnsQswSgEMRA8jQQjxfkOGNAuNWKEVQX9DPoqAUgknT41hQcAMP8L2+OdACpb2jbVMOO5Cy5Dof2L8/w==} resolution: {integrity: sha512-wB+Y6p2gbc1f2hKYeGNXRQ7P2xi3+JzD3PjSyC9Ss/yyujZhxSOtxBF0nzFXdI+7nmN0Qm4inwPDU/DVrIPb+A==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-link@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-link@2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-Qsx0cFZm4dxbkToXs5TcXbSoUdicv8db1gV1DYIZdETqjBm4wFjlzCUP7hPHFlvNfeSy1BzAMRt+RpeuiwvxWQ==} resolution: {integrity: sha512-Jj0oXSfQ8gZlzzwd669B8sEKBkoK8xV31Lu55tRv9PKHSU6p9CUqBuxY8qR+cquCtO28f3u0cdl5o4HzeIUL5A==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.5(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
linkifyjs: 4.1.3 linkifyjs: 4.1.3
dev: false dev: false
/@tiptap/extension-list-item@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-list-item@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-lPLKGKsHpM9ClUa8n7GEUn8pG6HCYU0vFruIy3l2t6jZdHkrgBnYtVGMZ13K8UDnj/hlAlccxku0D0P4mA1Vrg==} resolution: {integrity: sha512-3xig1q0jtOyV49TkAbvxBoOJdNypwq6vLYerfblhj6dK+hIIZUM33S+SmGl2+QaB25VwyeSHjiCvrJjB9PKWHQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-ordered-list@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-ordered-list@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-TpFy140O9Af1JciXt+xwqYUXxcJ6YG8zi/B5UDJujp+FH5sCmlYYBBnWxiFMhVaj6yEmA2eafu1qUkic/1X5Aw==} resolution: {integrity: sha512-h4HOv+TAMnoueh3CzUY2/Pp2n8eCdEQtKSfiMtHSO3NTTSlst0XEvq+3Z4K81F+ni3baXc+JUALP5dRVpI4apQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-paragraph@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-paragraph@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-m1KwyvTNJxsq7StbspbcOhxO4Wk4YpElDbqOouWi+H4c8azdpI5Pn96ZqhFeE9bSyjByg6OcB/wqoJsLbeFWdQ==} resolution: {integrity: sha512-M2rM3pfzziUb7xS9x2dANCokO89okbqg5IqU4VPkZhk0Mfq9czyCatt58TYkAsE3ccsGhdTYtFBTDeKBtsHUqg==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-placeholder@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/extension-placeholder@2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-UL4Fn9T33SoS7vdI3NnSxBJVeGUIgCIutgXZZ5J8CkcRoDIeS78z492z+6J+qGctHwTd0xUL5NzNJI82HfiTdg==} resolution: {integrity: sha512-eHPadx48gneDD8bTZeRnG4hOvRvctBPY5JlA03QQIoarrbmqsyv3zZSW8smBsRai9kwbXLhytdEFGruTKV9PjQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.5(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
dev: false dev: false
/@tiptap/extension-strike@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-strike@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-/a2EwQgA+PpG17V2tVRspcrIY0SN3blwcgM7lxdW4aucGkqSKnf7+91dkhQEwCZ//o8kv9mBCyRoCUcGy6S5Xg==} resolution: {integrity: sha512-0fRh0SwPgqi+ZKD2NpRrmIAHdsgf27ddEUfvlIuFG5b9zqFa6pRZGpXW/6LyBwU0+0bkjW8/Wg3otyaRGjvZGw==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-task-list@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-task-list@2.2.6(@tiptap/core@2.2.5):
resolution: {integrity: sha512-URh1Yzj/YZBOMkobK4/U8s1QYwIIqHm4b0YadLPPZx9IzTjyV/2bvIakphCmBtxWxeTXW5TbO9eNod3qatq21w==} resolution: {integrity: sha512-4ofrnm0jwk9OC5QH+b5wR4ck+J5wi+uOq7Qm2234GAqohjOzr4ndae68NSIv2XviDk04askCoZc+yZBxNwkhmQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.5(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-text@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-text@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-NlKHMPnRJXB+0AGtDlU0P2Pg+SdesA2lMMd7JzDUgJgL7pX2jOb8eUqSeOjFKuSzFSqYfH6C3o6mQiNhuQMv+g==} resolution: {integrity: sha512-wVpo0I/2tJsBK/2yNZfRXOsThOfHCdTY+FDNO/USx9MCJaJ3LPs3H1AuGO549zNmZgkD+1MqcZqrYt9n4i03cw==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/extension-underline@2.2.4(@tiptap/core@2.2.4): /@tiptap/extension-underline@2.2.6(@tiptap/core@2.2.5):
resolution: {integrity: sha512-jCHgIJMwtXlGHVy/j3L8/QvglHCikkHJw7YS5yf8E/8HlPh1tZfVy/IxdgacDOpUN30X+UPJZQDdVKymafgwdA==} resolution: {integrity: sha512-RaYEWuBHS6VQ2KXk+pP2b3xDN4vxmTb7+CF84mumR+CJUK4uAx01IDBUof+h/a4Sa58suNLQ6eHY33NmxPppnQ==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.5(@tiptap/pm@2.2.6)
dev: false dev: false
/@tiptap/html@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4): /@tiptap/html@2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-a6ORgv+X5CExUOdLVjAd4uIPPTrETLylyed/4vhaPIhe3fZKc+2oJrj87lxfUGasY4LV7Gpn+s08XM5fXyB61g==} resolution: {integrity: sha512-RdZ5Zr2b+LShyKaQKlWB3eLSzznH54Er8/78wKLuudE2xiUS1t25O1YgIrGUA1AFC1woSPJsS1uEvxwvKqE6eg==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.5(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
zeed-dom: 0.10.11 zeed-dom: 0.10.11
dev: false dev: false
/@tiptap/pm@2.2.4: /@tiptap/pm@2.2.6:
resolution: {integrity: sha512-Po0klR165zgtinhVp1nwMubjyKx6gAY9kH3IzcniYLCkqhPgiqnAcCr61TBpp4hfK8YURBS4ihvCB1dyfCyY8A==} resolution: {integrity: sha512-gSKJtsaMLiYNwcAdwgnlTVM9zHiHy6/WgJvXFmIoOnUgvMN10Bbr+KO5hoffwgLCCSpIWw6qJoVKMpHBexLm0w==}
dependencies: dependencies:
prosemirror-changeset: 2.2.1 prosemirror-changeset: 2.2.1
prosemirror-collab: 1.3.1 prosemirror-collab: 1.3.1
@ -8625,43 +8633,43 @@ packages:
prosemirror-view: 1.32.7 prosemirror-view: 1.32.7
dev: false dev: false
/@tiptap/starter-kit@2.2.4(@tiptap/pm@2.2.4): /@tiptap/starter-kit@2.2.6(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-Kbk7qUfIZg3+bNa3e/wBeDQt4jJB46uQgM+xy5NSY6H8NZP6gdmmap3aIrn9S/W/hGpxJl4RcXAeaT0CQji9XA==} resolution: {integrity: sha512-dWdLcx7g9DTYYzlnStft8vNLrnn+nUWj5Hx4i1dRRW31hBvIxnPwFYcEPKd+7xguozuUX5g+P4OYI6M3LOUxlA==}
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/extension-blockquote': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-blockquote': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-bold': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-bold': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-bullet-list': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-bullet-list': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-code': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-code': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-code-block': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) '@tiptap/extension-code-block': 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
'@tiptap/extension-document': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-document': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-dropcursor': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) '@tiptap/extension-dropcursor': 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
'@tiptap/extension-gapcursor': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) '@tiptap/extension-gapcursor': 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
'@tiptap/extension-hard-break': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-hard-break': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-heading': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-heading': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-history': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) '@tiptap/extension-history': 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
'@tiptap/extension-horizontal-rule': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) '@tiptap/extension-horizontal-rule': 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
'@tiptap/extension-italic': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-italic': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-list-item': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-list-item': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-ordered-list': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-ordered-list': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-paragraph': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-paragraph': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-strike': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-strike': 2.2.6(@tiptap/core@2.2.6)
'@tiptap/extension-text': 2.2.4(@tiptap/core@2.2.4) '@tiptap/extension-text': 2.2.6(@tiptap/core@2.2.6)
transitivePeerDependencies: transitivePeerDependencies:
- '@tiptap/pm' - '@tiptap/pm'
dev: false dev: false
/@tiptap/vue-3@2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4)(vue@3.3.13): /@tiptap/vue-3@2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6)(vue@3.3.13):
resolution: {integrity: sha512-6Rue56OUmDl/OT07QcLsH1UvYGUmV8OFSDCrLrUyku/2lAYHwHz6+KhAB5paZt70nEGIw03G1KCT074negj6NQ==} resolution: {integrity: sha512-F8hC133AF/48cvZReJun5TV35NtRcoH8LVEGsuHGNkH7BvJjXAciomvEO4HlSfqz1YT8M/hzRGNg1/R6ixv3bw==}
peerDependencies: peerDependencies:
'@tiptap/core': ^2.0.0 '@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0 '@tiptap/pm': ^2.0.0
vue: 3.3.13 vue: 3.3.13
dependencies: dependencies:
'@tiptap/core': 2.2.4(@tiptap/pm@2.2.4) '@tiptap/core': 2.2.5(@tiptap/pm@2.2.6)
'@tiptap/extension-bubble-menu': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) '@tiptap/extension-bubble-menu': 2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6)
'@tiptap/extension-floating-menu': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4) '@tiptap/extension-floating-menu': 2.2.6(@tiptap/core@2.2.5)(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.4 '@tiptap/pm': 2.2.6
vue: 3.3.13 vue: 3.3.13
dev: false dev: false
@ -8959,11 +8967,11 @@ packages:
/@types/leaflet.markercluster@1.5.4: /@types/leaflet.markercluster@1.5.4:
resolution: {integrity: sha512-tfMP8J62+wfsVLDLGh5Zh1JZxijCaBmVsMAX78MkLPwvPitmZZtSin5aWOVRhZrCS+pEOZwNzexbfWXlY+7yjg==} resolution: {integrity: sha512-tfMP8J62+wfsVLDLGh5Zh1JZxijCaBmVsMAX78MkLPwvPitmZZtSin5aWOVRhZrCS+pEOZwNzexbfWXlY+7yjg==}
dependencies: dependencies:
'@types/leaflet': 1.9.8 '@types/leaflet': 1.9.9
dev: true dev: true
/@types/leaflet@1.9.8: /@types/leaflet@1.9.9:
resolution: {integrity: sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==} resolution: {integrity: sha512-o0qD9ReJzWpGNIAY0O32NkpfM6rhV4sxnwVkz7x7Ah4Zy9sP+2T9Q3byccL5la1ZX416k+qiyvt8ksBavPPY7A==}
dependencies: dependencies:
'@types/geojson': 7946.0.10 '@types/geojson': 7946.0.10
dev: true dev: true
@ -14084,7 +14092,7 @@ packages:
functions-have-names: 1.2.3 functions-have-names: 1.2.3
get-intrinsic: 1.2.4 get-intrinsic: 1.2.4
globalthis: 1.0.3 globalthis: 1.0.3
has-property-descriptors: 1.0.0 has-property-descriptors: 1.0.2
dev: false dev: false
/es-define-property@1.0.0: /es-define-property@1.0.0:
@ -16588,12 +16596,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/has-property-descriptors@1.0.0:
resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
dependencies:
get-intrinsic: 1.2.4
dev: false
/has-property-descriptors@1.0.2: /has-property-descriptors@1.0.2:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
dependencies: dependencies:
@ -18637,7 +18639,7 @@ packages:
- supports-color - supports-color
dev: false dev: false
/knex@2.4.2(mysql2@3.9.3)(pg@8.11.4)(sqlite3@5.1.7)(tedious@16.6.1): /knex@2.4.2(mysql2@3.9.3)(pg@8.11.5)(sqlite3@5.1.7)(tedious@16.6.1):
resolution: {integrity: sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==} resolution: {integrity: sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==}
engines: {node: '>=12'} engines: {node: '>=12'}
hasBin: true hasBin: true
@ -18675,7 +18677,7 @@ packages:
interpret: 2.2.0 interpret: 2.2.0
lodash: 4.17.21 lodash: 4.17.21
mysql2: 3.9.3 mysql2: 3.9.3
pg: 8.11.4 pg: 8.11.5
pg-connection-string: 2.5.0 pg-connection-string: 2.5.0
rechoir: 0.8.0 rechoir: 0.8.0
resolve-from: 5.0.0 resolve-from: 5.0.0
@ -20077,7 +20079,7 @@ packages:
axios: 1.6.8(debug@4.3.4) axios: 1.6.8(debug@4.3.4)
emittery: 0.13.1 emittery: 0.13.1
is-docker: 2.2.1 is-docker: 2.2.1
knex: 2.4.2(mysql2@3.9.3)(pg@8.11.4)(sqlite3@5.1.7)(tedious@16.6.1) knex: 2.4.2(mysql2@3.9.3)(pg@8.11.5)(sqlite3@5.1.7)(tedious@16.6.1)
lodash: 4.17.21 lodash: 4.17.21
posthog-node: 1.3.0(debug@4.3.4) posthog-node: 1.3.0(debug@4.3.4)
snowflake-sdk: 1.8.0(asn1.js@5.4.1) snowflake-sdk: 1.8.0(asn1.js@5.4.1)
@ -20089,10 +20091,10 @@ packages:
- supports-color - supports-color
dev: false dev: false
/nc-lib-gui@0.204.9: /nc-lib-gui@0.205.1:
resolution: {integrity: sha512-jiFfiEV6cffhm4WD/9klApgSrmvL3A5Nq1kZwch7V5AvRBSQOROVg2XDfGvk83D0mceaW8fPMMbci1BvSBd+fg==} resolution: {integrity: sha512-WV1IVdOp8bXCB2/HcKCAXC45i4PnF6z8zHzMX46m6eXbGLofKWbVleHmO8yPZxWsfaQU2xDVuM9LzjIchZoUdw==}
dependencies: dependencies:
express: 4.18.3 express: 4.19.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: false dev: false
@ -21613,8 +21615,8 @@ packages:
/pg-connection-string@2.6.2: /pg-connection-string@2.6.2:
resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==}
/pg-connection-string@2.6.3: /pg-connection-string@2.6.4:
resolution: {integrity: sha512-77FxhhKJQH+xJx6tDqkhhMa0nZvv3U1HYLDQgwZxZafVD583++O5LXn5oo5HaQZ0vXwYcZA1koYAJM3JvD6Gtw==} resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==}
dev: false dev: false
/pg-int8@1.0.1: /pg-int8@1.0.1:
@ -21628,12 +21630,12 @@ packages:
dependencies: dependencies:
pg: 8.10.0 pg: 8.10.0
/pg-pool@3.6.2(pg@8.11.4): /pg-pool@3.6.2(pg@8.11.5):
resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==}
peerDependencies: peerDependencies:
pg: '>=8.0' pg: '>=8.0'
dependencies: dependencies:
pg: 8.11.4 pg: 8.11.5
dev: false dev: false
/pg-protocol@1.6.0: /pg-protocol@1.6.0:
@ -21670,8 +21672,8 @@ packages:
pg-types: 2.2.0 pg-types: 2.2.0
pgpass: 1.0.5 pgpass: 1.0.5
/pg@8.11.4: /pg@8.11.5:
resolution: {integrity: sha512-pWb7JKPxGk1UFbtq7jQ0m3IfPpb7LLACCEyN8/u9DYEom+Q/BSKy+4TRl4+Hh003AOYhppB/z+QK87/hx/bk0w==} resolution: {integrity: sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==}
engines: {node: '>= 8.0.0'} engines: {node: '>= 8.0.0'}
peerDependencies: peerDependencies:
pg-native: '>=3.0.1' pg-native: '>=3.0.1'
@ -21679,8 +21681,8 @@ packages:
pg-native: pg-native:
optional: true optional: true
dependencies: dependencies:
pg-connection-string: 2.6.3 pg-connection-string: 2.6.4
pg-pool: 3.6.2(pg@8.11.4) pg-pool: 3.6.2(pg@8.11.5)
pg-protocol: 1.6.1 pg-protocol: 1.6.1
pg-types: 2.2.0 pg-types: 2.2.0
pgpass: 1.0.5 pgpass: 1.0.5
@ -23915,9 +23917,6 @@ packages:
/sqlite3@5.1.6: /sqlite3@5.1.6:
resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==}
requiresBuild: true requiresBuild: true
peerDependenciesMeta:
node-gyp:
optional: true
dependencies: dependencies:
'@mapbox/node-pre-gyp': 1.0.11 '@mapbox/node-pre-gyp': 1.0.11
node-addon-api: 4.3.0 node-addon-api: 4.3.0
@ -23932,9 +23931,6 @@ packages:
/sqlite3@5.1.7: /sqlite3@5.1.7:
resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==}
requiresBuild: true requiresBuild: true
peerDependenciesMeta:
node-gyp:
optional: true
dependencies: dependencies:
bindings: 1.5.0 bindings: 1.5.0
node-addon-api: 7.0.0 node-addon-api: 7.0.0

458
scripts/docs/fr/020.getting-started/050.self-hosted/fr-010.installation.md

@ -0,0 +1,458 @@
***
titre : 'Installation'
description: 'Installation simple - prend environ trois minutes !'
balises : \['Open Source']
mots-clés : \['Installation NocoDB', 'Installation du docker NocoDB', 'Installation NocoDB nodejs', 'Essayage rapide NocoDB', 'Prérequis NocoDB']
-------------------------------------------------------------------------------------------------------------------------------------------------
Installation simple – prend environ trois minutes !
## Conditions préalables
* [Docker](https://www.docker.com/get-started) or [Node.js](https://nodejs.org/en/download)( > v18.x )
## Essai rapide
### Docker
Si vous utilisez Docker, vous pouvez essayer de cette manière !
<Tabs>
<TabItem value="sqlite" label="SQLite">
```bash
docker run -d --name nocodb \
-v "$(pwd)"/nocodb:/usr/app/data/ \
-p 8080:8080 \
nocodb/nocodb:latest
```
</TabItem>
<TabItem value="mysql" label="MySQL">
```bash
docker run -d --name nocodb-mysql \
-v "$(pwd)"/nocodb:/usr/app/data/ \
-p 8080:8080 \
-e NC_DB="mysql2://host.docker.internal:3306?u=root&p=password&d=d1" \
-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
nocodb/nocodb:latest
```
</TabItem>
<TabItem value="postgres" label="Postgres">
```bash
docker run -d --name nocodb-postgres \
-v "$(pwd)"/nocodb:/usr/app/data/ \
-p 8080:8080 \
-e NC_DB="pg://host.docker.internal:5432?u=root&p=password&d=d1" \
-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
nocodb/nocodb:latest
```
</TabItem>
<TabItem value="sqlserver" label="SQL Server">
```bash
docker run -d --name nocodb-mssql \
-v "$(pwd)"/nocodb:/usr/app/data/ \
-p 8080:8080 \
-e NC_DB="mssql://host.docker.internal:1433?u=root&p=password&d=d1" \
-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
nocodb/nocodb:latest
```
</TabItem>
</Tabs>
:::tip
Pour persister les données dans Docker, vous pouvez monter un volume sur `/usr/app/data/` depuis la version 0.10.6. Dans les versions antérieures, montez-le sur `/usr/src/app`. Sinon, vos données seront perdues après la recréation du conteneur.
:::
:::conseil
Si vous envisagez d'utiliser des caractères spéciaux, vous devrez peut-être ajuster vous-même le jeu de caractères et le classement lors de la création de la base de données. Veuillez consulter les exemples pour [Docker MySQL](https://github.com/nocodb/nocodb/issues/1340#issuecomment-1049481043).
:::
### Docker Compose
We provide different docker-compose.yml files under [ce répertoire](https://github.com/nocodb/nocodb/tree/master/docker-compose). Voici quelques exemples.
<Tabs>
<TabItem value="mysql" label="MySQL">
```bash
git clone https://github.com/nocodb/nocodb
cd nocodb/docker-compose/mysql
docker-compose up -d
```
</TabItem>
<TabItem value="postgres" label="Postgres">
```bash
git clone https://github.com/nocodb/nocodb
cd nocodb/docker-compose/pg
docker-compose up -d
```
</TabItem>
<TabItem value="sqlserver" label="SQL Server">
```bash
git clone https://github.com/nocodb/nocodb
cd nocodb/docker-compose/mssql
docker-compose up -d
```
</TabItem>
</Tabs>
:::tip
To persist data in docker you can mount volume at `/usr/app/data/`depuis la 0.10.6. Dans l'ancienne version, montez sur`/usr/src/app`.
Si vous envisagez de saisir des caractères spéciaux, vous devrez peut-être modifier vous-même le jeu de caractères et le classement lors de la création de la base de données. Veuillez consulter les exemples pour[MySQL Docker Composer](https://github.com/nocodb/nocodb/issues/1313#issuecomment-1046625974).
:::
### NPX
Vous pouvez exécuter la commande ci-dessous si vous avez besoin d'une configuration interactive.
```bash
npx create-nocodb-app
```
#### Aperçu:
<img width="587" alt="image" src="https://user-images.githubusercontent.com/35857179/161526235-5ee0d592-0105-4a57-aa53-b1048dca6aad.png" />
### Homebrew
```bash
brew tap nocodb/nocodb
brew install nocodb
nocodb
```
### Exécutables
Vous pouvez télécharger des exécutables directement et les exécuter sans aucune dépendance supplémentaire. Utilisez la bonne commande en fonction de votre plateforme.
<Tabs>
<TabItem value="MacOS (x64)" label="MacOS (x64)">
```bash
curl http://get.nocodb.com/macos-x64 -o nocodb -L \
&& chmod +x nocodb \
&& ./nocodb
```
</TabItem>
<TabItem value="MacOS (arm64)" label="MacOS (arm64)">
```bash
curl http://get.nocodb.com/macos-arm64 -o nocodb -L \
&& chmod +x nocodb \
&& ./nocodb
```
</TabItem>
<TabItem value="Linux (x64)" label="Linux (x64)">
```bash
curl http://get.nocodb.com/linux-x64 -o nocodb -L \
&& chmod +x nocodb \
&& ./nocodb
```
</TabItem>
<TabItem value="Linux (arm64)" label="Linux (arm64)">
```bash
curl http://get.nocodb.com/linux-arm64 -o nocodb -L \
&& chmod +x nocodb \
&& ./nocodb
```
</TabItem>
<TabItem value="Windows (x64)" label="Windows (x64)">
```bash
iwr http://get.nocodb.com/win-x64.exe -OutFile "Noco-win-x64.exe"
.\Noco-win-x64.exe
```
</TabItem>
<TabItem value="Windows (arm64)" label="Windows (arm64)">
```bash
iwr http://get.nocodb.com/win-arm64.exe -OutFile "Noco-win-arm64.exe"
.\Noco-win-arm64.exe
```
</TabItem>
</Tabs>
### Application de nœud
Nous fournissons une application NodeJS simple pour commencer.
```bash
git clone https://github.com/nocodb/nocodb-seed
cd nocodb-seed
npm install
npm start
```
### AWS ECS (Fargate)
<details>
<summary>Click to Expand</summary>
#### Créer un cluster ECS
```
aws ecs create-cluster \
--cluster-name <YOUR_ECS_CLUSTER>
```
#### Créer un groupe de journaux
```
aws logs create-log-group \
--log-group-name /ecs/<YOUR_APP_NAME>/<YOUR_CONTAINER_NAME>
```
#### Créer une définition de tâche ECS
Chaque fois que vous le créez, une nouvelle version sera ajoutée. Si elle n'existe pas, la version sera 1.
```bash
aws ecs register-task-definition \
--cli-input-json "file://./<YOUR_TASK_DEF_NAME>.json"
```
:::conseil
Ce fichier JSON définit la spécification du conteneur. Vous pouvez y définir des secrets tels que NC_DB et des variables d'environnement.
:::
Voici l'exemple de définition de tâche
```json
{
"family": "nocodb-sample-task-def",
"networkMode": "awsvpc",
"containerDefinitions": [{
"name": "<YOUR_CONTAINER_NAME>",
"image": "nocodb/nocodb:latest",
"essential": true,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/<YOUR_APP_NAME>/<YOUR_CONTAINER_NAME>",
"awslogs-region": "<YOUR_AWS_REGION>",
"awslogs-stream-prefix": "ecs"
}
},
"secrets": [{
"name": "<YOUR_SECRETS_NAME>",
"valueFrom": "<YOUR_SECRET_ARN>"
}],
"environment": [{
"name": "<YOUR_ENV_VARIABLE_NAME>",
"value": "<YOUR_ENV_VARIABLE_VALUE>"
}],
"portMappings": [{
"containerPort": 8080,
"hostPort": 8080,
"protocol": "tcp"
}]
}],
"requiresCompatibilities": [
"FARGATE"
],
"cpu": "256",
"memory": "512",
"executionRoleArn": "<YOUR_ECS_EXECUTION_ROLE_ARN>",
"taskRoleArn": "<YOUR_ECS_TASK_ROLE_ARN>"
}
```
#### Créer un service ECS
```bash
aws ecs create-service \
--cluster <YOUR_ECS_CLUSTER> \
--service-name <YOUR_SERVICE_NAME> \
--task-definition <YOUR_TASK_DEF>:<YOUR_TASK_DEF_VERSION> \
--desired-count <DESIRED_COUNT> \
--launch-type "FARGATE" \
--platform-version <VERSION> \
--health-check-grace-period-seconds <GRACE_PERIOD_IN_SECOND> \
--network-configuration "awsvpcConfiguration={subnets=["<YOUR_SUBSETS>"], securityGroups=["<YOUR_SECURITY_GROUPS>"], assignPublicIp=ENABLED}" \
--load-balancer targetGroupArn=<TARGET_GROUP_ARN>,containerName=<CONTAINER_NAME>,containerPort=<YOUR_CONTAINER_PORT>
```
:::conseil
Si votre service ne démarre pas, vous pouvez vérifier les journaux dans la console ECS ou dans Cloudwatch. Généralement, cela échoue en raison de la connexion entre le conteneur ECS et NC_DB. Assurez-vous que les groupes de sécurité ont les règles entrantes et sortantes correctes.
:::
</details>
### GCP (Cloud Run)
<details>
<summary>Click to Expand</summary>
#### Extraire l'image NocoDB sur Cloud Shell
Étant donné que Cloud Run ne prend en charge que les images de Google Container Registry (GCR) ou d'Artifact Registry, nous devons extraire l'image NocoDB, la marquer et la transférer dans GCP à l'aide de Cloud Shell. Voici quelques exemples de commandes que vous pouvez exécuter dans Cloud Shell.
```bash
# pull latest NocoDB image
docker pull nocodb/nocodb:latest
# tag the image
docker tag nocodb/nocodb:latest gcr.io/<MY_PROJECT_ID>/nocodb/nocodb:latest
# push the image to GCR
docker push gcr.io/<MY_PROJECT_ID>/nocodb/nocodb:latest
```
#### Déployer NocoDB sur Cloud Run
```bash
gcloud run deploy --image=gcr.io/<MY_PROJECT_ID>/nocodb/nocodb:latest \
--region=us-central1 \
--allow-unauthenticated \
--platform=managed
```
</details>
### Océan numérique (application)
<details>
<summary>Click to Expand</summary>
#### Créer des applications
Sur la page d'accueil, cliquez sur l'icône Créer et sélectionnez des applications (déployez votre code).
![Screenshot 2022-02-19 at 12 17 43 PM](https://user-images.githubusercontent.com/86527202/154790558-f8fe5580-5a58-412c-9c2e-145587712bf2.png)
#### Choose Source: Docker Hub
![Screenshot 2022-02-19 at 12 22 01 PM](https://user-images.githubusercontent.com/86527202/154790563-b5b6d5b4-0bdc-4718-8cea-0a7ee52f283b.png)
#### Choisissez la source : référentiel
Configurer le référentiel source comme`nocodb/nocodb`. Vous pouvez éventuellement choisir une balise de version si vous êtes intéressé par une version spécifique de NocoDB.
![Screenshot 2022-02-19 at 12 23 11 PM](https://user-images.githubusercontent.com/86527202/154790564-1dcb5e33-3a57-471a-a44c-835a410a0cb7.png)
#### \[Facultatif] Configurations supplémentaires
![Screenshot 2022-02-19 at 12 24 44 PM](https://user-images.githubusercontent.com/86527202/154790565-c0234b2e-ad50-4042-90b6-4f8798f1d585.png)
#### Nommez votre service Web
Choisissez un nom pour votre application NocoDB. Ce nom fera ultérieurement partie de l'URL
Choisissez la région la plus proche pour l'hébergement cloud![Screenshot 2022-02-19 at 12 28 11 PM](https://user-images.githubusercontent.com/86527202/154790567-a6e65e4e-9aa0-4edb-998e-da8803ad6e23.png)
#### Finaliser et lancer
* Sélectionnez le plan d'hébergement pour votre application NocoDB
* Cliquez sur "Lancer l'application"
![Screenshot 2022-02-19 at 12 29 23 PM](https://user-images.githubusercontent.com/86527202/154790570-62044713-5cca-4d06-82ec-f3cc257218a1.png)
L'application sera créée et l'URL sera disponible dans une minute ! L'URL ressemblera à quelque chose comme https://simply-nocodb-rsyir.ondigitalocean.app/
</details>
### Cloudron
<details>
<summary>Click to Expand</summary>
#### Accédez à l'App Store
Connectez-vous à Cloudron et sélectionnez App Store
![image](https://user-images.githubusercontent.com/35857179/194700146-aae90503-a8fd-4bc5-8397-39f0bc279606.png)
#### Rechercher NocoDB
![image](https://user-images.githubusercontent.com/35857179/194700181-b5303919-70b8-4cf8-bebe-7e75aca601f3.png)
#### Cliquez sur Installer
![image](https://user-images.githubusercontent.com/35857179/194700192-d702f5c2-2afa-45c5-9823-4ebe9e141b01.png)
#### Configurer NocoDB
![image](https://user-images.githubusercontent.com/35857179/194700230-c35e934f-bd93-4948-8f31-935483b30571.png)
#### Accédez à Mon application et lancez NocoDB
![image](https://user-images.githubusercontent.com/35857179/194700464-50098cb1-bf94-42bb-a63a-cc0aad671913.png)
</details>
### CapRover
<details>
<summary>Click to Expand</summary>
#### Connectez-vous et cliquez sur Applications/bases de données en un seul clic
![image](https://user-images.githubusercontent.com/35857179/194701420-7fe5c396-a488-456c-98de-6f2ee1151fc5.png)
#### Rechercher NocoDB
![image](https://user-images.githubusercontent.com/35857179/194701537-63e7efc5-013b-4ca9-8659-56e9d536e7d0.png)
#### Configurer NocoDB et déployer
![image](https://user-images.githubusercontent.com/35857179/194701576-19519df5-2aa4-435d-8fc6-7bc684b9cfe1.png)
</details>
### Chemin de fer
<details>
<summary>Click to Expand</summary>
#### Accédez aux modèles
Aller à[Modèles](https://railway.app/templates), Recherchez NocoDB et cliquez sur Déployer
![image](https://user-images.githubusercontent.com/35857179/194702833-1bea22ee-6dfa-4024-ac27-e33fe56e5500.png)
#### Configurer NocoDB et déployer
![image](https://user-images.githubusercontent.com/35857179/194702960-149393fe-b00f-4d84-9e54-22cb7616ba44.png)
</details>
### Prison de Fribussed / Freenas / Truens
Voir[ici](https://gist.github.com/Zamana/e9281d736f9e9ce5882c6f4b140a590e)fourni par[C.R. Zamana](https://github.com/Zamana).
## Exemples de démos
### Codesandbox
<iframe width="100%" height="500" src="https://codesandbox.io/embed/vigorous-firefly-80kq5?hidenavigation=1&theme=dark" title="Code Sandbox" frameBorder="0" allow="clipboard-write"></iframe>
### Déploiement de Docker avec une seule commande
<iframe width="100%" height="500" src="https://www.youtube.com/embed/K-UEecQyiOk" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen></iframe>
### Utiliser NPX
<iframe width="100%" height="500" src="https://www.youtube.com/embed/v6Nn75P1p7I" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen></iframe>

76
scripts/docs/fr/020.getting-started/050.self-hosted/fr-020.environment-variables.md

@ -0,0 +1,76 @@
***
titre: 'Variables d'environnement'
description: 'Variables d'environnement pour NocoDB !'
hide_table_of_contents: vrai
balises : ['Open Source']
mots-clés : ['Variables d'environnement NocoDB', 'Variables d'environnement NocoDB', 'Envs NocoDB', 'Env NocoDB']
------------------------------------------------------------------------------------------------------------------
Pour les cas d'utilisation en production, il est **recommandé** de configurer
* `NC_DB`,
* `NC_AUTH_JWT_SECRET`,
* `NC_PUBLIC_URL`,
* `NC_REDIS_URL`
| Variables | Commentaires | En cas d'absence |
|----------------------------------------|------------ -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------|------------------------ -------------------------------------------------- ----------------------|
| NC\_DB | Voir nos exemples d'URL de bases de données[ici](https://github.com/nocodb/nocodb#docker). | Un SQLite local sera créé dans le dossier racine si`NC_DB`n'est pas fourni |
| NC\_DB\_JSON | Peut être utilisé à la place de`NC_DB`et la valeur doit être une connexion Knex valide JSON | |
| NC\_DB\_JSON\_FILE | Peut être utilisé à la place de`NC_DB`et la valeur doit être un chemin valide vers la connexion knex JSON | |
| DATABASE\_URL | Peut être utilisé à la place de`NC_DB`et la valeur doit être au format URL JDBC | |
| DATABASE\_URL\_FILE | Peut être utilisé à la place de`DATABASE_URL`et la valeur doit être un chemin valide vers le fichier contenant le format URL JDBC. | |
| NC\_AUTH\_JWT\_SECRET | Secret JWT utilisé pour l'authentification et le stockage d'autres secrets | Un secret aléatoire sera généré |
| PORT | Pour définir le port d'exécution de l'application |`8080`|
| DB\_QUERY\_LIMIT\_DEFAULT | Limite de pagination | 25 |
| DB\_QUERY\_LIMIT\_GROUP\_BY\_GROUP | Limite de groupe par page | 10 |
| DB\_QUERY\_LIMIT\_GROUP\_BY\_RECORD | Limite d'enregistrement par groupe | 10 |
| DB\_QUERY\_LIMIT\_MAX | Limite de pagination maximale autorisée | 1000 |
| DB\_QUERY\_LIMIT\_MIN | Limite de pagination minimale autorisée | 1 |
| NC\_TOOL\_DIR | Répertoire d'applications pour conserver les métadonnées et les fichiers liés aux applications | La valeur par défaut est le répertoire de travail actuel. Dans Docker, les cartes vers`/usr/app/data/`pour le montage du volume. |
| NC\_PUBLIC\_URL | Utilisé pour envoyer des invitations par e-mail | Meilleure estimation à partir des paramètres de requête http |
| NC\_JWT\_EXPIRES\_IN | Heure d'expiration du jeton JWT |`10h`|
| NC\_CONNECT\_TO\_EXTERNAL\_DB\_DISABLED | Désactiver la création de projet avec une base de données externe | |
| NC\_INVITE\_ONLY\_SIGNUP | Supprimé depuis la version 0.99.0 et il est désormais recommandé d'utiliser[menu des paramètres du super-administrateur](/account-settings/oss-specific-details#enable--disable-signup). Autoriser les utilisateurs à s'inscrire uniquement via l'URL d'invitation, la valeur doit être n'importe quelle chaîne non vide. | |
| NUXT\_PUBLIC\_NC\_BACKEND\_URL | URL back-end personnalisée |`http://localhost:8080`sera utilisé |
| NC\_REQUEST\_BODY\_SIZE | Demander la taille du corps[limite](https://expressjs.com/en/resources/middleware/body-parser.html#limit)|`1048576`|
| NC\_EXPORT\_MAX\_TIMEOUT | Après NC\_EXPORT\_MAX\_TIMEOUT, le CSV est téléchargé par lots | La valeur par défaut 5000 (en millisecondes) sera utilisée |
| NC\_DISABLE\_TELE | Désactiver la télémétrie | |
| NC\_DASHBOARD\_URL | Chemin d'URL du tableau de bord personnalisé |`/dashboard` |
| NC\_GOOGLE\_CLIENT\_ID | Google client ID to enable Google authentication | |
| NC\_GOOGLE\_CLIENT\_SECRET | Google client secret to enable Google authentication | |
| NC\_MIGRATIONS\_DISABLED | Disable NocoDB migration | |
| NC\_MIN | If set to any non-empty string the default splash screen(initial welcome animation) and matrix screensaver will disable | |
| NC\_SENTRY\_DSN | For Sentry monitoring | |
| NC\_REDIS\_URL | Custom Redis URL. Example: `redis://:authpassword@127.0.0.1:6380/4`| Les métadonnées seront stockées en mémoire |
| NC\_DISABLE\_ERR\_REPORT | Désactiver le rapport d'erreurs | |
| NC\_DISABLE\_CACHE | À utiliser uniquement lors du débogage. En réglant ceci sur`true`- les métadonnées doivent être récupérées depuis la base de données au lieu de redis/cache. |`false`|
| AWS\_ACCESS\_KEY\_ID | Pour Litestream-identifiant de clé d'accès S3 | Si Litestream est configuré et`NC_DB`n'est pas présent. SQLite est sauvegardé sur S3 |
| AWS\_SECRET\_ACCESS\_KEY | Pour Litestream-clé d'accès secrète S3 | Si Litestream est configuré et`NC_DB`n'est pas présent. SQLite est sauvegardé sur S3 |
| AWS\_BUCKET | Pour Litestream-seau S3 | Si Litestream est configuré et`NC_DB`n'est pas présent. SQLite est sauvegardé sur S3 |
| AWS\_BUCKET\_PATH | Pour Litestream - Chemin du compartiment S3 (comme le dossier dans le compartiment S3) | Si Litestream est configuré et`NC_DB`n'est pas présent. SQLite est sauvegardé sur S3 |
| NC\_SMTP\_FROM | Pour le plugin SMTP - Adresse e-mail de l'expéditeur | |
| NC\_SMTP\_HOST | Pour le plugin SMTP - Valeur de l'hôte SMTP | |
| NC\_SMTP\_PORT | Pour le plugin SMTP - Valeur du port SMTP | |
| NC\_SMTP\_USERNAME | Pour le plugin SMTP (Facultatif) - Valeur du nom d'utilisateur SMTP pour l'authentification | |
| NC\_SMTP\_PASSWORD | Pour le plugin SMTP (Facultatif) - Valeur du mot de passe SMTP pour l'authentification | |
| NC\_SMTP\_SECURE | Pour le plugin SMTP (facultatif) - Pour activer la valeur définie sécurisée comme`true`toute autre valeur traitée comme fausse | |
| NC\_SMTP\_IGNORE\_TLS | Pour le plugin SMTP (Facultatif) - Pour ignorer la valeur définie par tls comme`true`toute autre valeur traitée comme fausse. Pour plus d'informations, visitez https://nodemailer.com/smtp/ | |
| NC\_S3\_BUCKET\_NAME | Pour le plug-in de stockage S3 - Nom du compartiment AWS S3 | |
| NC\_S3\_REGION | Pour le plug-in de stockage S3 - Région AWS S3 | |
| NC\_S3\_ACCESS\_KEY | Pour le plug-in de stockage S3 - Informations d'identification de clé d'accès AWS pour accéder aux ressources | |
| NC\_S3\_ACCESS\_SECRET | Pour le plug-in de stockage S3 - Informations d'identification secrètes d'accès AWS pour accéder aux ressources | |
| NC\_ADMIN\_EMAIL | Pour mettre à jour/créer un super administrateur avec l'e-mail et le mot de passe fournis | |
| NC\_ATTACHMENT\_FIELD\_SIZE | Pour définir la taille du champ de pièce jointe (en octets) | La valeur par défaut est 20 Mo |
| NC\_ADMIN\_PASSWORD | Pour mettre à jour/créer un super administrateur avec l’e-mail et le mot de passe fournis. Votre mot de passe doit contenir au moins 8 lettres avec une majuscule, un chiffre et une lettre spéciale (caractères spéciaux autorisés $&+,:;=?@#|'.^\*()%!\_-" ) | |
Note: Assurez-vous que votre mot de passe respecte les exigences de complexité pour une sécurité accrue.
| NODE\_OPTIONS | Pour passer Node.js[choix](https://nodejs.org/api/cli.html#node_optionsoptions)exemple | |
| NC\_MINIMAL\_DBS | Créez un nouveau fichier SQLite pour chaque projet. Tous les fichiers db sont stockés dans`nc_minimal_dbs`dossier dans le répertoire de travail actuel. (Cette option restreint la création de projets sur des sources externes) | |
| NC\_DISABLE\_AUDIT | Désactiver le journal d'audit |`false`|
| NC\_AUTOMATION\_LOG\_LEVEL | Valeurs possibles:`OFF`,`ERROR`,`ALL`. Voir[Webhooks](/automation/webhook/create-webhook#call-log)pour plus de détails. |`OFF`|
| NC\_SECURE\_ATTACHMENTS | Autoriser l'accès aux pièces jointes uniquement via des URL prédéfinies. Pour activer la valeur définie comme`true`toute autre valeur traitée comme fausse. (⚠ cela rendra les liens existants inaccessibles ⚠) |`false`|
| NC\_ATTACHMENT\_EXPIRE\_SECONDS | Combien de secondes avant l'expiration des URL de pièce jointe présignées. (Les pièces jointes expireront au moins dans les secondes définies et au plus 10 minutes après l'heure définie) | 7200 (2 heures) |
| NC\_ALLOW\_LOCAL\_HOOKS | Pour activer la valeur définie comme`true`toute autre valeur traitée comme fausse. (⚠ cela permettra aux webhooks d'appeler des liens locaux ce qui peut poser des problèmes de sécurité ⚠) |`false`|
Attention : Activer cette option peut augmenter les risques de sécurité en permettant aux webhooks d'accéder à des ressources locales.
| NC\_SANITIZE\_COLUMN\_NAME | Nettoyez le nom de la colonne lors de la création de la colonne. Pour activer la valeur définie comme`true`toute autre valeur traitée comme fausse. |`true`|

118
scripts/docs/fr/020.getting-started/fr-040.keyboard-shortcuts.md

@ -0,0 +1,118 @@
***
titre : 'Raccourcis clavier'
tags : \['Démarrer', 'Raccourcis', 'Hacks de productivité']
mots-clés : \['raccourcis clavier', 'raccourcis', 'clavier']
------------------------------------------------------------
## Actions rapides ☁ {#quick-actions}
:::note
Cette fonctionnalité est disponible uniquement dans la version hébergée de NocoDB Cloud.
:::
`⌘`+`K`(ou`Ctrl`+`K`sous Windows) est un raccourci clavier permettant de naviguer rapidement entre différents éléments d'espace de travail, de table, de vue ou de menu. Par exemple, si vous souhaitez accéder rapidement à la page « Jetons API », vous pouvez ouvrir le menu Actions rapides en utilisant ⌘+K, taper « Jeton » dans la zone de recherche et appuyer sur Entrée.
Ce raccourci est souvent appelé « Command-K ». C'est un excellent moyen de gagner du temps lorsque vous naviguez dans NocoDB.
Le menu Commande-K est également accessible via le bouton "Actions rapides" dans le coin supérieur gauche de l'écran.
![Quick Actions](/img/v2/cmd-k.png)
Pour naviguer dans le menu ⌘+K,
* Utiliser `↑` `↓` pour naviguer entre les éléments listés
* Utiliser`Enter`pour sélectionner un élément
* Utiliser`Backspace`pour passer au menu parent
* Utiliser`Esc`pour fermer le menu
## Vues récentes
:::note
Cette fonctionnalité est disponible uniquement dans la version hébergée de NocoDB Cloud.
:::
Accédez rapidement aux vues récemment visitées en utilisant`⌘`+`L`(ou`Ctrl`+`L`sous Windows). Les résultats de la recherche seront affichés dans une fenêtre modale ; cliquez sur le résultat pour ouvrir la vue.
![Recent Views](/img/v2/cmd-l.png)
Pour naviguer dans le menu ⌘+K,
* Utiliser`↑``↓`pour naviguer entre les éléments répertoriés
* Utiliser`Enter`pour sélectionner un élément
* Utiliser`Backspace`pour passer au menu parent
* Use `Esc`pour fermer le menu
## Rechercher dans Docs
:::note
Cette fonctionnalité est disponible uniquement dans la version hébergée de NocoDB Cloud.
:::
Recherchez rapidement des documents à partir de l'interface utilisateur de NocoDB en utilisant`⌘`+`J`(ou`Ctrl`+`J`sous Windows). Les résultats de la recherche seront affichés dans une fenêtre modale ; cliquez sur le résultat pour ouvrir la page dans un nouvel onglet.
Pour naviguer dans le menu ⌘+K,
* Utiliser`↑``↓`pour naviguer entre les éléments répertoriés
* Utiliser`Enter`pour sélectionner un élément
* Utiliser`Backspace`pour passer au menu parent
* Utiliser`Esc`pour fermer le menu
![Search in Docs](/img/v2/cmd-j.png)
## Raccourcis généraux
| Clé | Comportement |
|------------:|:--------------------------------|
|`alt`+`t`| Ouvre une nouvelle table modale |
|`alt`+`c`| Ouvre un nouveau champ modal |
|`alt`+`f`| Active/désactive le mode plein écran |
|`alt`+`i`| Ouvrir le bouton de partage modal |
|`⌘`+`k`| Ouvrir le modal Action rapide |
## Raccourcis de la vue Grille
| Clé | Comportement |
|----------------:|:------------------------------ -------------------------------------------------- -----|
|`←``→``↑``↓`| Navigation générale dans les cellules |
|`Delete`| Effacer la cellule |
|`Space`| Développer l'enregistrement actuel |
|`Tab`| Passer à la cellule suivante horizontalement ; si sur la dernière cellule, passer au début de l'enregistrement suivant |
|`Esc`| Quitter le mode EDITION de cellule |
|`Enter`| Basculez la cellule sélectionnée en mode EDIT ; ouvre modal/picker si la cellule est associée à un |
|`⌘`+`↑`| Aller au premier enregistrement dans ce champ (dans la même page) |
|`⌘`+`↓`| Aller au dernier enregistrement dans ce champ (dans la même page) |
|`⌘`+`←`| Aller au premier champ de cet enregistrement |
|`⌘`+`→`| Aller au dernier champ de cet enregistrement |
|`⌘`+`c`| Copier le contenu de la cellule |
|`⌘`+`v`| Coller le contenu copié |
|`alt`+`r`| Insère un nouvel enregistrement dans la vue grille |
|`alt`+`↑`| Aller à la dernière page de cette vue |
|`alt`+`↓`| Aller à la première page de cette vue |
|`alt`+`←`| Aller à la page précédente dans cette vue |
|`alt`+`→`| Passer à la page suivante dans cette vue |
## Raccourcis spécifiques au type de champ
| Type de données | Clé | Comportement |
|:----------------------:|------------:|:--------- --------------------------|
| Cellules de texte et numériques |`←``→`| Déplacer le curseur vers la gauche/droite |
| |`↑``↓`| Déplacer le curseur au début/fin |
| Sélection unique |`↑``↓`| Se déplacer entre les options |
| |`Enter`| Sélectionnez l'option |
| Sélection multiple |`↑``↓`| Se déplacer entre les options |
| |`Enter`| Sélectionner/désélectionner l'option |
| Lien |`↑``↓`| Se déplacer entre les options |
| |`Enter`| Lier la sélection actuelle |
| Case à cocher |`Enter`| Basculer |
| Évaluation |`<0 ~ Max>`| Entrez le numéro pour basculer la note |
## Raccourcis de formulaire développé
| Clé | Comportement |
|--------------:|:-------------------------------- --|
|`⌘`+`Enter`| Enregistrer l'élément de formulaire développé actuel |
|`alt`+`→`| Passer à l'enregistrement suivant |
|`alt`+`←`| Passer à l'enregistrement précédent |
|`alt`+`S`| Enregistrer l'enregistrement actuel du formulaire développé |
|`alt`+`N`| Créer un nouvel enregistrement |

39
scripts/docs/fr/030.workspaces/fr-020.create-workspace.md

@ -0,0 +1,39 @@
***
titre : "Créer un espace de travail"
description : 'Apprenez à créer un espace de travail dans NocoDB'
balises : \['Espaces de travail', 'Créer']
mots-clés : \['Espace de travail NocoDB', 'créer un espace de travail', 'menu contextuel de l'espace de travail', 'propriétaire de l'espace de travail', 'collaboration dans l'espace de travail', 'actions de l'espace de travail', 'espace de travail par défaut']
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Lors de votre inscription à NocoDB, un premier espace de travail est automatiquement généré en votre nom.
Ensuite, vous avez la possibilité soit [modifier son nom](/workspaces/actions-on-workspace#rename-workspace) ou créer un tout nouvel espace de travail.
Dans la section suivante, examinons le processus de création de vos espaces de travail NocoDB.
## Créer un espace de travail
1. Accédez au coin supérieur gauche de la barre latérale et sélectionnez le **Nom de l'espace de travail** pour accéder au **Menu contextuel de l'espace de travail**.
2. Dans le menu contextuel de Workspace, optez pour l'option**Créer un espace de travail**bouton.
3. Donnez un nom à l'espace de travail
4. Continuez en cliquant sur le**Créer un espace de travail**bouton.
![image](/img/v2/workspace/workspace-create.png)
![image](/img/v2/workspace/workspace-create-2.png)
Lorsque vous démarrez un nouvel espace de travail, vous en devenez automatiquement propriétaire.
Une fois votre espace de travail configuré, vous pouvez [créer des tableaux](/tables/create-table) et [inviter d'autres](/workspaces/workspace-collaboration) se joindre et travailler ensemble.
Vous pouvez inviter autant de personnes que vous le souhaitez et créer autant de tables que nécessaire dans votre espace de travail.
Mais n’oubliez pas qu’il ne peut y avoir qu’un seul propriétaire et que seul celui-ci peut supprimer l’espace de travail.
:::Info
L'Open Source NocoDB inclut un espace de travail par défaut et ne permet pas la création d'espaces de travail supplémentaires.
:::
## Articles Liés
* [Présentation de l'espace de travail](/workspaces/workspace-overview)
* [Inviter les membres de l'équipe dans l'espace de travail](/workspaces/workspace-collaboration)
* [Renommer l'espace de travail](/workspaces/actions-on-workspace#rename-workspace)
* [Supprimer l'espace de travail](/workspaces/actions-on-workspace#delete-workspace)

62
scripts/docs/fr/030.workspaces/fr-030.workspace-collaboration.md

@ -0,0 +1,62 @@
***
titre : « Collaboration dans un espace de travail »
description: 'Cet article explique comment inviter des membres dans votre espace de travail, modifier leurs rôles et la procédure pour les supprimer de l'espace de travail.'
balises : \['Espaces de travail', 'Collaboration', 'Membres', 'Inviter', 'Rôles', 'Autorisations']
mots-clés : \['Configuration de l'espace de travail', 'Inviter des membres', 'Rôles des membres', 'Autorisations d'accès', 'Affichage des membres de l'espace de travail', 'Modification des rôles des membres', 'Suppression de membres de l'espace de travail', 'Collaboration dans l'espace de travail', 'Collaboration d'équipe' , 'Contrôle d'accès', 'Autorisations basées sur les rôles', 'Gestion de l'espace de travail', 'Rôles utilisateur', 'Paramètres de l'espace de travail', 'Administration de l'espace de travail', 'Invitation des membres', 'Accès utilisateur', 'Gestion des membres', 'Espace de travail travail d'équipe', 'Organisation de l'espace de travail']
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
La prochaine étape logique après la création d'un espace de travail consiste à y inviter des membres. Dans cette section, nous vous guiderons tout au long du processus d’invitation de membres dans votre espace de travail.
Un guide complet concernant les rôles et les autorisations est accessible [ici](/roles-and-permissions/roles-permissions-overview).
## Inviter des membres à Workspace
1. Accédez à la page de configuration de Workspace en sélectionnant `Team & Settings` dans la barre latérale gauche.
2. Accéder à l'onglet `Members`.
3. Saisissez l'adresse e-mail du membre prévu pour l'invitation.
4. Cliquer sur le menu déroulant `Role`
5. Choisissez l'autorisation d'accès appropriée pour le membre dans le menu déroulant.
6. Terminez le processus en sélectionnant le bouton `Add Member(s)`.
![image](/img/v2/workspace/workspace-collaboration.png)
> **Conseil :**
Vous pouvez inviter plusieurs membres simultanément en saisissant leurs adresses e-mail séparées par des virgules.
## Répertorier les membres de l'espace de travail
L'onglet `Members` dans `Team & Settings` affiche une liste des utilisateurs qui ont obtenu l'accès à l'espace de travail.
1. Accédez à la page de configuration de Workspace en sélectionnant `Team & Settings` dans la barre latérale gauche.
2. Accéder à l'onglet `Members`.
![image](/img/v2/workspace/workspace-members-list.png)
## Modifier les rôles des membres de l'espace de travail
Vous pouvez modifier les autorisations d'accès des membres en suivant ces étapes :
1. Accédez à la page de configuration de Workspace en sélectionnant `Team & Settings` dans la barre latérale gauche.
2. Accéder à l'onglet `Members`.
3. Accédez au menu déroulant.
4. Sélectionnez la nouvelle option de rôle souhaitée.
![image](/img/v2/workspace/workspace-members-role-change.png)
## Supprimer des membres de l'espace de travail
Pour supprimer un membre de l'espace de travail, procédez comme suit :
1. Accédez à la page de configuration de Workspace en sélectionnant `Team & Settings` dans la barre latérale gauche.
2. Accéder à l'onglet `Members`.
3. Cliquez sur les ellipses verticales `⋮` pour ouvrir le menu contextuel.
4. Sélectionnez l'option `Remove User`.
![image](/img/v2/workspace/workspace-members-remove.png)
## Articles Liés
* [Présentation de l'espace de travail](/workspaces/workspace-overview)
* [Créer un espace de travail](/workspaces/create-workspace)
* [Renommer l'espace de travail](/workspaces/actions-on-workspace#rename-workspace)
* [Supprimer l'espace de travail](/workspaces/actions-on-workspace#delete-workspace)

39
scripts/docs/fr/040.bases/fr-020.create-base.md

@ -0,0 +1,39 @@
***
titre : 'Créer une base'
description: 'Apprenez à créer une base dans NocoDB.'
balises : \['Bases', 'Créer']
mots-clés : \['Base NocoDB', 'créer une base']
----------------------------------------------
## Créer une base à partir de zéro
Pour lancer la création d'une nouvelle base de données à partir de zéro, procédez comme suit :
1. Accédez à la barre latérale gauche et sélectionnez l'`+` icône adjacente à `Projects` ou cliquez sur le `+ New Project` bouton.
2. Saisissez le nom souhaité pour la base de données dans la fenêtre contextuelle présentée.
3. Terminez le processus en cliquant sur le `Create Database` bouton.
![image](/img/v2/base/base-create-1.png)![image](/img/v2/base/base-create-2.png)
:::Info
* Dès la création de la base, vous assumerez le rôle de`base owner.`
* Seulement le`base owner`possède le pouvoir de supprimer une base.
* Il est possible de créer plusieurs bases au sein d'un même espace de travail.
:::
Après avoir réussi à créer une base, vous serez redirigé vers le tableau de bord principal de la base. Ce hub central offre aux utilisateurs un accès rapide aux fonctionnalités essentielles telles que les paramètres de base et les outils de collaboration. Pour accéder au tableau de bord de base, cliquez simplement sur le nom de la base situé dans la barre latérale gauche.
Trouvez plus de détails sur l'ajout de tables à une base dans la section [les tables](/tables/create-table).
## Articles Liés
* [Aperçu des bases](/bases/base-overview)
* [Importer la base depuis Airtable](/bases/import-base-from-airtable)
* [Inviter les membres de l'équipe à travailler sur une base](/bases/base-collaboration)
* [Partager la base publiquement](/bases/share-base)
* [Renommer la base](/bases/actions-on-base#rename-base)
* [Base en double](/bases/actions-on-base#duplicate-base)
* [Base de marque-pages](/bases/actions-on-base#star-base)
* [Supprimer la base](/bases/actions-on-base#delete-base)

108
scripts/docs/fr/040.bases/fr-040.import-base-from-airtable.md

@ -0,0 +1,108 @@
***
title: « Importer Airtable vers NocoDB »
description: 'Une importation complète de votre Airtable vers n'importe quelle base de données MySQL ou Postgres en quelques minutes'
balises : \['Bases', 'Importer']
mots-clés : \['NocoDB', 'Airtable', 'Import Airtable', 'Airtable to NocoDB', 'Airtable to MySQL', 'Airtable to Postgres']
-------------------------------------------------------------------------------------------------------------------------
NocoDB propose un processus rationalisé pour transférer de manière transparente votre base de données Airtable vers divers systèmes de gestion de bases de données, notamment MySQL, Postgres et SQLite, en quelques minutes seulement. Cette fonctionnalité est particulièrement utile pour les utilisateurs qui souhaitent migrer leur base de données Airtable vers un système de gestion de base de données plus robuste et évolutif.
:::Info
L'importation depuis Airtable est en version bêta. Voir[Importation d'Airtable vers NocoDB](https://github.com/nocodb/nocodb/discussions/2122)pour les notes de migration.
:::
:::Info
Pour continuer, vous devez disposer d'informations d'identification Airtable valides. Assurez-vous d'avoir accès aux informations suivantes depuis votre compte Airtable :
* [Qu’est-ce qu’Opi ?](#retrieve-api-key) ou [Jeton d'accès personnel](#create-personal-access-token)
* [ID/URL de base partagée](#retrieve-share-base-id—url):::
Ouvrez le `Quick Import- AIRTABLE` modal commence le processus d’importation
1. Passez la souris sur le nom de la base dans la barre latérale gauche, cliquez sur l'icône`...`icône pour ouvrir le menu contextuel de la base
2. Sélectionner`Import Data`depuis le menu contextuel de base
3. Sélectionner`Airtable`
![import data](/img/v2/base/base-import-airtable-1.png)
Alternativement, vous pouvez également ouvrir le`Quick Import- AIRTABLE`modale du`Base dashboard`
1. Accédez à votre tableau de bord Base, cliquez sur`Import Data`
2. Sélectionner`Airtable`
![import data](/img/v2/base/base-import-from-dashboard-1.png)
![import data](/img/v2/base/base-import-from-dashboard-2.png)
Continuez avec les étapes suivantes sur`Quick Import- AIRTABLE`modal pour terminer le processus d’importation :
1. Saisir[Qu’est-ce qu’Opi ?](#retrieve-api-key)/[Jeton d'accès personnel](#create-personal-access-token)
2. Saisir[ID de base / URL partagés](#retrieve-share-base-id--url)
3. Configurer[Options d'importation Airtable](#configuration-options)(Facultatif)
4. Cliquez sur`Import`
![import data](/img/v2/base/base-import-airtable-2.png)
:::Info
Attendre jusqu'à`Go To Dashboard`Le bouton est activé sur le modal. Les détails de l'importation sont capturés dans la fenêtre du journal.
:::
![import data](/img/v2/base/base-import-airtable-3.png)
### Options de configuration
1. **Importer des données**: Si vous désactivez cette option, seules les tables et les vues seront créées (schéma), à l'exclusion des enregistrements de données réels.
2. **Importer des vues secondaires**: Si vous désactivez cette option, seule la vue de grille principale de chaque table sera importée, en omettant toutes les vues secondaires.
3. **Importer des champs de cumul**: Si vous désactivez cette option, vous pouvez ignorer l'importation des champs Rollup.
4. **Importer des champs de recherche**: Si vous désactivez cette option, vous pouvez ignorer l'importation des champs de recherche.
5. **Importer les champs des pièces jointes**: Si vous désactivez cette option, vous pouvez ignorer l'importation des champs de pièces jointes, qui stockent généralement les pièces jointes associées aux enregistrements.
6. **Importer des champs de formule**: Veuillez noter que l'importation de champs de formule depuis Airtable n'est actuellement pas prise en charge.
## Obtenez les informations d'identification Airtable
### Créer un jeton d'accès personnel
Voici les étapes pour générer un jeton d'accès personnel Airtable :
1. Visiter le [Airtable Créer des jetons](https://airtable.com/create/tokens) page et cliquez sur le bouton « Créer un jeton ».
2. Fournissez un nom significatif pour votre jeton dans le `Token name` champ.
3. Choisissez les étendues d’accès nécessaires, avec une exigence minimale de `data.records:read`.
4. Sélectionnez la base spécifique à laquelle vous souhaitez accéder avec ce jeton.
5. Confirmez vos choix en cliquant sur le `Create token` bouton.
6. Copiez le nouveau généré `Personal Access Token` pour votre usage.
Pour des informations détaillées, vous pouvez vous référer au[Guide du jeton d'accès personnel Airtable](https://airtable.com/developers/web/guides/personal-access-tokens).
![image](/img/v2/base/pat-1.png)
![image](/img/v2/base/pat-2.png)
![image](/img/v2/base/pat-3.png)
### Récupérer la clé API
* Copiez votre clé API Airtable depuis[Airtable crée une clé API](https://airtable.com/create/apikey)page![API Key](/img/v2/base/airtable-api-key.png)
### Récupérer l'ID/URL de base de partage
Voir[ici](https://support.airtable.com/hc/en-us/articles/205752117-Creating-a-base-share-link-or-a-view-share-link#basesharelink)pour des procédures détaillées.
1. Ouvrir`Share`menu dans votre Projet / Base
2. Ouvrir l'onglet`Share Publicly`
3. Activer`Turn on full base access`
4. Copier l'URL de base partagée générée
![Shared base](/img/v2/base/airtable-share-base.png)
## Articles Liés
* [Aperçu des bases](/bases/base-overview)
* [Créer une base vide](/bases/create-base)
* [Importer la base depuis Airtable](/bases/import-base-from-airtable)
* [Inviter les membres de l'équipe à travailler sur une base](/bases/base-collaboration)
* [Partager la base publiquement](/bases/share-base)
* [Renommer la base](/bases/actions-on-base#rename-base)
* [Base en double](/bases/actions-on-base#duplicate-base)
* [Base de marque-pages](/bases/actions-on-base#star-base)
* [Supprimer la base](/bases/actions-on-base#delete-base)

47
scripts/docs/fr/060.table-operations/fr-020.field-operations.md

@ -0,0 +1,47 @@
***
title: 'Masquer et réorganiser les champs'
description: 'Apprenez à masquer et à réorganiser les champs dans NocoDB.'
balises : \['Opérations sur les tables', 'Réorganiser', 'Afficher/masquer', 'Image de couverture', 'Vue Galerie', 'Vue Kanban', 'Vue Grille']
mots-clés : \['masquer le champ', 'afficher le champ', 'réorganiser le champ', 'image de couverture']
-----------------------------------------------------------------------------------------------------
Utiliser le`Fields`dans la barre d’outils pour gérer la visibilité des champs dans le système. Par défaut, tous les champs liés au système sont masqués, mais vous avez la possibilité de les activer en sélectionnant`Show system fields`.
:::Info
Pour la vue Galerie et la vue Kanban, le menu `Fields` est disponible dans la barre d’outils sous la forme `Edit Cards`.
:::
![Show system fields](/img/v2/table-operations/fields-show-system-fields.png)
### Réorganisation des champs
Vous pouvez réorganiser les champs. Accédez au menu `Fields` et réorganisez les champs en faisant glisser et en déposant le champ associé à l'aide de l’icône de déplacement.
![Reorder Fields](/img/v2/table-operations/fields-reorder.png)
### Afficher/Masquer les champs
Pour afficher ou masquer des champs, accédez au`Fields`et cliquez simplement sur le bouton bascule associé au champ correspondant pour le masquer ou l'afficher.
:::conseil
Pensez à créer différentes vues de grille avec des ensembles distincts de champs affichés dans chaque vue pour une personnalisation améliorée.
:::
![Show/Hide Fields](/img/v2/table-operations/fields-hide.png)
### Modifier le champ de couverture (Vue Galerie/Kanban)
Pour la vue Galerie et la vue Kanban, vous pouvez modifier le champ de couverture en cliquant sur le bouton `Change cover field` dans le menu `Edit Cards`. Vous pouvez sélectionner n’importe quel champ de type `Attachment` comme champ de couverture.
![Change cover field](/img/v2/table-operations/change-cover-image.png)
### Rubriques connexes
* [Filtre](filter)
* [Trier](sort)
* [Par groupe](group-by)
* [Hauteur de rangée](row-height)
* [Recherche rapide](search)
* [Télécharger](download)

50
scripts/docs/fr/060.table-operations/fr-050.group-by.md

@ -0,0 +1,50 @@
***
title: 'Regroupement des enregistrements'
description: 'Apprenez à regrouper des enregistrements dans NocoDB.'
balises : \['Opérations sur les tables', 'Regrouper par', 'Vue Grille']
mots-clés : \['Regrouper la table NocoDB par', 'regrouper par champ', 'grouper par''regrouper les enregistrements', 'regrouper','regrouper par dans la table']
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Le regroupement des enregistrements dans NocoDB permet une catégorisation efficace des données en catégories spécifiques `Groupes` et `Sous-groupes.` NocoDB prend en charge trois niveaux de séparation des enregistrements, fournissant ainsi un outil organisationnel puissant.
![Group By](/img/v2/table-operations/group-by-1.png)
### Ajouter ou modifier des groupes
Pour créer ou modifier un regroupement dans NocoDB, suivez ces étapes :
1. Cliquer sur`Group By`dans la barre d'outils.
2. Choisissez le champ par lequel vous souhaitez regrouper les enregistrements.
3. En option, vous pouvez trier les groupes par ordre croissant ou décroissant.
![Group By](/img/v2/table-operations/group-by-create.png)
:::Info
Vous pouvez ajouter des sous-groupes comportant jusqu'à trois niveaux pour affiner davantage votre catégorisation.
:::
![Group By](/img/v2/table-operations/group-by-nested.png)
### Suppression de groupes
Pour supprimer un regroupement dans NocoDB, effectuez ces actions :
1. Cliquer sur`Group By`dans la barre d'outils.
2. Localisez l'icône de la corbeille à droite du groupe que vous souhaitez supprimer et cliquez dessus.
::: Info
Pour désactiver`Group By`et revenez à la vue de la grille de la feuille de calcul standard, vous devez supprimer tous les groupes configurés.
:::
![Group By](/img/v2/table-operations/group-by-delete.png)
### Rubriques connexes
* [Les opérations sur le terrain](field-operations)
* [Filtre](filter)
* [Trier](sort)
* [Hauteur de rangée](row-height)
* [Recherche rapide](search)
* [Télécharger](download)
*

35
scripts/docs/fr/060.table-operations/fr-060.row-height.md

@ -0,0 +1,35 @@
***
titre : « Temps créé »
description: 'Cet article explique comment créer et utiliser un champ Heure de création.'
balises : \['Champs', 'Types de champs', 'Date et heure', 'Champs système']
mots-clés : \['Champs', 'Types de champs', 'Date et heure', 'Champs système', 'Heure de création']
--------------------------------------------------------------------------------------------------
Since version v0.204.0 (Jan 2024), NocoDB internally captures the time when record was created. This information is stored as a system field in the database & is hidden in the table by default. To view this information on the UI, you can either enable `Show System Fields` OU créer un `Created Time` champ manuellement en suivant les étapes ci-dessous.
Le champ système par défaut ne peut être masqué que dans l'interface utilisateur. Il ne peut être modifié, dupliqué ou supprimé.
:::note
* Lorsqu'il est connecté à une base de données externe, le champ `CreatedTime` n’est pas créé automatiquement. Vous pouvez créer un champ `CreatedTime` manuellement en suivant les étapes ci-dessous.
* `CreatedTime`Le champ est vide indique que l'enregistrement est antérieur à la fonctionnalité créée (v0.204.0, janvier 2024). Avant cette version de fonctionnalité, il était possible de le supprimer du tableau.
:::
## Créer un`CreatedTime`champ
1. Cliquer sur`+`icône à droite de`Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme`CreatedTime`dans la liste déroulante.
4. Cliquer sur`Save Field`bouton.
![image](/img/v2/fields/types/created-time.png)
## Affichage des cellules
Le champ `CreatedTime` est affiché comme un champ en lecture seule dans la vue tableau. Il est affiché sous forme de chaîne de date et heure au format `DD MMM YYYY, HH:mm`.
## Domaines connexes
* [Heure de la dernière modification](060.last-modified-time.md)
*

33
scripts/docs/fr/070.fields/040.field-types/010.text-based/fr-010.single-line-text.md

@ -0,0 +1,33 @@
***
titre : 'Texte sur une seule ligne'
description : 'Cet article explique comment créer et utiliser un champ de texte sur une seule ligne.'
description: 'Cet article explique comment créer et utiliser un champ de texte sur une seule ligne.'
balises : \['Champs', 'Types de champs', 'Types liés au texte', 'Texte sur une seule ligne']
mots-clés : \['Champs', 'Types de champs', 'Types liés au texte', 'Texte sur une seule ligne', 'Créer un champ de texte sur une seule ligne']
-----------------------------------------------------------------------------------------------------------------------------------------------
`Single line text`field est un simple champ basé sur du texte. Il peut contenir n’importe quelle valeur de texte. Il est généralement utilisé pour stocker des valeurs de texte courtes telles que le nom, l'e-mail, le numéro de téléphone, etc. Pour stocker des valeurs de texte sur plusieurs lignes, utilisez [Texte long](020.long-text.md) champ.
## Créer un champ de texte sur une seule ligne
1. Cliquer sur `+` icône à droite de `Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme `Single line text` dans la liste déroulante.
4. Définissez la valeur par défaut pour le champ (facultatif).
5. Cliquer sur `Save Field` bouton.
![image](/img/v2/fields/types/singlelinetext.png)
:::note
Spécifiez la valeur par défaut sans guillemets.
:::
## Champs de texte similaires
Voici les autres champs basés sur du texte disponibles dans NocoDB, conçus sur mesure pour des cas d'utilisation spécifiques.
* [Texte long](020.long-text.md)
* [URL](050.url.md)
* [E-mail](030.email.md)
* [Téléphone](040.phonenumber.md)

36
scripts/docs/fr/070.fields/040.field-types/010.text-based/fr-040.phonenumber.md

@ -0,0 +1,36 @@
***
titre : "Numéro de téléphone"
description: 'Cet article explique comment créer et utiliser un champ Numéro de téléphone.'
balises : ['Champs', 'Types de champs', 'Types de champs textuels', 'Numéro de téléphone']
mots-clés : ['Champs', 'Types de champs', 'Types de champs textuels', 'Numéro de téléphone', 'Créer un champ de numéro de téléphone']
-----------------------------------------------------------------------------------------------------------------------------------
`Phone number`Le champ est un champ de texte qui vous permet de stocker des numéros de téléphone. Il vous permet également de valider le numéro de téléphone.
## Créer un champ `Numéro de téléphone`
1. Cliquer sur l'icône `+` à droite de l'en-tête `Champs`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme `Numéro de téléphone` dans le menu déroulant.
4. Activez la validation en cochant l'option `Valider le numéro de téléphone` (facultatif).
5. Définissez la valeur par défaut pour le champ (facultatif).
6. Cliquez sur le bouton `Enregistrer le champ`.
![image](/img/v2/fields/types/phonenumber.png)
::: note
* Spécifiez la valeur par défaut sans guillemets.
* La validation garantit uniquement que la valeur saisie est un numéro de téléphone valide. Il ne vérifie pas si le numéro de téléphone existe.
:::
## Champs textuels similaires
Voici les autres champs textuels disponibles dans NocoDB, conçus sur mesure pour des cas d'utilisation spécifiques.
* [Texte sur une seule ligne](010.single-line-text.md)
* [Texte long](020.long-text.md)
* [URL](050.url.md)
* [E-mail](030.email.md)
*

39
scripts/docs/fr/070.fields/040.field-types/010.text-based/fr-050.url.md

@ -0,0 +1,39 @@
***
titre : 'URL'
description: 'Cet article explique comment créer et utiliser un champ URL.'
balises : \['Champs', 'Types de champs', 'Types basés sur du texte', 'URL']
mots-clés : \['Champs', 'Types de champs', 'Types basés sur texte', 'URL', 'Créer un champ URL']
------------------------------------------------------------------------------------------------
`URL`Le champ est un champ basé sur du texte, conçu sur mesure pour stocker les URL. Il s’agit d’un type particulier de`Single line text`champ avec
* Validation facultative pour l'URL
* Affichage des cellules sous forme de lien cliquable
## Créer un`URL`champ
1. Cliquer sur`+`icône à droite de`Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme`URL`dans la liste déroulante.
4. Activez la validation en activant l'option`Validate URL`case à cocher (facultatif).
5. Définissez la valeur par défaut pour le champ (facultatif).
6. Cliquer sur`Save Field`bouton.
![image](/img/v2/fields/types/url.png)
![image](/img/v2/fields/types/url.png)
:::note
* Spécifiez la valeur par défaut sans guillemets.
* La validation garantit uniquement que la valeur saisie est une URL valide. Il ne vérifie pas si l’URL existe.
:::
## Champs de texte similaires
Voici les autres champs textuels disponibles dans NocoDB, conçus sur mesure pour des cas d'utilisation spécifiques.
* [Texte sur une seule ligne](010.single-line-text.md)
* [Texte long](020.long-text.md)
* [E-mail](030.email.md)
* [Téléphone](040.phonenumber.md)

63
scripts/docs/fr/070.fields/040.field-types/030.select-based/fr-010.single-select.md

@ -0,0 +1,63 @@
***
titre : « Sélection unique »
description: 'Cet article explique comment créer et utiliser un champ à sélection unique.'
balises : \['Champs', 'Types de champs', 'Types de sélection', 'Sélection unique']
mots-clés : \['Champs', 'Types de champs', 'Sélectionner des types basés', 'Sélection unique', 'Créer un champ à sélection unique']
-----------------------------------------------------------------------------------------------------------------------------------
`Single select`Les champs vous permettent de sélectionner une seule option dans une liste d'options. Les options peuvent être définies dans la configuration du champ.
## Créer un seul champ de sélection
1. Cliquer sur `+` icône à droite de `Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme `SingleSelect` dans la liste déroulante.
4. Cliquer sur `Add option` bouton pour ajouter des options.
5. Définissez la valeur par défaut pour le champ. Les options sont renseignées dans la liste déroulante (facultatif).
6. Cliquer sur `Save Field` bouton.
![image](/img/v2/fields/types/singleselect.png)
## Modifier les options
### Options de renommage
Vous pouvez renommer les options en cliquant sur la zone de texte de l'option associée. Cliquer sur`Save Field`bouton pour enregistrer les modifications.
### Configurer la couleur pour les options
Vous pouvez reconfigurer la couleur d'arrière-plan pour chaque option. Ceci est utile lorsque vous souhaitez mettre en évidence certaines options. Par exemple, vous pouvez configurer `High` possibilité d'avoir `red` couleur de l'arrière-plan.
Pour configurer, cliquez sur le`color`icône à côté de l’option. Sélectionnez la couleur dans le sélecteur de couleurs et cliquez sur`Save Field`bouton.
![image](/img/v2/fields/types/options-change-colour.png)
### Options de commande
Vous pouvez réorganiser les options en faisant glisser et en déposant les options. Pour commander à nouveau, cliquez sur le`drag`à côté de l’option et faites-la glisser vers la position souhaitée. Cliquer sur`Save Field`bouton pour enregistrer la commande.
![image](/img/v2/fields/types/options-reorder.png)
::: Info
L'ordre défini pour les options sera également utilisé dans la liste déroulante des cellules.
:::
### Modifier les options
Vous pouvez renommer les options en cliquant sur la zone de texte de l'option associée. Cliquer sur`Save Field`bouton pour enregistrer les modifications.
### Options de suppression
Vous pouvez supprimer des options en cliquant sur le`x`icône à côté de l’option. Vous pouvez annuler la suppression en cliquant sur le`undo`icône à côté de l’option. Cliquer sur`Save Field`bouton pour enregistrer les modifications.
:::Info
* Lors de la suppression d'une option, la valeur de l'option sera supprimée de toutes les cellules.
* Si la valeur de l'option est définie comme valeur par défaut pour le champ, la valeur par défaut sera supprimée.
:::
![image](/img/v2/fields/types/options-remove.png)
## Champs basés sur la sélection similaires
* [Sélection multiple](020.multi-select.md)

21
scripts/docs/fr/070.fields/040.field-types/050.custom-types/fr-080.json.md

@ -0,0 +1,21 @@
***
titre : 'JSON'
balises : \['Champs', 'Types de champs', 'Types personnalisés', 'JSON']
-----------------------------------------------------------------------
`JSON` est un type de champ personnalisé qui vous permet de stocker des données JSON dans un champ. Utile pour stocker des données qui peuvent être représentées sous forme d'objet JSON, comme une réponse API ou un ensemble de paires clé-valeur.
## Créer un champ JSON
1. Cliquer sur `+` icône à droite de `Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme `JSON` dans la liste déroulante.
4. Configurer la valeur par défaut pour le champ (facultatif)
5. Cliquer sur `Save Field` bouton.
![image](/img/v2/fields/types/JSON.png)
### Affichage des cellules
La cellule affiche les données JSON stockées dans le champ. Cliquez sur l'icône de développement d'enregistrement pour ouvrir les données JSON dans un éditeur modal.

272
scripts/docs/fr/070.fields/040.field-types/060.formula/fr-030.string-functions.md

@ -0,0 +1,272 @@
***
titre : 'Fonctions de chaîne'
description: 'Cet article explique diverses fonctions de chaîne pouvant être utilisées dans les champs de formule.'
balises : \['Champs', 'Types de champs', 'Formule']
mots-clés : \['Champs', 'Types de champs', 'Formule', 'Créer un champ de formule', 'Fonctions de chaîne']
---------------------------------------------------------------------------------------------------------
Cette aide-mémoire fournit un guide de référence rapide pour diverses fonctions basées sur des chaînes couramment utilisées dans l'analyse et la programmation de données. Chaque fonction est accompagnée de sa syntaxe, d'un exemple d'utilisation et d'une brève description.
## CONCAT
La fonction CONCAT concatène une ou plusieurs chaînes en une seule chaîne.
#### Syntaxe
```plaintext
CONCAT(text, [text,...])
```
#### Sample
```plaintext
CONCAT('John', ' ', 'Doe') => 'John Doe'
```
## GAUCHE
La fonction LEFT récupère les premiers « n » caractères spécifiés depuis le début de la chaîne d’entrée.
#### Syntaxe
```plaintext
LEFT(text, count)
```
#### Échantillon
```plaintext
LEFT('123-456-7890', 3) => '123'
```
## LEN
La fonction LEN calcule et renvoie le nombre total de caractères présents dans la chaîne fournie.
#### Syntaxe
```plaintext
LEN(text)
```
#### Échantillon
```plaintext
LEN('Product Description') => 19
```
## INFÉRIEUR
La fonction LOWER transforme tous les caractères de la chaîne d'entrée en minuscules
#### Syntaxe
```plaintext
LOWER(text)
```
#### Échantillon
```plaintext
LOWER('User INPUT') => 'user input'
```
## MILIEU
La fonction MID récupère une sous-chaîne de la chaîne d'entrée en commençant à la position spécifiée et en s'étendant sur le nombre de caractères spécifié.
#### Syntaxe
```plaintext
MID(text, position, [count])
```
#### Échantillon
```plaintext
MID('This is a sentence', 5, 3) => 'is '
```
## REGEX\_EXTRACT
La fonction REGEX\_EXTRACT recherche dans la chaîne d'entrée la première occurrence du modèle d'expression régulière spécifié et renvoie la sous-chaîne correspondante.
#### Syntaxe
```plaintext
REGEX_EXTRACT(text, pattern)
```
#### Échantillon
```plaintext
REGEX_EXTRACT('Error: Something went wrong', 'Error: (.*)') => 'Something went wrong'
```
## REGEX\_MATCH
La fonction REGEX\_MATCH évalue si la chaîne d'entrée correspond au modèle d'expression régulière spécifié, renvoyant 1 s'il y a une correspondance et 0 s'il n'y a pas de correspondance.
#### Syntaxe
```plaintext
REGEX_MATCH(text, pattern)
```
#### Échantillon
```plaintext
REGEX_MATCH('123-45-6789', '\d{3}-\d{2}-\d{4}') => 1
```
## REGEX\_REPLACE
La fonction REGEX\_REPLACE identifie toutes les occurrences du modèle d'expression régulière spécifié dans la chaîne d'entrée et les remplace par la chaîne de remplacement fournie.
#### Syntaxe
```plaintext
REGEX_REPLACE(text, pattern, replacer)
```
#### Échantillon
```plaintext
REGEX_REPLACE('Replace all bugs', 'bug', 'feature') => 'Replace all features'
```
## REPEAT
La fonction REPEAT duplique la chaîne fournie le nombre de fois spécifié, facilitant la création de modèles ou de séquences répétés.
#### Syntaxe
```plaintext
REPEAT(text, count)
```
#### Échantillon
```plaintext
REPEAT('😃', 3) => '😃😃😃'
```
## REMPLACER
La fonction REPLACE identifie toutes les instances d'une sous-chaîne particulière dans la chaîne donnée et les remplace par une autre sous-chaîne spécifiée.
#### Syntaxe
```plaintext
REPLACE(text, srchStr, rplcStr)
```
#### Échantillon
```plaintext
REPLACE('Replace old text', 'old', 'new') => 'Replace new text'
```
## DROITE
La fonction RIGHT récupère les n derniers caractères de la fin de la chaîne d'entrée, vous permettant d'extraire une sous-chaîne en commençant par la droite.
#### Syntaxe
```plaintext
RIGHT(text, n)
```
#### Échantillon
```plaintext
RIGHT('file_name.txt', 3) => 'txt'
```
## RECHERCHE
La fonction SEARCH identifie la position de la sous-chaîne spécifiée dans la chaîne d'entrée, renvoyant l'index si elle est trouvée, et zéro sinon.
#### Syntaxe
```plaintext
SEARCH(text, srchStr)
```
#### Échantillon
```plaintext
SEARCH('user@example.com', '@') => 5
```
## SUBSTR
La fonction SUBSTR extrait une sous-chaîne de la chaîne d'entrée, en commençant à la position spécifiée et en s'étendant éventuellement pour le nombre de caractères spécifié.
#### Syntaxe
```plaintext
SUBSTR(text, position, [count])
```
#### Échantillon
```plaintext
SUBSTR('Extract this text', 9, 4) => 'this'
```
## GARNITURE
La fonction TRIM élimine tous les espaces de début ou de fin de la chaîne d'entrée.
#### Syntaxe
```plaintext
TRIM(text)
```
#### Échantillon
```plaintext
TRIM(' Trim this ') => 'Trim this'
```
## SUPÉRIEUR
La fonction UPPER transforme tous les caractères de la chaîne d'entrée en majuscules.
#### Syntaxe
```plaintext
UPPER(text)
```
#### Échantillon
```plaintext
UPPER('title') => 'TITLE'
```
## URL
La fonction URL vérifie si la chaîne d'entrée est une URL valide et la convertit en lien hypertexte
#### Syntaxe
```plaintext
URL(text)
```
#### Échantillon
```plaintext
URL('https://www.example.com') => a clickable link for https://www.example.com
```
## Articles Liés
* [Opérateurs numériques et logiques](015.operators.md)
* [Fonctions numériques](020.numeric-functions.md)
* [Fonctions de dates](040.date-functions.md)
* [Expressions conditionnelles](050.conditional-expressions.md)

121
scripts/docs/fr/070.fields/040.field-types/060.formula/fr-040.date-functions.md

@ -0,0 +1,121 @@
***
titre : 'Fonctions de date'
description : 'Cet article explique diverses fonctions de date qui peuvent être utilisées dans les champs de formule.'
description: 'Cet article explique diverses fonctions de date qui peuvent être utilisées dans les champs de formule.'
balises : \['Champs', 'Types de champs', 'Formule', 'Date et heure']
mots-clés : \['Champs', 'Types de champs', 'Formule', 'Date et heure', 'Créer un champ de formule', 'Fonctions de date']
------------------------------------------------------------------------------------------------------------------------
Cette aide-mémoire fournit un guide de référence rapide pour diverses fonctions de date couramment utilisées dans l'analyse et la programmation des données. Chaque fonction est accompagnée de sa syntaxe, d'un exemple d'utilisation et d'une brève description.
## DATETIME\_DIFF
La fonction DATETIME\_DIFF calcule la différence entre deux dates dans différentes unités.
#### Syntaxe
```plaintext
DATETIME_DIFF(date1, date2, ["milliseconds" | "ms" | "seconds" | "s" | "minutes" | "m" | "hours" | "h" | "days" | "d" | "weeks" | "w" | "months" | "M" | "quarters" | "Q" | "years" | "y"])
```
#### Échantillon
```plaintext
DATETIME_DIFF("2022/10/14", "2022/10/15", "seconds") => -86400
```
#### Remarque
Cette fonction compare deux dates et renvoie la différence dans l'unité spécifiée. Un résultat positif indique que la première date est postérieure à la seconde, tandis qu'un résultat négatif indique l'inverse.
***
## DATEADD
La fonction DATEADD ajoute une valeur spécifiée à une date ou une date/heure.
#### Syntaxe
```plaintext
DATEADD(date | datetime, value, ["day" | "week" | "month" | "year"])
```
#### Échantillon
```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
```
#### Exemple conditionnel
```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.
```
#### Remarque
Cette fonction prend en charge les champs date et datetime, et elle est capable de gérer les valeurs négatives.
***
## MAINTENANT
La fonction MAINTENANT renvoie l'heure et le jour actuels.
#### Syntaxe
```plaintext
NOW()
```
#### Échantillon
```plaintext
NOW() => 2022-05-19 17:20:43 (current date & time)
```
#### Exemple conditionnel
```plaintext
IF(NOW() < date, "true", "false") => If the current date is less than the specified date, it returns true. Otherwise, it returns false.
```
#### Remarque
Cette fonction fournit l'heure et le jour actuels, prenant en charge les champs datetime et les valeurs négatives.
***
## JOUR DE LA SEMAINE
La fonction WEEKDAY renvoie le jour de la semaine sous forme d'entier.
#### Syntaxe
```plaintext
WEEKDAY(date, [startDayOfWeek])
```
#### Échantillon
```plaintext
WEEKDAY(NOW()) => If today is Monday, it returns 0.
WEEKDAY(NOW(), "sunday") => If today is Monday, it returns 1.
```
#### Remarque
Renvoie le jour de la semaine sous forme d'entier, de 0 à 6, le lundi étant le jour de début par défaut. Il est possible de modifier le jour de début de la semaine en spécifiant un deuxième argument.
***
## Articles Liés
* [Opérateurs numériques et logiques](015.operators.md)
* [Fonctions numériques](020.numeric-functions.md)
* [Fonctions de chaîne](030.string-functions.md)
* [Expressions conditionnelles](050.conditional-expressions.md)

114
scripts/docs/fr/070.fields/040.field-types/060.formula/fr-050.conditional-expressions.md

@ -0,0 +1,114 @@
***
titre : « Expressions conditionnelles »
description: 'Cet article explique diverses expressions conditionnelles pouvant être utilisées dans les champs de formule.'
balises : \['Champs', 'Types de champs', 'Formule']
mots-clés : \['Champs', 'Types de champs', 'Formule', 'Créer un champ de formule', 'Expressions conditionnelles']
-----------------------------------------------------------------------------------------------------------------
Cet aide-mémoire fournit un guide de référence rapide pour diverses expressions conditionnelles couramment utilisées dans l'analyse de données et la programmation. Chaque expression est accompagnée de sa syntaxe, d'un exemple d'utilisation et d'une brève description.
## SI
La fonction IF dans les formules de programmation et de feuille de calcul permet d'effectuer des opérations conditionnelles. Il évalue une condition et renvoie une valeur si la condition est `TRUE`, ou une autre valeur si la condition est `FALSE`.
#### Syntaxe
```markdown
IF(expr, successCase, elseCase)
```
#### Échantillon
```markdown
IF({field} > 1, Value1, Value2)
Output
- `Value1` if `{field} > 1` evaluates to TRUE
- `Value2` otherwise
```
## CHANGER
La fonction SWITCH est un outil polyvalent permettant de gérer plusieurs cas. Il évalue l'expression donnée (expr) par rapport à une série de modèles et renvoie la valeur correspondante du premier modèle correspondant. Si aucun ne correspond, il renvoie la valeur par défaut.
#### Syntaxe
```markdown
SWITCH(expr, [pattern, value, ..., default])
```
#### Échantillon
```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
```
## ET
La fonction AND est un opérateur logique qui renvoie VRAI uniquement si toutes ses conditions sont vraies.
#### Syntaxe
```markdown
AND(expr1, [expr2,...])
```
#### Échantillon
```markdown
AND({field} > 2, {field} < 10)
Output
TRUE if both `{field} > 2` and `{field} < 10` evaluate to TRUE
```
## OU
La fonction OR, un autre opérateur logique, renvoie VRAI si au moins une de ses conditions est vraie.
#### Syntaxe
```markdown
OR(expr1, [expr2,...])
```
#### Échantillon
```markdown
OR({field} > 2, {field} < 10)
Output
TRUE if at least one of the conditions `{field} > 2` or `{field} < 10` evaluates to TRUE
```
::: conseil
Les opérateurs logiques, ainsi que les opérateurs numériques, peuvent être utilisés pour créer des `expressions`.
Exemples:
```
IF({marksSecured} > 80, "GradeA", "GradeB")
```
```
SWITCH({quarterNumber},
1, 'Jan-Mar',
2, 'Apr-Jun',
3, 'Jul-Sep',
4, 'Oct-Dec',
'INVALID'
)
```
:::
## Articles Liés
* [Opérateurs numériques et logiques](015.operators.md)
* [Fonctions numériques](020.numeric-functions.md)
* [Fonctions de chaîne](030.string-functions.md)
* [Fonctions de dates](040.date-functions.md)
*

96
scripts/docs/fr/070.fields/040.field-types/070.date-time-based/fr-010.date-time.md

@ -0,0 +1,96 @@
***
titre : 'Date Heure'
description: 'Cet article explique comment créer et utiliser un champ Date Heure.'
balises : \['Champs', 'Types de champs', 'Date et heure']
mots-clés : \['Champs', 'Types de champs', 'Date et heure', 'Créer un champ date/heure']
----------------------------------------------------------------------------------------
`Date Time`Le type de champ est utilisé pour stocker les valeurs de date et d’heure dans un seul champ.
## Créer un champ date/heure
1. Cliquer sur `+` icône à droite de `Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme`DateTime`dans la liste déroulante.
4. Configurer`Date Format`
5. Configurer`Time Format`
6. Configurer la valeur par défaut (facultatif)
7. Cliquer sur`Save Field`bouton.
![image](/img/v2/fields/types/datetime.png)
### Formats de dates pris en charge
| Formater | Exemple |
|--------------|--------------|
| AAAA-MM-JJ | 2023-09-22 |
| AAAA/MM/JJ | 2023/09/22 |
| JJ-MM-AAAA | 22-09-2023 |
| MM-JJ-AAAA | 22/09/2023 |
| JJ/MM/AAAA | 22/09/2023 |
| MM/JJ/AAAA | 22/09/2023 |
| JJ MM AAAA | 22 09 2023 |
| MM JJ AAAA | 22 09 2023 |
| AAAA MM JJ | 2023 09 22 |
### Formats d'heure pris en charge
| Formater | Exemple |
|---------------|------------|
| HH:mm:ss | 12:45:30 |
| HH:mm | 14h20 |
## Domaines connexes
* [Date](020.date.md)
* [Temps](030.time.md)
* [Durée](040.duration.md)
***
titre : 'Date Heure'
description: 'Cet article explique comment créer et utiliser un champ Date Heure.'
balises : \['Champs', 'Types de champs', 'Date et heure']
mots-clés : \['Champs', 'Types de champs', 'Date et heure', 'Créer un champ date/heure']
----------------------------------------------------------------------------------------
`Date Time`Le type de champ est utilisé pour stocker les valeurs de date et d’heure dans un seul champ.
## Créer un champ date/heure
1. Cliquer sur`+`icône à droite de`Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme`DateTime`dans la liste déroulante.
4. Configurer`Date Format`
5. Configurer`Time Format`
6. Configurer la valeur par défaut (facultatif)
7. Cliquer sur`Save Field`bouton.
![image](/img/v2/fields/types/datetime.png)
### Formats de dates pris en charge
| Formater | Exemple |
|--------------|--------------|
| AAAA-MM-JJ | 2023-09-22 |
| AAAA/MM/JJ | 2023/09/22 |
| JJ-MM-AAAA | 22-09-2023 |
| MM-JJ-AAAA | 22/09/2023 |
| JJ/MM/AAAA | 22/09/2023 |
| MM/JJ/AAAA | 22/09/2023 |
| JJ MM AAAA | 22 09 2023 |
| MM JJ AAAA | 22 09 2023 |
| AAAA MM JJ | 2023 09 22 |
### Formats d'heure pris en charge
| Formater | Exemple |
|---------------|------------|
| HH:mm:ss | 12:45:30 |
| HH:mm | 14h20 |
## Domaines connexes
* [Date](020.date.md)
* [Temps](030.time.md)
* [Durée](040.duration.md)

34
scripts/docs/fr/070.fields/040.field-types/070.date-time-based/fr-050.created-time.md

@ -0,0 +1,34 @@
***
Titre : « Temps créé »
description: 'Cet article explique comment créer et utiliser un champ Heure de création.'
balises : \['Champs', 'Types de champs', 'Date et heure', 'Champs système']
mots-clés : \['Champs', 'Types de champs', 'Date et heure', 'Champs système', 'Heure de création']
--------------------------------------------------------------------------------------------------
Since version v0.204.0 (Jan 2024), NocoDB internally captures the time when record was created. This information is stored as a system field in the database & is hidden in the table by default. To view this information on the UI, you can either enable `Show System Fields` ou créer un `Created Time` champ manuellement en suivant les étapes ci-dessous.
Le champ système par défaut ne peut être masqué que dans l'interface utilisateur. Il ne peut être modifié, dupliqué ou supprimé.
:::note
* Lorsqu'il est connecté à une base de données externe, `CreatedTime` le champ n’est pas créé automatiquement. Vous pouvez créer un `CreatedTime` champ manuellement en suivant les étapes ci-dessous.
* `CreatedTime` le champ est vide indique que l'enregistrement est antérieur à la fonctionnalité créée (v0.204.0, janvier 2024). Avant cette version de fonctionnalité, il était possible de le supprimer du tableau.
:::
## Créer un `CreatedTime` champ
1. Cliquer sur `+` icône à droite de `Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme `CreatedTime` dans la liste déroulante.
4. Cliquer sur `Save Field` bouton.
![image](/img/v2/fields/types/created-time.png)
## Affichage des cellules
`CreatedTime` field is displayed as a read-only field in the table view. It is displayed as a date & time string in the format `DD MMM YYYY, HH:mm`.
## Domaines connexes
* [Heure de la dernière modification](060.last-modified-time.md)

34
scripts/docs/fr/070.fields/040.field-types/080.user-based/fr-010.user.md

@ -0,0 +1,34 @@
***
titre : 'Utilisateur'
description: 'Cet article explique comment créer et utiliser un champ Utilisateur.'
balises : \['Champs', 'Types de champs', 'Utilisateur']
mots-clés : \['Champs', 'Types de champs', 'Utilisateur', 'Champ Créer un utilisateur']
---------------------------------------------------------------------------------------
`User` Le type de champ vous permet d'attribuer un utilisateur de votre espace de travail actuel à un enregistrement. Par exemple, vous pouvez créer une `Task` table avec un `User` type de champ pour attribuer une tâche à un utilisateur. Vous pouvez également configurer le champ pour permettre l'affectation de plusieurs utilisateurs à un enregistrement.
## Créer un champ Utilisateur
1. Cliquer sur`+`icône à droite de`Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme`User`dans la liste déroulante.
4. Configurer`Allow adding multiple users`champ bascule (facultatif).
5. Configurer la valeur par défaut (facultatif)
6. Cliquer sur`Save Field`bouton.
![image](/img/v2/fields/types/user-field.png)
### Affichage des cellules
`User` L'affichage du champ est tout à fait identique à `Select` champ. Il est affiché sous forme de liste déroulante dans la vue tableau. Cliquez sur la liste déroulante pour sélectionner un utilisateur. Si `Allow adding multiple users` est activé, vous pouvez sélectionner plusieurs utilisateurs dans la liste déroulante.
![image](/img/v2/fields/types/user-field-cell.png)
:::note
* Si un utilisateur est supprimé de l'espace de travail, il sera supprimé de la liste déroulante. Si un tel utilisateur a déjà été affecté à un enregistrement, il sera affiché tel quel.
* Pour supprimer un utilisateur d'un enregistrement, cliquez sur l'icône`x`icône à côté du nom d'utilisateur.
* Si le nom d'affichage n'est pas défini pour un utilisateur, l'adresse e-mail de l'utilisateur sera affichée.
:::

35
scripts/docs/fr/070.fields/040.field-types/080.user-based/fr-020.created-by.md

@ -0,0 +1,35 @@
***
titre : "Créé par"
description: 'Cet article explique comment créer et utiliser un champ Créé par.'
balises : \['Champs', 'Types de champs', 'Créé par']
mots-clés : \['Champs', 'Types de champs', 'Créé par']
------------------------------------------------------
Depuis la version v0.204.0 (janvier 2024), NocoDB capture en interne l'utilisateur qui a créé un enregistrement. Ces informations sont stockées sous forme de champ système dans la base de données et ne sont pas incluses dans le tableau par défaut. Pour visualiser ces informations sur le tableau, vous pouvez créer un `Created By` champ manuellement en suivant les étapes ci-dessous.
## Créer un `Created By` champ
1. Cliquer sur`+`icône à droite de`Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme`CreatedBy`dans la liste déroulante.
4. Cliquer sur`Save Field`bouton.
![image](/img/v2/fields/types/created-by.png)
> **Note:**
* Lorsqu’il est connecté à une base de données externe, les informations d’enregistrement créées par l’utilisateur ne sont pas capturées automatiquement. Vous pouvez créer un `Created By` champ manuellement en suivant les étapes ci-dessus. Pour les connexions à une base de données externe, ce champ n'est pas un champ système et peut être supprimé.
* `Created By` le champ est vide indique que l'enregistrement est soit
* est antérieur à la fonctionnalité créée par (v0.204.0, janvier 2024). Avant cette version de fonctionnalité, ces informations n'étaient pas capturées.
* a été créé à l’aide d’un formulaire partagé.
* connexion à une base de données externe : les informations ne sont capturées qu'après la création explicite du champ.
## Affichage des cellules
Le champ `Created By` est affiché en tant que champ en lecture seule dans la vue tableau. Il affiche l'adresse e-mail de l'utilisateur qui a créé l'enregistrement si aucun nom d'affichage n'est défini pour l'utilisateur. Si l’utilisateur a défini un nom d’affichage, le nom d’affichage s’affiche.
## Domaines connexes
* [Dernière modification par](030.last-modified-by.md)
*

36
scripts/docs/fr/070.fields/040.field-types/080.user-based/fr-030.last-modified-by.md

@ -0,0 +1,36 @@
***
titre : "Dernière modification par"
description: 'Cet article explique comment créer et utiliser un champ Dernière modification par.'
balises : \['Champs', 'Types de champs', 'Dernière modification par']
mots-clés : \['Champs', 'Types de champs', 'Dernière modification par']
-----------------------------------------------------------------------
Depuis la version v0.204.0 (janvier 2024), NocoDB capture en interne l'utilisateur qui a modifié un enregistrement pour la dernière fois. Ces informations sont stockées sous forme de champ système dans la base de données et ne sont pas incluses dans le tableau par défaut. Pour visualiser ces informations sur le tableau, vous pouvez créer un `Last Modified By` champ manuellement en suivant les étapes ci-dessous.
## Créer un`Last Modified By`champ
1. Cliquer sur`+`icône à droite de`Fields header`
2. Dans la liste déroulante modale, entrez le nom du champ (facultatif).
3. Sélectionnez le type de champ comme`LastModifiedBy`dans la liste déroulante.
4. Cliquer sur`Save Field` button.
![image](/img/v2/fields/types/last-modified-by.png)
:::note
* When connected to an external database, user last modifying record information is not captured automatically. Vous pouvez créer un champ `Last Modified By` manuellement en suivant les étapes ci-dessus. Pour les connexions à une base de données externe, ce champ n'est pas un champ système et peut être supprimé.
* `Last Modified By` Le champ est vide indique que l'enregistrement est soit
* l'enregistrement a été créé et n'a jamais été modifié.
* est antérieur à la dernière fonctionnalité modifiée par (v0.204.0, janvier 2024). Avant cette version de fonctionnalité, ces informations n'étaient pas capturées.
* connexion à une base de données externe : les informations ne sont capturées qu'après la création explicite du champ.
* `Last Modified By` field is not updated when a record is initially created (is initialized to NULL).
:::
## Affichage des cellules
Le champ `Last Modified By` est affiché en lecture seule dans la vue tableau. Affiche l'adresse e-mail de l'utilisateur qui a modifié l'enregistrement pour la dernière fois si aucun nom d'affichage n'est défini pour l'utilisateur. Si l’utilisateur a défini un nom d’affichage, le nom d’affichage s’affiche.
## Domaines connexes
* [Créé par](020.created-by.md)

40
scripts/docs/fr/070.fields/fr-030.display-value.md

@ -0,0 +1,40 @@
***
titre : "Afficher la valeur"
description: 'Cet article explique comment définir la valeur d'affichage d'une table et son utilisation.'
balises : \['Champs', 'Valeur d'affichage']
mots-clés : \['valeur d'affichage', 'valeur d'affichage dans nocoDB']
-------------------------------------------------------------------------------------------------------
# Valeur d'affichage
# Valeur d’affichage
Le`Display Value`, comme son nom l'indique, sert de valeur principale ou principale dans un enregistrement d'une table, et c'est généralement l'attribut par lequel nous identifions ou associons cet enregistrement spécifique. S'il est conseillé que la valeur d'affichage soit liée à un champ avec des identifiants uniques, comme une clé primaire, il est important de noter que cette unicité n'est pas toujours appliquée au niveau de la base de données.
## Use of Display Value
* Within a spreadsheet, `Display Value` sont toujours mis en évidence afin qu’il soit plus facile de reconnaître sur quel disque nous travaillons.
* Et quand `Links` sont créés entre deux tableaux – c’est la valeur d’affichage qui apparaît dans `Linked records` modal.
Exemple :
Exemple:
Afficher la valeur mise en évidence dans le tableau Acteur![display value](/img/v2/fields/display-value.png)
Afficher la valeur associée au champ Liens\
La valeur indiquée dans`Link Records`modal lors de l'ajout d'un nouveau lien est les enregistrements associés`Display value`
![display value- links field](/img/v2/fields/display-value-in-linked-record.png)
## Définir la valeur d'affichage
Cliquez sur l’icône déroulante (🔽) dans le champ cible. Cliquez sur `Set as Display Value`.
![display value set](/img/v2/fields/set-as-display-value.png)
:: Info
Comment la valeur d'affichage est-elle identifiée pour les tables de base de données existantes ?
* Il s'agit généralement du premier champ après la clé primaire qui n'est pas un nombre.
* S'il n'y a aucun champ qui ne soit pas un nombre, alors le champ adjacent à la clé primaire est choisi.
Puis-je modifier la valeur d'affichage vers un autre champ dans les tableaux ?
* Oui, vous pouvez utiliser la même manière mentionnée ci-dessus pour définir la valeur d'affichage.
::

42
scripts/docs/fr/090.views/040.view-types/fr-020.gallery.md

@ -0,0 +1,42 @@
***
titre : 'Galerie'
description: 'Apprenez à utiliser la vue Galerie dans NocoDB.'
balises : \['Vues', 'Vue Galerie']
mots-clés : \['Vue galerie NocoDB', 'vue galerie', 'galerie']
-------------------------------------------------------------
La vue Galerie permet d'afficher des images en vignettes accompagnées d'autres champs, offrant une interface intuitive pour la visualisation. Ce type d'affichage est idéal pour visualiser des images dans une interface de type galerie. Comme dans Galerie, vous pouvez également effectuer diverses opérations sur la vue Galerie, telles que le tri, le filtrage, le regroupement et la recherche. Vous pouvez également exporter les données de la vue galerie au format CSV ou Excel. Cette section couvrira toutes les opérations pouvant être effectuées sur la vue galerie.
![1010-2 Gallery](/img/v2/views/gallery.png)
## Actions d’affichage de la galerie
1. [Créer une nouvelle vue de galerie](/views/create-view/#create-new-view)
2. [Renommer une vue de galerie existante](/views/actions-on-view#rename-view)
3. [Dupliquer une vue de galerie](/views/actions-on-view#duplicate-view)
4. [Supprimer une vue Galerie](/views/actions-on-view#delete-view)
5. [Partager une vue de la galerie](/views/share-view)
6. [Verrouiller la vue Galerie pour les modifications](/views/views-overview#view-permission-types)
## Opérations de la vue Galerie
1. [Réorganiser les champs dans la galerie](/table-operations/field-operations#rearranging-fields)
2. [Afficher ou masquer les champs dans la galerie](/table-operations/field-operations#showhide-fields)
3. [Appliquer des filtres pour afficher des enregistrements spécifiques dans la galerie](/table-operations/filter)
4. [Trier les enregistrements de la galerie selon un ou plusieurs critères](/table-operations/sort)
5. [Effectuer des recherches rapides pour des données spécifiques dans les champs](/table-operations/search)
6. [Exporter les données au format CSV ou Excel](/table-operations/download#download-data)
7. [Changer l'image de couverture](/table-operations/field-operations#change-cover-field-gallerykanban-view)
## Opérations d'enregistrement de la vue Galerie
1. **Ajouter un nouvel enregistrement à la galerie** : Actuellement, il n'est pas possible d'insérer un nouvel enregistrement directement sur la vue galerie. Cependant, vous pouvez ajouter un nouvel enregistrement à la table et il sera affiché dans la vue galerie.
2. **Modifier un enregistrement existant dans la galerie**: Cliquez sur la carte dans la vue Galerie pour ouvrir l'enregistrement dans la vue d'enregistrement développée. Vous pouvez modifier l'enregistrement dans la vue formulaire et l'enregistrer.
3. **Supprimer un seul enregistrement de la galerie**: Cliquez sur la carte dans la vue Galerie pour ouvrir l'enregistrement dans la vue d'enregistrement développée. Vous pouvez supprimer l'enregistrement dans la vue Formulaire à l'aide du menu contextuel de l'enregistrement développé.
4. **Supprimer plusieurs enregistrements en bloc de la galerie**: Actuellement, il n'est pas possible de supprimer plusieurs enregistrements en masse directement dans la vue Galerie. Cependant, vous pouvez supprimer plusieurs enregistrements en bloc de la vue grille et cela sera reflété dans la vue galerie.
## Articles Liés
[Vue Grille](/views/view-types/grid)\
[Vue Formulaire](/views/view-types/form)\
[Vue Kanban](/views/view-types/kanban)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save