Browse Source

Merge pull request #7121 from nocodb/develop

pull/7122/head 0.202.9
github-actions[bot] 10 months ago committed by GitHub
parent
commit
0f95765fe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      markdown/readme/languages/spanish.md
  2. 4
      packages/nc-gui/assets/nc-icons/credit-card.svg
  3. 6
      packages/nc-gui/assets/nc-icons/layers.svg
  4. 6
      packages/nc-gui/components.d.ts
  5. 6
      packages/nc-gui/components/cell/DatePicker.vue
  6. 6
      packages/nc-gui/components/cell/DateTimePicker.vue
  7. 25
      packages/nc-gui/components/cell/MultiSelect.vue
  8. 347
      packages/nc-gui/components/cell/RichText.vue
  9. 244
      packages/nc-gui/components/cell/RichText/LinkOptions.vue
  10. 381
      packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue
  11. 68
      packages/nc-gui/components/cell/RichText/SelectedBubbleMenuPopup.vue
  12. 12
      packages/nc-gui/components/cell/SingleSelect.vue
  13. 173
      packages/nc-gui/components/cell/TextArea.vue
  14. 8
      packages/nc-gui/components/cell/attachment/Modal.vue
  15. 10
      packages/nc-gui/components/cell/attachment/index.vue
  16. 15
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  17. 8
      packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue
  18. 3
      packages/nc-gui/components/nc/Button.vue
  19. 5
      packages/nc-gui/components/nc/Switch.vue
  20. 8
      packages/nc-gui/components/smartsheet/Cell.vue
  21. 33
      packages/nc-gui/components/smartsheet/Form.vue
  22. 8
      packages/nc-gui/components/smartsheet/Pagination.vue
  23. 2
      packages/nc-gui/components/smartsheet/Toolbar.vue
  24. 7
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  25. 123
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  26. 4
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  27. 38
      packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
  28. 26
      packages/nc-gui/components/smartsheet/column/RichLongTextDefaultValue.vue
  29. 4
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  30. 91
      packages/nc-gui/components/smartsheet/details/Fields.vue
  31. 10
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  32. 123
      packages/nc-gui/components/smartsheet/grid/Table.vue
  33. 8
      packages/nc-gui/components/smartsheet/grid/usePaginationShortcuts.ts
  34. 6
      packages/nc-gui/components/smartsheet/header/Cell.vue
  35. 8
      packages/nc-gui/components/smartsheet/header/CellIcon.ts
  36. 6
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  37. 4
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  38. 98
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  39. 14
      packages/nc-gui/components/tabs/Smartsheet.vue
  40. 5
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  41. 5
      packages/nc-gui/components/virtual-cell/HasMany.vue
  42. 13
      packages/nc-gui/components/virtual-cell/Links.vue
  43. 8
      packages/nc-gui/components/virtual-cell/Lookup.vue
  44. 5
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  45. 3
      packages/nc-gui/components/virtual-cell/QrCode.vue
  46. 11
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  47. 1
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  48. 5
      packages/nc-gui/components/virtual-cell/components/ListItem.vue
  49. 4
      packages/nc-gui/composables/useData.ts
  50. 1
      packages/nc-gui/composables/useGlobal/state.ts
  51. 1
      packages/nc-gui/composables/useGlobal/types.ts
  52. 4
      packages/nc-gui/composables/useMultiSelect/index.ts
  53. 59
      packages/nc-gui/composables/useTableNew.ts
  54. 6
      packages/nc-gui/composables/useViewData.ts
  55. 131
      packages/nc-gui/helpers/dbTiptapExtensions/links.ts
  56. 186
      packages/nc-gui/helpers/dbTiptapExtensions/task-item.ts
  57. 16
      packages/nc-gui/lang/ar.json
  58. 16
      packages/nc-gui/lang/bn_IN.json
  59. 112
      packages/nc-gui/lang/cs.json
  60. 16
      packages/nc-gui/lang/da.json
  61. 16
      packages/nc-gui/lang/de.json
  62. 15
      packages/nc-gui/lang/en.json
  63. 104
      packages/nc-gui/lang/es.json
  64. 16
      packages/nc-gui/lang/eu.json
  65. 16
      packages/nc-gui/lang/fa.json
  66. 16
      packages/nc-gui/lang/fi.json
  67. 16
      packages/nc-gui/lang/fr.json
  68. 16
      packages/nc-gui/lang/he.json
  69. 16
      packages/nc-gui/lang/hi.json
  70. 16
      packages/nc-gui/lang/hr.json
  71. 16
      packages/nc-gui/lang/id.json
  72. 16
      packages/nc-gui/lang/it.json
  73. 16
      packages/nc-gui/lang/ja.json
  74. 1024
      packages/nc-gui/lang/ko.json
  75. 16
      packages/nc-gui/lang/lv.json
  76. 20
      packages/nc-gui/lang/nl.json
  77. 16
      packages/nc-gui/lang/no.json
  78. 16
      packages/nc-gui/lang/pl.json
  79. 16
      packages/nc-gui/lang/pt.json
  80. 16
      packages/nc-gui/lang/pt_BR.json
  81. 16
      packages/nc-gui/lang/ru.json
  82. 16
      packages/nc-gui/lang/sk.json
  83. 16
      packages/nc-gui/lang/sl.json
  84. 16
      packages/nc-gui/lang/sv.json
  85. 16
      packages/nc-gui/lang/th.json
  86. 16
      packages/nc-gui/lang/tr.json
  87. 34
      packages/nc-gui/lang/uk.json
  88. 30
      packages/nc-gui/lang/vi.json
  89. 252
      packages/nc-gui/lang/zh-Hans.json
  90. 16
      packages/nc-gui/lang/zh-Hant.json
  91. 1
      packages/nc-gui/lib/acl.ts
  92. 8
      packages/nc-gui/lib/types.ts
  93. 2
      packages/nc-gui/nuxt-shim.d.ts
  94. 68
      packages/nc-gui/package.json
  95. 2
      packages/nc-gui/pages/signin.vue
  96. 3
      packages/nc-gui/store/tables.ts
  97. 11
      packages/nc-gui/store/views.ts
  98. 202
      packages/nc-gui/utils/formulaUtils.ts
  99. 9
      packages/nc-gui/utils/urlUtils.ts
  100. 8
      packages/noco-docs/docs/070.fields/040.field-types/060.formula/015.operators.md
  101. Some files were not shown because too many files have changed in this diff Show More

4
markdown/readme/languages/spanish.md

@ -194,8 +194,8 @@ Por favor diríjase a [Contribution Guide](https://github.com/nocodb/nocodb/blob
# Por qué estamos construyendo esto? # Por qué estamos construyendo esto?
La mayoría de las empresas de Internet emplean una hoja de cálculo o una base de datos para resolver sus necesidades comerciales. Las hojas de cálculo son utilizadas por billones de personas o más de manera colaborativa todos los días. Sin embargo, estamos lejos de trabajar a velocidades similares en bases de datos, ya que son herramientas computacionalmente más poderosas. Los intentos de resolver esto con soluciones SaaS han significado horribles controles de acceso, dependencia de un proveedor, dependencia de datos, cambios abruptos de precios y lo que es más importante, un techo de cristal sobre lo que es posible en el futuro." La mayoría de las empresas de Internet emplean una hoja de cálculo o una base de datos para resolver sus necesidades comerciales. Las hojas de cálculo son utilizadas por billones de personas o más de manera colaborativa todos los días. Sin embargo, estamos lejos de trabajar a velocidades similares en bases de datos, ya que son herramientas computacionalmente más poderosas. Los intentos de resolver esto con soluciones SaaS han significado horribles controles de acceso, dependencia de un proveedor, dependencia de datos, cambios abruptos de precios y lo que es más importante, un techo de cristal sobre lo que es posible en el futuro.
# Nuestra misión # Nuestra misión
Nuestra misión es proporcionar la interfaz sin-código más potente para bases de datos, la cual es open-source para negocios de Internet en el mundo. Esto no solo democratizaría el acceso a una poderosa herramienta de computación, sino que también producirá a billones de personas o más con habilidades radicales de perfección y construcción en Internet." Nuestra misión es proporcionar la interfaz sin-código más potente para bases de datos, la cual es open-source para negocios de Internet en el mundo. Esto no solo democratizaría el acceso a una poderosa herramienta de computación, sino que también producirá a miles de millones de personas o más con habilidades radicales de perfección y construcción en Internet.

4
packages/nc-gui/assets/nc-icons/credit-card.svg

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2.66602H1.99999C1.26361 2.66602 0.666656 3.26297 0.666656 3.99935V11.9993C0.666656 12.7357 1.26361 13.3327 1.99999 13.3327H14C14.7364 13.3327 15.3333 12.7357 15.3333 11.9993V3.99935C15.3333 3.26297 14.7364 2.66602 14 2.66602Z" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M14 2.66602H1.99999C1.26361 2.66602 0.666656 3.26297 0.666656 3.99935V11.9993C0.666656 12.7357 1.26361 13.3327 1.99999 13.3327H14C14.7364 13.3327 15.3333 12.7357 15.3333 11.9993V3.99935C15.3333 3.26297 14.7364 2.66602 14 2.66602Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.666656 6.66602H15.3333" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M0.666656 6.66602H15.3333" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 567 B

6
packages/nc-gui/assets/nc-icons/layers.svg

@ -1,8 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_656_42199)"> <g clip-path="url(#clip0_656_42199)">
<path d="M1.33325 11.334L7.99992 14.6673L14.6666 11.334" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M1.33325 11.334L7.99992 14.6673L14.6666 11.334" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33325 8L7.99992 11.3333L14.6666 8" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M1.33325 8L7.99992 11.3333L14.6666 8" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99992 1.33398L1.33325 4.66732L7.99992 8.00065L14.6666 4.66732L7.99992 1.33398Z" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M7.99992 1.33398L1.33325 4.66732L7.99992 8.00065L14.6666 4.66732L7.99992 1.33398Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g> </g>
<defs> <defs>
<clipPath id="clip0_656_42199"> <clipPath id="clip0_656_42199">

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 727 B

6
packages/nc-gui/components.d.ts vendored

@ -76,6 +76,7 @@ declare module '@vue/runtime-core' {
CilFullscreen: typeof import('~icons/cil/fullscreen')['default'] CilFullscreen: typeof import('~icons/cil/fullscreen')['default']
CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default'] CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default']
ClaritySuccessLine: typeof import('~icons/clarity/success-line')['default'] ClaritySuccessLine: typeof import('~icons/clarity/success-line')['default']
IcBaselineArrowOutward: typeof import('~icons/ic/baseline-arrow-outward')['default']
IcBaselineMoreVert: typeof import('~icons/ic/baseline-more-vert')['default'] IcBaselineMoreVert: typeof import('~icons/ic/baseline-more-vert')['default']
IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default'] IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default']
IcRoundEdit: typeof import('~icons/ic/round-edit')['default'] IcRoundEdit: typeof import('~icons/ic/round-edit')['default']
@ -123,6 +124,7 @@ declare module '@vue/runtime-core' {
MdiCodeTags: typeof import('~icons/mdi/code-tags')['default'] MdiCodeTags: typeof import('~icons/mdi/code-tags')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default'] MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
MdiCurrencyUsd: typeof import('~icons/mdi/currency-usd')['default'] MdiCurrencyUsd: typeof import('~icons/mdi/currency-usd')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDiscord: typeof import('~icons/mdi/discord')['default'] MdiDiscord: typeof import('~icons/mdi/discord')['default']
MdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['default'] MdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default'] MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
@ -130,9 +132,13 @@ declare module '@vue/runtime-core' {
MdiFileDocumentMultipleOutline: typeof import('~icons/mdi/file-document-multiple-outline')['default'] MdiFileDocumentMultipleOutline: typeof import('~icons/mdi/file-document-multiple-outline')['default']
MdiFileDocumentOutline: typeof import('~icons/mdi/file-document-outline')['default'] MdiFileDocumentOutline: typeof import('~icons/mdi/file-document-outline')['default']
MdiFlag: typeof import('~icons/mdi/flag')['default'] MdiFlag: typeof import('~icons/mdi/flag')['default']
MdiFormatBold: typeof import('~icons/mdi/format-bold')['default']
MdiFormatItalic: typeof import('~icons/mdi/format-italic')['default']
MdiFormatUnderline: typeof import('~icons/mdi/format-underline')['default']
MdiHeart: typeof import('~icons/mdi/heart')['default'] MdiHeart: typeof import('~icons/mdi/heart')['default']
MdiHistory: typeof import('~icons/mdi/history')['default'] MdiHistory: typeof import('~icons/mdi/history')['default']
MdiKeyStar: typeof import('~icons/mdi/key-star')['default'] MdiKeyStar: typeof import('~icons/mdi/key-star')['default']
MdiLink: typeof import('~icons/mdi/link')['default']
MdiLinkVariant: typeof import('~icons/mdi/link-variant')['default'] MdiLinkVariant: typeof import('~icons/mdi/link-variant')['default']
MdiLoading: typeof import('~icons/mdi/loading')['default'] MdiLoading: typeof import('~icons/mdi/loading')['default']
MdiLogin: typeof import('~icons/mdi/login')['default'] MdiLogin: typeof import('~icons/mdi/login')['default']

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

@ -32,8 +32,6 @@ const columnMeta = inject(ColumnInj, null)!
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
const isEditColumn = inject(EditColumnInj, ref(false)) const isEditColumn = inject(EditColumnInj, ref(false))
const active = inject(ActiveCellInj, ref(false)) const active = inject(ActiveCellInj, ref(false))
@ -188,9 +186,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
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) || isLockedMode.value return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
? false
: open.value
}) })
// use the default date picker open sync only to close the picker // use the default date picker open sync only to close the picker

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

@ -37,8 +37,6 @@ const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false)) const editable = inject(EditModeInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
const { t } = useI18n() const { t } = useI18n()
const isEditColumn = inject(EditColumnInj, ref(false)) const isEditColumn = inject(EditColumnInj, ref(false))
@ -126,9 +124,7 @@ const open = ref(false)
const isOpen = computed(() => { const isOpen = computed(() => {
if (readOnly.value) return false if (readOnly.value) return false
return readOnly.value || (localState.value && isPk) || isLockedMode.value return readOnly.value || (localState.value && isPk) ? false : open.value && (active.value || editable.value)
? false
: open.value && (active.value || editable.value)
}) })
const randomClass = `picker_${Math.floor(Math.random() * 99999)}` const randomClass = `picker_${Math.floor(Math.random() * 99999)}`

25
packages/nc-gui/components/cell/MultiSelect.vue

@ -49,8 +49,6 @@ const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)! const readOnly = inject(ReadonlyInj)!
const isLockedMode = inject(IsLockedInj, ref(false))
const isEditable = inject(EditModeInj, ref(false)) const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false)) const activeCell = inject(ActiveCellInj, ref(false))
@ -134,9 +132,13 @@ const vModel = computed({
const selectedTitles = computed(() => const selectedTitles = computed(() =>
modelValue modelValue
? typeof modelValue === 'string' ? Array.isArray(modelValue)
? isMysql(column.value.source_id) ? modelValue
? modelValue.split(',').sort((a, b) => { : isMysql(column.value.source_id)
? modelValue
.toString()
.split(',')
.sort((a, b) => {
const opa = options.value.find((el) => el.title === a) const opa = options.value.find((el) => el.title === a)
const opb = options.value.find((el) => el.title === b) const opb = options.value.find((el) => el.title === b)
if (opa && opb) { if (opa && opb) {
@ -144,8 +146,7 @@ const selectedTitles = computed(() =>
} }
return 0 return 0
}) })
: modelValue.split(',') : modelValue.toString().split(',')
: modelValue
: [], : [],
) )
@ -343,11 +344,7 @@ const selectedOpts = computed(() => {
</script> </script>
<template> <template>
<div <div class="nc-multi-select h-full w-full flex items-center" :class="{ 'read-only': readOnly }" @click="toggleMenu">
class="nc-multi-select h-full w-full flex items-center"
:class="{ 'read-only': readOnly || isLockedMode }"
@click="toggleMenu"
>
<div <div
v-if="!active" v-if="!active"
class="flex flex-wrap" class="flex flex-wrap"
@ -386,9 +383,9 @@ const selectedOpts = computed(() => {
:bordered="false" :bordered="false"
clear-icon clear-icon
:show-search="!isMobileMode" :show-search="!isMobileMode"
:show-arrow="editAllowed && !(readOnly || isLockedMode)" :show-arrow="editAllowed && !readOnly"
:open="isOpen && editAllowed" :open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed || isLockedMode" :disabled="readOnly || !editAllowed"
:class="{ 'caret-transparent': !hasEditRoles }" :class="{ 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`"
@search="search" @search="search"

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

@ -0,0 +1,347 @@
<script lang="ts" setup>
import StarterKit from '@tiptap/starter-kit'
import TaskList from '@tiptap/extension-task-list'
import { EditorContent, useEditor } from '@tiptap/vue-3'
import TurndownService from 'turndown'
import { marked } from 'marked'
import { generateJSON } from '@tiptap/html'
import Underline from '@tiptap/extension-underline'
import { TaskItem } from '@/helpers/dbTiptapExtensions/task-item'
import { Link } from '@/helpers/dbTiptapExtensions/links'
const props = defineProps<{
value?: string | null
readonly?: boolean
syncValueChange?: boolean
showMenu?: boolean
fullMode?: boolean
}>()
const emits = defineEmits(['update:value'])
const turndownService = new TurndownService({})
turndownService.addRule('lineBreak', {
filter: (node) => {
return node.nodeName === 'BR'
},
replacement: () => {
return '<br />'
},
})
turndownService.addRule('taskList', {
filter: (node) => {
return node.nodeName === 'LI' && !!node.getAttribute('data-checked')
},
replacement: (content, node: any) => {
// Remove the first \n\n and last \n\n
const processContent = content.replace(/^\n\n/, '').replace(/\n\n$/, '')
const isChecked = node.getAttribute('data-checked') === 'true'
return `[${isChecked ? 'x' : ' '}] ${processContent}\n\n`
},
})
const checkListItem = {
name: 'checkListItem',
level: 'block',
tokenizer(src: string) {
src = src.split('\n\n')[0]
const isMatched = src.startsWith('[ ]') || src.startsWith('[x]') || src.startsWith('[X]')
if (isMatched) {
const isNotChecked = src.startsWith('[ ]')
let text = src.slice(3)
if (text[0] === ' ') text = text.slice(1)
const token = {
// Token to generate
type: 'checkListItem',
raw: src,
text,
tokens: [],
checked: !isNotChecked,
}
;(this as any).lexer.inline(token.text, token.tokens) // Queue this data to be processed for inline tokens
return token
}
return false
},
renderer(token: any) {
return `<ul data-type="taskList"><li data-checked="${
token.checked ? 'true' : 'false'
}" data-type="taskItem"><label><input type="checkbox" ${
token.checked ? 'checked="checked"' : ''
}><span></span></label><div>${(this as any).parser.parseInline(token.tokens)}</div></li></ul>` // parseInline to turn child tokens into HTML
},
}
marked.use({ extensions: [checkListItem] })
const editorDom = ref<HTMLElement | null>(null)
const vModel = useVModel(props, 'value', emits, { defaultValue: '' })
const tiptapExtensions = [
StarterKit,
TaskList,
TaskItem.configure({
nested: true,
}),
Underline,
Link,
]
const editor = useEditor({
extensions: tiptapExtensions,
onUpdate: ({ editor }) => {
const markdown = turndownService
.turndown(editor.getHTML().replaceAll(/<p><\/p>/g, '<br />'))
.replaceAll(/\n\n<br \/>\n\n/g, '<br>\n\n')
vModel.value = markdown
},
editable: !props.readonly,
})
const setEditorContent = (contentMd: any) => {
if (!editor.value) return
const selection = editor.value.view.state.selection
const contentHtml = contentMd ? marked.parse(contentMd) : '<p></p>'
const content = generateJSON(contentHtml, tiptapExtensions)
editor.value.chain().setContent(content).setTextSelection(selection.to).run()
setTimeout(() => {
;(editor.value!.state as any).history$.prevRanges = null
;(editor.value!.state as any).history$.done.eventCount = 0
}, 100)
}
if (props.syncValueChange) {
watch(vModel, () => {
setEditorContent(vModel.value)
})
}
watch(editorDom, () => {
if (!editorDom.value) return
setEditorContent(vModel.value)
// Focus editor after editor is mounted
setTimeout(() => {
editor.value?.chain().focus().run()
}, 50)
})
</script>
<template>
<div
class="h-full"
:class="{
'flex flex-col flex-grow nc-rich-text-full': props.fullMode,
'nc-rich-text-embed': !props.fullMode,
}"
>
<div v-if="props.showMenu" class="absolute top-0 right-0.5">
<CellRichTextSelectedBubbleMenu v-if="editor" :editor="editor" embed-mode />
</div>
<CellRichTextSelectedBubbleMenuPopup v-if="editor" :editor="editor" />
<CellRichTextLinkOptions v-if="editor" :editor="editor" />
<EditorContent
ref="editorDom"
:editor="editor"
class="flex flex-col nc-textarea-rich-editor w-full flex-grow"
:class="{
'ml-1 mt-2.5': props.fullMode,
}"
/>
</div>
</template>
<style lang="scss">
.nc-text-rich-scroll {
&::-webkit-scrollbar-thumb {
@apply bg-transparent;
}
}
.nc-text-rich-scroll:hover {
&::-webkit-scrollbar-thumb {
@apply bg-gray-200;
}
}
.nc-rich-text-embed {
.ProseMirror {
@apply !border-transparent;
}
}
.nc-rich-text-full {
@apply px-1.75;
.ProseMirror {
@apply !p-2;
max-height: calc(min(60vh, 100rem));
min-height: 8rem;
}
}
.nc-textarea-rich-editor {
.ProseMirror {
@apply flex-grow pt-1 border-1 border-gray-200 rounded-lg pr-1 mr-2;
> * {
@apply ml-1;
}
}
.ProseMirror-focused {
// remove all border
outline: none;
@apply border-brand-500;
}
p {
@apply !mb-1;
}
ul {
li {
@apply ml-4;
list-style-type: disc;
}
}
ol {
@apply -ml-6 !pl-4;
li {
list-style-type: decimal;
}
}
ul,
ol {
@apply !my-0;
}
ul[data-type='taskList'] {
@apply;
li {
@apply !ml-0 flex flex-row gap-x-2;
list-style-type: none;
input {
@apply mt-0.75 flex rounded-sm;
z-index: -10;
}
// Unchecked
input:not(:checked) {
// Add border to checkbox
border-width: 1.5px;
@apply border-gray-700;
}
}
}
// Pre tag is the parent wrapper for Code block
pre {
border-color: #d0d5dd;
border: 1px;
color: black;
font-family: 'JetBrainsMono', monospace;
padding: 1rem;
border-radius: 0.5rem;
@apply overflow-auto mt-3 bg-gray-100;
code {
@apply !px-0;
}
}
code {
@apply rounded-md px-2 py-1 bg-gray-100;
color: inherit;
font-size: 0.8rem;
}
h1 {
font-weight: 700;
font-size: 1.85rem;
margin-bottom: 0.1rem;
}
h2 {
font-weight: 600;
font-size: 1.55rem;
margin-bottom: 0.1em;
}
h3 {
font-weight: 600;
font-size: 1.15rem;
margin-bottom: 0.1em;
}
blockquote {
border-left: 3px solid #d0d5dd;
padding: 0 1em;
color: #666;
margin: 1em 0;
font-style: italic;
}
hr {
@apply !border-gray-300;
border: 0;
border-top: 1px solid #ccc;
margin: 1.5em 0;
}
pre {
height: fit-content;
}
}
.nc-rich-text-full {
.ProseMirror {
overflow-y: scroll;
overflow-x: hidden;
scrollbar-width: thin !important;
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
&::-webkit-scrollbar-track {
-webkit-border-radius: 10px;
border-radius: 10px;
margin-top: 4px;
margin-bottom: 4px;
}
&::-webkit-scrollbar-track-piece {
width: 0px;
}
&::-webkit-scrollbar {
@apply bg-transparent;
}
&::-webkit-scrollbar-thumb {
-webkit-border-radius: 10px;
border-radius: 10px;
width: 4px;
@apply bg-gray-300;
}
&::-webkit-scrollbar-thumb:hover {
@apply bg-gray-400;
}
}
}
</style>

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

@ -0,0 +1,244 @@
<script lang="ts" setup>
import type { Editor } from '@tiptap/vue-3'
import { BubbleMenu } from '@tiptap/vue-3'
import { getMarkRange } from '@tiptap/core'
import type { Mark } from 'prosemirror-model'
const props = defineProps<Props>()
interface Props {
editor: Editor
}
const editor = computed(() => props.editor)
const inputRef = ref<HTMLInputElement>()
const linkNodeMark = ref<Mark | undefined>()
const href = ref('')
const isLinkOptionsVisible = ref(false)
// This is used to prevent the menu from showing up after a link is deleted, an edge case when the link with empty placeholder text is deleted.
// This is because checkLinkMark is not called in that case
const justDeleted = ref(false)
// This function is called by BubbleMenu on selection change
// It is used to check if the link mark is active and only show the menu if it is
const checkLinkMark = (editor: Editor) => {
if (!editor.view.editable) return false
if (justDeleted.value) {
setTimeout(() => {
justDeleted.value = false
}, 100)
return false
}
const activeNode = editor?.state?.selection?.$from?.nodeBefore || editor?.state?.selection?.$from?.nodeAfter
const isLinkMarkedStoredInEditor = editor?.state?.storedMarks?.some((mark: Mark) => mark.type.name === 'link')
const isActiveNodeMarkActive = activeNode?.marks?.some((mark: Mark) => mark.type.name === 'link') || isLinkMarkedStoredInEditor
if (isActiveNodeMarkActive) {
linkNodeMark.value = activeNode?.marks.find((mark: Mark) => mark.type.name === 'link')
href.value = linkNodeMark.value?.attrs?.href
}
if (isLinkMarkedStoredInEditor) {
linkNodeMark.value = editor?.state?.storedMarks?.find((mark: Mark) => mark.type.name === 'link')
href.value = linkNodeMark.value?.attrs?.href
}
const isTextSelected = editor?.state?.selection?.from !== editor?.state?.selection?.to
// check if active node is a text node
const showLinkOptions = isActiveNodeMarkActive && !isTextSelected
isLinkOptionsVisible.value = !!showLinkOptions
return showLinkOptions
}
function notStartingWithNetworkProtocol(inputString: string) {
const pattern = /^(?![^:]+:\/\/).*/
const isMatch = pattern.test(inputString)
return isMatch
}
const onChange = () => {
const isLinkMarkedStoredInEditor = editor.value.state?.storedMarks?.some((mark: Mark) => mark.type.name === 'link')
let formatedHref = href.value
if (
isValidURL(href.value) &&
href.value.length > 0 &&
!href.value.startsWith('/') &&
notStartingWithNetworkProtocol(href.value)
) {
formatedHref = `https://${href.value}`
}
if (isLinkMarkedStoredInEditor) {
editor.value.view.dispatch(
editor.value.view.state.tr
.removeStoredMark(editor.value.schema.marks.link)
.addStoredMark(editor.value.schema.marks.link.create({ href: formatedHref })),
)
} else if (linkNodeMark.value) {
const selection = editor.value.state?.selection
const markSelection = getMarkRange(selection.$anchor, editor.value.schema.marks.link) as any
editor.value.view.dispatch(
editor.value.view.state.tr
.removeMark(markSelection.from, markSelection.to, editor.value.schema.marks.link)
.addMark(markSelection.from, markSelection.to, editor.value.schema.marks.link.create({ href: formatedHref })),
)
}
}
const onDelete = () => {
const isLinkMarkedStoredInEditor = editor.value.state?.storedMarks?.some((mark: Mark) => mark.type.name === 'link')
if (isLinkMarkedStoredInEditor) {
editor.value.view.dispatch(editor.value.view.state.tr.removeStoredMark(editor.value.schema.marks.link))
} else if (linkNodeMark.value) {
const selection = editor.value.state.selection
const markSelection = getMarkRange(selection.$anchor, editor.value.schema.marks.link) as any
editor.value.view.dispatch(
editor.value.view.state.tr.removeMark(markSelection.from, markSelection.to, editor.value.schema.marks.link),
)
}
justDeleted.value = true
}
const handleKeyDown = (e: any) => {
const isCtrlPressed = isMac() ? e.metaKey : e.ctrlKey
// Ctrl + Z/ Meta + Z
if (isCtrlPressed && e.key === 'z') {
e.preventDefault()
editor.value.commands.undo()
}
// Ctrl + Shift + Z/ Meta + Shift + Z
if (isCtrlPressed && e.shiftKey && e.key === 'z') {
e.preventDefault()
editor.value.commands.redo()
}
}
const onInputBoxEnter = () => {
inputRef.value?.blur()
editor.value.chain().focus().run()
}
const handleInputBoxKeyDown = (e: any) => {
if (e.key === 'ArrowDown' || e.key === 'Escape') {
editor.value.chain().focus().run()
}
}
watch(isLinkOptionsVisible, (value, oldValue) => {
if (value && !oldValue) {
const isPlaceholderEmpty =
!editor.value.state.selection.$from.nodeBefore?.textContent && !editor.value.state.selection.$from.nodeAfter?.textContent
if (!isPlaceholderEmpty) return
setTimeout(() => {
inputRef.value?.focus()
}, 100)
}
})
const openLink = () => {
if (href.value) {
window.open(href.value, '_blank', 'noopener,noreferrer')
}
}
</script>
<template>
<BubbleMenu :editor="editor" :tippy-options="{ duration: 100, maxWidth: 600 }" :should-show="(checkLinkMark as any)">
<div
v-if="!justDeleted"
ref="wrapperRef"
class="relative bubble-menu nc-text-area-rich-link-options flex flex-col bg-gray-50 py-1 px-1 rounded-lg"
data-testid="nc-text-area-rich-link-options"
@keydown.stop="handleKeyDown"
>
<div class="flex items-center gap-x-1">
<div class="!border-1 !border-gray-200 !py-0.5 bg-gray-100 rounded-md !z-10">
<a-input
ref="inputRef"
v-model:value="href"
class="nc-text-area-rich-link-option-input flex-1 !w-96 !mx-0.5 !px-1.5 !py-0.5 !rounded-md z-10"
:bordered="false"
placeholder="Enter a link"
@change="onChange"
@press-enter="onInputBoxEnter"
@keydown="handleInputBoxKeyDown"
/>
</div>
<NcTooltip overlay-class-name="nc-text-area-rich-link-options">
<template #title> Open link </template>
<NcButton
:class="{
'!text-gray-300 cursor-not-allowed': href.length === 0,
}"
data-testid="nc-text-area-rich-link-options-open-link"
size="small"
type="text"
@click="openLink"
>
<IcBaselineArrowOutward />
</NcButton>
</NcTooltip>
<NcTooltip overlay-class-name="nc-text-area-rich-link-options">
<template #title> Delete link </template>
<NcButton
class="!duration-0 !hover:(text-red-400 bg-red-50)"
data-testid="nc-text-area-rich-link-options-open-delete"
size="small"
type="text"
@click="onDelete"
>
<MdiDeleteOutline />
</NcButton>
</NcTooltip>
<div class="absolute -bottom-1.5 left-0 right-0 w-full flex flex-row justify-center">
<div
class="flex h-2.5 w-2.5 bg-white border-gray-200 border-r-1 border-b-1 transform rotate-45"
:style="{
boxShadow: '1px 1px 3px rgba(231, 231, 233, 1)',
}"
></div>
</div>
</div>
</div>
</BubbleMenu>
</template>
<style lang="scss">
.bubble-menu {
// shadow
@apply shadow-gray-200 shadow-sm;
}
.nc-text-area-rich-link-options {
.ant-popover-inner-content {
@apply !shadow-none !p-0;
}
.ant-popover-arrow {
@apply !shadow-none;
.ant-popover-arrow-content {
@apply !shadow-none !bg-gray-100;
}
}
.ant-popover-inner {
@apply !shadow-none !bg-gray-100 py-1.5 px-2.5 text-xs !rounded-sm;
}
}
</style>

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

@ -0,0 +1,381 @@
<script lang="ts" setup>
import type { Editor } from '@tiptap/vue-3'
import MdiFormatBulletList from '~icons/mdi/format-list-bulleted'
import MdiFormatStrikeThrough from '~icons/mdi/format-strikethrough'
import MdiFormatListNumber from '~icons/mdi/format-list-numbered'
import MdiFormatListCheckbox from '~icons/mdi/format-list-checkbox'
import MsFormatH1 from '~icons/material-symbols/format-h1'
import MsFormatH2 from '~icons/material-symbols/format-h2'
import MsFormatH3 from '~icons/material-symbols/format-h3'
import TablerBlockQuote from '~icons/tabler/blockquote'
import MsCode from '~icons/material-symbols/code'
import MsFormatQuote from '~icons/material-symbols/format-quote'
interface Props {
editor: Editor
embedMode?: boolean
}
const props = defineProps<Props>()
const editor = computed(() => props.editor)
const embedMode = computed(() => props.embedMode)
const cmdOrCtrlKey = computed(() => {
return isMac() ? '⌘' : 'CTRL'
})
const shiftKey = computed(() => {
return isMac() ? '⇧' : 'Shift'
})
const altKey = computed(() => {
return isMac() ? '⌥' : 'Alt'
})
const onToggleLink = () => {
const activeNode = editor.value?.state?.selection?.$from?.nodeBefore || editor.value?.state?.selection?.$from?.nodeAfter
const isLinkMarkedStoredInEditor = editor.value?.state?.storedMarks?.some((mark: any) => mark.type.name === 'link')
const isActiveNodeMarkActive = activeNode?.marks?.some((mark: any) => mark.type.name === 'link') || isLinkMarkedStoredInEditor
if (isActiveNodeMarkActive) {
editor.value!.chain().focus().unsetLink().run()
} else {
if (editor.value.state.selection.empty) {
editor
.value!.chain()
.focus()
.insertContent(' ')
.setTextSelection({ from: editor.value!.state.selection.$from.pos, to: editor.value!.state.selection.$from.pos + 1 })
.toggleLink({
href: '',
})
.setTextSelection({ from: editor.value!.state.selection.$from.pos, to: editor.value!.state.selection.$from.pos + 1 })
.deleteSelection()
.run()
} else {
editor
.value!.chain()
.focus()
.setLink({
href: '',
})
.selectTextblockEnd()
.run()
}
setTimeout(() => {
const linkInput = document.querySelector('.nc-text-area-rich-link-option-input')
if (linkInput) {
;(linkInput as any).focus()
}
}, 100)
}
}
</script>
<template>
<div
class="bubble-menu flex flex-row gap-x-1 bg-gray-100 py-1 rounded-lg px-1"
:class="{
'embed-mode': embedMode,
'full-mode': !embedMode,
}"
>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.bold') }}
</div>
<div class="text-xs">{{ cmdOrCtrlKey }} B</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('bold') }"
:disabled="editor.isActive('codeBlock')"
@click="editor!.chain().focus().toggleBold().run()"
>
<MdiFormatBold />
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.italic') }}
</div>
<div>{{ cmdOrCtrlKey }} I</div>
</div>
</template>
<NcButton
size="small"
type="text"
:disabled="editor.isActive('codeBlock')"
:class="{ 'is-active': editor.isActive('italic') }"
@click=";(editor!.chain().focus() as any).toggleItalic().run()"
>
<MdiFormatItalic />
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.underline') }}
</div>
<div>{{ cmdOrCtrlKey }} U</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('underline') }"
:disabled="editor.isActive('codeBlock')"
@click="editor!.chain().focus().toggleUnderline().run()"
>
<MdiFormatUnderline />
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.strike') }}
</div>
<div>{{ shiftKey }} {{ cmdOrCtrlKey }} S</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('strike') }"
:disabled="editor.isActive('codeBlock')"
@click="editor!.chain().focus().toggleStrike().run()"
>
<MdiFormatStrikeThrough />
</NcButton>
</NcTooltip>
<NcTooltip v-if="embedMode">
<template #title> {{ $t('general.code') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('codeBlock') }"
@click="editor!.chain().focus().toggleCodeBlock().run()"
>
<MsCode />
</NcButton>
</NcTooltip>
<NcTooltip v-else :disabled="editor.isActive('codeBlock')">
<template #title> {{ $t('general.quote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('code') }"
:disabled="editor.isActive('codeBlock')"
@click="editor!.chain().focus().toggleCode().run()"
>
<MsFormatQuote />
</NcButton>
</NcTooltip>
<div class="divider"></div>
<template v-if="embedMode">
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading1') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 1</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 1 }).run()"
>
<MsFormatH1 />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading2') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 2</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 2 }).run()"
>
<MsFormatH2 />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading3') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 3</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 3 }).run()"
>
<MsFormatH3 />
</NcButton>
</NcTooltip>
<div class="divider"></div>
</template>
<NcTooltip v-if="embedMode">
<template #title> {{ $t('labels.blockQuote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor!.chain().focus().toggleBlockquote().run()"
>
<TablerBlockQuote class="-mt-0.25" />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.bulletList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('bulletList') }"
@click="editor!.chain().focus().toggleBulletList().run()"
>
<MdiFormatBulletList />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.numberedList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('orderedList') }"
@click="editor!.chain().focus().toggleOrderedList().run()"
>
<MdiFormatListNumber />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.taskList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('taskList') }"
@click="editor!.chain().focus().toggleTaskList().run()"
>
<MdiFormatListCheckbox />
</NcButton>
</NcTooltip>
<div class="divider"></div>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<template #title> {{ $t('general.link') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('link') }"
:disabled="editor.isActive('codeBlock')"
@click="onToggleLink"
>
<div class="flex flex-row items-center px-0.5">
<MdiLink />
<div class="!text-xs !ml-1">{{ $t('general.link') }}</div>
</div>
</NcButton>
</NcTooltip>
</div>
</template>
<style lang="scss">
.bubble-menu-hidden {
[data-tippy-root] {
opacity: 0;
height: 0;
overflow: hidden;
z-index: -1;
user-select: none;
}
}
.bubble-text-format-button-icon {
@apply px-1.5 py-0 border-1 border-gray-300 rounded-sm items-center justify-center;
font-size: 0.8rem;
font-weight: 600;
}
.bubble-text-format-button {
@apply rounded-md py-1 my-0 pl-2.5 pr-3 cursor-pointer items-center gap-x-2.5 hover:bg-gray-100;
}
.bubble-menu.full-mode {
@apply border-gray-100
box-shadow: 0px 0px 1.2rem 0 rgb(230, 230, 230) !important;
}
.bubble-menu.embed-mode {
@apply border-transparent !shadow-none;
}
.embed-mode.bubble-menu {
@apply !py-0 !my-0 !border-0;
.divider {
@apply my-0 !h-11 border-gray-100;
}
.nc-button {
@apply !mt-1.65;
}
}
.bubble-menu {
// shadow
@apply bg-white;
border-width: 1px;
.nc-button.is-active {
@apply !hover:outline-gray-200 bg-gray-100 text-brand-500;
outline: 1px;
}
.divider {
@apply border-r-1 border-gray-200 !h-6 !mx-0.5 my-1;
}
.ant-select-selector {
@apply !rounded-md;
}
.ant-select-selector .ant-select-selection-item {
@apply !text-xs;
}
.ant-btn-loading-icon {
@apply pb-0.5;
}
}
</style>

68
packages/nc-gui/components/cell/RichText/SelectedBubbleMenuPopup.vue

@ -0,0 +1,68 @@
<script lang="ts" setup>
import type { Editor } from '@tiptap/vue-3'
import { BubbleMenu } from '@tiptap/vue-3'
const props = defineProps<Props>()
interface Props {
editor: Editor
}
const editor = computed(() => props.editor)
// Debounce show menu to prevent flickering
const showMenu = computed(() => {
if (!editor) return false
return !editor.value.state.selection.empty
})
const showMenuDebounced = ref(false)
watchDebounced(
() => showMenu.value,
(value) => {
showMenuDebounced.value = value
},
{
debounce: 200,
maxWait: 800,
immediate: true,
},
)
const handleEditorMouseDown = (e: MouseEvent) => {
const domsInEvent = document.elementsFromPoint(e.clientX, e.clientY) as HTMLElement[]
const isBubble = domsInEvent.some((dom) => dom?.classList?.contains('bubble-menu'))
if (isBubble) return
const pageContent = document.querySelector('.nc-textarea-rich-editor')
pageContent?.classList.add('bubble-menu-hidden')
}
const handleEditorMouseUp = (e: MouseEvent) => {
const domsInEvent = document.elementsFromPoint(e.clientX, e.clientY) as HTMLElement[]
const isBubble = domsInEvent.some((dom) => dom?.classList?.contains('bubble-menu'))
if (isBubble) return
setTimeout(() => {
const pageContent = document.querySelector('.nc-textarea-rich-editor')
pageContent?.classList.remove('bubble-menu-hidden')
}, 100)
}
onMounted(() => {
document.addEventListener('mouseup', handleEditorMouseUp)
document.addEventListener('mousedown', handleEditorMouseDown)
})
onUnmounted(() => {
document.removeEventListener('mouseup', handleEditorMouseUp)
document.removeEventListener('mousedown', handleEditorMouseDown)
})
</script>
<template>
<BubbleMenu :editor="editor" :update-delay="300" :tippy-options="{ duration: 100, maxWidth: 600 }">
<CellRichTextSelectedBubbleMenu v-if="showMenuDebounced" :editor="editor" />
</BubbleMenu>
</template>

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

@ -43,8 +43,6 @@ const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)! const readOnly = inject(ReadonlyInj)!
const isLockedMode = inject(IsLockedInj, ref(false))
const isEditable = inject(EditModeInj, ref(false)) const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false)) const activeCell = inject(ActiveCellInj, ref(false))
@ -264,11 +262,7 @@ const selectedOpt = computed(() => {
</script> </script>
<template> <template>
<div <div class="h-full w-full flex items-center nc-single-select" :class="{ 'read-only': readOnly }" @click="toggleMenu">
class="h-full w-full flex items-center nc-single-select"
:class="{ 'read-only': readOnly || isLockedMode }"
@click="toggleMenu"
>
<div v-if="!(active || isEditable)"> <div v-if="!(active || isEditable)">
<a-tag v-if="selectedOpt" class="rounded-tag" :color="selectedOpt.color"> <a-tag v-if="selectedOpt" class="rounded-tag" :color="selectedOpt.color">
<span <span
@ -295,8 +289,8 @@ const selectedOpt = computed(() => {
:allow-clear="!column.rqd && editAllowed" :allow-clear="!column.rqd && editAllowed"
:bordered="false" :bordered="false"
:open="isOpen && editAllowed" :open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed || isLockedMode" :disabled="readOnly || !editAllowed"
:show-arrow="hasEditRoles && !(readOnly || isLockedMode) && active && vModel === null" :show-arrow="hasEditRoles && !readOnly && active && vModel === null"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`"
:show-search="!isMobileMode && isOpen && active" :show-search="!isMobileMode && isOpen && active"
@select="onSelect" @select="onSelect"

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

@ -33,19 +33,33 @@ const isForm = inject(IsFormInj, ref(false))
const { showNull } = useGlobal() const { showNull } = useGlobal()
const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' }) const vModel = useVModel(props, 'modelValue', emits, { defaultValue: column?.value.cdf ? String(column?.value.cdf) : '' })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const position = ref<
| {
top: number
left: number
}
| undefined
>({
top: 200,
left: 600,
})
const isDragging = ref(false)
const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value && (el as HTMLTextAreaElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value && (el as HTMLTextAreaElement)?.focus()
const height = computed(() => { const height = computed(() => {
if (!rowHeight.value) return 60 if (!rowHeight.value || rowHeight.value === 1) return 36
return rowHeight.value * 60 return rowHeight.value * 36
}) })
const isVisible = ref(false) const isVisible = ref(false)
const inputWrapperRef = ref<HTMLElement | null>(null) const inputWrapperRef = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLTextAreaElement | null>(null) const inputRef = ref<HTMLTextAreaElement | null>(null)
@ -67,16 +81,95 @@ onClickOutside(inputWrapperRef, (e) => {
isVisible.value = false isVisible.value = false
}) })
const onDblClick = () => { const onTextClick = () => {
if (!props.virtual) return if (!props.virtual) return
isVisible.value = true isVisible.value = true
editEnabled.value = true editEnabled.value = true
} }
const isRichMode = computed(() => {
let meta: any = {}
if (typeof column?.value?.meta === 'string') {
meta = JSON.parse(column?.value?.meta)
} else {
meta = column?.value?.meta ?? {}
}
return meta?.richMode
})
const onExpand = () => {
isVisible.value = true
const { top, left } = inputWrapperRef.value?.getBoundingClientRect() ?? { top: 0, left: 0 }
position.value = {
top: top + 42,
left,
}
}
const onMouseMove = (e: MouseEvent) => {
if (!isDragging.value) return
e.stopPropagation()
position.value = {
top: e.clientY - 22,
left: e.clientX - 46,
}
}
const onMouseUp = (e: MouseEvent) => {
if (!isDragging.value) return
e.stopPropagation()
isDragging.value = false
position.value = undefined
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
watch(position, () => {
const dom = document.querySelector('.nc-textarea-dropdown-active') as HTMLElement
if (!dom) return
if (!position.value) return
// Set left and top of dom
setTimeout(() => {
if (!position.value) return
dom.style.left = `${position.value.left}px`
dom.style.top = `${position.value.top}px`
}, 100)
})
const dragStart = () => {
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
isDragging.value = true
}
watch(editEnabled, () => {
if (editEnabled.value) {
isVisible.value = true
}
})
</script> </script>
<template> <template>
<NcDropdown v-model:visible="isVisible" class="overflow-visible" :trigger="[]" placement="bottomLeft"> <NcDropdown
v-model:visible="isVisible"
class="overflow-visible"
:trigger="[]"
placement="bottomLeft"
:overlay-class-name="isVisible ? 'nc-textarea-dropdown-active' : undefined"
>
<div <div
class="flex flex-row pt-0.5 w-full" class="flex flex-row pt-0.5 w-full"
:class="{ :class="{
@ -85,8 +178,19 @@ const onDblClick = () => {
'h-full': isForm, 'h-full': isForm,
}" }"
> >
<div
v-if="isRichMode"
class="w-full cursor-pointer"
:style="{
maxHeight: `${height}px !important`,
minHeight: `${height}px !important`,
}"
@dblclick="onExpand"
>
<LazyCellRichText v-model:value="vModel" sync-value-change readonly class="!pointer-events-none" />
</div>
<textarea <textarea
v-if="editEnabled && !isVisible" v-else-if="editEnabled && !isVisible"
:ref="focus" :ref="focus"
v-model="vModel" v-model="vModel"
rows="4" rows="4"
@ -123,39 +227,50 @@ const onDblClick = () => {
'word-break': 'break-word', 'word-break': 'break-word',
'white-space': 'pre-line', 'white-space': 'pre-line',
}" }"
@click="onDblClick" @click="onTextClick"
/> />
<span v-else>{{ vModel }}</span> <span v-else>{{ vModel }}</span>
<div <NcTooltip
v-if="active && !isExpandedFormOpen" v-if="!isVisible"
class="!absolute right-0 bottom-0 h-6 w-5 group cursor-pointer flex justify-end gap-1 items-center active:(ring ring-accent ring-opacity-100) rounded border-none p-1 hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)" placement="bottom"
:class="{ 'right-2 bottom-2': editEnabled }" class="!absolute right-0 bottom-1 !hidden nc-text-area-expand-btn"
data-testid="attachment-cell-file-picker-button" :class="{ 'right-0 bottom-1': editEnabled, '!bottom-0': !isRichMode }"
@click.stop="isVisible = !isVisible"
> >
<NcTooltip placement="bottom"> <template #title>{{ $t('title.expand') }}</template>
<template #title>{{ $t('title.expand') }}</template> <NcButton type="secondary" size="xsmall" data-testid="attachment-cell-file-picker-button" @click.stop="onExpand">
<component <component :is="iconMap.expand" class="transform group-hover:(!text-grey-800 ) scale-120 text-gray-700 text-xs" />
:is="iconMap.expand" </NcButton>
class="transform dark:(!text-white) group-hover:(!text-grey-800 scale-120) text-gray-500 text-xs" </NcTooltip>
/>
</NcTooltip>
</div>
</div> </div>
<template #overlay> <template #overlay>
<div ref="inputWrapperRef" class="flex flex-col min-w-120 min-h-70 py-3 pl-3 pr-1 expanded-cell-input"> <div
v-if="isVisible"
ref="inputWrapperRef"
class="flex flex-col min-w-200 min-h-70 py-3 expanded-cell-input relative"
:class="{
'cursor-move': isDragging,
}"
>
<div <div
v-if="column" v-if="column"
class="flex flex-row gap-x-1 items-center font-medium pb-2.5 mb-1 py-1 mr-3 ml-1 border-b-1 border-gray-100" class="flex flex-row gap-x-1 items-center font-medium pl-3 pb-2.5 border-b-1 border-gray-100 cursor-move"
:class="{
'select-none': isDragging,
}"
@mousedown="dragStart"
> >
<SmartsheetHeaderCellIcon class="flex" /> <SmartsheetHeaderCellIcon class="flex" />
<div class="flex"> <div class="flex max-w-38">
{{ column.title }} <span class="truncate">
{{ column.title }}
</span>
</div> </div>
</div> </div>
<a-textarea <a-textarea
v-if="!isRichMode"
ref="inputRef" ref="inputRef"
v-model:value="vModel" v-model:value="vModel"
class="p-1 !pt-1 !pr-3 !border-0 !border-r-0 !focus:outline-transparent nc-scrollbar-md !text-black !cursor-text" class="p-1 !pt-1 !pr-3 !border-0 !border-r-0 !focus:outline-transparent nc-scrollbar-md !text-black !cursor-text"
@ -166,13 +281,19 @@ const onDblClick = () => {
@keydown.stop @keydown.stop
@keydown.escape="isVisible = false" @keydown.escape="isVisible = false"
/> />
<LazyCellRichText v-else-if="isVisible" v-model:value="vModel" show-menu full-mode :read-only="readOnly" />
</div> </div>
</template> </template>
</NcDropdown> </NcDropdown>
</template> </template>
<style> <style lang="scss" scoped>
textarea:focus { textarea:focus {
box-shadow: none; box-shadow: none;
} }
:deep(.nc-text-area-expand-btn) {
@apply !block;
}
</style> </style>

8
packages/nc-gui/components/cell/attachment/Modal.vue

@ -25,8 +25,6 @@ const {
renameFile, renameFile,
} = useAttachmentCell()! } = useAttachmentCell()!
const isLocked = inject(IsLockedInj, ref(false))
const dropZoneRef = ref<HTMLDivElement>() const dropZoneRef = ref<HTMLDivElement>()
const sortableRef = ref<HTMLDivElement>() const sortableRef = ref<HTMLDivElement>()
@ -96,7 +94,7 @@ const handleFileDelete = (i: number) => {
<template #title> <template #title>
<div class="flex gap-4"> <div class="flex gap-4">
<div <div
v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)" v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic)"
class="nc-attach-file group" class="nc-attach-file group"
data-testid="attachment-expand-file-picker-button" data-testid="attachment-expand-file-picker-button"
@click="open" @click="open"
@ -143,7 +141,7 @@ const handleFileDelete = (i: number) => {
<template #title> {{ $t('title.removeFile') }} </template> <template #title> {{ $t('title.removeFile') }} </template>
<component <component
:is="iconMap.closeCircle" :is="iconMap.closeCircle"
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic && !isLocked)" v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic)"
class="nc-attachment-remove" class="nc-attachment-remove"
@click.stop="onRemoveFileClick(item.title, i)" @click.stop="onRemoveFileClick(item.title, i)"
/> />
@ -157,7 +155,7 @@ const handleFileDelete = (i: number) => {
</div> </div>
</a-tooltip> </a-tooltip>
<a-tooltip v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)" placement="bottom"> <a-tooltip v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic)" placement="bottom">
<template #title> {{ $t('title.renameFile') }} </template> <template #title> {{ $t('title.renameFile') }} </template>
<div class="nc-attachment-download group-hover:(opacity-100) mr-[35px]"> <div class="nc-attachment-download group-hover:(opacity-100) mr-[35px]">

10
packages/nc-gui/components/cell/attachment/index.vue

@ -43,8 +43,6 @@ const sortableRef = ref<HTMLDivElement>()
const currentCellRef = inject(CurrentCellInj, dropZoneInjection.value) const currentCellRef = inject(CurrentCellInj, dropZoneInjection.value)
const isLockedMode = inject(IsLockedInj, ref(false))
const isGallery = inject(IsGalleryInj, ref(false)) const isGallery = inject(IsGalleryInj, ref(false))
const isKanban = inject(IsKanbanInj, ref(false)) const isKanban = inject(IsKanbanInj, ref(false))
@ -69,18 +67,14 @@ const {
open: _open, open: _open,
FileIcon, FileIcon,
selectedImage, selectedImage,
isReadonly: _isReadonly, isReadonly,
storedFiles, storedFiles,
} = useProvideAttachmentCell(updateModelValue) } = useProvideAttachmentCell(updateModelValue)
const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, _isReadonly) const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, isReadonly)
const active = inject(ActiveCellInj, ref(false)) const active = inject(ActiveCellInj, ref(false))
const isReadonly = computed(() => {
return isLockedMode.value || _isReadonly.value
})
const { state: rowState } = useSmartsheetRowStoreOrThrow() const { state: rowState } = useSmartsheetRowStoreOrThrow()
const { isOverDropZone } = useDropZone(currentCellRef as any, onDrop) const { isOverDropZone } = useDropZone(currentCellRef as any, onDrop)

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

@ -5,11 +5,12 @@ import { message } from 'ant-design-vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { ProjectRoleInj, TreeViewInj, useNuxtApp, useRoles, useTabs } from '#imports' import { ProjectRoleInj, TreeViewInj, useNuxtApp, useRoles, useTabs } from '#imports'
import type { SidebarTableNode } from '~/lib'
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
base: BaseType base: BaseType
table: TableType table: SidebarTableNode
sourceIndex: number sourceIndex: number
}>(), }>(),
{ sourceIndex: 0 }, { sourceIndex: 0 },
@ -44,7 +45,7 @@ provide(SidebarTableInj, table)
const { setMenuContext, openRenameTableDialog, duplicateTable } = inject(TreeViewInj)! const { setMenuContext, openRenameTableDialog, duplicateTable } = inject(TreeViewInj)!
const { loadViews: _loadViews } = useViewsStore() const { loadViews: _loadViews } = useViewsStore()
const { activeView } = storeToRefs(useViewsStore()) const { activeView, activeViewTitleOrId } = storeToRefs(useViewsStore())
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore()) const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
// todo: temp // todo: temp
@ -135,7 +136,7 @@ watch(
) )
const isTableOpened = computed(() => { const isTableOpened = computed(() => {
return openedTableId.value === table.value?.id && activeView.value?.is_default return openedTableId.value === table.value?.id && (activeView.value?.is_default || !activeViewTitleOrId.value)
}) })
</script> </script>
@ -174,7 +175,15 @@ const isTableOpened = computed(() => {
class="nc-sidebar-node-btn nc-sidebar-expand" class="nc-sidebar-node-btn nc-sidebar-expand"
@click.stop="onExpand" @click.stop="onExpand"
> >
<GeneralLoader
v-if="table.isViewsLoading"
class="flex w-4 h-4 !text-gray-600 !mt-0.75"
:class="{
'!visible': !isExpanded,
}"
/>
<GeneralIcon <GeneralIcon
v-else
icon="triangleFill" icon="triangleFill"
class="nc-sidebar-source-node-btns group-hover:visible invisible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.5 !text-gray-600 rotate-90" class="nc-sidebar-source-node-btns group-hover:visible invisible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.5 !text-gray-600 rotate-90"
:class="{ '!rotate-180': isExpanded }" :class="{ '!rotate-180': isExpanded }"

8
packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue

@ -16,6 +16,8 @@ const { metas } = useMetas()
const workspaceStore = useWorkspace() const workspaceStore = useWorkspace()
const isLocked = inject(IsLockedInj, ref(false))
const isUpdating = ref({ const isUpdating = ref({
public: false, public: false,
password: false, password: false,
@ -269,10 +271,6 @@ function onChangeTheme(color: string) {
const isPublicShared = computed(() => { const isPublicShared = computed(() => {
return !!activeView.value?.uuid return !!activeView.value?.uuid
}) })
const isPublicShareDisabled = computed(() => {
return false
})
</script> </script>
<template> <template>
@ -286,7 +284,7 @@ const isPublicShareDisabled = computed(() => {
:checked="isPublicShared" :checked="isPublicShared"
:loading="isUpdating.public" :loading="isUpdating.public"
class="share-view-toggle !mt-0.25" class="share-view-toggle !mt-0.25"
:disabled="isPublicShareDisabled" :disabled="isLocked"
@click="toggleShare" @click="toggleShare"
/> />
</div> </div>

3
packages/nc-gui/components/nc/Button.vue

@ -172,7 +172,8 @@ useEventListener(NcButton, 'mousedown', () => {
@apply p-0 h-5.75 min-w-5.75 rounded-md; @apply p-0 h-5.75 min-w-5.75 rounded-md;
} }
.nc-button.ant-btn[disabled] { .nc-button.ant-btn[disabled],
.ant-btn-text.nc-button.ant-btn[disabled] {
box-shadow: none !important; box-shadow: none !important;
@apply bg-gray-50 border-0 text-gray-300 cursor-not-allowed md:(hover:bg-gray-50); @apply bg-gray-50 border-0 text-gray-300 cursor-not-allowed md:(hover:bg-gray-50);
} }

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

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

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

@ -87,8 +87,6 @@ const isGrid = inject(IsGridInj, ref(false))
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false)) const isSurveyForm = inject(IsSurveyFormInj, ref(false))
const isEditColumnMenu = inject(EditColumnInj, ref(false)) const isEditColumnMenu = inject(EditColumnInj, ref(false))
@ -255,11 +253,7 @@ onUnmounted(() => {
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" /> <LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" /> <LazyCellText v-else v-model="vModel" />
<div <div
v-if=" v-if="(isPublic && readOnly && !isForm) || (isSystemColumn(column) && !isAttachment(column) && !isTextArea(column))"
(isLocked || (isPublic && readOnly && !isForm) || isSystemColumn(column)) &&
!isAttachment(column) &&
!isTextArea(column)
"
class="nc-locked-overlay" class="nc-locked-overlay"
/> />
</template> </template>

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

@ -48,6 +48,8 @@ const formState = reactive({})
const secondsRemain = ref(0) const secondsRemain = ref(0)
const isLocked = inject(IsLockedInj, ref(false))
const isEditable = isUIAllowed('viewFieldEdit' as Permission) const isEditable = isUIAllowed('viewFieldEdit' as Permission)
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
@ -347,6 +349,8 @@ watch(submitted, (v) => {
}) })
function handleMouseUp(col: Record<string, any>, hiddenColIndex: number) { function handleMouseUp(col: Record<string, any>, hiddenColIndex: number) {
if (isLocked.value) return
if (!moved.value) { if (!moved.value) {
const index = localColumns.value.length const index = localColumns.value.length
col.order = (index ? localColumns.value[index - 1].order : 0) + 1 col.order = (index ? localColumns.value[index - 1].order : 0) + 1
@ -378,6 +382,12 @@ watch(view, (nextView) => {
reloadEventHook.trigger() reloadEventHook.trigger()
} }
}) })
const onFormItemClick = (element: any) => {
if (isLocked.value) return
activeRow.value = element.title
}
</script> </script>
<template> <template>
@ -427,6 +437,7 @@ watch(view, (nextView) => {
type="secondary" type="secondary"
class="nc-form-add-all" class="nc-form-add-all"
data-testid="nc-form-add-all" data-testid="nc-form-add-all"
:disabled="isLocked"
@click="addAllColumns" @click="addAllColumns"
> >
<!-- Add all --> <!-- Add all -->
@ -438,6 +449,7 @@ watch(view, (nextView) => {
type="secondary" type="secondary"
class="nc-form-remove-all" class="nc-form-remove-all"
data-testid="nc-form-remove-all" data-testid="nc-form-remove-all"
:disabled="isLocked"
@click="removeAllColumns" @click="removeAllColumns"
> >
<!-- Remove all --> <!-- Remove all -->
@ -451,6 +463,7 @@ watch(view, (nextView) => {
item-key="id" item-key="id"
draggable=".item" draggable=".item"
group="form-inputs" group="form-inputs"
:disabled="isLocked"
class="flex flex-col gap-2" class="flex flex-col gap-2"
@start="drag = true" @start="drag = true"
@end="drag = false" @end="drag = false"
@ -459,6 +472,9 @@ watch(view, (nextView) => {
<a-card <a-card
size="small" size="small"
class="!border-0 color-transition cursor-pointer item hover:(bg-primary ring-1 ring-accent ring-opacity-100) bg-opacity-10 !rounded !shadow-lg" class="!border-0 color-transition cursor-pointer item hover:(bg-primary ring-1 ring-accent ring-opacity-100) bg-opacity-10 !rounded !shadow-lg"
:class="{
'!bg-gray-50 !hover:bg-gray-50 !hover:ring-transparent !cursor-not-allowed': isLocked,
}"
:data-testid="`nc-form-hidden-column-${element.label || element.title}`" :data-testid="`nc-form-hidden-column-${element.label || element.title}`"
@mousedown="moved = false" @mousedown="moved = false"
@mousemove="moved = false" @mousemove="moved = false"
@ -484,7 +500,7 @@ watch(view, (nextView) => {
</a-card> </a-card>
</template> </template>
<template #footer> <template v-if="!isLocked" #footer>
<div <div
class="my-4 select-none border-dashed border-2 border-gray-400 py-3 text-gray-400 text-center nc-drag-n-drop-to-hide" class="my-4 select-none border-dashed border-2 border-gray-400 py-3 text-gray-400 text-center nc-drag-n-drop-to-hide"
data-testid="nc-drag-n-drop-to-hide" data-testid="nc-drag-n-drop-to-hide"
@ -553,6 +569,7 @@ watch(view, (nextView) => {
autosize autosize
size="large" size="large"
hide-details hide-details
:disabled="isLocked"
placeholder="Form Title" placeholder="Form Title"
:bordered="false" :bordered="false"
data-testid="nc-form-heading" data-testid="nc-form-heading"
@ -580,7 +597,7 @@ watch(view, (nextView) => {
hide-details hide-details
:placeholder="$t('msg.info.formDesc')" :placeholder="$t('msg.info.formDesc')"
:bordered="false" :bordered="false"
:disabled="!isEditable" :disabled="!isEditable || isLocked"
data-testid="nc-form-sub-heading" data-testid="nc-form-sub-heading"
@blur="updateView" @blur="updateView"
@click="updateView" @click="updateView"
@ -598,6 +615,7 @@ watch(view, (nextView) => {
group="form-inputs" group="form-inputs"
class="h-full" class="h-full"
:move="onMoveCallback" :move="onMoveCallback"
:disabled="isLocked"
@change="onMove($event)" @change="onMove($event)"
@start="drag = true" @start="drag = true"
@end="drag = false" @end="drag = false"
@ -610,12 +628,15 @@ watch(view, (nextView) => {
{ {
'bg-primary bg-opacity-5 ring-0.5 ring-accent ring-opacity-100': activeRow === element.title, 'bg-primary bg-opacity-5 ring-0.5 ring-accent ring-opacity-100': activeRow === element.title,
}, },
{
'!hover:bg-white !ring-0 !cursor-auto': isLocked,
},
]" ]"
data-testid="nc-form-fields" data-testid="nc-form-fields"
@click="activeRow = element.title" @click="onFormItemClick(element)"
> >
<div <div
v-if="isUIAllowed('viewFieldEdit') && !isRequired(element, element.required)" v-if="isUIAllowed('viewFieldEdit') && !isRequired(element, element.required) && !isLocked"
class="absolute flex top-2 right-2" class="absolute flex top-2 right-2"
> >
<component <component
@ -795,9 +816,9 @@ watch(view, (nextView) => {
</a-card> </a-card>
</a-form> </a-form>
<a-divider /> <a-divider v-if="!isLocked" />
<div v-if="isEditable" class="px-4 flex flex-col gap-2"> <div v-if="isEditable && !isLocked" class="px-4 flex flex-col gap-2">
<!-- After form is submitted --> <!-- After form is submitted -->
<div class="text-lg text-gray-700"> <div class="text-lg text-gray-700">
{{ $t('msg.info.afterFormSubmitted') }} {{ $t('msg.info.afterFormSubmitted') }}

8
packages/nc-gui/components/smartsheet/Pagination.vue

@ -48,15 +48,15 @@ const size = computed(() => vPaginationData.value?.pageSize ?? 25)
const page = computed({ const page = computed({
get: () => vPaginationData?.value?.page ?? 1, get: () => vPaginationData?.value?.page ?? 1,
set: async (p) => { set: async (p) => {
isViewDataLoading.value = true isPaginationLoading.value = true
try { try {
await changePage?.(p) await changePage?.(p)
isViewDataLoading.value = false isPaginationLoading.value = false
} catch (e) { } catch (e) {
if (axios.isCancel(e)) { if (axios.isCancel(e)) {
return return
} }
isViewDataLoading.value = false isPaginationLoading.value = false
} }
}, },
}) })
@ -100,7 +100,7 @@ const renderAltOrOptlKey = () => {
'ml-8': alignLeft, 'ml-8': alignLeft,
}" }"
> >
<div v-if="isPaginationLoading" class="flex flex-row justify-center item-center min-h-10 min-w-42"> <div v-if="isViewDataLoading" class="flex flex-row justify-center item-center min-h-10 min-w-42">
<a-skeleton :active="true" :title="true" :paragraph="false" class="-mt-1 max-w-60" /> <a-skeleton :active="true" :title="true" :paragraph="false" class="-mt-1 max-w-60" />
</div> </div>
<NcPagination <NcPagination

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

@ -39,7 +39,7 @@ const { allowCSVDownload } = useSharedView()
<!-- <LazySmartsheetToolbarQrScannerButton v-if="isMobileMode && (isGrid || isKanban || isGallery)" /> --> <!-- <LazySmartsheetToolbarQrScannerButton v-if="isMobileMode && (isGrid || isKanban || isGallery)" /> -->
<LazySmartsheetToolbarExport v-if="(!isPublic && !isUIAllowed('dataInsert')) || (isPublic && allowCSVDownload)" /> <LazySmartsheetToolbarExport v-if="isPublic && allowCSVDownload" />
<div class="flex-1" /> <div class="flex-1" />
</template> </template>

7
packages/nc-gui/components/smartsheet/VirtualCell.vue

@ -31,6 +31,7 @@ const props = defineProps<{
modelValue: any modelValue: any
row?: Row row?: Row
active?: boolean active?: boolean
readOnly?: boolean
}>() }>()
const emit = defineEmits(['update:modelValue', 'navigate', 'save']) const emit = defineEmits(['update:modelValue', 'navigate', 'save'])
@ -38,12 +39,14 @@ const emit = defineEmits(['update:modelValue', 'navigate', 'save'])
const column = toRef(props, 'column') const column = toRef(props, 'column')
const active = toRef(props, 'active', false) const active = toRef(props, 'active', false)
const row = toRef(props, 'row') const row = toRef(props, 'row')
const readOnly = toRef(props, 'readOnly', false)
provide(ColumnInj, column) provide(ColumnInj, column)
provide(ActiveCellInj, active) provide(ActiveCellInj, active)
provide(RowInj, row) provide(RowInj, row)
provide(CellValueInj, toRef(props, 'modelValue')) provide(CellValueInj, toRef(props, 'modelValue'))
provide(SaveRowInj, () => emit('save')) provide(SaveRowInj, () => emit('save'))
provide(ReadonlyInj, readOnly)
const isGrid = inject(IsGridInj, ref(false)) const isGrid = inject(IsGridInj, ref(false))
@ -94,7 +97,9 @@ onUnmounted(() => {
<div <div
ref="elementToObserve" ref="elementToObserve"
class="nc-virtual-cell w-full flex items-center" class="nc-virtual-cell w-full flex items-center"
:class="{ 'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm }" :class="{
'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm,
}"
@keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)" @keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)" @keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)"
> >

123
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -37,6 +37,7 @@ const props = defineProps<{
hideType?: boolean hideType?: boolean
hideAdditionalOptions?: boolean hideAdditionalOptions?: boolean
fromTableExplorer?: boolean fromTableExplorer?: boolean
readonly?: boolean
}>() }>()
const emit = defineEmits(['submit', 'cancel', 'mounted', 'add', 'update']) const emit = defineEmits(['submit', 'cancel', 'mounted', 'add', 'update'])
@ -66,6 +67,8 @@ const isForm = inject(IsFormInj, ref(false))
const isKanban = inject(IsKanbanInj, ref(false)) const isKanban = inject(IsKanbanInj, ref(false))
const readOnly = computed(() => props.readonly)
const { isMysql, isMssql, isXcdbBase } = useBase() const { isMysql, isMssql, isXcdbBase } = useBase()
const reloadDataTrigger = inject(ReloadViewDataHookInj) const reloadDataTrigger = inject(ReloadViewDataHookInj)
@ -114,6 +117,8 @@ const reloadMetaAndData = async () => {
const saving = ref(false) const saving = ref(false)
async function onSubmit() { async function onSubmit() {
if (readOnly.value) return
saving.value = true saving.value = true
const saved = await addOrUpdate(reloadMetaAndData, props.columnPosition) const saved = await addOrUpdate(reloadMetaAndData, props.columnPosition)
saving.value = false saving.value = false
@ -134,7 +139,7 @@ async function onSubmit() {
// focus and select the column name field // focus and select the column name field
const antInput = ref() const antInput = ref()
watchEffect(() => { watchEffect(() => {
if (antInput.value && formState.value) { if (antInput.value && formState.value && !readOnly.value) {
// todo: replace setTimeout // todo: replace setTimeout
setTimeout(() => { setTimeout(() => {
// focus and select input only if active element is not an input or textarea // focus and select input only if active element is not an input or textarea
@ -215,9 +220,10 @@ if (props.fromTableExplorer) {
:class="{ :class="{
'bg-white': !props.fromTableExplorer, 'bg-white': !props.fromTableExplorer,
'w-[400px]': !props.embedMode, 'w-[400px]': !props.embedMode,
'!w-146': isTextArea(formState) && formState.meta.richMode,
'!w-[600px]': formState.uidt === UITypes.Formula && !props.embedMode, '!w-[600px]': formState.uidt === UITypes.Formula && !props.embedMode,
'!w-[500px]': formState.uidt === UITypes.Attachment && !props.embedMode && !appInfo.ee, '!w-[500px]': formState.uidt === UITypes.Attachment && !props.embedMode && !appInfo.ee,
'shadow-lg border-1 border-gray-50 shadow-gray-100 rounded-md p-6': !embedMode, 'shadow-lg border-1 border-gray-100 shadow-gray-300 rounded-xl p-6': !embedMode,
}" }"
@keydown="handleEscape" @keydown="handleEscape"
@click.stop @click.stop
@ -232,6 +238,7 @@ if (props.fromTableExplorer) {
<input <input
ref="antInput" ref="antInput"
v-model="formState.title" v-model="formState.title"
:disabled="readOnly"
class="flex flex-grow nc-fields-input text-lg font-bold outline-none bg-inherit" class="flex flex-grow nc-fields-input text-lg font-bold outline-none bg-inherit"
:contenteditable="true" :contenteditable="true"
/> />
@ -247,7 +254,7 @@ if (props.fromTableExplorer) {
ref="antInput" ref="antInput"
v-model:value="formState.title" v-model:value="formState.title"
class="nc-column-name-input !rounded !mt-1" class="nc-column-name-input !rounded !mt-1"
:disabled="isKanban" :disabled="isKanban || readOnly"
@input="onAlter(8)" @input="onAlter(8)"
/> />
</a-form-item> </a-form-item>
@ -263,7 +270,7 @@ if (props.fromTableExplorer) {
v-model:value="formState.uidt" v-model:value="formState.uidt"
show-search show-search
class="nc-column-type-input !rounded" class="nc-column-type-input !rounded"
:disabled="isKanban" :disabled="isKanban || readOnly"
dropdown-class-name="nc-dropdown-column-type " dropdown-class-name="nc-dropdown-column-type "
@change="onUidtOrIdTypeChange" @change="onUidtOrIdTypeChange"
@dblclick="showDeprecated = !showDeprecated" @dblclick="showDeprecated = !showDeprecated"
@ -285,28 +292,32 @@ if (props.fromTableExplorer) {
</div> --> </div> -->
</div> </div>
<LazySmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" /> <template v-if="!readOnly">
<LazySmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" /> <LazySmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" />
<LazySmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" /> <LazySmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" />
<LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" /> <LazySmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" />
<LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" /> <LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" />
<LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" /> <LazySmartsheetColumnLongTextOptions v-if="formState.uidt === UITypes.LongText" v-model:value="formState" />
<LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" /> <LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" />
<LazySmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" v-model:value="formState" /> <LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" />
<LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" /> <LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />
<LazySmartsheetColumnDecimalOptions v-if="formState.uidt === UITypes.Decimal" v-model:value="formState" /> <LazySmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" v-model:value="formState" />
<LazySmartsheetColumnDateTimeOptions v-if="formState.uidt === UITypes.DateTime" v-model:value="formState" /> <LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" />
<LazySmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" v-model:value="formState" /> <LazySmartsheetColumnDecimalOptions v-if="formState.uidt === UITypes.Decimal" v-model:value="formState" />
<LazySmartsheetColumnLinkedToAnotherRecordOptions <LazySmartsheetColumnDateTimeOptions v-if="formState.uidt === UITypes.DateTime" v-model:value="formState" />
v-if="!isEdit && (formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links)" <LazySmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" v-model:value="formState" />
v-model:value="formState" <LazySmartsheetColumnLinkedToAnotherRecordOptions
/> v-if="!isEdit && (formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links)"
<LazySmartsheetColumnLinkOptions v-if="isEdit && formState.uidt === UITypes.Links" v-model:value="formState" /> :key="formState.uidt"
<LazySmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" /> v-model:value="formState"
<SmartsheetColumnSelectOptions />
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect" <LazySmartsheetColumnLinkOptions v-if="isEdit && formState.uidt === UITypes.Links" v-model:value="formState" />
v-model:value="formState" <LazySmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" />
/> <SmartsheetColumnSelectOptions
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect"
v-model:value="formState"
/>
</template>
</div> </div>
<a-checkbox <a-checkbox
v-if="formState.meta && columnToValidate.includes(formState.uidt)" v-if="formState.meta && columnToValidate.includes(formState.uidt)"
@ -317,41 +328,47 @@ if (props.fromTableExplorer) {
{{ `${$t('msg.acceptOnlyValid')} ${formState.uidt}` }} {{ `${$t('msg.acceptOnlyValid')} ${formState.uidt}` }}
</span> </span>
</a-checkbox> </a-checkbox>
<div class="!my-3"> <template v-if="!readOnly">
<!-- <div class="!my-3">
Default Value for JSON & LongText is not supported in MySQL <!--
Default Value is Disabled for MSSQL --> Default Value for JSON & LongText is not supported in MySQL
<LazySmartsheetColumnDefaultValue Default Value is Disabled for MSSQL -->
v-if=" <LazySmartsheetColumnRichLongTextDefaultValue
v-if="isTextArea(formState) && formState.meta.richMode"
v-model:value="formState"
/>
<LazySmartsheetColumnDefaultValue
v-else-if="
!isVirtualCol(formState) && !isVirtualCol(formState) &&
!isAttachment(formState) && !isAttachment(formState) &&
!isMssql(meta!.source_id) && !isMssql(meta!.source_id) &&
!(isMysql(meta!.source_id) && (isJSON(formState) || isTextArea(formState))) !(isMysql(meta!.source_id) && (isJSON(formState) || isTextArea(formState)))
" "
v-model:value="formState"
/>
</div>
<div
v-if="!props.hideAdditionalOptions && !isVirtualCol(formState.uidt) && (!appInfo.ee || (appInfo.ee && !isXcdbBase(meta!.source_id) && formState.uidt === UITypes.SpecificDBType))"
class="text-xs cursor-pointer text-gray-400 nc-more-options mb-1 mt-4 flex items-center gap-1 justify-end"
@click="advancedOptions = !advancedOptions"
>
{{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
<component :is="advancedOptions ? MdiMinusIcon : MdiPlusIcon" />
</div>
<Transition name="layout" mode="out-in">
<div v-if="advancedOptions" class="overflow-hidden">
<LazySmartsheetColumnAttachmentOptions v-if="appInfo.ee && isAttachment(formState)" v-model:value="formState" />
<LazySmartsheetColumnAdvancedOptions
v-if="formState.uidt !== UITypes.Attachment"
v-model:value="formState" v-model:value="formState"
:advanced-db-options="advancedOptions || formState.uidt === UITypes.SpecificDBType"
/> />
</div> </div>
</Transition>
<div
v-if="!props.hideAdditionalOptions && !isVirtualCol(formState.uidt) && (!appInfo.ee || (appInfo.ee && !isXcdbBase(meta!.source_id) && formState.uidt === UITypes.SpecificDBType))"
class="text-xs cursor-pointer text-gray-400 nc-more-options mb-1 mt-4 flex items-center gap-1 justify-end"
@click="advancedOptions = !advancedOptions"
>
{{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
<component :is="advancedOptions ? MdiMinusIcon : MdiPlusIcon" />
</div>
<Transition name="layout" mode="out-in">
<div v-if="advancedOptions" class="overflow-hidden">
<LazySmartsheetColumnAttachmentOptions v-if="appInfo.ee && isAttachment(formState)" v-model:value="formState" />
<LazySmartsheetColumnAdvancedOptions
v-if="formState.uidt !== UITypes.Attachment"
v-model:value="formState"
:advanced-db-options="advancedOptions || formState.uidt === UITypes.SpecificDBType"
/>
</div>
</Transition>
</template>
<template v-if="props.fromTableExplorer"> <template v-if="props.fromTableExplorer">
<a-form-item></a-form-item> <a-form-item></a-form-item>

4
packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

@ -74,7 +74,7 @@ const validators = {
const availableFunctions = formulaList const availableFunctions = formulaList
const availableBinOps = ['+', '-', '*', '/', '>', '<', '==', '<=', '>=', '!='] const availableBinOps = ['+', '-', '*', '/', '>', '<', '==', '<=', '>=', '!=', '&']
const autocomplete = ref(false) const autocomplete = ref(false)
@ -434,7 +434,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
} }
if (col.uidt === UITypes.Formula) { if (col.uidt === UITypes.Formula) {
const foundType = getRootDataType(jsep((col as any).formula_raw)) const foundType = getRootDataType(jsep(col.colOptions?.formula_raw))
if (foundType === 'N/A') { if (foundType === 'N/A') {
typeErrors.add(t('msg.formula.notSupportedToReferenceColumn', { columnName: col.title })) typeErrors.add(t('msg.formula.notSupportedToReferenceColumn', { columnName: col.title }))
} else if (expectedType !== foundType) { } else if (expectedType !== foundType) {

38
packages/nc-gui/components/smartsheet/column/LongTextOptions.vue

@ -0,0 +1,38 @@
<!-- File not in use for now -->
<script setup lang="ts">
import { useVModel } from '#imports'
const props = defineProps<{
value: any
}>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const richMode = computed({
get: () => vModel.value.meta?.richMode,
set: (value) => {
if (!vModel.value.meta) vModel.value.meta = {}
vModel.value.meta.richMode = value
},
})
watch(richMode, () => {
vModel.value.cdf = null
})
</script>
<template>
<div class="flex flex-col mt-2 gap-2">
<a-form-item>
<div class="flex flex-row items-center">
<NcSwitch v-model:checked="richMode" :name="$t('labels.enableRichText')" size="small">
<div class="text-xs">{{ $t('labels.enableRichText') }}</div>
</NcSwitch>
</div>
</a-form-item>
</div>
</template>

26
packages/nc-gui/components/smartsheet/column/RichLongTextDefaultValue.vue

@ -0,0 +1,26 @@
<script lang="ts" setup>
const props = defineProps<{
value: any
}>()
const emits = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emits)
const cdfValue = computed({
get: () => vModel.value.cdf,
set: (value) => {
vModel.value.cdf = value
},
})
</script>
<template>
<div>
<div class="!my-3 text-xs">{{ $t('placeholder.defaultValue') }}</div>
<div class="flex flex-row gap-2">
<div class="border-1 relative pt-11 flex items-center w-full px-0 my-[-4px] border-gray-300 rounded-md !max-h-70 !pb-2.5">
<LazyCellRichText v-model:value="cdfValue" class="border-t-1 border-gray-100" show-menu full-mode />
</div>
</div>
</div>
</template>

4
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -113,7 +113,7 @@ onMounted(() => {
op.title = op.title.replace(/^'/, '').replace(/'$/, '') op.title = op.title.replace(/^'/, '').replace(/'$/, '')
} }
if (vModel.value.cdf) { if (vModel.value.cdf && typeof vModel.value.cdf === 'string') {
const fndDefaultOption = options.value.find((el) => el.title === vModel.value.cdf) const fndDefaultOption = options.value.find((el) => el.title === vModel.value.cdf)
if (!fndDefaultOption) { if (!fndDefaultOption) {
vModel.value.cdf = vModel.value.cdf.replace(/^'/, '').replace(/'$/, '') vModel.value.cdf = vModel.value.cdf.replace(/^'/, '').replace(/'$/, '')
@ -249,7 +249,7 @@ const undoRemoveRenderedOption = (index: number) => {
// Removes the Select Option from cdf if the option is removed // Removes the Select Option from cdf if the option is removed
watch(vModel.value, (next) => { watch(vModel.value, (next) => {
const cdfs = (next.cdf ?? '').split(',') const cdfs = (next.cdf ?? '').toString().split(',')
const values = (next.colOptions.options ?? []).map((col) => { const values = (next.colOptions.options ?? []).map((col) => {
return col.title.replace(/^'/, '').replace(/'$/, '') return col.title.replace(/^'/, '').replace(/'$/, '')
}) })

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

@ -40,6 +40,8 @@ const { getMeta } = useMetas()
const { meta, view } = useSmartsheetStoreOrThrow() const { meta, view } = useSmartsheetStoreOrThrow()
const isLocked = inject(IsLockedInj, ref(false))
const { openedViewsTab } = storeToRefs(useViewsStore()) const { openedViewsTab } = storeToRefs(useViewsStore())
const moveOps = ref<moveOp[]>([]) const moveOps = ref<moveOp[]>([])
@ -587,6 +589,8 @@ const toggleVisibility = async (checked: boolean, field: Field) => {
useEventListener(document, 'keydown', async (e: KeyboardEvent) => { useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
if (isLocked.value) return
if (cmdOrCtrl && e.key.toLowerCase() === 's') { if (cmdOrCtrl && e.key.toLowerCase() === 's') {
if (openedViewsTab.value !== 'field') return if (openedViewsTab.value !== 'field') return
e.preventDefault() e.preventDefault()
@ -625,7 +629,11 @@ onKeyDown('ArrowUp', () => {
}) })
onKeyDown('Delete', () => { onKeyDown('Delete', () => {
if (isLocked.value) return
if (document.activeElement?.tagName === 'INPUT') return if (document.activeElement?.tagName === 'INPUT') return
if (document.activeElement?.tagName === 'TEXTAREA') return
const isDeletedField = fieldStatus(activeField.value) === 'delete' const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) { if (!isDeletedField && activeField.value) {
onFieldDelete(activeField.value) onFieldDelete(activeField.value)
@ -633,7 +641,11 @@ onKeyDown('Delete', () => {
}) })
onKeyDown('Backspace', () => { onKeyDown('Backspace', () => {
if (isLocked.value) return
if (document.activeElement?.tagName === 'INPUT') return if (document.activeElement?.tagName === 'INPUT') return
if (document.activeElement?.tagName === 'TEXTAREA') return
const isDeletedField = fieldStatus(activeField.value) === 'delete' const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) { if (!isDeletedField && activeField.value) {
onFieldDelete(activeField.value) onFieldDelete(activeField.value)
@ -641,7 +653,7 @@ onKeyDown('Backspace', () => {
}) })
onKeyDown('ArrowRight', () => { onKeyDown('ArrowRight', () => {
if (document.activeElement?.tagName === 'INPUT') return if (document.activeElement?.tagName === 'TEXTAREA') return
if (activeField.value) { if (activeField.value) {
const input = document.querySelector('.nc-fields-input') const input = document.querySelector('.nc-fields-input')
if (input) { if (input) {
@ -659,11 +671,15 @@ const onClickCopyFieldUrl = async (field: ColumnType) => {
const keys = useMagicKeys() const keys = useMagicKeys()
whenever(keys.meta_s, () => { whenever(keys.meta_s, () => {
if (isLocked.value) return
if (!meta.value?.id) return if (!meta.value?.id) return
if (openedViewsTab.value === 'field') saveChanges() if (openedViewsTab.value === 'field') saveChanges()
}) })
whenever(keys.ctrl_s, () => { whenever(keys.ctrl_s, () => {
if (isLocked.value) return
if (!meta.value?.id) return if (!meta.value?.id) return
if (openedViewsTab.value === 'field') saveChanges() if (openedViewsTab.value === 'field') saveChanges()
}) })
@ -718,9 +734,9 @@ const onFieldOptionUpdate = () => {
</template> </template>
</a-input> </a-input>
<div class="flex gap-2"> <div class="flex gap-2">
<NcTooltip> <NcTooltip :disabled="isLocked">
<template #title> {{ `${renderAltOrOptlKey()} + C` }} </template> <template #title> {{ `${renderAltOrOptlKey()} + C` }} </template>
<NcButton type="secondary" size="small" class="mr-1" :disabled="loading" @click="addField()"> <NcButton type="secondary" size="small" class="mr-1" :disabled="loading || isLocked" @click="addField()">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<GeneralIcon icon="plus" class="w-3" /> <GeneralIcon icon="plus" class="w-3" />
New Field New Field
@ -730,19 +746,22 @@ const onFieldOptionUpdate = () => {
<NcButton <NcButton
type="secondary" type="secondary"
size="small" size="small"
:disabled="!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1" :disabled="(!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1) || isLocked"
@click="clearChanges()" @click="clearChanges()"
> >
Reset Reset
</NcButton> </NcButton>
<NcTooltip> <NcTooltip :disabled="isLocked">
<template #title> {{ `${renderCmdOrCtrlKey()} + S` }} </template> <template #title> {{ `${renderCmdOrCtrlKey()} + S` }} </template>
<NcButton <NcButton
type="primary" type="primary"
size="small" size="small"
:loading="loading" :loading="loading"
:disabled="isColumnsValid ? !loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1 : true" :disabled="
(isColumnsValid ? !loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1 : true) ||
isLocked
"
@click="saveChanges()" @click="saveChanges()"
> >
Save changes Save changes
@ -752,7 +771,7 @@ const onFieldOptionUpdate = () => {
</div> </div>
<div class="flex flex-row rounded-lg border-1 border-gray-200"> <div class="flex flex-row rounded-lg border-1 border-gray-200">
<div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto w-full flex-grow-1 nc-fields-height"> <div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto w-full flex-grow-1 nc-fields-height">
<Draggable v-model="fields" item-key="id" @change="onMove($event)"> <Draggable v-model="fields" :disabled="isLocked" item-key="id" @change="onMove($event)">
<template #item="{ element: field }"> <template #item="{ element: field }">
<div <div
v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv" v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv"
@ -761,9 +780,16 @@ const onFieldOptionUpdate = () => {
@click="changeField(field, $event)" @click="changeField(field, $event)"
> >
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6"> <div class="flex items-center flex-1 py-2.5 gap-1 w-2/6">
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-600 mr-1" /> <component
:is="iconMap.drag"
class="cursor-move !h-3.75 text-gray-600 mr-1"
:class="{
'opacity-0 !cursor-default': isLocked,
}"
/>
<NcCheckbox <NcCheckbox
v-if="field.id && viewFieldsMap[field.id]" v-if="field.id && viewFieldsMap[field.id]"
:disabled="isLocked"
:checked=" :checked="
visibilityOps.find((op) => op.column.fk_column_id === field.id)?.visible ?? viewFieldsMap[field.id].show visibilityOps.find((op) => op.column.fk_column_id === field.id)?.visible ?? viewFieldsMap[field.id].show
" "
@ -882,27 +908,29 @@ const onFieldOptionUpdate = () => {
</NcButton> </NcButton>
</div> </div>
</NcTooltip> </NcTooltip>
<a-menu-divider class="my-1.5" /> <a-menu-divider v-if="!isLocked" class="my-1.5" />
</template> </template>
<NcMenuItem key="table-explorer-duplicate" @click="duplicateField(field)"> <template v-if="!isLocked">
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span> <NcMenuItem key="table-explorer-duplicate" @click="duplicateField(field)">
</NcMenuItem> <Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span>
<NcMenuItem v-if="!field.pv" key="table-explorer-insert-above" @click="addField(field, true)"> </NcMenuItem>
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span> <NcMenuItem v-if="!field.pv" key="table-explorer-insert-above" @click="addField(field, true)">
</NcMenuItem> <Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span>
<NcMenuItem key="table-explorer-insert-below" @click="addField(field)"> </NcMenuItem>
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span> <NcMenuItem key="table-explorer-insert-below" @click="addField(field)">
</NcMenuItem> <Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span>
</NcMenuItem>
<a-menu-divider class="my-1.5" />
<a-menu-divider class="my-1.5" />
<NcMenuItem key="table-explorer-delete" class="!hover:bg-red-50" @click="onFieldDelete(field)">
<div class="text-red-500"> <NcMenuItem key="table-explorer-delete" class="!hover:bg-red-50" @click="onFieldDelete(field)">
<GeneralIcon icon="delete" class="group-hover:text-accent -ml-0.25 -mt-0.75 mr-0.5" /> <div class="text-red-500">
Delete <GeneralIcon icon="delete" class="group-hover:text-accent -ml-0.25 -mt-0.75 mr-0.5" />
</div> Delete
</NcMenuItem> </div>
</NcMenuItem>
</template>
</NcMenu> </NcMenu>
</template> </template>
</NcDropdown> </NcDropdown>
@ -927,7 +955,13 @@ const onFieldOptionUpdate = () => {
@click="changeField(displayColumn, $event)" @click="changeField(displayColumn, $event)"
> >
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6"> <div class="flex items-center flex-1 py-2.5 gap-1 w-2/6">
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-200 mr-1" /> <component
:is="iconMap.drag"
class="cursor-move !h-3.75 text-gray-200 mr-1"
:class="{
'opacity-0 !cursor-default': isLocked,
}"
/>
<NcCheckbox :disabled="true" :checked="true" /> <NcCheckbox :disabled="true" :checked="true" />
<SmartsheetHeaderCellIcon <SmartsheetHeaderCellIcon
v-if="displayColumn" v-if="displayColumn"
@ -1046,6 +1080,7 @@ const onFieldOptionUpdate = () => {
:preload="fieldState(activeField)" :preload="fieldState(activeField)"
:table-explorer-columns="fields" :table-explorer-columns="fields"
embed-mode embed-mode
:readonly="isLocked"
from-table-explorer from-table-explorer
@update="onFieldUpdate" @update="onFieldUpdate"
@add="onFieldAdd" @add="onFieldAdd"

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

@ -88,6 +88,8 @@ const isRecordLinkCopied = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const readOnly = computed(() => !isUIAllowed('dataEdit') || isPublic.value)
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const { addOrEditStackRow } = useKanbanViewStoreOrThrow() const { addOrEditStackRow } = useKanbanViewStoreOrThrow()
@ -200,6 +202,8 @@ const save = async () => {
await _save(undefined, undefined, { await _save(undefined, undefined, {
kanbanClbk, kanbanClbk,
}) })
_loadRow()
reloadTrigger?.trigger() reloadTrigger?.trigger()
} }
isUnsavedFormExist.value = false isUnsavedFormExist.value = false
@ -667,6 +671,7 @@ export default {
:class="{ :class="{
'px-1': isReadOnlyVirtualCell(col), 'px-1': isReadOnlyVirtualCell(col),
}" }"
:read-only="readOnly"
/> />
<LazySmartsheetCell <LazySmartsheetCell
@ -675,7 +680,7 @@ export default {
:column="col" :column="col"
:edit-enabled="true" :edit-enabled="true"
:active="true" :active="true"
:read-only="isPublic" :read-only="readOnly"
@update:model-value="changedColumns.add(col.title)" @update:model-value="changedColumns.add(col.title)"
/> />
</SmartsheetDivDataCell> </SmartsheetDivDataCell>
@ -735,6 +740,7 @@ export default {
v-model="_row.row[col.title]" v-model="_row.row[col.title]"
:row="_row" :row="_row"
:column="col" :column="col"
:read-only="readOnly"
/> />
<LazySmartsheetCell <LazySmartsheetCell
@ -743,7 +749,7 @@ export default {
:column="col" :column="col"
:edit-enabled="true" :edit-enabled="true"
:active="true" :active="true"
:read-only="isPublic" :read-only="readOnly"
@update:model-value="changedColumns.add(col.title)" @update:model-value="changedColumns.add(col.title)"
/> />
</LazySmartsheetDivDataCell> </LazySmartsheetDivDataCell>

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

@ -142,7 +142,12 @@ const { getMeta } = useMetas()
const { addUndo, clone, defineViewScope } = useUndoRedo() const { addUndo, clone, defineViewScope } = useUndoRedo()
const { isViewColumnsLoading, updateGridViewColumn, gridViewCols, resizingColOldWith } = useViewColumnsOrThrow() const {
isViewColumnsLoading: _isViewColumnsLoading,
updateGridViewColumn,
gridViewCols,
resizingColOldWith,
} = useViewColumnsOrThrow()
const { isExpandedFormCommentMode } = storeToRefs(useConfigStore()) const { isExpandedFormCommentMode } = storeToRefs(useConfigStore())
@ -179,9 +184,11 @@ const fillHandle = ref<HTMLElement>()
const gridRect = useElementBounding(gridWrapper) const gridRect = useElementBounding(gridWrapper)
const isViewColumnsLoading = computed(() => _isViewColumnsLoading.value || !meta.value)
// #Permissions // #Permissions
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const hasEditPermission = computed(() => isUIAllowed('dataEdit') && !isLocked.value) const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value) const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value)
const { onDrag, onDragStart, draggedCol, dragColPlaceholderDomRef, toBeDroppedColId } = useColumnDrag({ const { onDrag, onDragStart, draggedCol, dragColPlaceholderDomRef, toBeDroppedColId } = useColumnDrag({
@ -344,7 +351,7 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
} }
function makeEditable(row: Row, col: ColumnType) { function makeEditable(row: Row, col: ColumnType) {
if (!hasEditPermission.value || editEnabled.value || isView || isLocked.value || readOnly.value || isSystemColumn(col)) { if (!hasEditPermission.value || editEnabled.value || isView || readOnly.value || isSystemColumn(col)) {
return return
} }
@ -375,9 +382,7 @@ function makeEditable(row: Row, col: ColumnType) {
// #Computed // #Computed
const isAddingEmptyRowAllowed = computed( const isAddingEmptyRowAllowed = computed(() => !isView && hasEditPermission.value && !isSqlView.value && !isPublicView.value)
() => !isView && !isLocked.value && hasEditPermission.value && !isSqlView.value && !isPublicView.value,
)
const visibleColLength = computed(() => fields.value?.length) const visibleColLength = computed(() => fields.value?.length)
@ -409,7 +414,9 @@ const dummyRowDataForLoading = computed(() => {
}) })
const showSkeleton = computed( const showSkeleton = computed(
() => disableSkeleton !== true && (isViewDataLoading.value || isPaginationLoading.value || isViewColumnsLoading.value), () =>
(disableSkeleton !== true && (isViewDataLoading.value || isPaginationLoading.value || isViewColumnsLoading.value)) ||
!meta.value,
) )
// #Grid // #Grid
@ -443,9 +450,7 @@ async function openNewRecordHandler() {
} }
const onDraftRecordClick = () => { const onDraftRecordClick = () => {
if (!isLocked?.value) { openNewRecordFormHook.trigger()
openNewRecordFormHook.trigger()
}
} }
const onNewRecordToGridClick = () => { const onNewRecordToGridClick = () => {
@ -552,6 +557,8 @@ const {
return true return true
} }
if (isExpandedCellInputExist()) return
// skip keyboard event handling if there is a drawer / modal // skip keyboard event handling if there is a drawer / modal
if (isDrawerOrModalExist()) { if (isDrawerOrModalExist()) {
return true return true
@ -560,7 +567,9 @@ const {
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
const altOrOptionKey = e.altKey const altOrOptionKey = e.altKey
if (e.key === ' ') { if (e.key === ' ') {
if (isCellActive.value && !editEnabled.value && hasEditPermission.value && activeCell.row !== null) { const isRichModalOpen = isExpandedCellInputExist()
if (isCellActive.value && !editEnabled.value && hasEditPermission.value && activeCell.row !== null && !isRichModalOpen) {
e.preventDefault() e.preventDefault()
const row = dataRef.value[activeCell.row] const row = dataRef.value[activeCell.row]
expandForm?.(row) expandForm?.(row)
@ -756,6 +765,9 @@ onClickOutside(tableBodyEl, (e) => {
if (activeCell.row === null || activeCell.col === null) return if (activeCell.row === null || activeCell.col === null) return
const isRichModalOpen = isExpandedCellInputExist()
if (isRichModalOpen) return
const activeCol = fields.value[activeCell.col] const activeCol = fields.value[activeCell.col]
if (editEnabled.value && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return if (editEnabled.value && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return
@ -978,7 +990,6 @@ const refreshFillHandle = () => {
const showFillHandle = computed( const showFillHandle = computed(
() => () =>
!readOnly.value && !readOnly.value &&
!isLocked.value &&
!editEnabled.value && !editEnabled.value &&
(!selectedRange.isEmpty() || (activeCell.row !== null && activeCell.col !== null)) && (!selectedRange.isEmpty() || (activeCell.row !== null && activeCell.col !== null)) &&
!dataRef.value[(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) ?? -1]?.rowMeta?.new, !dataRef.value[(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) ?? -1]?.rowMeta?.new,
@ -1057,14 +1068,18 @@ useEventListener(document, 'mouseup', () => {
/** handle keypress events */ /** handle keypress events */
useEventListener(document, 'keydown', async (e: KeyboardEvent) => { useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
if (e.key === 'Alt') { const isRichModalOpen = isExpandedCellInputExist()
if (e.key === 'Alt' && !isRichModalOpen) {
altModifier.value = true altModifier.value = true
} }
}) })
/** handle keypress events */ /** handle keypress events */
useEventListener(document, 'keyup', async (e: KeyboardEvent) => { useEventListener(document, 'keyup', async (e: KeyboardEvent) => {
if (e.key === 'Alt') { const isRichModalOpen = isExpandedCellInputExist()
if (e.key === 'Alt' && !isRichModalOpen) {
altModifier.value = false altModifier.value = false
disableUrlOverlay.value = false disableUrlOverlay.value = false
} }
@ -1111,22 +1126,6 @@ onBeforeUnmount(async () => {
reloadViewDataHook?.on(reloadViewDataHandler) reloadViewDataHook?.on(reloadViewDataHandler)
openNewRecordFormHook?.on(openNewRecordHandler) openNewRecordFormHook?.on(openNewRecordHandler)
// TODO: Use CSS animations
const showLoaderAfterDelay = ref(false)
watch([isViewDataLoading, showSkeleton, isPaginationLoading], () => {
if (!isViewDataLoading.value && !showSkeleton.value && !isPaginationLoading.value) {
showLoaderAfterDelay.value = false
return
}
showLoaderAfterDelay.value = false
setTimeout(() => {
showLoaderAfterDelay.value = true
}, 500)
})
// #Watchers // #Watchers
// reset context menu target on hide // reset context menu target on hide
@ -1217,7 +1216,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
} }
const loaderText = computed(() => { const loaderText = computed(() => {
if (isViewDataLoading.value) { if (isPaginationLoading.value) {
if (paginationDataRef.value?.totalRows && paginationDataRef.value?.pageSize) { if (paginationDataRef.value?.totalRows && paginationDataRef.value?.pageSize) {
return `Loading page<br/>${paginationDataRef.value.page} of ${Math.ceil( return `Loading page<br/>${paginationDataRef.value.page} of ${Math.ceil(
paginationDataRef.value?.totalRows / paginationDataRef.value?.pageSize, paginationDataRef.value?.totalRows / paginationDataRef.value?.pageSize,
@ -1256,10 +1255,7 @@ onKeyStroke('ArrowDown', onDown)
></div> ></div>
</div> </div>
<div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative" :class="gridWrapperClass"> <div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative" :class="gridWrapperClass">
<div <div v-show="isPaginationLoading" class="flex items-center justify-center absolute l-0 t-0 w-full h-full z-10 pb-10">
v-show="showSkeleton && !isPaginationLoading && showLoaderAfterDelay"
class="flex items-center justify-center absolute l-0 t-0 w-full h-full z-10 pb-10"
>
<div class="flex flex-col justify-center gap-2"> <div class="flex flex-col justify-center gap-2">
<GeneralLoader size="xlarge" /> <GeneralLoader size="xlarge" />
<span class="text-center" v-html="loaderText"></span> <span class="text-center" v-html="loaderText"></span>
@ -1487,7 +1483,7 @@ onKeyStroke('ArrowDown', onDown)
> >
<div class="items-center flex gap-1 min-w-[60px]"> <div class="items-center flex gap-1 min-w-[60px]">
<div <div
v-if="!readOnly || !isLocked || isMobileMode" v-if="!readOnly || isMobileMode"
class="nc-row-no sm:min-w-4 text-xs text-gray-500" class="nc-row-no sm:min-w-4 text-xs text-gray-500"
:class="{ toggle: !readOnly, hidden: row.rowMeta.selected }" :class="{ toggle: !readOnly, hidden: row.rowMeta.selected }"
> >
@ -1513,29 +1509,28 @@ onKeyStroke('ArrowDown', onDown)
class="!flex items-center" class="!flex items-center"
:data-testid="`row-save-spinner-${rowIndex}`" :data-testid="`row-save-spinner-${rowIndex}`"
/> />
<template v-else-if="!isLocked">
<span <span
v-if="row.rowMeta?.commentCount && expandForm" v-if="row.rowMeta?.commentCount && expandForm"
v-e="['c:expanded-form:open']" v-e="['c:expanded-form:open']"
class="py-1 px-3 rounded-full text-xs cursor-pointer select-none transform hover:(scale-110)" class="py-1 px-3 rounded-full text-xs cursor-pointer select-none transform hover:(scale-110)"
:style="{ backgroundColor: enumColor.light[row.rowMeta.commentCount % enumColor.light.length] }" :style="{ backgroundColor: enumColor.light[row.rowMeta.commentCount % enumColor.light.length] }"
@click="expandAndLooseFocus(row, state)"
>
{{ row.rowMeta.commentCount }}
</span>
<div
v-else-if="!row.rowMeta.saving"
class="cursor-pointer flex items-center border-1 border-gray-100 active:ring rounded p-1 hover:(bg-gray-50)"
>
<component
:is="iconMap.expand"
v-if="expandForm"
v-e="['c:row-expand:open']"
class="select-none transform hover:(text-black scale-120) nc-row-expand"
@click="expandAndLooseFocus(row, state)" @click="expandAndLooseFocus(row, state)"
> />
{{ row.rowMeta.commentCount }} </div>
</span>
<div
v-else
class="cursor-pointer flex items-center border-1 border-gray-100 active:ring rounded p-1 hover:(bg-gray-50)"
>
<component
:is="iconMap.expand"
v-if="expandForm"
v-e="['c:row-expand:open']"
class="select-none transform hover:(text-black scale-120) nc-row-expand"
@click="expandAndLooseFocus(row, state)"
/>
</div>
</template>
</div> </div>
</div> </div>
</td> </td>
@ -1727,16 +1722,14 @@ onKeyStroke('ArrowDown', onDown)
{{ $t('general.clear') }} {{ $t('general.clear') }}
</div> </div>
</NcMenuItem> </NcMenuItem>
<NcDivider /> <NcDivider />
<NcMenuItem <NcMenuItem
v-if="contextMenuTarget && !isLocked && selectedRange.isSingleCell() && isUIAllowed('commentEdit') && !isMobileMode" v-if="contextMenuTarget && selectedRange.isSingleCell() && isUIAllowed('commentEdit') && !isMobileMode"
class="nc-base-menu-item" class="nc-base-menu-item"
@click="commentRow(contextMenuTarget.row)" @click="commentRow(contextMenuTarget.row)"
> >
<div v-e="['a:row:comment']" class="flex gap-2 items-center"> <div v-e="['a:row:comment']" class="flex gap-2 items-center">
<MdiMessageOutline class="h-4 w-4" /> <MdiMessageOutline class="h-4 w-4" />
{{ $t('general.comment') }} {{ $t('general.comment') }}
</div> </div>
</NcMenuItem> </NcMenuItem>
@ -1784,7 +1777,7 @@ onKeyStroke('ArrowDown', onDown)
:extra-style="paginationStyleRef?.extraStyle" :extra-style="paginationStyleRef?.extraStyle"
> >
<template #add-record> <template #add-record>
<div v-if="isAddingEmptyRowAllowed" class="flex ml-1"> <div v-if="isAddingEmptyRowAllowed && !showSkeleton && !isPaginationLoading" class="flex ml-1">
<NcButton <NcButton
v-if="isMobileMode" v-if="isMobileMode"
v-e="[isAddNewRecordGridMode ? 'c:row:add:grid' : 'c:row:add:form']" v-e="[isAddNewRecordGridMode ? 'c:row:add:grid' : 'c:row:add:form']"
@ -1820,8 +1813,7 @@ onKeyStroke('ArrowDown', onDown)
> >
<div <div
v-e="['c:row:add:grid']" v-e="['c:row:add:grid']"
:class="{ 'group': !isLocked, 'disabled-ring': isLocked }" class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-grid group"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-grid"
@click="onNewRecordToGridClick" @click="onNewRecordToGridClick"
> >
<div class="flex flex-row items-center justify-between w-full"> <div class="flex flex-row items-center justify-between w-full">
@ -1837,8 +1829,7 @@ onKeyStroke('ArrowDown', onDown)
</div> </div>
<div <div
v-e="['c:row:add:form']" v-e="['c:row:add:form']"
:class="{ 'group': !isLocked, 'disabled-ring': isLocked }" class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-form group"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-form"
@click="onNewRecordToFormClick" @click="onNewRecordToFormClick"
> >
<div class="flex flex-row items-center justify-between w-full"> <div class="flex flex-row items-center justify-between w-full">

8
packages/nc-gui/components/smartsheet/grid/usePaginationShortcuts.ts

@ -26,6 +26,8 @@ const usePaginationShortcuts = ({
} }
const onLeft = async (e: KeyboardEvent) => { const onLeft = async (e: KeyboardEvent) => {
if (isExpandedCellInputExist()) return
if (!e.altKey) return if (!e.altKey) return
e.preventDefault() e.preventDefault()
@ -36,6 +38,8 @@ const usePaginationShortcuts = ({
} }
const onRight = async (e: KeyboardEvent) => { const onRight = async (e: KeyboardEvent) => {
if (isExpandedCellInputExist()) return
if (!e.altKey) return if (!e.altKey) return
e.preventDefault() e.preventDefault()
@ -47,6 +51,8 @@ const usePaginationShortcuts = ({
} }
const onDown = async (e: KeyboardEvent) => { const onDown = async (e: KeyboardEvent) => {
if (isExpandedCellInputExist()) return
if (!e.altKey) return if (!e.altKey) return
e.preventDefault() e.preventDefault()
@ -56,6 +62,8 @@ const usePaginationShortcuts = ({
} }
const onUp = async (e: KeyboardEvent) => { const onUp = async (e: KeyboardEvent) => {
if (isExpandedCellInputExist()) return
if (!e.altKey) return if (!e.altKey) return
e.preventDefault() e.preventDefault()

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

@ -17,6 +17,8 @@ const hideMenu = toRef(props, 'hideMenu')
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false)) const isSurveyForm = inject(IsSurveyFormInj, ref(false))
const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false)) const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
@ -46,12 +48,16 @@ const closeAddColumnDropdown = () => {
} }
const openHeaderMenu = () => { const openHeaderMenu = () => {
if (isLocked.value) return
if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit') && !isMobileMode.value) { if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit') && !isMobileMode.value) {
editColumnDropdown.value = true editColumnDropdown.value = true
} }
} }
const openDropDown = (e: Event) => { const openDropDown = (e: Event) => {
if (isLocked.value) return
if (isForm.value || isExpandedForm.value || (!isUIAllowed('fieldEdit') && !isMobileMode.value)) return if (isForm.value || isExpandedForm.value || (!isUIAllowed('fieldEdit') && !isMobileMode.value)) return
e.preventDefault() e.preventDefault()

8
packages/nc-gui/components/smartsheet/header/CellIcon.ts

@ -103,7 +103,9 @@ export default defineComponent({
setup(props) { setup(props) {
const columnMeta = toRef(props, 'columnMeta') const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta) const injectedColumn = inject(ColumnInj, columnMeta)
const column = computed(() => columnMeta.value ?? injectedColumn.value)
const { sqlUis } = storeToRefs(useBase()) const { sqlUis } = storeToRefs(useBase())
@ -112,9 +114,9 @@ export default defineComponent({
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value)) const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
return () => { return () => {
if (!column.value) return null if (!column.value && !columnMeta.value) return null
return h(renderIcon(column.value, abstractType.value), { return h(renderIcon((columnMeta.value ?? column.value)!, abstractType.value), {
class: 'text-inherit mx-1', class: 'text-inherit mx-1',
}) })
} }

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

@ -36,6 +36,8 @@ const editColumnDropdown = ref(false)
const isDropDownOpen = ref(false) const isDropDownOpen = ref(false)
const isLocked = inject(IsLockedInj, ref(false))
provide(ColumnInj, column) provide(ColumnInj, column)
const { metas } = useMetas() const { metas } = useMetas()
@ -124,12 +126,16 @@ const closeAddColumnDropdown = () => {
} }
const openHeaderMenu = () => { const openHeaderMenu = () => {
if (isLocked.value) return
if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit') && !isMobileMode.value) { if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit') && !isMobileMode.value) {
editColumnDropdown.value = true editColumnDropdown.value = true
} }
} }
const openDropDown = (e: Event) => { const openDropDown = (e: Event) => {
if (isLocked.value) return
if (isForm.value || isExpandedForm.value || (!isUIAllowed('fieldEdit') && !isMobileMode.value)) return if (isForm.value || isExpandedForm.value || (!isUIAllowed('fieldEdit') && !isMobileMode.value)) return
e.preventDefault() e.preventDefault()

4
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts

@ -78,7 +78,9 @@ export default defineComponent({
setup(props) { setup(props) {
const columnMeta = toRef(props, 'columnMeta') const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta) as Ref<ColumnType & { colOptions: LookupType | RollupType }> const injectedColumn = inject(ColumnInj, columnMeta) as Ref<ColumnType & { colOptions: LookupType | RollupType }>
const column = computed(() => columnMeta.value ?? injectedColumn.value)
let relationColumn: ColumnType let relationColumn: ColumnType

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

@ -18,8 +18,6 @@ const { isUIAllowed } = useRoles()
const isPublicView = inject(IsPublicInj, ref(false)) const isPublicView = inject(IsPublicInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { t } = useI18n() const { t } = useI18n()
@ -58,7 +56,7 @@ const quickImportDialogs: Record<(typeof quickImportDialogTypes)[number], Ref<bo
) as Record<QuickImportDialogType, Ref<boolean>> ) as Record<QuickImportDialogType, Ref<boolean>>
const onImportClick = (dialog: any) => { const onImportClick = (dialog: any) => {
if (isLocked.value) return if (lockType.value === LockType.Locked) return
emits('closeModal') emits('closeModal')
dialog.value = true dialog.value = true
@ -163,10 +161,17 @@ const onDelete = async () => {
</NcTooltip> </NcTooltip>
<NcDivider /> <NcDivider />
<template v-if="!view?.is_default"> <template v-if="!view?.is_default">
<NcMenuItem @click="onRenameMenuClick"> <NcMenuItem v-if="lockType !== LockType.Locked" @click="onRenameMenuClick">
<GeneralIcon icon="edit" /> <GeneralIcon icon="edit" />
{{ $t('activity.renameView') }} {{ $t('activity.renameView') }}
</NcMenuItem> </NcMenuItem>
<NcTooltip v-else>
<template #title> {{ $t('msg.info.disabledAsViewLocked') }} </template>
<NcMenuItem class="!cursor-not-allowed !text-gray-400">
<GeneralIcon icon="edit" />
{{ $t('activity.renameView') }}
</NcMenuItem>
</NcTooltip>
<NcMenuItem @click="onDuplicate"> <NcMenuItem @click="onDuplicate">
<GeneralIcon icon="duplicate" class="nc-view-copy-icon" /> <GeneralIcon icon="duplicate" class="nc-view-copy-icon" />
{{ $t('labels.duplicateView') }} {{ $t('labels.duplicateView') }}
@ -205,7 +210,7 @@ const onDelete = async () => {
}, },
]" ]"
class="nc-base-menu-item" class="nc-base-menu-item"
:class="{ disabled: isLocked }" :class="{ disabled: lockType === LockType.Locked }"
> >
<component :is="iconMap.upload" /> <component :is="iconMap.upload" />
{{ `${$t('general.upload')} ${type.toUpperCase()}` }} {{ `${$t('general.upload')} ${type.toUpperCase()}` }}
@ -234,47 +239,62 @@ const onDelete = async () => {
<LazySmartsheetToolbarExportSubActions /> <LazySmartsheetToolbarExportSubActions />
</NcSubMenu> </NcSubMenu>
<NcDivider />
</template> </template>
<NcSubMenu v-if="isUIAllowed('viewCreateOrEdit')" key="lock-type" class="scrollbar-thin-dull max-h-90vh overflow-auto !py-0"> <template v-if="isUIAllowed('viewCreateOrEdit')">
<template #title> <NcDivider />
<div
v-e="[ <NcSubMenu key="lock-type" class="scrollbar-thin-dull max-h-90vh overflow-auto !py-0">
'c:navdraw:preview-as', <template #title>
{ <div
sidebar: props.inSidebar, v-e="[
}, 'c:navdraw:preview-as',
]" {
class="flex flex-row items-center gap-x-3" sidebar: props.inSidebar,
> },
<div> ]"
{{ $t('labels.viewMode') }} class="flex flex-row items-center gap-x-3"
</div> >
<div class="nc-base-menu-item flex !flex-shrink group !py-1 !px-1 rounded-md bg-brand-50"> <div>
<LazySmartsheetToolbarLockType {{ $t('labels.viewMode') }}
hide-tick </div>
:type="lockType" <div class="nc-base-menu-item flex !flex-shrink group !py-1 !px-1 rounded-md bg-brand-50">
class="flex nc-view-actions-lock-type !text-brand-500 !flex-shrink" <LazySmartsheetToolbarLockType
/> hide-tick
:type="lockType"
class="flex nc-view-actions-lock-type !text-brand-500 !flex-shrink"
/>
</div>
<div class="flex flex-grow"></div>
</div> </div>
<div class="flex flex-grow"></div> </template>
</div>
</template>
<template #expandIcon></template> <template #expandIcon></template>
<div class="flex py-3 px-4 font-bold uppercase text-xs text-gray-500">{{ $t('labels.viewMode') }}</div> <div class="flex py-3 px-4 font-bold uppercase text-xs text-gray-500">{{ $t('labels.viewMode') }}</div>
<a-menu-item class="!mx-1 !py-2 !rounded-md nc-view-action-lock-subaction"> <a-menu-item class="!mx-1 !py-2 !rounded-md nc-view-action-lock-subaction">
<LazySmartsheetToolbarLockType :type="LockType.Collaborative" @click="changeLockType(LockType.Collaborative)" /> <LazySmartsheetToolbarLockType :type="LockType.Collaborative" @click="changeLockType(LockType.Collaborative)" />
</a-menu-item> </a-menu-item>
<a-menu-item class="!mx-1 !py-2 !rounded-md nc-view-action-lock-subaction">
<LazySmartsheetToolbarLockType :type="LockType.Locked" @click="changeLockType(LockType.Locked)" />
</a-menu-item>
</NcSubMenu>
</template>
<a-menu-item class="!mx-1 !py-2 !rounded-md nc-view-action-lock-subaction">
<LazySmartsheetToolbarLockType :type="LockType.Locked" @click="changeLockType(LockType.Locked)" />
</a-menu-item>
</NcSubMenu>
<template v-if="!view.is_default"> <template v-if="!view.is_default">
<NcDivider /> <NcDivider />
<NcMenuItem class="!hover:bg-red-50 !text-red-500" @click="onDelete"> <NcTooltip v-if="lockType === LockType.Locked">
<template #title> {{ $t('msg.info.disabledAsViewLocked') }} </template>
<NcMenuItem class="!cursor-not-allowed !text-gray-400">
<GeneralIcon icon="delete" class="nc-view-delete-icon" />
{{
$t('general.deleteEntity', {
entity: $t('objects.view'),
})
}}
</NcMenuItem>
</NcTooltip>
<NcMenuItem v-else class="!hover:bg-red-50 !text-red-500" @click="onDelete">
<GeneralIcon icon="delete" class="nc-view-delete-icon" /> <GeneralIcon icon="delete" class="nc-view-delete-icon" />
{{ {{
$t('general.deleteEntity', { $t('general.deleteEntity', {

14
packages/nc-gui/components/tabs/Smartsheet.vue

@ -171,21 +171,21 @@ watch([activeViewTitleOrId, activeTableId], () => {
<LazySmartsheetToolbar v-if="!isForm" /> <LazySmartsheetToolbar v-if="!isForm" />
<div class="flex flex-row w-full" style="height: calc(100% - var(--topbar-height))"> <div class="flex flex-row w-full" style="height: calc(100% - var(--topbar-height))">
<Transition name="layout" mode="out-in"> <Transition name="layout" mode="out-in">
<template v-if="meta"> <div class="flex flex-1 min-h-0 w-3/4">
<div class="flex flex-1 min-h-0 w-3/4"> <div class="h-full flex-1 min-w-0 min-h-0 bg-white">
<div v-if="activeView" class="h-full flex-1 min-w-0 min-h-0 bg-white"> <LazySmartsheetGrid v-if="isGrid || !meta || !activeView" ref="grid" />
<LazySmartsheetGrid v-if="isGrid" ref="grid" />
<LazySmartsheetGallery v-else-if="isGallery" /> <template v-if="activeView && meta">
<LazySmartsheetGallery v-if="isGallery" />
<LazySmartsheetForm v-else-if="isForm && !$route.query.reload" /> <LazySmartsheetForm v-else-if="isForm && !$route.query.reload" />
<LazySmartsheetKanban v-else-if="isKanban" /> <LazySmartsheetKanban v-else-if="isKanban" />
<LazySmartsheetMap v-else-if="isMap" /> <LazySmartsheetMap v-else-if="isMap" />
</div> </template>
</div> </div>
</template> </div>
</Transition> </Transition>
</div> </div>
</div> </div>

5
packages/nc-gui/components/virtual-cell/BelongsTo.vue

@ -6,7 +6,6 @@ import {
CellValueInj, CellValueInj,
ColumnInj, ColumnInj,
IsFormInj, IsFormInj,
IsLockedInj,
IsUnderLookupInj, IsUnderLookupInj,
ReadonlyInj, ReadonlyInj,
ReloadRowDataHookInj, ReloadRowDataHookInj,
@ -35,8 +34,6 @@ const readOnly = inject(ReadonlyInj, ref(false))
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false)) const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
@ -103,7 +100,7 @@ const belongsToColumn = computed(
</div> </div>
<div <div
v-if="!readOnly && !isLocked && (isUIAllowed('dataEdit') || isForm) && !isUnderLookup" v-if="!readOnly && (isUIAllowed('dataEdit') || isForm) && !isUnderLookup"
class="flex justify-end gap-1 min-h-[30px] items-center" class="flex justify-end gap-1 min-h-[30px] items-center"
> >
<GeneralIcon <GeneralIcon

5
packages/nc-gui/components/virtual-cell/HasMany.vue

@ -5,7 +5,6 @@ import {
CellValueInj, CellValueInj,
ColumnInj, ColumnInj,
IsFormInj, IsFormInj,
IsLockedInj,
IsUnderLookupInj, IsUnderLookupInj,
ReadonlyInj, ReadonlyInj,
ReloadRowDataHookInj, ReloadRowDataHookInj,
@ -31,8 +30,6 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj)
const isUnderLookup = inject(IsUnderLookupInj, ref(false)) const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const listItemsDlg = ref(false) const listItemsDlg = ref(false)
@ -121,7 +118,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</template> </template>
</div> </div>
<div v-if="!isLocked && !isUnderLookup" class="flex justify-end gap-1 min-h-[30px] items-center"> <div v-if="!isUnderLookup" class="flex justify-end gap-1 min-h-[30px] items-center">
<GeneralIcon <GeneralIcon
icon="expand" icon="expand"
class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand" class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"

13
packages/nc-gui/components/virtual-cell/Links.vue

@ -17,8 +17,6 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false)) const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const colTitle = computed(() => column.value?.title || '') const colTitle = computed(() => column.value?.title || '')
@ -79,15 +77,13 @@ const onAttachRecord = () => {
const openChildList = () => { const openChildList = () => {
if (isUnderLookup.value) return if (isUnderLookup.value) return
if (!isLocked.value) { childListDlg.value = true
childListDlg.value = true
}
} }
useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEvent) => { useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
if (isLocked.value || listItemsDlg.value) return if (listItemsDlg.value) return
childListDlg.value = true childListDlg.value = true
e.stopPropagation() e.stopPropagation()
break break
@ -112,7 +108,7 @@ const openListDlg = () => {
<div class="flex w-full group items-center nc-links-wrapper" @dblclick.stop="openChildList"> <div class="flex w-full group items-center nc-links-wrapper" @dblclick.stop="openChildList">
<div class="block flex-shrink truncate"> <div class="block flex-shrink truncate">
<component <component
:is="isLocked || isUnderLookup ? 'span' : 'a'" :is="isUnderLookup ? 'span' : 'a'"
v-e="['c:cell:links:modal:open']" v-e="['c:cell:links:modal:open']"
:title="textVal" :title="textVal"
class="text-center nc-datatype-link underline-transparent" class="text-center nc-datatype-link underline-transparent"
@ -124,14 +120,13 @@ const openListDlg = () => {
</div> </div>
<div class="flex-grow" /> <div class="flex-grow" />
<div v-if="!isLocked && !isUnderLookup" class="!xs:hidden flex justify-end hidden group-hover:flex items-center"> <div v-if="!isUnderLookup" class="!xs:hidden flex justify-end hidden group-hover:flex items-center">
<MdiPlus <MdiPlus
v-if="(!readOnly && isUIAllowed('dataEdit')) || isForm" v-if="(!readOnly && isUIAllowed('dataEdit')) || isForm"
class="select-none !text-md text-gray-700 nc-action-icon nc-plus" class="select-none !text-md text-gray-700 nc-action-icon nc-plus"
@click.stop="openListDlg" @click.stop="openListDlg"
/> />
</div> </div>
<LazyVirtualCellComponentsListItems <LazyVirtualCellComponentsListItems
v-if="listItemsDlg || childListDlg" v-if="listItemsDlg || childListDlg"
v-model="listItemsDlg" v-model="listItemsDlg"

8
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -110,9 +110,13 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
> >
<template v-if="lookupColumn"> <template v-if="lookupColumn">
<!-- Render virtual cell --> <!-- Render virtual cell -->
<div v-if="isVirtualCol(lookupColumn)"> <div v-if="isVirtualCol(lookupColumn)" class="flex">
<!-- If non-belongs-to LTAR column then pass the array value, else iterate and render -->
<template <template
v-if="lookupColumn.uidt === UITypes.LinkToAnotherRecord && lookupColumn.colOptions.type === RelationTypes.BELONGS_TO" v-if="
lookupColumn.uidt !== UITypes.LinkToAnotherRecord ||
(lookupColumn.uidt === UITypes.LinkToAnotherRecord && lookupColumn.colOptions.type === RelationTypes.BELONGS_TO)
"
> >
<LazySmartsheetVirtualCell <LazySmartsheetVirtualCell
v-for="(v, i) of arrValue" v-for="(v, i) of arrValue"

5
packages/nc-gui/components/virtual-cell/ManyToMany.vue

@ -6,7 +6,6 @@ import {
CellValueInj, CellValueInj,
ColumnInj, ColumnInj,
IsFormInj, IsFormInj,
IsLockedInj,
IsUnderLookupInj, IsUnderLookupInj,
ReadonlyInj, ReadonlyInj,
ReloadRowDataHookInj, ReloadRowDataHookInj,
@ -33,8 +32,6 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj)
const isUnderLookup = inject(IsUnderLookupInj, ref(false)) const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const listItemsDlg = ref(false) const listItemsDlg = ref(false)
@ -123,7 +120,7 @@ const m2mColumn = computed(
</template> </template>
</div> </div>
<div v-if="(!isLocked && !isUnderLookup) || isForm" class="flex justify-end gap-1 min-h-[30px] items-center"> <div v-if="!isUnderLookup || isForm" class="flex justify-end gap-1 min-h-[30px] items-center">
<GeneralIcon <GeneralIcon
icon="expand" icon="expand"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand" class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"

3
packages/nc-gui/components/virtual-cell/QrCode.vue

@ -92,9 +92,10 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = us
:style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem` }" :style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem` }"
:src="qrCode" :src="qrCode"
:alt="$t('title.qrCode')" :alt="$t('title.qrCode')"
class="min-w-[1.4em]"
@click="showQrModal" @click="showQrModal"
/> />
<img v-else-if="showQrCode" class="mx-auto" :src="qrCode" :alt="$t('title.qrCode')" @click="showQrModal" /> <img v-else-if="showQrCode" class="mx-auto min-w-[1.4em]" :src="qrCode" :alt="$t('title.qrCode')" @click="showQrModal" />
</div> </div>
<div v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs"> <div v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs">
{{ $t('msg.warning.nonEditableFields.computedFieldUnableToClear') }} {{ $t('msg.warning.nonEditableFields.computedFieldUnableToClear') }}

11
packages/nc-gui/components/virtual-cell/components/ItemChip.vue

@ -4,7 +4,6 @@ import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { import {
ActiveCellInj, ActiveCellInj,
IsFormInj, IsFormInj,
IsLockedInj,
ReadonlyInj, ReadonlyInj,
iconMap, iconMap,
inject, inject,
@ -37,13 +36,11 @@ const active = inject(ActiveCellInj, ref(false))
const isForm = inject(IsFormInj)! const isForm = inject(IsFormInj)!
const isLocked = inject(IsLockedInj, ref(false))
const { open } = useExpandedFormDetached() const { open } = useExpandedFormDetached()
function openExpandedForm() { function openExpandedForm() {
const rowId = extractPkFromRow(item, relatedTableMeta.value.columns as ColumnType[]) const rowId = extractPkFromRow(item, relatedTableMeta.value.columns as ColumnType[])
if (!readOnly.value && !isLocked.value && !readonlyProp && rowId) { if (!readOnly.value && !readonlyProp && rowId) {
open({ open({
isOpen: true, isOpen: true,
row: { row: item, rowMeta: {}, oldRow: { ...item } }, row: { row: item, rowMeta: {}, oldRow: { ...item } },
@ -98,11 +95,7 @@ export default {
</template> </template>
</span> </span>
<div <div v-show="active || isForm" v-if="showUnlinkButton && !readOnly && isUIAllowed('dataEdit')" class="flex items-center">
v-show="active || isForm"
v-if="showUnlinkButton && !readOnly && !isLocked && isUIAllowed('dataEdit')"
class="flex items-center"
>
<component <component
:is="iconMap.closeThick" :is="iconMap.closeThick"
class="nc-icon unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500" class="nc-icon unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500"

1
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -166,6 +166,7 @@ const isDataExist = computed<boolean>(() => {
const linkOrUnLink = (rowRef: Record<string, string>, id: string) => { const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
if (isSharedBase.value) return if (isSharedBase.value) return
if (readonly.value) return
if (isPublic.value && !isForm.value) return if (isPublic.value && !isForm.value) return
if (isNew.value || isChildrenListLinked.value[parseInt(id)]) { if (isNew.value || isChildrenListLinked.value[parseInt(id)]) {

5
packages/nc-gui/components/virtual-cell/components/ListItem.vue

@ -77,6 +77,7 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
'!bg-white': isLoading, '!bg-white': isLoading,
'!border-1': isLinked && !isLoading, '!border-1': isLinked && !isLoading,
'!hover:border-gray-400': !isLinked, '!hover:border-gray-400': !isLinked,
'!cursor-auto !hover:bg-white': readonly,
}" }"
:body-style="{ padding: 0 }" :body-style="{ padding: 0 }"
:hoverable="false" :hoverable="false"
@ -109,7 +110,7 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
v-if="isLinked && !isLoading" v-if="isLinked && !isLoading"
class="text-brand-500 text-0.875" class="text-brand-500 text-0.875"
:class="{ :class="{
'!group-hover:mr-12': fields.length === 0, '!group-hover:mr-12': fields.length === 0 && !readonly,
}" }"
> >
<LinkIcon class="w-4 h-4" /> <LinkIcon class="w-4 h-4" />
@ -118,7 +119,7 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
<MdiLoading <MdiLoading
v-else-if="isLoading" v-else-if="isLoading"
:class="{ :class="{
'!group-hover:mr-8': fields.length === 0, '!group-hover:mr-8': fields.length === 0 && !readonly,
}" }"
class="w-6 h-6 !text-brand-500 animate-spin" class="w-6 h-6 !text-brand-500 animate-spin"
/> />

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

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

1
packages/nc-gui/composables/useGlobal/state.ts

@ -109,6 +109,7 @@ export function useGlobalState(storageKey = 'nocodb-gui-v2'): State {
automationLogLevel: 'OFF', automationLogLevel: 'OFF',
disableEmailAuth: false, disableEmailAuth: false,
dashboardPath: '/dashboard', dashboardPath: '/dashboard',
inviteOnlySignup: false,
}) })
/** reactive token payload */ /** reactive token payload */

1
packages/nc-gui/composables/useGlobal/types.ts

@ -34,6 +34,7 @@ export interface AppInfo {
disableEmailAuth: boolean disableEmailAuth: boolean
mainSubDomain?: string mainSubDomain?: string
dashboardPath: string dashboardPath: string
inviteOnlySignup: boolean
} }
export interface StoredState { export interface StoredState {

4
packages/nc-gui/composables/useMultiSelect/index.ts

@ -486,6 +486,10 @@ export function useMultiSelect(
return true return true
} }
if (isExpandedCellInputExist()) {
return
}
if (!isCellActive.value || activeCell.row === null || activeCell.col === null) { if (!isCellActive.value || activeCell.row === null || activeCell.col === null) {
return return
} }

59
packages/nc-gui/composables/useTableNew.ts

@ -1,5 +1,7 @@
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn } from 'nocodb-sdk' import { UITypes, isSystemColumn } from 'nocodb-sdk'
import type { SidebarTableNode } from '~/lib'
import { import {
Modal, Modal,
SYSTEM_COLUMNS, SYSTEM_COLUMNS,
@ -58,7 +60,7 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
const tables = computed(() => baseTables.value.get(param.baseId) || []) const tables = computed(() => baseTables.value.get(param.baseId) || [])
const base = computed(() => bases.value.get(param.baseId)) const base = computed(() => bases.value.get(param.baseId))
const openTable = async (table: TableType) => { const openTable = async (table: SidebarTableNode) => {
if (!table.base_id) return if (!table.base_id) return
let base = bases.value.get(table.base_id) let base = bases.value.get(table.base_id)
@ -82,24 +84,49 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
baseIdOrBaseId = route.value.params.baseId as string baseIdOrBaseId = route.value.params.baseId as string
} }
await getMeta(table.id as string, (route.value.params?.viewId as string) !== table.id) const navigateToTable = async () => {
if (openedViewsTab.value === 'view') {
await navigateTo({
path: `/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}`,
query: route.value.query,
})
}
await loadViews({ tableId: table.id as string }) table.isViewsLoading = true
const views = viewsByTable.value.get(table.id as string) ?? [] try {
if (openedViewsTab.value !== 'view' && views.length && views[0].id) { await loadViews({ tableId: table.id as string })
// find the default view and navigate to it, if not found navigate to the first one
const defaultView = views.find((v) => v.is_default) || views[0]
await navigateTo({ const views = viewsByTable.value.get(table.id as string) ?? []
path: `/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}/${defaultView.id}/${openedViewsTab.value}`, if (openedViewsTab.value !== 'view' && views.length && views[0].id) {
query: route.value.query, // find the default view and navigate to it, if not found navigate to the first one
}) const defaultView = views.find((v) => v.is_default) || views[0]
} else
await navigateTo({ await navigateTo({
path: `/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}`, path: `/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}/${defaultView.id}/${openedViewsTab.value}`,
query: route.value.query, query: route.value.query,
}) })
}
} catch (e) {
console.error(e)
} finally {
table.isViewsLoading = false
}
}
const loadTableMeta = async () => {
table.isMetaLoading = true
try {
await getMeta(table.id as string)
} catch (e) {
console.error(e)
} finally {
table.isMetaLoading = false
}
}
await Promise.all([navigateToTable(), loadTableMeta()])
} }
const createTable = async () => { const createTable = async () => {

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

@ -42,10 +42,6 @@ export function useViewData(
const meta = computed(() => _meta.value || activeTable.value) const meta = computed(() => _meta.value || activeTable.value)
const metaId = computed(() => _meta.value?.id || activeTableId.value) const metaId = computed(() => _meta.value?.id || activeTableId.value)
if (!meta.value) {
throw new Error('Table meta is not available')
}
const { t } = useI18n() const { t } = useI18n()
const optimisedQuery = useState('optimisedQuery', () => true) const optimisedQuery = useState('optimisedQuery', () => true)
@ -190,6 +186,8 @@ export function useViewData(
controller.value = CancelToken.source() controller.value = CancelToken.source()
isPaginationLoading.value = true
const response = !isPublic.value const response = !isPublic.value
? await api.dbViewRow.list( ? await api.dbViewRow.list(
'noco', 'noco',

131
packages/nc-gui/helpers/dbTiptapExtensions/links.ts

@ -0,0 +1,131 @@
import TiptapLink from '@tiptap/extension-link'
import { mergeAttributes } from '@tiptap/core'
import { Plugin, TextSelection } from 'prosemirror-state'
import type { AddMarkStep, Step } from 'prosemirror-transform'
export const Link = TiptapLink.extend({
addOptions() {
return {
openOnClick: true,
linkOnPaste: true,
autolink: true,
protocols: [],
HTMLAttributes: {
target: '_blank',
rel: 'noopener noreferrer nofollow',
class: null,
},
validate: undefined,
internal: false,
}
},
addAttributes() {
return {
href: {
default: null,
},
target: {
default: this.options.HTMLAttributes.target,
},
class: {
default: this.options.HTMLAttributes.class,
},
}
},
renderHTML({ HTMLAttributes }) {
const attr = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)
return ['a', attr, 0]
},
addKeyboardShortcuts() {
return {
'Mod-j': () => {
const selection = this.editor.view.state.selection
this.editor
.chain()
.toggleLink({
href: '',
})
.setTextSelection(selection.to)
.run()
setTimeout(() => {
const linkInput = document.querySelector('.nc-text-area-rich-link-option-input')
if (linkInput) {
;(linkInput as any).focus()
}
}, 100)
},
'Space': () => {
// If we press space twice we stop the link mark and have normal text
const editor = this.editor
const selection = editor.view.state.selection
const nodeBefore = selection.$to.nodeBefore
const nodeAfter = selection.$to.nodeAfter
if (!nodeBefore) return false
const nodeBeforeText = nodeBefore.text!
// If we are not inside a link, we don't do anything
if (
!nodeBefore?.marks.some((mark) => mark.type.name === 'link') ||
nodeAfter?.marks.some((mark) => mark.type.name === 'link')
) {
return false
}
// Last text character should be a space
if (nodeBeforeText[nodeBeforeText.length - 1] !== ' ') {
return false
}
editor.view.dispatch(
editor.view.state.tr.removeMark(selection.$to.pos - 1, selection.$to.pos, editor.view.state.schema.marks.link),
)
return true
},
} as any
},
addProseMirrorPlugins() {
return [
// To have proseMirror plugins from the parent extension
...(this.parent?.() ?? []),
new Plugin({
//
// Put cursor at the end of the link when we add a link
//
appendTransaction: (transactions, _, newState) => {
try {
if (transactions.length !== 1) return null
const steps = transactions[0].steps
if (steps.length !== 1) return null
const step: Step = steps[0] as Step
const stepJson = step.toJSON()
// Ignore we are not adding a mark(i.e link, bold, etc)
if (stepJson.stepType !== 'addMark') return null
const addMarkStep: AddMarkStep = step as AddMarkStep
if (!addMarkStep) return null
if (addMarkStep.from === addMarkStep.to) return null
if (addMarkStep.mark.type.name !== 'link') return null
const { tr } = newState
return tr.setSelection(new TextSelection(tr.doc.resolve(addMarkStep.to)))
} catch (e) {
console.error(e)
return null
}
},
}),
]
},
}).configure({
openOnClick: false,
})

186
packages/nc-gui/helpers/dbTiptapExtensions/task-item.ts

@ -0,0 +1,186 @@
import type { KeyboardShortcutCommand } from '@tiptap/core'
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'
import type { Node as ProseMirrorNode } from '@tiptap/pm/model'
export interface TaskItemOptions {
onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean
nested: boolean
HTMLAttributes: Record<string, any>
taskListTypeName: string
}
export const inputRegex = /^\s*\[( |x)?\]\s$/i
export const TaskItem = Node.create<TaskItemOptions>({
name: 'taskItem',
addOptions() {
return {
nested: false,
HTMLAttributes: {},
taskListTypeName: 'taskList',
}
},
content() {
return this.options.nested ? 'paragraph block*' : 'paragraph+'
},
defining: true,
addAttributes() {
return {
checked: {
default: false,
keepOnSplit: false,
parseHTML: (element) => element.getAttribute('data-checked') === 'true',
renderHTML: (attributes) => ({
'data-checked': attributes.checked,
}),
},
}
},
parseHTML() {
return [
{
tag: `li[data-type="${this.name}"]`,
priority: 51,
},
]
},
renderHTML({ node, HTMLAttributes }) {
return [
'li',
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
'data-type': this.name,
}),
[
'label',
[
'input',
{
type: 'checkbox',
checked: node.attrs.checked ? 'checked' : null,
},
],
['span'],
],
['div', 0],
]
},
addKeyboardShortcuts() {
const shortcuts: {
[key: string]: KeyboardShortcutCommand
} = {
'Enter': () => this.editor.commands.splitListItem(this.name),
'Shift-Tab': () => this.editor.commands.liftListItem(this.name),
}
if (!this.options.nested) {
return shortcuts
}
return {
...shortcuts,
Tab: () => this.editor.commands.sinkListItem(this.name),
}
},
addNodeView() {
return ({ node, HTMLAttributes, getPos, editor }) => {
const listItem = document.createElement('li')
const checkboxWrapper = document.createElement('label')
const checkboxStyler = document.createElement('span')
const checkbox = document.createElement('input')
const content = document.createElement('div')
checkboxWrapper.contentEditable = 'false'
checkbox.type = 'checkbox'
checkbox.addEventListener('change', (event) => {
// if the editor isn’t editable and we don't have a handler for
// readonly checks we have to undo the latest change
if (!editor.isEditable && !this.options.onReadOnlyChecked) {
checkbox.checked = !checkbox.checked
return
}
const { checked } = event.target as any
if (editor.isEditable && typeof getPos === 'function') {
editor
.chain()
.focus(undefined, { scrollIntoView: false })
.command(({ tr }) => {
const position = getPos()
const currentNode = tr.doc.nodeAt(position)
tr.setNodeMarkup(position, undefined, {
...currentNode?.attrs,
checked,
})
return true
})
.run()
}
if (!editor.isEditable && this.options.onReadOnlyChecked) {
// Reset state if onReadOnlyChecked returns false
if (!this.options.onReadOnlyChecked(node, checked)) {
checkbox.checked = !checkbox.checked
}
}
})
Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {
listItem.setAttribute(key, value)
})
listItem.dataset.checked = node.attrs.checked
if (node.attrs.checked) {
checkbox.setAttribute('checked', 'checked')
}
checkboxWrapper.append(checkbox, checkboxStyler)
listItem.append(checkboxWrapper, content)
Object.entries(HTMLAttributes).forEach(([key, value]) => {
listItem.setAttribute(key, value)
})
return {
dom: listItem,
contentDOM: content,
update: (updatedNode) => {
if (updatedNode.type !== this.type) {
return false
}
listItem.dataset.checked = updatedNode.attrs.checked
if (updatedNode.attrs.checked) {
checkbox.setAttribute('checked', 'checked')
} else {
checkbox.removeAttribute('checked')
}
return true
},
}
}
},
addInputRules() {
return [
wrappingInputRule({
find: inputRegex,
type: this.type,
getAttributes: (match) => ({
checked: match[match.length - 1].toLowerCase() === 'x',
}),
}),
]
},
})

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "إرسال", "submit": "إرسال",
"create": "إنشاء", "create": "إنشاء",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "الذهاب إلى لوحة التحكم", "goToDashboard": "الذهاب إلى لوحة التحكم",
"importing": "الاستيراد", "importing": "الاستيراد",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "متداخلة متقطعة", "flattenNested": "متداخلة متقطعة",
"downloadAllowed": "التحميل المسموح به", "downloadAllowed": "التحميل المسموح به",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "জমিন", "submit": "জমিন",
"create": "সি", "create": "সি",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Importing",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed", "downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -1,12 +1,12 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "Create New Interface",
"connect_data_sources": "Connect data sources", "connect_data_sources": "Připojit zdroje dat",
"alert": "Alert", "alert": "Alert",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.",
"create_interface": "Create interface", "create_interface": "Create interface",
"project_name": "Base Name", "project_name": "Název projektu",
"connect": "Connect", "connect": "Connect",
"buttonActionTypes": { "buttonActionTypes": {
"open_external_url": "Open external link", "open_external_url": "Open external link",
@ -67,28 +67,30 @@
"null": "Null", "null": "Null",
"escape": "Escape", "escape": "Escape",
"hex": "Hex", "hex": "Hex",
"clear": "Clear", "clear": "Vyprázdnit",
"slack": "Slack", "slack": "Slack",
"comment": "Comment", "comment": "Komentář",
"microsoftTeams": "Microsoft Teams", "microsoftTeams": "Microsoft Teams",
"discord": "Discord", "discord": "Discord",
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Potvrdit", "submit": "Potvrdit",
"create": "Vytvořit", "create": "Vytvořit",
"createEntity": "Create {entity}", "createEntity": "Vytvořit {entity}",
"creating": "Creating", "creating": "Vytváření",
"creatingEntity": "Creating {entity}", "creatingEntity": "Vytvářím {entity}",
"details": "Details", "details": "Podrobnosti",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplikovat", "duplicate": "Duplikovat",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
"action": "Action", "action": "Action",
"insert": "Vložit", "insert": "Vložit",
"delete": "Smazat", "delete": "Smazat",
"deleteEntity": "Delete {entity}", "deleteEntity": "Odstranit {entity}",
"bulkInsert": "Bulk Insert", "bulkInsert": "Bulk Insert",
"bulkDelete": "Bulk Delete", "bulkDelete": "Bulk Delete",
"bulkUpdate": "Bulk Update", "bulkUpdate": "Bulk Update",
@ -100,7 +102,7 @@
"install": "Instalovat", "install": "Instalovat",
"show": "Zobrazit", "show": "Zobrazit",
"access": "Access", "access": "Access",
"visibility": "Visibility", "visibility": "Viditelnost",
"hide": "Skrýt", "hide": "Skrýt",
"deprecated": "Deprecated", "deprecated": "Deprecated",
"showAll": "Zobrazit vše", "showAll": "Zobrazit vše",
@ -134,7 +136,7 @@
"after": "Po", "after": "Po",
"before": "Před", "before": "Před",
"search": "Hledat", "search": "Hledat",
"searchIn": "Search In", "searchIn": "Hledat v",
"notification": "Oznámení", "notification": "Oznámení",
"reference": "Odkaz", "reference": "Odkaz",
"function": "Funkce", "function": "Funkce",
@ -160,8 +162,8 @@
"sortDesc": "Seřadit sestupně", "sortDesc": "Seřadit sestupně",
"move": "Move", "move": "Move",
"geoDataField": "Pole geodat", "geoDataField": "Pole geodat",
"type": "Type", "type": "Typ",
"name": "Name", "name": "Název",
"changes": "Changes", "changes": "Changes",
"new": "New", "new": "New",
"old": "Old", "old": "Old",
@ -228,19 +230,19 @@
"commenter": "Komentátor", "commenter": "Komentátor",
"viewer": "Sledující", "viewer": "Sledující",
"noaccess": "No Access", "noaccess": "No Access",
"superAdmin": "Super Admin", "superAdmin": "Hlavní administrátor",
"orgLevelCreator": "Tvůrce na úrovni organizace", "orgLevelCreator": "Tvůrce na úrovni organizace",
"orgLevelViewer": "Prohlížeč na úrovni organizace" "orgLevelViewer": "Prohlížeč na úrovni organizace"
}, },
"sqlVIew": "SQL pohled", "sqlVIew": "SQL pohled",
"rowHeight": "Record Height", "rowHeight": "Výška řádku",
"heightClass": { "heightClass": {
"short": "Short", "short": "Krátký",
"medium": "Medium", "medium": "Střední",
"tall": "Tall", "tall": "Vyšší",
"extra": "Extra" "extra": "Nejvyšší"
}, },
"externalDb": "External Database" "externalDb": "Externí databáze"
}, },
"datatype": { "datatype": {
"ID": "ID", "ID": "ID",
@ -295,15 +297,15 @@
"isNotNull": "není null" "isNotNull": "není null"
}, },
"title": { "title": {
"docs": "Docs", "docs": "Dokumentace",
"forum": "Forum", "forum": "Fórum",
"parameter": "Parameter", "parameter": "Parameter",
"headers": "Headers", "headers": "Headers",
"parameterName": "Parameter Name", "parameterName": "Parameter Name",
"currencyLocale": "Currency Locale", "currencyLocale": "Currency Locale",
"currencyCode": "Currency Code", "currencyCode": "Kód měny",
"searchMembers": "Search Members", "searchMembers": "Vyhledávání členů",
"noMembersFound": "No members found", "noMembersFound": "Nenalezeni žádní členové",
"dateJoined": "Date Joined", "dateJoined": "Date Joined",
"tokenName": "Token name", "tokenName": "Token name",
"inDesktop": "in Desktop", "inDesktop": "in Desktop",
@ -312,7 +314,7 @@
"qrCode": "QR Code", "qrCode": "QR Code",
"termsOfService": "Terms of Service", "termsOfService": "Terms of Service",
"updateSelectedRows": "Update Selected Records", "updateSelectedRows": "Update Selected Records",
"noFiltersAdded": "No filters added", "noFiltersAdded": "Nepřidány žádné filtry",
"editCards": "Edit Cards", "editCards": "Edit Cards",
"noFieldsFound": "No fields found", "noFieldsFound": "No fields found",
"displayValue": "Display Value", "displayValue": "Display Value",
@ -325,7 +327,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"downloadFile": "Download File", "downloadFile": "Stáhnout soubor",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
"renamingWs": "Renaming Workspace", "renamingWs": "Renaming Workspace",
@ -341,7 +343,7 @@
"erdView": "Zobrazení ERD", "erdView": "Zobrazení ERD",
"newBase": "New Data Source", "newBase": "New Data Source",
"newProj": "Nový projekt", "newProj": "Nový projekt",
"createBase": "Create Base", "createBase": "Vytvořit projekt",
"myProject": "Moje projekty", "myProject": "Moje projekty",
"formTitle": "Název formuláře", "formTitle": "Název formuláře",
"collaborative": "Collaborative", "collaborative": "Collaborative",
@ -391,15 +393,15 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"accountSettings": "Account Settings", "accountSettings": "Nastavení účtu",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
"userManagement": "User Management", "userManagement": "User Management",
"accountManagement": "Account management", "accountManagement": "Account management",
"licence": "Licence", "licence": "Licence",
"allowAllMimeTypes": "Allow All Mime Types", "allowAllMimeTypes": "Allow All Mime Types",
"defaultView": "Default View", "defaultView": "Základní pohled",
"relations": "Relations", "relations": "Relace",
"switchLanguage": "Switch Language", "switchLanguage": "Switch Language",
"renameFile": "Rename File", "renameFile": "Rename File",
"links": { "links": {
@ -411,11 +413,22 @@
} }
}, },
"labels": { "labels": {
"downloadData": "Download Data", "heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Stáhnout data",
"blockQuote": "Block Quote",
"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",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "ID Pohledu: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Subject",
"body": "Body", "body": "Body",
@ -423,16 +436,17 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Kopírovat URL záznamu",
"duplicateRecord": "Duplicate record", "duplicateRecord": "Duplicate record",
"binaryEncodingFormat": "Binary encoding format", "binaryEncodingFormat": "Binary encoding format",
"syntax": "Syntax", "syntax": "Syntax",
"examples": "Examples", "examples": "Examples",
"durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).", "durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).",
"addHeader": "Add Header", "addHeader": "Add Header",
"enterDefaultUrlOptional": "Enter default URL (Optional)", "enterDefaultUrlOptional": "Zadejte výchozí URL (volitelné)",
"negative": "Negative", "negative": "Negative",
"discard": "Discard", "discard": "Discard",
"default": "Default", "default": "Default",
@ -442,7 +456,7 @@
"timeFormat": "Time Format", "timeFormat": "Time Format",
"singularLabel": "Singular Label", "singularLabel": "Singular Label",
"pluralLabel": "Plural Label", "pluralLabel": "Plural Label",
"optional": "(Optional)", "optional": "(nepovinné)",
"clickToMake": "Click to make", "clickToMake": "Click to make",
"visibleForRole": "visible for role:", "visibleForRole": "visible for role:",
"inUI": "in UI Dashboard", "inUI": "in UI Dashboard",
@ -450,12 +464,12 @@
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download", "clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Kliknutím zkopírujete ID pohledu",
"viewMode": "View Mode", "viewMode": "Režim zobrazení",
"searchUsers": "Search Users", "searchUsers": "Search Users",
"superAdmin": "Super Admin", "superAdmin": "Super Admin",
"allTables": "All Tables", "allTables": "All Tables",
"members": "Members", "members": "Členové",
"dataSources": "Data Sources", "dataSources": "Data Sources",
"connectDataSource": "Connect a Data Source", "connectDataSource": "Connect a Data Source",
"searchProjects": "Search Bases", "searchProjects": "Search Bases",
@ -470,13 +484,13 @@
"accountDetails": "Account Details", "accountDetails": "Account Details",
"controlAppearance": "Control your Appearance.", "controlAppearance": "Control your Appearance.",
"accountEmailID": "Account Email ID", "accountEmailID": "Account Email ID",
"backToWorkspace": "Back to Workspace", "backToWorkspace": "Zpět na přehled projektů",
"untitledToken": "Untitled token", "untitledToken": "Untitled token",
"tableName": "Název tabulky", "tableName": "Název tabulky",
"dashboardName": "Dashboard name", "dashboardName": "Dashboard name",
"createView": "Create a View", "createView": "Vytvořit pohled",
"creatingView": "Creating View", "creatingView": "Creating View",
"duplicateView": "Duplicate View", "duplicateView": "Duplikovat pohled",
"duplicateGridView": "Duplicate Grid View", "duplicateGridView": "Duplicate Grid View",
"createGridView": "Create Grid View", "createGridView": "Create Grid View",
"duplicateGalleryView": "Duplicate Gallery View", "duplicateGalleryView": "Duplicate Gallery View",
@ -579,7 +593,7 @@
"requestDataSource": "Požadujete zdroj dat, který potřebujete?", "requestDataSource": "Požadujete zdroj dat, který potřebujete?",
"apiKey": "API klíč", "apiKey": "API klíč",
"personalAccessToken": "Personal Access Token", "personalAccessToken": "Personal Access Token",
"sharedBaseUrl": "Shared Base URL", "sharedBaseUrl": "Sdílet URL projektu",
"importData": "Importovat data", "importData": "Importovat data",
"importSecondaryViews": "Import sekundárních zobrazení", "importSecondaryViews": "Import sekundárních zobrazení",
"importRollupColumns": "Import sloupců rollupu", "importRollupColumns": "Import sloupců rollupu",
@ -591,6 +605,7 @@
"goToDashboard": "Přejít na přehled", "goToDashboard": "Přejít na přehled",
"importing": "Importování", "importing": "Importování",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Zploštění vnořených", "flattenNested": "Zploštění vnořených",
"downloadAllowed": "Stažení povoleno", "downloadAllowed": "Stažení povoleno",
@ -647,12 +662,12 @@
"outOfSync": "Out of sync", "outOfSync": "Out of sync",
"newSource": "New Data Source", "newSource": "New Data Source",
"newWebhook": "New Webhook", "newWebhook": "New Webhook",
"enablePublicAccess": "Enable Public Access", "enablePublicAccess": "Povolit veřejný přístup",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?", "doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access", "editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing", "enabledPublicViewing": "Enable public viewing",
"restrictAccessWithPassword": "Restrict access with password", "restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access", "manageProjectAccess": "Správa přístupu k projektu",
"allowDownload": "Allow Download", "allowDownload": "Allow Download",
"surveyMode": "Survey Mode", "surveyMode": "Survey Mode",
"rtlOrientation": "RTL Orientation", "rtlOrientation": "RTL Orientation",
@ -700,10 +715,10 @@
"filter": "Filtr", "filter": "Filtr",
"addFilter": "Přidat filtr", "addFilter": "Přidat filtr",
"share": "Sdílet", "share": "Sdílet",
"groupBy": "Group By", "groupBy": "Seskupit dle",
"addSubGroup": "Add subgroup", "addSubGroup": "Add subgroup",
"shareBase": { "shareBase": {
"label": "Share base", "label": "Sdílet projekt",
"disable": "Zakázat sdílenou základnu", "disable": "Zakázat sdílenou základnu",
"enable": "Kdokoli s odkazem", "enable": "Kdokoli s odkazem",
"link": "Sdílený základní odkaz" "link": "Sdílený základní odkaz"
@ -767,7 +782,7 @@
"ListView": "Seznam pohledů", "ListView": "Seznam pohledů",
"copyView": "Kopírovat pohled", "copyView": "Kopírovat pohled",
"renameView": "Přejmenovat pohled", "renameView": "Přejmenovat pohled",
"uploadData": "Upload Data", "uploadData": "Nahrát data",
"deleteView": "Odstranit pohled", "deleteView": "Odstranit pohled",
"createGrid": "Vytvoření zobrazení mřížky", "createGrid": "Vytvoření zobrazení mřížky",
"createGallery": "Vytvořit zobrazení galerie", "createGallery": "Vytvořit zobrazení galerie",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operace Vložit není v aktivní buňce podporována.", "pasteNotSupported": "Operace Vložit není v aktivní buňce podporována.",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Indsend", "submit": "Indsend",
"create": "Opret", "create": "Opret",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplikat", "duplicate": "Duplikat",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Gå til Dashboard", "goToDashboard": "Gå til Dashboard",
"importing": "Import af", "importing": "Import af",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flade, indlejrede", "flattenNested": "Flade, indlejrede",
"downloadAllowed": "Download tilladt", "downloadAllowed": "Download tilladt",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Indsæt er ikke understøttet på den aktive celle", "pasteNotSupported": "Indsæt er ikke understøttet på den aktive celle",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Bestätigen", "submit": "Bestätigen",
"create": "Erstellen", "create": "Erstellen",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplizieren", "duplicate": "Duplizieren",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Zum Dashboard gehen", "goToDashboard": "Zum Dashboard gehen",
"importing": "Wird importiert", "importing": "Wird importiert",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Verflachen Verschachtelt", "flattenNested": "Verflachen Verschachtelt",
"downloadAllowed": "Download erlaubt", "downloadAllowed": "Download erlaubt",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Der Vorgang Einfügen wird auf der aktiven Zelle nicht unterstützt", "pasteNotSupported": "Der Vorgang Einfügen wird auf der aktiven Zelle nicht unterstützt",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Submit", "submit": "Submit",
"create": "Create", "create": "Create",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -1033,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -1,45 +1,45 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "Crear nueva interfaz",
"connect_data_sources": "Connect data sources", "connect_data_sources": "Conectar bases de datos",
"alert": "Alert", "alert": "Alerta",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "No se ha conectado ninguna base de datos. Conecte las bases de datos para crear interfaces. Omita este paso y luego añada bases de datos desde la página de inicio de la base.",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Seleccione las bases de datos que desea vincular a esta interfaz.",
"create_interface": "Create interface", "create_interface": "Crear interfaz",
"project_name": "Base Name", "project_name": "Nombre de la base",
"connect": "Connect", "connect": "Conectar",
"buttonActionTypes": { "buttonActionTypes": {
"open_external_url": "Open external link", "open_external_url": "Abrir enlace externo",
"delete_record": "Delete record", "delete_record": "Borrar fila",
"update_record": "Update record", "update_record": "Actualizar fila",
"open_layout": "Open layout" "open_layout": "Abrir diagrama"
}, },
"widgets": { "widgets": {
"static_text": "Text", "static_text": "Texto",
"chart": "Chart", "chart": "Gráfico",
"table": "Table", "table": "Tabla",
"image": "Image", "image": "Imagen",
"map": "Map", "map": "Mapa",
"button": "Button", "button": "Botón",
"number": "Number", "number": "Número",
"bar_chart": "Bar Chart", "bar_chart": "Gráfico de barras",
"line_chart": "Line Chart", "line_chart": "Gráfico de Líneas",
"area_chart": "Area Chart", "area_chart": "Gráfico de área",
"pie_chart": "Pie Chart", "pie_chart": "Gráfico de tarta",
"donut_chart": "Donut Chart", "donut_chart": "Gráfico de dona",
"scatter_plot": "Scatter Plot", "scatter_plot": "Gráfico de dispersión",
"bubble_chart": "Bubble Chart", "bubble_chart": "Gráfico de burbujas",
"radar_chart": "Radar Chart", "radar_chart": "Gráfico de radar",
"polar_area_chart": "Polar Area Chart", "polar_area_chart": "Gráfico de área polar",
"radial_bar_chart": "Radial Bar Chart", "radial_bar_chart": "Gráfico de barra radial",
"heatmap_chart": "Heatmap Chart", "heatmap_chart": "Mapa de calor",
"treemap_chart": "Treemap Chart", "treemap_chart": "Gráfico de árbol",
"box_plot_chart": "Box Plot Chart", "box_plot_chart": "Gráfico de cajas",
"candlestick_chart": "Candlestick Chart" "candlestick_chart": "Gráfico de velas"
} }
}, },
"general": { "general": {
"quit": "Quit", "quit": "Salir",
"home": "Inicio", "home": "Inicio",
"load": "Cargar", "load": "Cargar",
"open": "Abrir", "open": "Abrir",
@ -47,24 +47,24 @@
"yes": "Sí", "yes": "Sí",
"no": "No", "no": "No",
"ok": "Ok", "ok": "Ok",
"back": "Back", "back": "Volver",
"and": "Y", "and": "Y",
"or": "O", "or": "O",
"add": "Agregar", "add": "Agregar",
"edit": "Editar", "edit": "Editar",
"link": "Link", "link": "Enlace",
"links": "Links", "links": "Enlaces",
"remove": "Eliminar", "remove": "Eliminar",
"import": "Import", "import": "Importar",
"logout": "Log Out", "logout": "Cerrar sesión",
"empty": "Empty", "empty": "Empty",
"changeIcon": "Change Icon", "changeIcon": "Cambiar icono",
"save": "Salvar", "save": "Salvar",
"available": "Available", "available": "Disponible",
"abort": "Abort", "abort": "Cancelar",
"saving": "Saving", "saving": "Guardando",
"cancel": "Cancelar", "cancel": "Cancelar",
"null": "Null", "null": "Nulo",
"escape": "Escape", "escape": "Escape",
"hex": "Hex", "hex": "Hex",
"clear": "Clear", "clear": "Clear",
@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Enviar", "submit": "Enviar",
"create": "Crear", "create": "Crear",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicar", "duplicate": "Duplicar",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Ir al panel de control", "goToDashboard": "Ir al panel de control",
"importing": "Importación de", "importing": "Importación de",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Aplanar anidado", "flattenNested": "Aplanar anidado",
"downloadAllowed": "Descarga permitida", "downloadAllowed": "Descarga permitida",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "No se admite la operación de pegado en la celda activa", "pasteNotSupported": "No se admite la operación de pegado en la celda activa",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Bidali", "submit": "Bidali",
"create": "Sortu", "create": "Sortu",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Bikoiztu", "duplicate": "Bikoiztu",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Importing",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed", "downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "ارسال", "submit": "ارسال",
"create": "ایجاد", "create": "ایجاد",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "کپی کردن", "duplicate": "کپی کردن",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "رفتن به داشبورد", "goToDashboard": "رفتن به داشبورد",
"importing": "در حال ایمپورت", "importing": "در حال ایمپورت",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "مسطح کردن تو در تو", "flattenNested": "مسطح کردن تو در تو",
"downloadAllowed": "اجازه دانلود", "downloadAllowed": "اجازه دانلود",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "عملیات جایگذاری در سلول فعال پشتیبانی نمیشود", "pasteNotSupported": "عملیات جایگذاری در سلول فعال پشتیبانی نمیشود",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Lähetä", "submit": "Lähetä",
"create": "Luoda", "create": "Luoda",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplikaatti", "duplicate": "Duplikaatti",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Siirry kojelautaan", "goToDashboard": "Siirry kojelautaan",
"importing": "Tuo", "importing": "Tuo",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Lataa sallittu", "downloadAllowed": "Lataa sallittu",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Liitä-toimintoa ei tueta aktiivisessa solussa.", "pasteNotSupported": "Liitä-toimintoa ei tueta aktiivisessa solussa.",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Soumettre", "submit": "Soumettre",
"create": "Créer", "create": "Créer",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Dupliquer", "duplicate": "Dupliquer",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Accéder au tableau de bord", "goToDashboard": "Accéder au tableau de bord",
"importing": "Importation de", "importing": "Importation de",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Aplatir imbriqué", "flattenNested": "Aplatir imbriqué",
"downloadAllowed": "Téléchargement autorisé", "downloadAllowed": "Téléchargement autorisé",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "L'opération de collage n'est pas prise en charge sur la cellule active", "pasteNotSupported": "L'opération de collage n'est pas prise en charge sur la cellule active",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "שלח", "submit": "שלח",
"create": "צור", "create": "צור",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "שכפל", "duplicate": "שכפל",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "מייבא", "importing": "מייבא",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed", "downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "परसत करन", "submit": "परसत करन",
"create": "सजन करन", "create": "सजन करन",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Importing",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed", "downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "podnijeti", "submit": "podnijeti",
"create": "Stvoriti", "create": "Stvoriti",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Dupliciraj", "duplicate": "Dupliciraj",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Idi na nadzornu ploču", "goToDashboard": "Idi na nadzornu ploču",
"importing": "Uvozim", "importing": "Uvozim",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Ispravi ugniježđene strukture", "flattenNested": "Ispravi ugniježđene strukture",
"downloadAllowed": "Preuzimanje dozvoljeno", "downloadAllowed": "Preuzimanje dozvoljeno",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operacija \"lijepljenje\" nije podržana u aktivnoj ćeliji", "pasteNotSupported": "Operacija \"lijepljenje\" nije podržana u aktivnoj ćeliji",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Kirim", "submit": "Kirim",
"create": "Membuat", "create": "Membuat",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplikat", "duplicate": "Duplikat",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Buka Dasbor", "goToDashboard": "Buka Dasbor",
"importing": "Mengimpor", "importing": "Mengimpor",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Ratakan Bersarang", "flattenNested": "Ratakan Bersarang",
"downloadAllowed": "Unduh diizinkan", "downloadAllowed": "Unduh diizinkan",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operasi tempel tidak didukung pada sel aktif", "pasteNotSupported": "Operasi tempel tidak didukung pada sel aktif",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Invia", "submit": "Invia",
"create": "Crea", "create": "Crea",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicato", "duplicate": "Duplicato",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Vada alla Dashboard", "goToDashboard": "Vada alla Dashboard",
"importing": "Importazione", "importing": "Importazione",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Appiattisci annidato", "flattenNested": "Appiattisci annidato",
"downloadAllowed": "Download consentito", "downloadAllowed": "Download consentito",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "L'operazione di incollamento non è supportata sulla cella attiva.", "pasteNotSupported": "L'operazione di incollamento non è supportata sulla cella attiva.",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "送信", "submit": "送信",
"create": "作成", "create": "作成",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "複製", "duplicate": "複製",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "ダッシュボードに移動", "goToDashboard": "ダッシュボードに移動",
"importing": "インポート中", "importing": "インポート中",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "入れ子を平坦化", "flattenNested": "入れ子を平坦化",
"downloadAllowed": "ダウンロードを許可", "downloadAllowed": "ダウンロードを許可",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "現在アクティブなセルでは貼り付けはサポートされていません", "pasteNotSupported": "現在アクティブなセルでは貼り付けはサポートされていません",
"roles": { "roles": {

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

File diff suppressed because it is too large Load Diff

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Iesniegt", "submit": "Iesniegt",
"create": "Izveidot", "create": "Izveidot",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Dublikāts", "duplicate": "Dublikāts",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Dodieties uz paneli", "goToDashboard": "Dodieties uz paneli",
"importing": "Importēšana", "importing": "Importēšana",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Izlīdzināt ligzdotās", "flattenNested": "Izlīdzināt ligzdotās",
"downloadAllowed": "Lejupielādēt atļauts", "downloadAllowed": "Lejupielādēt atļauts",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Aktīvajā šūnā netiek atbalstīta operācija \"ielīmēt\".", "pasteNotSupported": "Aktīvajā šūnā netiek atbalstīta operācija \"ielīmēt\".",
"roles": { "roles": {

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

@ -11,11 +11,11 @@
"buttonActionTypes": { "buttonActionTypes": {
"open_external_url": "Open external link", "open_external_url": "Open external link",
"delete_record": "Delete record", "delete_record": "Delete record",
"update_record": "Update record", "update_record": "Update Record",
"open_layout": "Open layout" "open_layout": "Open layout"
}, },
"widgets": { "widgets": {
"static_text": "Text", "static_text": "Tekst",
"chart": "Chart", "chart": "Chart",
"table": "Table", "table": "Table",
"image": "Image", "image": "Image",
@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Indienen", "submit": "Indienen",
"create": "Creëren", "create": "Creëren",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Dubbele", "duplicate": "Dubbele",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Ga naar het dashboard", "goToDashboard": "Ga naar het dashboard",
"importing": "Importeren", "importing": "Importeren",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Afvlakken genest", "flattenNested": "Afvlakken genest",
"downloadAllowed": "Downloaden toegestaan", "downloadAllowed": "Downloaden toegestaan",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Plakken wordt niet ondersteund op de actieve cel", "pasteNotSupported": "Plakken wordt niet ondersteund op de actieve cel",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Sende inn", "submit": "Sende inn",
"create": "Opprett", "create": "Opprett",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Importing",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed", "downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Zapisz", "submit": "Zapisz",
"create": "Utwórz", "create": "Utwórz",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplikuj", "duplicate": "Duplikuj",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Przejdź do panelu", "goToDashboard": "Przejdź do panelu",
"importing": "Importowanie", "importing": "Importowanie",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Spłaszcz zagnieżdżone", "flattenNested": "Spłaszcz zagnieżdżone",
"downloadAllowed": "Pobieranie dozwolone", "downloadAllowed": "Pobieranie dozwolone",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operacja wklejania nie jest obsługiwana w aktywnej komórce", "pasteNotSupported": "Operacja wklejania nie jest obsługiwana w aktywnej komórce",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Enviar", "submit": "Enviar",
"create": "Criar", "create": "Criar",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicado", "duplicate": "Duplicado",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Ir para Painel de controlo", "goToDashboard": "Ir para Painel de controlo",
"importing": "Importação", "importing": "Importação",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Ninhada achatada", "flattenNested": "Ninhada achatada",
"downloadAllowed": "Download permitido", "downloadAllowed": "Download permitido",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "A operação de colar não é suportada na célula activa", "pasteNotSupported": "A operação de colar não é suportada na célula activa",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Enviar", "submit": "Enviar",
"create": "Criar", "create": "Criar",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplicar", "duplicate": "Duplicar",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Ir para o Painel de Controle", "goToDashboard": "Ir para o Painel de Controle",
"importing": "Importando", "importing": "Importando",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Achatamento aninhado", "flattenNested": "Achatamento aninhado",
"downloadAllowed": "Download permitido", "downloadAllowed": "Download permitido",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "A operação de colar não é suportada na célula activa", "pasteNotSupported": "A operação de colar não é suportada na célula activa",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Отправить", "submit": "Отправить",
"create": "Создать", "create": "Создать",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Копировать", "duplicate": "Копировать",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Перейти к Дашборду", "goToDashboard": "Перейти к Дашборду",
"importing": "Импорт", "importing": "Импорт",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Вложенные", "flattenNested": "Вложенные",
"downloadAllowed": "Скачивание разрешено", "downloadAllowed": "Скачивание разрешено",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Операция Вставки не поддерживается в выделенной ячейке", "pasteNotSupported": "Операция Вставки не поддерживается в выделенной ячейке",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Odoslať", "submit": "Odoslať",
"create": "Vytvoriť stránku", "create": "Vytvoriť stránku",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplikát", "duplicate": "Duplikát",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Prejsť na prístrojový panel", "goToDashboard": "Prejsť na prístrojový panel",
"importing": "Importovanie", "importing": "Importovanie",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Sploštenie vnorených", "flattenNested": "Sploštenie vnorených",
"downloadAllowed": "Stiahnuť povolené", "downloadAllowed": "Stiahnuť povolené",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operácia Vložiť nie je podporovaná v aktívnej bunke", "pasteNotSupported": "Operácia Vložiť nie je podporovaná v aktívnej bunke",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Pošlji", "submit": "Pošlji",
"create": "Ustvari", "create": "Ustvari",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Duplikatni", "duplicate": "Duplikatni",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Pojdite na nadzorno ploščo", "goToDashboard": "Pojdite na nadzorno ploščo",
"importing": "Uvoz", "importing": "Uvoz",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Ploščati vgnezdeno", "flattenNested": "Ploščati vgnezdeno",
"downloadAllowed": "Prenos dovoljen", "downloadAllowed": "Prenos dovoljen",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operacija Vstavi ni podprta v aktivni celici", "pasteNotSupported": "Operacija Vstavi ni podprta v aktivni celici",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Skicka in", "submit": "Skicka in",
"create": "Skapa", "create": "Skapa",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Dubbla", "duplicate": "Dubbla",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Gå till instrumentpanelen", "goToDashboard": "Gå till instrumentpanelen",
"importing": "Importera", "importing": "Importera",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Platta och inbäddade", "flattenNested": "Platta och inbäddade",
"downloadAllowed": "Nedladdning tillåten", "downloadAllowed": "Nedladdning tillåten",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Klistra in stöds inte på den aktiva cellen.", "pasteNotSupported": "Klistra in stöds inte på den aktiva cellen.",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "สง", "submit": "สง",
"create": "สราง", "create": "สราง",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "คดลอก", "duplicate": "คดลอก",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Importing",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "อนญาตใหดาวนโหลด", "downloadAllowed": "อนญาตใหดาวนโหลด",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Gönder", "submit": "Gönder",
"create": "Oluştur", "create": "Oluştur",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Yinelenen", "duplicate": "Yinelenen",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Gösterge Tablosuna Git", "goToDashboard": "Gösterge Tablosuna Git",
"importing": "İthalat", "importing": "İthalat",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Yuvalanmış Düzleştir", "flattenNested": "Yuvalanmış Düzleştir",
"downloadAllowed": "İndirme izni verildi", "downloadAllowed": "İndirme izni verildi",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Etkin hücre üzerinde yapıştırma işlemi desteklenmiyor", "pasteNotSupported": "Etkin hücre üzerinde yapıştırma işlemi desteklenmiyor",
"roles": { "roles": {

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

@ -1,15 +1,15 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "Створити новий інтерфейс",
"connect_data_sources": "Connect data sources", "connect_data_sources": "Підключення джерел даних",
"alert": "Alert", "alert": "Оповіщення",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "Немає баз даних підключено. З’єднайте бази даних для створення інтерфейсів. Пропустіть цей крок і додайте бази даних з основної домашньої сторінки пізніше.",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Виберіть базу даних, які Ви хочете прив'язати до цього інтерфейсу.",
"create_interface": "Create interface", "create_interface": "Створення інтерфейсу",
"project_name": "Base Name", "project_name": "Назва бази",
"connect": "Connect", "connect": "Підключитися",
"buttonActionTypes": { "buttonActionTypes": {
"open_external_url": "Open external link", "open_external_url": "Відкрити зовнішнє посилання",
"delete_record": "Delete record", "delete_record": "Delete record",
"update_record": "Update record", "update_record": "Update record",
"open_layout": "Open layout" "open_layout": "Open layout"
@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Надіслати", "submit": "Надіслати",
"create": "Створити", "create": "Створити",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Дублювати", "duplicate": "Дублювати",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Панель керування", "goToDashboard": "Панель керування",
"importing": "Імпорт", "importing": "Імпорт",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Вкладені", "flattenNested": "Вкладені",
"downloadAllowed": "Завантаження дозволене", "downloadAllowed": "Завантаження дозволене",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Операція вставки не підтримується в активній комірці", "pasteNotSupported": "Операція вставки не підтримується в активній комірці",
"roles": { "roles": {

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

@ -1,6 +1,6 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "Tạo giao diện mới",
"connect_data_sources": "Connect data sources", "connect_data_sources": "Connect data sources",
"alert": "Alert", "alert": "Alert",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.",
@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Nộp", "submit": "Nộp",
"create": "Tạo ra", "create": "Tạo ra",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "Tạo bản sao", "duplicate": "Tạo bản sao",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -163,7 +165,7 @@
"type": "Type", "type": "Type",
"name": "Name", "name": "Name",
"changes": "Changes", "changes": "Changes",
"new": "New", "new": "Mới",
"old": "Old", "old": "Old",
"data": "Data", "data": "Data",
"source": "Source", "source": "Source",
@ -339,7 +341,7 @@
"hideSidebar": "Hide Sidebar", "hideSidebar": "Hide Sidebar",
"creatingTable": "Creating Table", "creatingTable": "Creating Table",
"erdView": "ERD View", "erdView": "ERD View",
"newBase": "New Data Source", "newBase": "Nguồn dữ liệu mới",
"newProj": "Dự án mới", "newProj": "Dự án mới",
"createBase": "Create Base", "createBase": "Create Base",
"myProject": "Những dự án của tôi", "myProject": "Những dự án của tôi",
@ -390,7 +392,7 @@
"generateRandomName": "Generate Random Name", "generateRandomName": "Generate Random Name",
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Thêm mới token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard", "goToDashboard": "Go to Dashboard",
"importing": "Importing", "importing": "Importing",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested", "flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed", "downloadAllowed": "Download allowed",
@ -602,7 +617,7 @@
"extraConnectionParameters": "Extra connection parameters", "extraConnectionParameters": "Extra connection parameters",
"commentsOnly": "Comments only", "commentsOnly": "Comments only",
"documentation": "Documentation", "documentation": "Documentation",
"subscribeNewsletter": "Subscribe to our weekly newsletter", "subscribeNewsletter": "Đăng ký nhận tin hàng tuần của chúng tôi",
"signUpWithProvider": "Sign up with {provider}", "signUpWithProvider": "Sign up with {provider}",
"signInWithProvider": "Sign in with {provider}", "signInWithProvider": "Sign in with {provider}",
"agreeToTos": "By signing up, you agree to the Terms of Service", "agreeToTos": "By signing up, you agree to the Terms of Service",
@ -799,7 +814,7 @@
"clearCell": "Clear cell", "clearCell": "Clear cell",
"addFilterGroup": "Add Filter Group", "addFilterGroup": "Add Filter Group",
"linkRecord": "Link record", "linkRecord": "Link record",
"addNewRecord": "Add new record", "addNewRecord": "Thêm bản ghi mới",
"newRecord": "New record", "newRecord": "New record",
"tableNameCreateNewRecord": "{tableName}: Create new record", "tableNameCreateNewRecord": "{tableName}: Create new record",
"gotSavedLinkedSuccessfully": "{tableName} '{recordTitle}' got saved & linked successfully", "gotSavedLinkedSuccessfully": "{tableName} '{recordTitle}' got saved & linked successfully",
@ -920,7 +935,7 @@
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
"enterPassword": "Enter password", "enterPassword": "Enter password",
"bySigningUp": "By signing up, you agree to the", "bySigningUp": "By signing up, you agree to the",
"subscribeToOurWeeklyNewsletter": "Subscribe to our weekly newsletter", "subscribeToOurWeeklyNewsletter": "Đăng ký nhận tin hàng tuần của chúng tôi",
"verifyingPassword": "Verifying Password", "verifyingPassword": "Verifying Password",
"thisSharedViewIsProtected": "This shared view is protected", "thisSharedViewIsProtected": "This shared view is protected",
"successfullySubmittedFormData": "Successfully submitted form data", "successfullySubmittedFormData": "Successfully submitted form data",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell", "pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": { "roles": {

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

@ -1,27 +1,27 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "创建新界面",
"connect_data_sources": "Connect data sources", "connect_data_sources": "连接数据源",
"alert": "Alert", "alert": "警告",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "尚未连接数据库,连接数据库以构建界面。跳过此步骤,稍后从数据库主页添加数据库。",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "选择要链接到此界面的数据库。",
"create_interface": "Create interface", "create_interface": "创建界面",
"project_name": "Base Name", "project_name": "项目名",
"connect": "Connect", "connect": "连接",
"buttonActionTypes": { "buttonActionTypes": {
"open_external_url": "Open external link", "open_external_url": "打开外部链接",
"delete_record": "Delete record", "delete_record": "删除行",
"update_record": "Update record", "update_record": "更新记录",
"open_layout": "Open layout" "open_layout": "Open layout"
}, },
"widgets": { "widgets": {
"static_text": "Text", "static_text": "文本",
"chart": "Chart", "chart": "Chart",
"table": "Table", "table": "表格",
"image": "Image", "image": "图片",
"map": "Map", "map": "地图",
"button": "Button", "button": "按钮",
"number": "Number", "number": "数字",
"bar_chart": "Bar Chart", "bar_chart": "Bar Chart",
"line_chart": "Line Chart", "line_chart": "Line Chart",
"area_chart": "Area Chart", "area_chart": "Area Chart",
@ -39,7 +39,7 @@
} }
}, },
"general": { "general": {
"quit": "Quit", "quit": "退出",
"home": "首页", "home": "首页",
"load": "加载", "load": "加载",
"open": "打开", "open": "打开",
@ -47,16 +47,16 @@
"yes": "是", "yes": "是",
"no": "否", "no": "否",
"ok": "行", "ok": "行",
"back": "Back", "back": "返回",
"and": "与", "and": "与",
"or": "或", "or": "或",
"add": "添加", "add": "添加",
"edit": "编辑", "edit": "编辑",
"link": "Link", "link": "链接",
"links": "Links", "links": "链接",
"remove": "移除", "remove": "移除",
"import": "Import", "import": "导入",
"logout": "Log Out", "logout": "退出登录",
"empty": "Empty", "empty": "Empty",
"changeIcon": "Change Icon", "changeIcon": "Change Icon",
"save": "保存", "save": "保存",
@ -69,19 +69,21 @@
"hex": "Hex", "hex": "Hex",
"clear": "Clear", "clear": "Clear",
"slack": "Slack", "slack": "Slack",
"comment": "Comment", "comment": "评论",
"microsoftTeams": "Microsoft Teams", "microsoftTeams": "Microsoft Teams",
"discord": "Discord", "discord": "Discord",
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "提交", "submit": "提交",
"create": "创建", "create": "创建",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
"creating": "Creating", "creating": "Creating",
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "跳过",
"code": "代码",
"duplicate": "复制", "duplicate": "复制",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -105,7 +107,7 @@
"deprecated": "Deprecated", "deprecated": "Deprecated",
"showAll": "显示全部", "showAll": "显示全部",
"hideAll": "隐藏全部", "hideAll": "隐藏全部",
"notFound": "Not found", "notFound": "未找到",
"showMore": "显示更多", "showMore": "显示更多",
"showOptions": "显示选项", "showOptions": "显示选项",
"hideOptions": "隐藏选项", "hideOptions": "隐藏选项",
@ -125,8 +127,8 @@
"upload": "上传", "upload": "上传",
"download": "下载", "download": "下载",
"default": "默认", "default": "默认",
"base": "Source", "base": "",
"datasource": "Data Source", "datasource": "数据源",
"more": "更多的", "more": "更多的",
"less": "较少的", "less": "较少的",
"event": "事件", "event": "事件",
@ -158,39 +160,39 @@
"hideField": "隐藏字段", "hideField": "隐藏字段",
"sortAsc": "升序", "sortAsc": "升序",
"sortDesc": "降序", "sortDesc": "降序",
"move": "Move", "move": "移动",
"geoDataField": "地理数据字段", "geoDataField": "地理数据字段",
"type": "Type", "type": "类型",
"name": "Name", "name": "名称",
"changes": "Changes", "changes": "更新",
"new": "New", "new": "新的",
"old": "Old", "old": "旧的",
"data": "Data", "data": "数据",
"source": "Source", "source": "",
"destination": "Destination", "destination": "Destination",
"active": "Active", "active": "Active",
"inactive": "Inactive", "inactive": "Inactive",
"linked": "linked", "linked": "linked",
"finish": "Finish", "finish": "Finish",
"min": "Min", "min": "最小",
"max": "Max", "max": "最大",
"avg": "Avg", "avg": "平均值",
"sum": "Sum", "sum": "求和",
"count": "Count", "count": "总计",
"countDistinct": "Count Distinct", "countDistinct": "Count Distinct",
"sumDistinct": "Sum Distinct", "sumDistinct": "Sum Distinct",
"avgDistinct": "Avg Distinct", "avgDistinct": "Avg Distinct",
"join": "Join", "join": "Join",
"options": "Options", "options": "选项",
"primaryValue": "Primary Value", "primaryValue": "Primary Value",
"useSurveyMode": "Use Survey Mode", "useSurveyMode": "Use Survey Mode",
"shift": "Shift", "shift": "Shift",
"enter": "Enter", "enter": "回车键",
"seconds": "Seconds" "seconds": ""
}, },
"objects": { "objects": {
"workspace": "Workspace", "workspace": "工作区",
"workspaces": "Workspaces", "workspaces": "工作区",
"project": "项目", "project": "项目",
"projects": "项目", "projects": "项目",
"table": "表格", "table": "表格",
@ -207,7 +209,7 @@
"webhooks": "Webhooks.", "webhooks": "Webhooks.",
"view": "视图", "view": "视图",
"views": "视图", "views": "视图",
"sidebar": "Sidebar", "sidebar": "侧边栏",
"viewType": { "viewType": {
"grid": "表格", "grid": "表格",
"gallery": "画廊", "gallery": "画廊",
@ -220,7 +222,7 @@
"users": "用户", "users": "用户",
"role": "角色", "role": "角色",
"roles": "角色", "roles": "角色",
"developer": "Developer", "developer": "开发人员",
"roleType": { "roleType": {
"owner": "所有者", "owner": "所有者",
"creator": "创造者", "creator": "创造者",
@ -238,7 +240,7 @@
"short": "Short", "short": "Short",
"medium": "Medium", "medium": "Medium",
"tall": "Tall", "tall": "Tall",
"extra": "Extra" "extra": "备注"
}, },
"externalDb": "External Database" "externalDb": "External Database"
}, },
@ -295,8 +297,8 @@
"isNotNull": "有值(Not Null)" "isNotNull": "有值(Not Null)"
}, },
"title": { "title": {
"docs": "Docs", "docs": "文档",
"forum": "Forum", "forum": "论坛",
"parameter": "Parameter", "parameter": "Parameter",
"headers": "Headers", "headers": "Headers",
"parameterName": "Parameter Name", "parameterName": "Parameter Name",
@ -325,14 +327,14 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"downloadFile": "Download File", "downloadFile": "下载文件",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
"renamingWs": "Renaming Workspace", "renamingWs": "Renaming Workspace",
"renameWs": "Rename Workspace", "renameWs": "Rename Workspace",
"deleteWs": "Delete Workspace", "deleteWs": "Delete Workspace",
"deletingWs": "Deleting Workspace", "deletingWs": "Deleting Workspace",
"copyAuthToken": "Copy Auth Token", "copyAuthToken": "复制 Auth 令牌",
"copiedAuthToken": "Copied Auth Token", "copiedAuthToken": "Copied Auth Token",
"copyInviteToken": "Copy Invite Token", "copyInviteToken": "Copy Invite Token",
"showSidebar": "Show Sidebar", "showSidebar": "Show Sidebar",
@ -351,7 +353,7 @@
"teamAndAuth": "团队和认证", "teamAndAuth": "团队和认证",
"rolesUserMgmt": "角色和用户管理", "rolesUserMgmt": "角色和用户管理",
"userMgmt": "用户管理", "userMgmt": "用户管理",
"apiTokens": "API Tokens", "apiTokens": "API 令牌",
"apiTokenMgmt": "API Tokens 管理", "apiTokenMgmt": "API Tokens 管理",
"rolesMgmt": "角色管理", "rolesMgmt": "角色管理",
"projMeta": "项目基础信息", "projMeta": "项目基础信息",
@ -378,11 +380,11 @@
"swaggerDocumentation": "Swagger 文档", "swaggerDocumentation": "Swagger 文档",
"quickImportFrom": "快速导入自", "quickImportFrom": "快速导入自",
"quickImport": "快速导入", "quickImport": "快速导入",
"quickImportAirtable": "Quick Import - Airtable", "quickImportAirtable": "快速导入 - Airtable",
"quickImportCSV": "Quick Import - CSV", "quickImportCSV": "快速导入 - CSV",
"quickImportExcel": "Quick Import - Excel", "quickImportExcel": "快速导入 - Excel",
"quickImportJSON": "Quick Import - JSON", "quickImportJSON": "快速导入 - JSON",
"jsonEditor": "JSON Editor", "jsonEditor": "JSON编辑器",
"comingSoon": "Coming Soon", "comingSoon": "Coming Soon",
"advancedSettings": "高级设置", "advancedSettings": "高级设置",
"codeSnippet": "代码片段", "codeSnippet": "代码片段",
@ -390,17 +392,17 @@
"generateRandomName": "随机名称", "generateRandomName": "随机名称",
"findRowByScanningCode": "通过扫描二维码或条码查找行", "findRowByScanningCode": "通过扫描二维码或条码查找行",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "添加新令牌(Token)",
"accountSettings": "Account Settings", "accountSettings": "账户设置",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "密码重置",
"tokens": "Tokens", "tokens": "令牌",
"userManagement": "User Management", "userManagement": "User Management",
"accountManagement": "Account management", "accountManagement": "Account management",
"licence": "Licence", "licence": "许可证",
"allowAllMimeTypes": "Allow All Mime Types", "allowAllMimeTypes": "Allow All Mime Types",
"defaultView": "Default View", "defaultView": "默认视图",
"relations": "Relations", "relations": "Relations",
"switchLanguage": "Switch Language", "switchLanguage": "切换语言",
"renameFile": "Rename File", "renameFile": "Rename File",
"links": { "links": {
"noAction": "No Action", "noAction": "No Action",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "一级标题",
"heading2": "二级标题",
"heading3": "三级标题",
"bold": "粗体",
"italic": "斜体",
"underline": "下划线",
"strike": "删除线",
"taskList": "任务列表",
"bulletList": "无序列表",
"numberedList": "有序列表",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -432,7 +446,7 @@
"examples": "Examples", "examples": "Examples",
"durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).", "durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).",
"addHeader": "Add Header", "addHeader": "Add Header",
"enterDefaultUrlOptional": "Enter default URL (Optional)", "enterDefaultUrlOptional": "输入默认 URL(可选)",
"negative": "Negative", "negative": "Negative",
"discard": "Discard", "discard": "Discard",
"default": "Default", "default": "Default",
@ -442,21 +456,21 @@
"timeFormat": "Time Format", "timeFormat": "Time Format",
"singularLabel": "Singular Label", "singularLabel": "Singular Label",
"pluralLabel": "Plural Label", "pluralLabel": "Plural Label",
"optional": "(Optional)", "optional": "(可选)",
"clickToMake": "Click to make", "clickToMake": "点击创建",
"visibleForRole": "visible for role:", "visibleForRole": "visible for role:",
"inUI": "in UI Dashboard", "inUI": "in UI Dashboard",
"projectSettings": "Base Settings", "projectSettings": "基础设置",
"clickToHide": "Click to hide", "clickToHide": "点击隐藏",
"clickToDownload": "Click to download", "clickToDownload": "点击下载",
"forRole": "for role", "forRole": "for role",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "点击复制视图ID",
"viewMode": "View Mode", "viewMode": "视图模式",
"searchUsers": "Search Users", "searchUsers": "搜索用户",
"superAdmin": "Super Admin", "superAdmin": "超级管理员",
"allTables": "All Tables", "allTables": "所有表格",
"members": "Members", "members": "Members",
"dataSources": "Data Sources", "dataSources": "数据源",
"connectDataSource": "Connect a Data Source", "connectDataSource": "Connect a Data Source",
"searchProjects": "Search Bases", "searchProjects": "Search Bases",
"createdBy": "创建自", "createdBy": "创建自",
@ -514,12 +528,12 @@
"where": "在哪里", "where": "在哪里",
"cache": "缓存", "cache": "缓存",
"chat": "聊天", "chat": "聊天",
"showOrHide": "Show or Hide", "showOrHide": "显示或隐藏",
"airtable": "Airtable", "airtable": "Airtable",
"csv": "CSV", "csv": "CSV",
"csvFile": "CSV File", "csvFile": "CSV文件",
"json": "JSON", "json": "JSON",
"jsonFile": "JSON File", "jsonFile": "JSON 文件",
"excel": "Excel", "excel": "Excel",
"microsoftExcel": "Microsoft Excel", "microsoftExcel": "Microsoft Excel",
"email": "电子邮件", "email": "电子邮件",
@ -530,7 +544,7 @@
"created": "创建了", "created": "创建了",
"sqlOutput": "输出 SQL", "sqlOutput": "输出 SQL",
"addOption": "添加选项", "addOption": "添加选项",
"interfaceColor": "Interface Color", "interfaceColor": "界面颜色",
"qrCodeValueColumn": "二维码要显示的数据", "qrCodeValueColumn": "二维码要显示的数据",
"barcodeValueColumn": "条形码要显示的数据", "barcodeValueColumn": "条形码要显示的数据",
"barcodeFormat": "条形码码制", "barcodeFormat": "条形码码制",
@ -561,14 +575,14 @@
"followNocodb": "关注 NocoDB", "followNocodb": "关注 NocoDB",
"communityTranslated": "(Community Translated)" "communityTranslated": "(Community Translated)"
}, },
"twitter": "Twitter", "twitter": "推特(Twitter",
"docReference": "参考文档", "docReference": "参考文档",
"selectUserRole": "选择用户角色", "selectUserRole": "选择用户角色",
"childTable": "子表", "childTable": "子表",
"childColumn": "子列", "childColumn": "子列",
"childField": "Child field", "childField": "Child field",
"linkToAnotherRecord": "关联到其他表", "linkToAnotherRecord": "关联到其他表",
"links": "Links", "links": "链接",
"onUpdate": "更新时", "onUpdate": "更新时",
"onDelete": "删除时", "onDelete": "删除时",
"account": "帐户", "account": "帐户",
@ -591,6 +605,7 @@
"goToDashboard": "转到仪表板", "goToDashboard": "转到仪表板",
"importing": "导入中", "importing": "导入中",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "扁平化嵌套", "flattenNested": "扁平化嵌套",
"downloadAllowed": "允许下载", "downloadAllowed": "允许下载",
@ -605,7 +620,7 @@
"subscribeNewsletter": "订阅我们的每周新闻", "subscribeNewsletter": "订阅我们的每周新闻",
"signUpWithProvider": "使用 {provider} 注册", "signUpWithProvider": "使用 {provider} 注册",
"signInWithProvider": "使用 {provider} 登录", "signInWithProvider": "使用 {provider} 登录",
"agreeToTos": "注册即表您同意服务条款", "agreeToTos": "注册即表您同意服务条款",
"welcomeToNc": "欢迎来到NocoDB!", "welcomeToNc": "欢迎来到NocoDB!",
"inviteOnlySignup": "只能通过邀请链接注册账户", "inviteOnlySignup": "只能通过邀请链接注册账户",
"nextRow": "下一行", "nextRow": "下一行",
@ -634,35 +649,35 @@
"attachFile": "Attach File", "attachFile": "Attach File",
"viewAttachment": "View Attachments", "viewAttachment": "View Attachments",
"attachmentDrop": "Click or drop a file into cell", "attachmentDrop": "Click or drop a file into cell",
"addFiles": "Add File(s)", "addFiles": "添加文件(一个或多个)",
"hideInUI": "Hide in UI", "hideInUI": "Hide in UI",
"addBase": "Add Base", "addBase": "Add Base",
"addParameter": "Add Parameter", "addParameter": "Add Parameter",
"submitAnotherForm": "Submit Another Form", "submitAnotherForm": "Submit Another Form",
"dragAndDropFieldsHereToAdd": "Drag and drop fields here to add", "dragAndDropFieldsHereToAdd": "Drag and drop fields here to add",
"editSource": "Edit Data Source", "editSource": "Edit Data Source",
"enterText": "Enter text", "enterText": "输入文本",
"okEditBase": "Ok & Edit Base", "okEditBase": "Ok & Edit Base",
"showInUI": "Show in UI", "showInUI": "Show in UI",
"outOfSync": "Out of sync", "outOfSync": "Out of sync",
"newSource": "New Data Source", "newSource": "新建数据源",
"newWebhook": "New Webhook", "newWebhook": "新建 Webhook",
"enablePublicAccess": "Enable Public Access", "enablePublicAccess": "启用公共访问",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?", "doYouWantToSaveTheChanges": "您要保存这些更改吗?",
"editingAccess": "Editing access", "editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing", "enabledPublicViewing": "启用公开查看",
"restrictAccessWithPassword": "Restrict access with password", "restrictAccessWithPassword": "使用密码限制访问",
"manageProjectAccess": "Manage Base Access", "manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download", "allowDownload": "允许下载",
"surveyMode": "Survey Mode", "surveyMode": "Survey Mode",
"rtlOrientation": "RTL Orientation", "rtlOrientation": "RTL Orientation",
"useTheme": "Use Theme", "useTheme": "使用主题",
"copyLink": "Copy Link", "copyLink": "复制链接",
"copiedLink": "Link Copied", "copiedLink": "Link Copied",
"copyInviteLink": "Copy invite link", "copyInviteLink": "Copy invite link",
"copiedInviteLink": "Copied invite link", "copiedInviteLink": "Copied invite link",
"copyUrl": "复制链接", "copyUrl": "复制链接",
"moreColors": "More Colors", "moreColors": "更多颜色",
"moveProject": "Move Base", "moveProject": "Move Base",
"createProject": "创建项目", "createProject": "创建项目",
"importProject": "导入项目", "importProject": "导入项目",
@ -674,7 +689,7 @@
"deleteProject": "删除项目", "deleteProject": "删除项目",
"refreshProject": "刷新项目", "refreshProject": "刷新项目",
"saveProject": "保存项目", "saveProject": "保存项目",
"saveAndQuit": "Save & Quit", "saveAndQuit": "保存并退出",
"deleteKanbanStack": "是否删除此类别?", "deleteKanbanStack": "是否删除此类别?",
"createProjectExtended": { "createProjectExtended": {
"extDB": "新建 <br>从外部数据库", "extDB": "新建 <br>从外部数据库",
@ -808,15 +823,15 @@
"toggleCommentsDraw": "切换评论视图", "toggleCommentsDraw": "切换评论视图",
"expandRecord": "展开记录", "expandRecord": "展开记录",
"deleteRecord": "删除记录", "deleteRecord": "删除记录",
"fullWidth": "Full width", "fullWidth": "使用宽屏",
"exitFullWidth": "Exit full width", "exitFullWidth": "退出宽屏",
"markAllAsRead": "Mark all as read", "markAllAsRead": "Mark all as read",
"column": { "column": {
"delete": "Delete Field", "delete": "删除字段",
"addNumber": "Add Number Field", "addNumber": "添加数字字段",
"addSingleLineText": "Add SingleLineText Field", "addSingleLineText": "添加单行文本字段",
"addLongText": "Add LongText Field", "addLongText": "添加长文本字段",
"addOther": "Add Other Field" "addOther": "添加其它字段"
}, },
"erd": { "erd": {
"showColumns": "显示列", "showColumns": "显示列",
@ -839,7 +854,7 @@
"openInOpenStreetMap": "OSM" "openInOpenStreetMap": "OSM"
}, },
"toggleMobileMode": "切换移动模式", "toggleMobileMode": "切换移动模式",
"startCommenting": "Start commenting!" "startCommenting": "开始评论"
}, },
"tooltip": { "tooltip": {
"reachedSourceLimit": "Limited to only one data source for the moment", "reachedSourceLimit": "Limited to only one data source for the moment",
@ -876,7 +891,7 @@
"selectTeamsChannels": "Select Microsoft Teams channels", "selectTeamsChannels": "Select Microsoft Teams channels",
"selectDiscordChannels": "Select Discord channels", "selectDiscordChannels": "Select Discord channels",
"selectMattermostChannels": "Select Mattermost channels", "selectMattermostChannels": "Select Mattermost channels",
"webhookTitle": "Webhook Title", "webhookTitle": "Webhook 标题",
"barcodeColumn": "Select a field for the Barcode value", "barcodeColumn": "Select a field for the Barcode value",
"notFoundContent": "No valid field Type can be found.", "notFoundContent": "No valid field Type can be found.",
"selectBarcodeFormat": "Select a Barcode format", "selectBarcodeFormat": "Select a Barcode format",
@ -913,14 +928,14 @@
"decimal6": "1.000000", "decimal6": "1.000000",
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "",
"key": "Key" "key": ""
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "点击复制字段ID",
"enterPassword": "Enter password", "enterPassword": "输入密码",
"bySigningUp": "By signing up, you agree to the", "bySigningUp": "注册即表示您同意服务条款",
"subscribeToOurWeeklyNewsletter": "Subscribe to our weekly newsletter", "subscribeToOurWeeklyNewsletter": "订阅我们的每周的新闻",
"verifyingPassword": "Verifying Password", "verifyingPassword": "Verifying Password",
"thisSharedViewIsProtected": "This shared view is protected", "thisSharedViewIsProtected": "This shared view is protected",
"successfullySubmittedFormData": "Successfully submitted form data", "successfullySubmittedFormData": "Successfully submitted form data",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "处于活动状态的单元格不能粘贴内容", "pasteNotSupported": "处于活动状态的单元格不能粘贴内容",
"roles": { "roles": {
@ -1187,10 +1203,10 @@
"userConfirmation": "I understand that this action is irreversible", "userConfirmation": "I understand that this action is irreversible",
"pageNotFound": "Page Not Found", "pageNotFound": "Page Not Found",
"makeLineBreak": "to make a line break", "makeLineBreak": "to make a line break",
"goToPrevious": "Go to previous", "goToPrevious": "转到上一个",
"goToNext": "Go to next", "goToNext": "转到下一个",
"thankYou": "Thank you!", "thankYou": "谢谢你!",
"submittedFormData": "You have successfully submitted the form data." "submittedFormData": "您已成功提交表单数据。"
}, },
"error": { "error": {
"nameRequired": "Name Required", "nameRequired": "Name Required",

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "提交", "submit": "提交",
"create": "建立", "create": "建立",
"createEntity": "Create {entity}", "createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code",
"duplicate": "複製", "duplicate": "複製",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -411,7 +413,18 @@
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"taskList": "Task List",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote",
"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",
@ -423,6 +436,7 @@
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Icon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "前往儀表板", "goToDashboard": "前往儀表板",
"importing": "匯入中", "importing": "匯入中",
"formatJson": "Format JSON", "formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers", "firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "展平嵌套", "flattenNested": "展平嵌套",
"downloadAllowed": "允許下載", "downloadAllowed": "允許下載",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
}, },
"info": { "info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.", "basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "不支援在啟用的儲存格中執行貼上", "pasteNotSupported": "不支援在啟用的儲存格中執行貼上",
"roles": { "roles": {

1
packages/nc-gui/lib/acl.ts

@ -84,6 +84,7 @@ const rolePermissions = {
filterChildrenRead: true, filterChildrenRead: true,
viewFieldEdit: true, viewFieldEdit: true,
csvTableImport: true, csvTableImport: true,
excelTableImport: true,
}, },
}, },
[ProjectRoles.COMMENTER]: { [ProjectRoles.COMMENTER]: {

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

@ -1,4 +1,4 @@
import type { BaseType, ColumnType, FilterType, MetaType, PaginatedType, Roles, RolesObj, ViewTypes } from 'nocodb-sdk' import type { BaseType, ColumnType, FilterType, MetaType, PaginatedType, Roles, RolesObj, TableType, ViewTypes } from 'nocodb-sdk'
import type { I18n } from 'vue-i18n' import type { I18n } from 'vue-i18n'
import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider' import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider'
import type { UploadFile } from 'ant-design-vue' import type { UploadFile } from 'ant-design-vue'
@ -171,6 +171,11 @@ type ViewPageType = 'view' | 'webhook' | 'api' | 'field' | 'relation'
type NcButtonSize = 'xxsmall' | 'xsmall' | 'small' | 'medium' type NcButtonSize = 'xxsmall' | 'xsmall' | 'small' | 'medium'
interface SidebarTableNode extends TableType {
isMetaLoading?: boolean
isViewsLoading?: boolean
}
export { export {
User, User,
ProjectMetaInfo, ProjectMetaInfo,
@ -195,4 +200,5 @@ export {
Users, Users,
ViewPageType, ViewPageType,
NcButtonSize, NcButtonSize,
SidebarTableNode,
} }

2
packages/nc-gui/nuxt-shim.d.ts vendored

@ -3,7 +3,7 @@ import type { UseGlobalReturn } from './composables/useGlobal/types'
import type { NocoI18n } from './lib' import type { NocoI18n } from './lib'
import type { TabType } from './composables' import type { TabType } from './composables'
declare module '#app/nuxt' { declare module '#app' {
interface NuxtApp { interface NuxtApp {
$api: BaseAPI<any> $api: BaseAPI<any>
/** {@link import('./plugins/tele') Telemetry} */ /** {@link import('./plugins/tele') Telemetry} */

68
packages/nc-gui/package.json

@ -65,7 +65,7 @@
"locale-codes": "^1.3.1", "locale-codes": "^1.3.1",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"monaco-sql-languages": "^0.11.0", "monaco-sql-languages": "^0.11.0",
"nocodb-sdk": "0.202.8", "nocodb-sdk": "workspace:^",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"parse-github-url": "^1.0.2", "parse-github-url": "^1.0.2",
"pinia": "^2.1.7", "pinia": "^2.1.7",
@ -90,50 +90,60 @@
"vue3-grid-layout-next": "^1.0.6", "vue3-grid-layout-next": "^1.0.6",
"vue3-text-clamp": "^0.1.2", "vue3-text-clamp": "^0.1.2",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"xlsx": "^0.18.5" "xlsx": "^0.18.5",
"@tiptap/extension-link": "2.0.4",
"@tiptap/extension-task-list": "2.0.4",
"@tiptap/extension-underline": "^2.1.12",
"@tiptap/html": "2.0.4",
"@tiptap/pm": "^2.1.12",
"@tiptap/starter-kit": "^2.1.12",
"marked": "^4.3.0",
"turndown": "^7.1.2",
"@tiptap/vue-3": "2.0.4"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.26.3", "@antfu/eslint-config": "^0.26.3",
"@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@iconify-json/ant-design": "^1.1.10", "@iconify-json/ant-design": "^1.1.12",
"@iconify-json/bi": "^1.1.20", "@iconify-json/bi": "^1.1.21",
"@iconify-json/carbon": "^1.1.21", "@iconify-json/carbon": "^1.1.23",
"@iconify-json/cil": "^1.1.5", "@iconify-json/cil": "^1.1.7",
"@iconify-json/clarity": "^1.1.9", "@iconify-json/clarity": "^1.1.11",
"@iconify-json/eva": "^1.1.7", "@iconify-json/eva": "^1.1.9",
"@iconify-json/ic": "^1.1.14", "@iconify-json/ic": "^1.1.16",
"@iconify-json/ion": "^1.1.12", "@iconify-json/ion": "^1.1.14",
"@iconify-json/la": "^1.1.5", "@iconify-json/la": "^1.1.7",
"@iconify-json/logos": "^1.1.38", "@iconify-json/logos": "^1.1.40",
"@iconify-json/lucide": "^1.1.141", "@iconify-json/lucide": "^1.1.143",
"@iconify-json/material-symbols": "^1.1.63", "@iconify-json/material-symbols": "^1.1.65",
"@iconify-json/mdi": "^1.1.55", "@iconify-json/mdi": "^1.1.57",
"@iconify-json/mi": "^1.1.5", "@iconify-json/mi": "^1.1.7",
"@iconify-json/ph": "^1.1.6", "@iconify-json/ph": "^1.1.8",
"@iconify-json/ri": "^1.1.12", "@iconify-json/ri": "^1.1.14",
"@iconify-json/simple-icons": "^1.1.79", "@iconify-json/simple-icons": "^1.1.81",
"@iconify-json/system-uicons": "^1.1.9", "@iconify-json/system-uicons": "^1.1.11",
"@iconify-json/tabler": "^1.1.97", "@iconify-json/tabler": "^1.1.100",
"@iconify-json/vscode-icons": "^1.1.29", "@iconify-json/vscode-icons": "^1.1.31",
"@intlify/unplugin-vue-i18n": "^0.12.3", "@intlify/unplugin-vue-i18n": "^0.12.3",
"@nuxt/image-edge": "1.0.0-28336957.57c0f74", "@nuxt/image-edge": "1.1.0-28346300.6030589",
"@types/d3-scale": "^4.0.8", "@types/d3-scale": "^4.0.8",
"@types/dagre": "^0.7.52", "@types/dagre": "^0.7.52",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@types/leaflet.markercluster": "^1.5.4", "@types/leaflet.markercluster": "^1.5.4",
"@types/papaparse": "^5.3.11", "@types/papaparse": "^5.3.14",
"@types/parse-github-url": "^1.0.3", "@types/parse-github-url": "^1.0.3",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/showdown": "^2.0.4", "@types/showdown": "^2.0.6",
"@types/sortablejs": "^1.13.0", "@types/sortablejs": "^1.13.0",
"@types/splitpanes": "^2.2.5", "@types/splitpanes": "^2.2.6",
"@types/tinycolor2": "^1.4.6", "@types/tinycolor2": "^1.4.6",
"@types/validator": "^13.11.6", "@types/validator": "^13.11.7",
"@types/vue-barcode-reader": "^0.0.3", "@types/vue-barcode-reader": "^0.0.3",
"@types/turndown": "^5.0.4",
"@unocss/nuxt": "^0.51.13", "@unocss/nuxt": "^0.51.13",
"@vitest/ui": "^0.18.1", "@vitest/ui": "^0.18.1",
"@vue/compiler-sfc": "^3.3.8", "@vue/compiler-sfc": "^3.3.9",
"@vue/test-utils": "^2.0.2", "@vue/test-utils": "^2.0.2",
"@vueuse/nuxt": "^10.2.1", "@vueuse/nuxt": "^10.2.1",
"@windicss/plugin-animations": "^1.0.9", "@windicss/plugin-animations": "^1.0.9",
@ -143,7 +153,7 @@
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"happy-dom": "^6.0.4", "happy-dom": "^6.0.4",
"nuxt": "^3.8.1", "nuxt": "^3.8.2",
"nuxt-windicss": "^2.6.1", "nuxt-windicss": "^2.6.1",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"sass": "^1.69.5", "sass": "^1.69.5",

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

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

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

@ -1,6 +1,7 @@
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 { useTitle } from '@vueuse/core'
import type { SidebarTableNode } from '~/lib'
export const useTablesStore = defineStore('tablesStore', () => { export const useTablesStore = defineStore('tablesStore', () => {
const { includeM2M, ncNavigateTo } = useGlobal() const { includeM2M, ncNavigateTo } = useGlobal()
@ -12,7 +13,7 @@ export const useTablesStore = defineStore('tablesStore', () => {
const router = useRouter() const router = useRouter()
const route = router.currentRoute const route = router.currentRoute
const baseTables = ref<Map<string, TableType[]>>(new Map()) const baseTables = ref<Map<string, SidebarTableNode[]>>(new Map())
const basesStore = useBases() const basesStore = useBases()
// const baseStore = useBase() // const baseStore = useBase()

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

@ -110,6 +110,8 @@ export const useViewsStore = defineStore('viewsStore', () => {
}, },
}) })
const isActiveViewLocked = computed(() => activeView.value?.lock_type === 'locked')
// Used for Grid View Pagination // Used for Grid View Pagination
const isPaginationLoading = ref(true) const isPaginationLoading = ref(true)
@ -184,9 +186,13 @@ export const useViewsStore = defineStore('viewsStore', () => {
isViewDataLoading.value = true isViewDataLoading.value = true
try { try {
if (tablesStore.activeTable) tablesStore.activeTable.isViewsLoading = true
await loadViews() await loadViews()
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} finally {
if (tablesStore.activeTable) tablesStore.activeTable.isViewsLoading = false
} }
}, },
{ immediate: true }, { immediate: true },
@ -271,10 +277,6 @@ export const useViewsStore = defineStore('viewsStore', () => {
} }
} }
watch(activeViewTitleOrId, () => {
isPaginationLoading.value = true
})
watch(activeView, (view) => { watch(activeView, (view) => {
if (!view) return if (!view) return
if (!view.base_id) return if (!view.base_id) return
@ -317,6 +319,7 @@ export const useViewsStore = defineStore('viewsStore', () => {
removeFromRecentViews, removeFromRecentViews,
activeSorts, activeSorts,
activeNestedFilters, activeNestedFilters,
isActiveViewLocked,
} }
}) })

202
packages/nc-gui/utils/formulaUtils.ts

@ -416,6 +416,208 @@ const formulas: Record<string, any> = {
syntax: 'WEEKDAY(date, [startDayOfWeek])', syntax: 'WEEKDAY(date, [startDayOfWeek])',
examples: ['WEEKDAY("2021-06-09")', 'WEEKDAY(NOW(), "sunday")'], examples: ['WEEKDAY("2021-06-09")', 'WEEKDAY(NOW(), "sunday")'],
}, },
TRUE: {
type: formulaTypes.NUMERIC,
validation: {
args: {
max: 0,
},
},
description: 'Returns 1',
syntax: 'TRUE()',
examples: ['TRUE()'],
},
FALSE: {
type: formulaTypes.NUMERIC,
validation: {
args: {
max: 0,
},
},
description: 'Returns 0',
syntax: 'FALSE()',
examples: ['FALSE()'],
},
REGEX_MATCH: {
type: formulaTypes.STRING,
validation: {
args: {
rqd: 2,
},
},
description: 'Returns 1 if the input text matches a regular expression or 0 if it does not.',
syntax: 'REGEX_MATCH(string, regex)',
examples: ['REGEX_MATCH({title}, "abc.*")'],
},
REGEX_EXTRACT: {
type: formulaTypes.STRING,
validation: {
args: {
rqd: 2,
},
},
description: 'Returns the first match of a regular expression in a string.',
syntax: 'REGEX_EXTRACT(string, regex)',
examples: ['REGEX_EXTRACT({title}, "abc.*")'],
},
REGEX_REPLACE: {
type: formulaTypes.STRING,
validation: {
args: {
rqd: 3,
},
},
description: 'Replaces all matches of a regular expression in a string with a replacement string',
syntax: 'REGEX_MATCH(string, regex, replacement)',
examples: ['REGEX_EXTRACT({title}, "abc.*", "abcd")'],
},
BLANK: {
type: formulaTypes.STRING,
validation: {
args: {
rqd: 0,
},
},
description: 'Returns a blank value(null)',
syntax: 'BLANK()',
examples: ['BLANK()'],
},
XOR: {
type: formulaTypes.NUMERIC,
validation: {
args: {
min: 1,
},
},
description: 'Returns true if an odd number of arguments are true, and false otherwise.',
syntax: 'XOR(expression, [exp2, ...])',
examples: ['XOR(TRUE(), FALSE(), TRUE())'],
},
EVEN: {
type: formulaTypes.NUMERIC,
validation: {
args: {
rqd: 1,
},
},
description: 'Returns the nearest even integer that is greater than or equal to the specified value',
syntax: 'EVEN(value)',
examples: ['EVEN({column})'],
},
ODD: {
type: formulaTypes.NUMERIC,
validation: {
args: {
rqd: 1,
},
},
description: 'Returns the nearest odd integer that is greater than or equal to the specified value',
syntax: 'ODD(value)',
examples: ['ODD({column})'],
},
RECORD_ID: {
validation: {
args: {
rqd: 0,
},
},
description: 'Returns the record id of the current record',
syntax: 'RECORD_ID()',
examples: ['RECORD_ID()'],
},
COUNTA: {
validation: {
args: {
min: 1,
},
},
description: 'Counts the number of non-empty arguments',
syntax: 'COUNTA(value1, [value2, ...])',
examples: ['COUNTA({field1}, {field2})'],
},
COUNT: {
validation: {
args: {
min: 1,
},
},
description: 'Count the number of arguments that are numbers',
syntax: 'COUNT(value1, [value2, ...])',
examples: ['COUNT({field1}, {field2})'],
},
COUNTALL: {
validation: {
args: {
min: 1,
},
},
description: 'Counts the number of arguments',
syntax: 'COUNTALL(value1, [value2, ...])',
examples: ['COUNTALL({field1}, {field2})'],
},
ROUNDDOWN: {
type: formulaTypes.NUMERIC,
validation: {
args: {
min: 1,
max: 2,
},
},
description:
'Round down the value after the decimal point to the number of decimal places given by "precision"(default is 0)',
syntax: 'ROUNDDOWN(value, [precision])',
examples: ['ROUNDDOWN({field1})', 'ROUNDDOWN({field1}, 2)'],
},
ROUNDUP: {
type: formulaTypes.NUMERIC,
validation: {
args: {
min: 1,
max: 2,
},
},
description: 'Round up the value after the decimal point to the number of decimal places given by "precision"(default is 0)',
syntax: 'ROUNDUP(value, [precision])',
examples: ['ROUNDUP({field1})', 'ROUNDUP({field1}, 2)'],
},
VALUE: {
validation: {
args: {
rqd: 1,
},
},
description:
'Extract the numeric value from a string, if `%` or `-` is present, it will handle it accordingly and return the numeric value',
syntax: 'VALUE(value)',
examples: ['VALUE({field})', 'VALUE("abc10000%")', 'VALUE("$10000")'],
},
// Disabling these functions for now; these act as alias for CreatedAt & UpdatedAt fields;
// Issue: Error noticed if CreatedAt & UpdatedAt fields are removed from the table after creating these formulas
//
// CREATED_TIME: {
// validation: {
// args: {
// rqd: 0,
// },
// },
// description: 'Returns the created time of the current record if it exists',
// syntax: 'CREATED_TIME()',
// examples: ['CREATED_TIME()'],
// },
// LAST_MODIFIED_TIME: {
// validation: {
// args: {
// rqd: 0,
// },
// },
// description: 'Returns the last modified time of the current record if it exists',
// syntax: ' LAST_MODIFIED_TIME()',
// examples: [' LAST_MODIFIED_TIME()'],
// },
} }
const formulaList = Object.keys(formulas) const formulaList = Object.keys(formulas)

9
packages/nc-gui/utils/urlUtils.ts

@ -6,8 +6,15 @@ export const replaceUrlsWithLink = (text: string): boolean | string => {
} }
const rawText = text.toString() const rawText = text.toString()
// create a temporary element to sanitise the string
// by encoding any html code
const tempEl = document.createElement('div')
tempEl.textContent = rawText
const sanitisedText = tempEl.innerHTML
let found = false let found = false
const out = rawText.replace(/URI::\((.*?)\)/g, (_, url) => { const out = sanitisedText.replace(/URI::\((.*?)\)/g, (_, url) => {
found = true found = true
const a = document.createElement('a') const a = document.createElement('a')
a.textContent = url a.textContent = url

8
packages/noco-docs/docs/070.fields/040.field-types/060.formula/015.operators.md

@ -2,7 +2,7 @@
title: 'Numeric and Logical Operators' title: 'Numeric and Logical Operators'
description: 'This article explains various numeric and logical operators that can be used in formula fields.' description: 'This article explains various numeric and logical operators that can be used in formula fields.'
tags: ['Fields', 'Field types', 'Formula'] tags: ['Fields', 'Field types', 'Formula']
keywords: ['Fields', 'Field types', 'Formula', 'Create formula field', 'Numeric operators', 'Logical operators'] keywords: ['Fields', 'Field types', 'Formula', 'Create formula field', 'Numeric operators', 'Logical operators', 'String operators']
--- ---
@ -32,6 +32,12 @@ Example: `({field1} + ({field2} * {field3}) / (3 - {field4} ))`
| `==` | `{field1} == {field2}` | Equal to | | `==` | `{field1} == {field2}` | Equal to |
| `!=` | `{field1} != {field2}` | Not equal to | | `!=` | `{field1} != {field2}` | Not equal to |
### String operators
| Operator | Sample | Description |
|----------|--------------------------|--------------------------|
| `&` | `{field1} & {field2}` | String concatenation |
## Related Articles ## Related Articles
- [Numeric Functions](020.numeric-functions.md) - [Numeric Functions](020.numeric-functions.md)

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

Loading…
Cancel
Save