Browse Source

Merge pull request #7121 from nocodb/develop

pull/7122/head 0.202.9
github-actions[bot] 11 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. 167
      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. 27
      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. 55
      packages/nc-gui/components/smartsheet/details/Fields.vue
  31. 10
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  32. 83
      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. 36
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  39. 10
      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. 11
      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. 41
      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?
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 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">
<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="M0.666656 6.66602H15.3333" 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="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</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">
<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 8L7.99992 11.3333L14.6666 8" 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="#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="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="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<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']
CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['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']
IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['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']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
MdiCurrencyUsd: typeof import('~icons/mdi/currency-usd')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDiscord: typeof import('~icons/mdi/discord')['default']
MdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['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']
MdiFileDocumentOutline: typeof import('~icons/mdi/file-document-outline')['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']
MdiHistory: typeof import('~icons/mdi/history')['default']
MdiKeyStar: typeof import('~icons/mdi/key-star')['default']
MdiLink: typeof import('~icons/mdi/link')['default']
MdiLinkVariant: typeof import('~icons/mdi/link-variant')['default']
MdiLoading: typeof import('~icons/mdi/loading')['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 isLockedMode = inject(IsLockedInj, ref(false))
const isEditColumn = inject(EditColumnInj, ref(false))
const active = inject(ActiveCellInj, ref(false))
@ -188,9 +186,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
const isOpen = computed(() => {
if (readOnly.value) return false
return ((readOnly.value || (localState.value && isPk)) && !active.value && !editable.value) || isLockedMode.value
? false
: open.value
return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
})
// use the default date picker open sync only to close the picker

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 isLockedMode = inject(IsLockedInj, ref(false))
const { t } = useI18n()
const isEditColumn = inject(EditColumnInj, ref(false))
@ -126,9 +124,7 @@ const open = ref(false)
const isOpen = computed(() => {
if (readOnly.value) return false
return readOnly.value || (localState.value && isPk) || isLockedMode.value
? false
: open.value && (active.value || editable.value)
return readOnly.value || (localState.value && isPk) ? false : open.value && (active.value || editable.value)
})
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 isLockedMode = inject(IsLockedInj, ref(false))
const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false))
@ -134,9 +132,13 @@ const vModel = computed({
const selectedTitles = computed(() =>
modelValue
? typeof modelValue === 'string'
? isMysql(column.value.source_id)
? modelValue.split(',').sort((a, b) => {
? Array.isArray(modelValue)
? modelValue
: isMysql(column.value.source_id)
? modelValue
.toString()
.split(',')
.sort((a, b) => {
const opa = options.value.find((el) => el.title === a)
const opb = options.value.find((el) => el.title === b)
if (opa && opb) {
@ -144,8 +146,7 @@ const selectedTitles = computed(() =>
}
return 0
})
: modelValue.split(',')
: modelValue
: modelValue.toString().split(',')
: [],
)
@ -343,11 +344,7 @@ const selectedOpts = computed(() => {
</script>
<template>
<div
class="nc-multi-select h-full w-full flex items-center"
:class="{ 'read-only': readOnly || isLockedMode }"
@click="toggleMenu"
>
<div class="nc-multi-select h-full w-full flex items-center" :class="{ 'read-only': readOnly }" @click="toggleMenu">
<div
v-if="!active"
class="flex flex-wrap"
@ -386,9 +383,9 @@ const selectedOpts = computed(() => {
:bordered="false"
clear-icon
:show-search="!isMobileMode"
:show-arrow="editAllowed && !(readOnly || isLockedMode)"
:show-arrow="editAllowed && !readOnly"
:open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed || isLockedMode"
:disabled="readOnly || !editAllowed"
:class="{ 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`"
@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 isLockedMode = inject(IsLockedInj, ref(false))
const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false))
@ -264,11 +262,7 @@ const selectedOpt = computed(() => {
</script>
<template>
<div
class="h-full w-full flex items-center nc-single-select"
:class="{ 'read-only': readOnly || isLockedMode }"
@click="toggleMenu"
>
<div class="h-full w-full flex items-center nc-single-select" :class="{ 'read-only': readOnly }" @click="toggleMenu">
<div v-if="!(active || isEditable)">
<a-tag v-if="selectedOpt" class="rounded-tag" :color="selectedOpt.color">
<span
@ -295,8 +289,8 @@ const selectedOpt = computed(() => {
:allow-clear="!column.rqd && editAllowed"
:bordered="false"
:open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed || isLockedMode"
:show-arrow="hasEditRoles && !(readOnly || isLockedMode) && active && vModel === null"
:disabled="readOnly || !editAllowed"
:show-arrow="hasEditRoles && !readOnly && active && vModel === null"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`"
:show-search="!isMobileMode && isOpen && active"
@select="onSelect"

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

@ -33,19 +33,33 @@ const isForm = inject(IsFormInj, ref(false))
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 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 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 inputWrapperRef = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLTextAreaElement | null>(null)
@ -67,16 +81,95 @@ onClickOutside(inputWrapperRef, (e) => {
isVisible.value = false
})
const onDblClick = () => {
const onTextClick = () => {
if (!props.virtual) return
isVisible.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>
<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
class="flex flex-row pt-0.5 w-full"
:class="{
@ -85,8 +178,19 @@ const onDblClick = () => {
'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
v-if="editEnabled && !isVisible"
v-else-if="editEnabled && !isVisible"
:ref="focus"
v-model="vModel"
rows="4"
@ -123,39 +227,50 @@ const onDblClick = () => {
'word-break': 'break-word',
'white-space': 'pre-line',
}"
@click="onDblClick"
@click="onTextClick"
/>
<span v-else>{{ vModel }}</span>
<div
v-if="active && !isExpandedFormOpen"
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)"
:class="{ 'right-2 bottom-2': editEnabled }"
data-testid="attachment-cell-file-picker-button"
@click.stop="isVisible = !isVisible"
<NcTooltip
v-if="!isVisible"
placement="bottom"
class="!absolute right-0 bottom-1 !hidden nc-text-area-expand-btn"
:class="{ 'right-0 bottom-1': editEnabled, '!bottom-0': !isRichMode }"
>
<NcTooltip placement="bottom">
<template #title>{{ $t('title.expand') }}</template>
<component
:is="iconMap.expand"
class="transform dark:(!text-white) group-hover:(!text-grey-800 scale-120) text-gray-500 text-xs"
/>
<NcButton type="secondary" size="xsmall" data-testid="attachment-cell-file-picker-button" @click.stop="onExpand">
<component :is="iconMap.expand" class="transform group-hover:(!text-grey-800 ) scale-120 text-gray-700 text-xs" />
</NcButton>
</NcTooltip>
</div>
</div>
<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
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" />
<div class="flex">
<div class="flex max-w-38">
<span class="truncate">
{{ column.title }}
</span>
</div>
</div>
<a-textarea
v-if="!isRichMode"
ref="inputRef"
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"
@ -166,13 +281,19 @@ const onDblClick = () => {
@keydown.stop
@keydown.escape="isVisible = false"
/>
<LazyCellRichText v-else-if="isVisible" v-model:value="vModel" show-menu full-mode :read-only="readOnly" />
</div>
</template>
</NcDropdown>
</template>
<style>
<style lang="scss" scoped>
textarea:focus {
box-shadow: none;
}
:deep(.nc-text-area-expand-btn) {
@apply !block;
}
</style>

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

@ -25,8 +25,6 @@ const {
renameFile,
} = useAttachmentCell()!
const isLocked = inject(IsLockedInj, ref(false))
const dropZoneRef = ref<HTMLDivElement>()
const sortableRef = ref<HTMLDivElement>()
@ -96,7 +94,7 @@ const handleFileDelete = (i: number) => {
<template #title>
<div class="flex gap-4">
<div
v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)"
v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic)"
class="nc-attach-file group"
data-testid="attachment-expand-file-picker-button"
@click="open"
@ -143,7 +141,7 @@ const handleFileDelete = (i: number) => {
<template #title> {{ $t('title.removeFile') }} </template>
<component
:is="iconMap.closeCircle"
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic && !isLocked)"
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic)"
class="nc-attachment-remove"
@click.stop="onRemoveFileClick(item.title, i)"
/>
@ -157,7 +155,7 @@ const handleFileDelete = (i: number) => {
</div>
</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>
<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 isLockedMode = inject(IsLockedInj, ref(false))
const isGallery = inject(IsGalleryInj, ref(false))
const isKanban = inject(IsKanbanInj, ref(false))
@ -69,18 +67,14 @@ const {
open: _open,
FileIcon,
selectedImage,
isReadonly: _isReadonly,
isReadonly,
storedFiles,
} = useProvideAttachmentCell(updateModelValue)
const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, _isReadonly)
const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, isReadonly)
const active = inject(ActiveCellInj, ref(false))
const isReadonly = computed(() => {
return isLockedMode.value || _isReadonly.value
})
const { state: rowState } = useSmartsheetRowStoreOrThrow()
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 { ProjectRoleInj, TreeViewInj, useNuxtApp, useRoles, useTabs } from '#imports'
import type { SidebarTableNode } from '~/lib'
const props = withDefaults(
defineProps<{
base: BaseType
table: TableType
table: SidebarTableNode
sourceIndex: number
}>(),
{ sourceIndex: 0 },
@ -44,7 +45,7 @@ provide(SidebarTableInj, table)
const { setMenuContext, openRenameTableDialog, duplicateTable } = inject(TreeViewInj)!
const { loadViews: _loadViews } = useViewsStore()
const { activeView } = storeToRefs(useViewsStore())
const { activeView, activeViewTitleOrId } = storeToRefs(useViewsStore())
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
// todo: temp
@ -135,7 +136,7 @@ watch(
)
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>
@ -174,7 +175,15 @@ const isTableOpened = computed(() => {
class="nc-sidebar-node-btn nc-sidebar-expand"
@click.stop="onExpand"
>
<GeneralLoader
v-if="table.isViewsLoading"
class="flex w-4 h-4 !text-gray-600 !mt-0.75"
:class="{
'!visible': !isExpanded,
}"
/>
<GeneralIcon
v-else
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="{ '!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 isLocked = inject(IsLockedInj, ref(false))
const isUpdating = ref({
public: false,
password: false,
@ -269,10 +271,6 @@ function onChangeTheme(color: string) {
const isPublicShared = computed(() => {
return !!activeView.value?.uuid
})
const isPublicShareDisabled = computed(() => {
return false
})
</script>
<template>
@ -286,7 +284,7 @@ const isPublicShareDisabled = computed(() => {
:checked="isPublicShared"
:loading="isUpdating.public"
class="share-view-toggle !mt-0.25"
:disabled="isPublicShareDisabled"
:disabled="isLocked"
@click="toggleShare"
/>
</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;
}
.nc-button.ant-btn[disabled] {
.nc-button.ant-btn[disabled],
.ant-btn-text.nc-button.ant-btn[disabled] {
box-shadow: none !important;
@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>
<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 />
</a-switch>
</span>
</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 isLocked = inject(IsLockedInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false))
const isEditColumnMenu = inject(EditColumnInj, ref(false))
@ -255,11 +253,7 @@ onUnmounted(() => {
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div
v-if="
(isLocked || (isPublic && readOnly && !isForm) || isSystemColumn(column)) &&
!isAttachment(column) &&
!isTextArea(column)
"
v-if="(isPublic && readOnly && !isForm) || (isSystemColumn(column) && !isAttachment(column) && !isTextArea(column))"
class="nc-locked-overlay"
/>
</template>

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

@ -48,6 +48,8 @@ const formState = reactive({})
const secondsRemain = ref(0)
const isLocked = inject(IsLockedInj, ref(false))
const isEditable = isUIAllowed('viewFieldEdit' as Permission)
const meta = inject(MetaInj, ref())
@ -347,6 +349,8 @@ watch(submitted, (v) => {
})
function handleMouseUp(col: Record<string, any>, hiddenColIndex: number) {
if (isLocked.value) return
if (!moved.value) {
const index = localColumns.value.length
col.order = (index ? localColumns.value[index - 1].order : 0) + 1
@ -378,6 +382,12 @@ watch(view, (nextView) => {
reloadEventHook.trigger()
}
})
const onFormItemClick = (element: any) => {
if (isLocked.value) return
activeRow.value = element.title
}
</script>
<template>
@ -427,6 +437,7 @@ watch(view, (nextView) => {
type="secondary"
class="nc-form-add-all"
data-testid="nc-form-add-all"
:disabled="isLocked"
@click="addAllColumns"
>
<!-- Add all -->
@ -438,6 +449,7 @@ watch(view, (nextView) => {
type="secondary"
class="nc-form-remove-all"
data-testid="nc-form-remove-all"
:disabled="isLocked"
@click="removeAllColumns"
>
<!-- Remove all -->
@ -451,6 +463,7 @@ watch(view, (nextView) => {
item-key="id"
draggable=".item"
group="form-inputs"
:disabled="isLocked"
class="flex flex-col gap-2"
@start="drag = true"
@end="drag = false"
@ -459,6 +472,9 @@ watch(view, (nextView) => {
<a-card
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="{
'!bg-gray-50 !hover:bg-gray-50 !hover:ring-transparent !cursor-not-allowed': isLocked,
}"
:data-testid="`nc-form-hidden-column-${element.label || element.title}`"
@mousedown="moved = false"
@mousemove="moved = false"
@ -484,7 +500,7 @@ watch(view, (nextView) => {
</a-card>
</template>
<template #footer>
<template v-if="!isLocked" #footer>
<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"
data-testid="nc-drag-n-drop-to-hide"
@ -553,6 +569,7 @@ watch(view, (nextView) => {
autosize
size="large"
hide-details
:disabled="isLocked"
placeholder="Form Title"
:bordered="false"
data-testid="nc-form-heading"
@ -580,7 +597,7 @@ watch(view, (nextView) => {
hide-details
:placeholder="$t('msg.info.formDesc')"
:bordered="false"
:disabled="!isEditable"
:disabled="!isEditable || isLocked"
data-testid="nc-form-sub-heading"
@blur="updateView"
@click="updateView"
@ -598,6 +615,7 @@ watch(view, (nextView) => {
group="form-inputs"
class="h-full"
:move="onMoveCallback"
:disabled="isLocked"
@change="onMove($event)"
@start="drag = true"
@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,
},
{
'!hover:bg-white !ring-0 !cursor-auto': isLocked,
},
]"
data-testid="nc-form-fields"
@click="activeRow = element.title"
@click="onFormItemClick(element)"
>
<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"
>
<component
@ -795,9 +816,9 @@ watch(view, (nextView) => {
</a-card>
</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 -->
<div class="text-lg text-gray-700">
{{ $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({
get: () => vPaginationData?.value?.page ?? 1,
set: async (p) => {
isViewDataLoading.value = true
isPaginationLoading.value = true
try {
await changePage?.(p)
isViewDataLoading.value = false
isPaginationLoading.value = false
} catch (e) {
if (axios.isCancel(e)) {
return
}
isViewDataLoading.value = false
isPaginationLoading.value = false
}
},
})
@ -100,7 +100,7 @@ const renderAltOrOptlKey = () => {
'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" />
</div>
<NcPagination

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

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

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

@ -31,6 +31,7 @@ const props = defineProps<{
modelValue: any
row?: Row
active?: boolean
readOnly?: boolean
}>()
const emit = defineEmits(['update:modelValue', 'navigate', 'save'])
@ -38,12 +39,14 @@ const emit = defineEmits(['update:modelValue', 'navigate', 'save'])
const column = toRef(props, 'column')
const active = toRef(props, 'active', false)
const row = toRef(props, 'row')
const readOnly = toRef(props, 'readOnly', false)
provide(ColumnInj, column)
provide(ActiveCellInj, active)
provide(RowInj, row)
provide(CellValueInj, toRef(props, 'modelValue'))
provide(SaveRowInj, () => emit('save'))
provide(ReadonlyInj, readOnly)
const isGrid = inject(IsGridInj, ref(false))
@ -94,7 +97,9 @@ onUnmounted(() => {
<div
ref="elementToObserve"
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.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)"
>

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

@ -37,6 +37,7 @@ const props = defineProps<{
hideType?: boolean
hideAdditionalOptions?: boolean
fromTableExplorer?: boolean
readonly?: boolean
}>()
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 readOnly = computed(() => props.readonly)
const { isMysql, isMssql, isXcdbBase } = useBase()
const reloadDataTrigger = inject(ReloadViewDataHookInj)
@ -114,6 +117,8 @@ const reloadMetaAndData = async () => {
const saving = ref(false)
async function onSubmit() {
if (readOnly.value) return
saving.value = true
const saved = await addOrUpdate(reloadMetaAndData, props.columnPosition)
saving.value = false
@ -134,7 +139,7 @@ async function onSubmit() {
// focus and select the column name field
const antInput = ref()
watchEffect(() => {
if (antInput.value && formState.value) {
if (antInput.value && formState.value && !readOnly.value) {
// todo: replace setTimeout
setTimeout(() => {
// focus and select input only if active element is not an input or textarea
@ -215,9 +220,10 @@ if (props.fromTableExplorer) {
:class="{
'bg-white': !props.fromTableExplorer,
'w-[400px]': !props.embedMode,
'!w-146': isTextArea(formState) && formState.meta.richMode,
'!w-[600px]': formState.uidt === UITypes.Formula && !props.embedMode,
'!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"
@click.stop
@ -232,6 +238,7 @@ if (props.fromTableExplorer) {
<input
ref="antInput"
v-model="formState.title"
:disabled="readOnly"
class="flex flex-grow nc-fields-input text-lg font-bold outline-none bg-inherit"
:contenteditable="true"
/>
@ -247,7 +254,7 @@ if (props.fromTableExplorer) {
ref="antInput"
v-model:value="formState.title"
class="nc-column-name-input !rounded !mt-1"
:disabled="isKanban"
:disabled="isKanban || readOnly"
@input="onAlter(8)"
/>
</a-form-item>
@ -263,7 +270,7 @@ if (props.fromTableExplorer) {
v-model:value="formState.uidt"
show-search
class="nc-column-type-input !rounded"
:disabled="isKanban"
:disabled="isKanban || readOnly"
dropdown-class-name="nc-dropdown-column-type "
@change="onUidtOrIdTypeChange"
@dblclick="showDeprecated = !showDeprecated"
@ -285,10 +292,12 @@ if (props.fromTableExplorer) {
</div> -->
</div>
<template v-if="!readOnly">
<LazySmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" />
<LazySmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" />
<LazySmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" />
<LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" />
<LazySmartsheetColumnLongTextOptions v-if="formState.uidt === UITypes.LongText" v-model:value="formState" />
<LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" />
<LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" />
<LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />
@ -299,6 +308,7 @@ if (props.fromTableExplorer) {
<LazySmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" v-model:value="formState" />
<LazySmartsheetColumnLinkedToAnotherRecordOptions
v-if="!isEdit && (formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links)"
:key="formState.uidt"
v-model:value="formState"
/>
<LazySmartsheetColumnLinkOptions v-if="isEdit && formState.uidt === UITypes.Links" v-model:value="formState" />
@ -307,6 +317,7 @@ if (props.fromTableExplorer) {
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect"
v-model:value="formState"
/>
</template>
</div>
<a-checkbox
v-if="formState.meta && columnToValidate.includes(formState.uidt)"
@ -317,12 +328,17 @@ if (props.fromTableExplorer) {
{{ `${$t('msg.acceptOnlyValid')} ${formState.uidt}` }}
</span>
</a-checkbox>
<template v-if="!readOnly">
<div class="!my-3">
<!--
Default Value for JSON & LongText is not supported in MySQL
Default Value is Disabled for MSSQL -->
<LazySmartsheetColumnRichLongTextDefaultValue
v-if="isTextArea(formState) && formState.meta.richMode"
v-model:value="formState"
/>
<LazySmartsheetColumnDefaultValue
v-if="
v-else-if="
!isVirtualCol(formState) &&
!isAttachment(formState) &&
!isMssql(meta!.source_id) &&
@ -352,6 +368,7 @@ if (props.fromTableExplorer) {
/>
</div>
</Transition>
</template>
<template v-if="props.fromTableExplorer">
<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 availableBinOps = ['+', '-', '*', '/', '>', '<', '==', '<=', '>=', '!=']
const availableBinOps = ['+', '-', '*', '/', '>', '<', '==', '<=', '>=', '!=', '&']
const autocomplete = ref(false)
@ -434,7 +434,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}
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') {
typeErrors.add(t('msg.formula.notSupportedToReferenceColumn', { columnName: col.title }))
} 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(/'$/, '')
}
if (vModel.value.cdf) {
if (vModel.value.cdf && typeof vModel.value.cdf === 'string') {
const fndDefaultOption = options.value.find((el) => el.title === vModel.value.cdf)
if (!fndDefaultOption) {
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
watch(vModel.value, (next) => {
const cdfs = (next.cdf ?? '').split(',')
const cdfs = (next.cdf ?? '').toString().split(',')
const values = (next.colOptions.options ?? []).map((col) => {
return col.title.replace(/^'/, '').replace(/'$/, '')
})

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

@ -40,6 +40,8 @@ const { getMeta } = useMetas()
const { meta, view } = useSmartsheetStoreOrThrow()
const isLocked = inject(IsLockedInj, ref(false))
const { openedViewsTab } = storeToRefs(useViewsStore())
const moveOps = ref<moveOp[]>([])
@ -587,6 +589,8 @@ const toggleVisibility = async (checked: boolean, field: Field) => {
useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
if (isLocked.value) return
if (cmdOrCtrl && e.key.toLowerCase() === 's') {
if (openedViewsTab.value !== 'field') return
e.preventDefault()
@ -625,7 +629,11 @@ onKeyDown('ArrowUp', () => {
})
onKeyDown('Delete', () => {
if (isLocked.value) return
if (document.activeElement?.tagName === 'INPUT') return
if (document.activeElement?.tagName === 'TEXTAREA') return
const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) {
onFieldDelete(activeField.value)
@ -633,7 +641,11 @@ onKeyDown('Delete', () => {
})
onKeyDown('Backspace', () => {
if (isLocked.value) return
if (document.activeElement?.tagName === 'INPUT') return
if (document.activeElement?.tagName === 'TEXTAREA') return
const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) {
onFieldDelete(activeField.value)
@ -641,7 +653,7 @@ onKeyDown('Backspace', () => {
})
onKeyDown('ArrowRight', () => {
if (document.activeElement?.tagName === 'INPUT') return
if (document.activeElement?.tagName === 'TEXTAREA') return
if (activeField.value) {
const input = document.querySelector('.nc-fields-input')
if (input) {
@ -659,11 +671,15 @@ const onClickCopyFieldUrl = async (field: ColumnType) => {
const keys = useMagicKeys()
whenever(keys.meta_s, () => {
if (isLocked.value) return
if (!meta.value?.id) return
if (openedViewsTab.value === 'field') saveChanges()
})
whenever(keys.ctrl_s, () => {
if (isLocked.value) return
if (!meta.value?.id) return
if (openedViewsTab.value === 'field') saveChanges()
})
@ -718,9 +734,9 @@ const onFieldOptionUpdate = () => {
</template>
</a-input>
<div class="flex gap-2">
<NcTooltip>
<NcTooltip :disabled="isLocked">
<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">
<GeneralIcon icon="plus" class="w-3" />
New Field
@ -730,19 +746,22 @@ const onFieldOptionUpdate = () => {
<NcButton
type="secondary"
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()"
>
Reset
</NcButton>
<NcTooltip>
<NcTooltip :disabled="isLocked">
<template #title> {{ `${renderCmdOrCtrlKey()} + S` }} </template>
<NcButton
type="primary"
size="small"
: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()"
>
Save changes
@ -752,7 +771,7 @@ const onFieldOptionUpdate = () => {
</div>
<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">
<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 }">
<div
v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv"
@ -761,9 +780,16 @@ const onFieldOptionUpdate = () => {
@click="changeField(field, $event)"
>
<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
v-if="field.id && viewFieldsMap[field.id]"
:disabled="isLocked"
:checked="
visibilityOps.find((op) => op.column.fk_column_id === field.id)?.visible ?? viewFieldsMap[field.id].show
"
@ -882,9 +908,10 @@ const onFieldOptionUpdate = () => {
</NcButton>
</div>
</NcTooltip>
<a-menu-divider class="my-1.5" />
<a-menu-divider v-if="!isLocked" class="my-1.5" />
</template>
<template v-if="!isLocked">
<NcMenuItem key="table-explorer-duplicate" @click="duplicateField(field)">
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span>
</NcMenuItem>
@ -903,6 +930,7 @@ const onFieldOptionUpdate = () => {
Delete
</div>
</NcMenuItem>
</template>
</NcMenu>
</template>
</NcDropdown>
@ -927,7 +955,13 @@ const onFieldOptionUpdate = () => {
@click="changeField(displayColumn, $event)"
>
<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" />
<SmartsheetHeaderCellIcon
v-if="displayColumn"
@ -1046,6 +1080,7 @@ const onFieldOptionUpdate = () => {
:preload="fieldState(activeField)"
:table-explorer-columns="fields"
embed-mode
:readonly="isLocked"
from-table-explorer
@update="onFieldUpdate"
@add="onFieldAdd"

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

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

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

@ -142,7 +142,12 @@ const { getMeta } = useMetas()
const { addUndo, clone, defineViewScope } = useUndoRedo()
const { isViewColumnsLoading, updateGridViewColumn, gridViewCols, resizingColOldWith } = useViewColumnsOrThrow()
const {
isViewColumnsLoading: _isViewColumnsLoading,
updateGridViewColumn,
gridViewCols,
resizingColOldWith,
} = useViewColumnsOrThrow()
const { isExpandedFormCommentMode } = storeToRefs(useConfigStore())
@ -179,9 +184,11 @@ const fillHandle = ref<HTMLElement>()
const gridRect = useElementBounding(gridWrapper)
const isViewColumnsLoading = computed(() => _isViewColumnsLoading.value || !meta.value)
// #Permissions
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 { 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) {
if (!hasEditPermission.value || editEnabled.value || isView || isLocked.value || readOnly.value || isSystemColumn(col)) {
if (!hasEditPermission.value || editEnabled.value || isView || readOnly.value || isSystemColumn(col)) {
return
}
@ -375,9 +382,7 @@ function makeEditable(row: Row, col: ColumnType) {
// #Computed
const isAddingEmptyRowAllowed = computed(
() => !isView && !isLocked.value && hasEditPermission.value && !isSqlView.value && !isPublicView.value,
)
const isAddingEmptyRowAllowed = computed(() => !isView && hasEditPermission.value && !isSqlView.value && !isPublicView.value)
const visibleColLength = computed(() => fields.value?.length)
@ -409,7 +414,9 @@ const dummyRowDataForLoading = computed(() => {
})
const showSkeleton = computed(
() => disableSkeleton !== true && (isViewDataLoading.value || isPaginationLoading.value || isViewColumnsLoading.value),
() =>
(disableSkeleton !== true && (isViewDataLoading.value || isPaginationLoading.value || isViewColumnsLoading.value)) ||
!meta.value,
)
// #Grid
@ -443,9 +450,7 @@ async function openNewRecordHandler() {
}
const onDraftRecordClick = () => {
if (!isLocked?.value) {
openNewRecordFormHook.trigger()
}
}
const onNewRecordToGridClick = () => {
@ -552,6 +557,8 @@ const {
return true
}
if (isExpandedCellInputExist()) return
// skip keyboard event handling if there is a drawer / modal
if (isDrawerOrModalExist()) {
return true
@ -560,7 +567,9 @@ const {
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
const altOrOptionKey = e.altKey
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()
const row = dataRef.value[activeCell.row]
expandForm?.(row)
@ -756,6 +765,9 @@ onClickOutside(tableBodyEl, (e) => {
if (activeCell.row === null || activeCell.col === null) return
const isRichModalOpen = isExpandedCellInputExist()
if (isRichModalOpen) return
const activeCol = fields.value[activeCell.col]
if (editEnabled.value && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return
@ -978,7 +990,6 @@ const refreshFillHandle = () => {
const showFillHandle = computed(
() =>
!readOnly.value &&
!isLocked.value &&
!editEnabled.value &&
(!selectedRange.isEmpty() || (activeCell.row !== null && activeCell.col !== null)) &&
!dataRef.value[(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) ?? -1]?.rowMeta?.new,
@ -1057,14 +1068,18 @@ useEventListener(document, 'mouseup', () => {
/** handle keypress events */
useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
if (e.key === 'Alt') {
const isRichModalOpen = isExpandedCellInputExist()
if (e.key === 'Alt' && !isRichModalOpen) {
altModifier.value = true
}
})
/** handle keypress events */
useEventListener(document, 'keyup', async (e: KeyboardEvent) => {
if (e.key === 'Alt') {
const isRichModalOpen = isExpandedCellInputExist()
if (e.key === 'Alt' && !isRichModalOpen) {
altModifier.value = false
disableUrlOverlay.value = false
}
@ -1111,22 +1126,6 @@ onBeforeUnmount(async () => {
reloadViewDataHook?.on(reloadViewDataHandler)
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
// reset context menu target on hide
@ -1217,7 +1216,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
}
const loaderText = computed(() => {
if (isViewDataLoading.value) {
if (isPaginationLoading.value) {
if (paginationDataRef.value?.totalRows && paginationDataRef.value?.pageSize) {
return `Loading page<br/>${paginationDataRef.value.page} of ${Math.ceil(
paginationDataRef.value?.totalRows / paginationDataRef.value?.pageSize,
@ -1256,10 +1255,7 @@ onKeyStroke('ArrowDown', onDown)
></div>
</div>
<div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative" :class="gridWrapperClass">
<div
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 v-show="isPaginationLoading" 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">
<GeneralLoader size="xlarge" />
<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
v-if="!readOnly || !isLocked || isMobileMode"
v-if="!readOnly || isMobileMode"
class="nc-row-no sm:min-w-4 text-xs text-gray-500"
:class="{ toggle: !readOnly, hidden: row.rowMeta.selected }"
>
@ -1513,7 +1509,7 @@ onKeyStroke('ArrowDown', onDown)
class="!flex items-center"
:data-testid="`row-save-spinner-${rowIndex}`"
/>
<template v-else-if="!isLocked">
<span
v-if="row.rowMeta?.commentCount && expandForm"
v-e="['c:expanded-form:open']"
@ -1524,7 +1520,7 @@ onKeyStroke('ArrowDown', onDown)
{{ row.rowMeta.commentCount }}
</span>
<div
v-else
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
@ -1535,7 +1531,6 @@ onKeyStroke('ArrowDown', onDown)
@click="expandAndLooseFocus(row, state)"
/>
</div>
</template>
</div>
</div>
</td>
@ -1727,16 +1722,14 @@ onKeyStroke('ArrowDown', onDown)
{{ $t('general.clear') }}
</div>
</NcMenuItem>
<NcDivider />
<NcMenuItem
v-if="contextMenuTarget && !isLocked && selectedRange.isSingleCell() && isUIAllowed('commentEdit') && !isMobileMode"
v-if="contextMenuTarget && selectedRange.isSingleCell() && isUIAllowed('commentEdit') && !isMobileMode"
class="nc-base-menu-item"
@click="commentRow(contextMenuTarget.row)"
>
<div v-e="['a:row:comment']" class="flex gap-2 items-center">
<MdiMessageOutline class="h-4 w-4" />
{{ $t('general.comment') }}
</div>
</NcMenuItem>
@ -1784,7 +1777,7 @@ onKeyStroke('ArrowDown', onDown)
:extra-style="paginationStyleRef?.extraStyle"
>
<template #add-record>
<div v-if="isAddingEmptyRowAllowed" class="flex ml-1">
<div v-if="isAddingEmptyRowAllowed && !showSkeleton && !isPaginationLoading" class="flex ml-1">
<NcButton
v-if="isMobileMode"
v-e="[isAddNewRecordGridMode ? 'c:row:add:grid' : 'c:row:add:form']"
@ -1820,8 +1813,7 @@ onKeyStroke('ArrowDown', onDown)
>
<div
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"
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"
@click="onNewRecordToGridClick"
>
<div class="flex flex-row items-center justify-between w-full">
@ -1837,8 +1829,7 @@ onKeyStroke('ArrowDown', onDown)
</div>
<div
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"
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"
@click="onNewRecordToFormClick"
>
<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) => {
if (isExpandedCellInputExist()) return
if (!e.altKey) return
e.preventDefault()
@ -36,6 +38,8 @@ const usePaginationShortcuts = ({
}
const onRight = async (e: KeyboardEvent) => {
if (isExpandedCellInputExist()) return
if (!e.altKey) return
e.preventDefault()
@ -47,6 +51,8 @@ const usePaginationShortcuts = ({
}
const onDown = async (e: KeyboardEvent) => {
if (isExpandedCellInputExist()) return
if (!e.altKey) return
e.preventDefault()
@ -56,6 +62,8 @@ const usePaginationShortcuts = ({
}
const onUp = async (e: KeyboardEvent) => {
if (isExpandedCellInputExist()) return
if (!e.altKey) return
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 isLocked = inject(IsLockedInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false))
const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
@ -46,12 +48,16 @@ const closeAddColumnDropdown = () => {
}
const openHeaderMenu = () => {
if (isLocked.value) return
if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit') && !isMobileMode.value) {
editColumnDropdown.value = true
}
}
const openDropDown = (e: Event) => {
if (isLocked.value) return
if (isForm.value || isExpandedForm.value || (!isUIAllowed('fieldEdit') && !isMobileMode.value)) return
e.preventDefault()

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

@ -103,7 +103,9 @@ export default defineComponent({
setup(props) {
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())
@ -112,9 +114,9 @@ export default defineComponent({
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
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',
})
}

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

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

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

@ -78,7 +78,9 @@ export default defineComponent({
setup(props) {
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

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

@ -18,8 +18,6 @@ const { isUIAllowed } = useRoles()
const isPublicView = inject(IsPublicInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const { $api, $e } = useNuxtApp()
const { t } = useI18n()
@ -58,7 +56,7 @@ const quickImportDialogs: Record<(typeof quickImportDialogTypes)[number], Ref<bo
) as Record<QuickImportDialogType, Ref<boolean>>
const onImportClick = (dialog: any) => {
if (isLocked.value) return
if (lockType.value === LockType.Locked) return
emits('closeModal')
dialog.value = true
@ -163,10 +161,17 @@ const onDelete = async () => {
</NcTooltip>
<NcDivider />
<template v-if="!view?.is_default">
<NcMenuItem @click="onRenameMenuClick">
<NcMenuItem v-if="lockType !== LockType.Locked" @click="onRenameMenuClick">
<GeneralIcon icon="edit" />
{{ $t('activity.renameView') }}
</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">
<GeneralIcon icon="duplicate" class="nc-view-copy-icon" />
{{ $t('labels.duplicateView') }}
@ -205,7 +210,7 @@ const onDelete = async () => {
},
]"
class="nc-base-menu-item"
:class="{ disabled: isLocked }"
:class="{ disabled: lockType === LockType.Locked }"
>
<component :is="iconMap.upload" />
{{ `${$t('general.upload')} ${type.toUpperCase()}` }}
@ -234,10 +239,12 @@ const onDelete = async () => {
<LazySmartsheetToolbarExportSubActions />
</NcSubMenu>
<NcDivider />
</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')">
<NcDivider />
<NcSubMenu key="lock-type" class="scrollbar-thin-dull max-h-90vh overflow-auto !py-0">
<template #title>
<div
v-e="[
@ -272,9 +279,22 @@ const onDelete = async () => {
<LazySmartsheetToolbarLockType :type="LockType.Locked" @click="changeLockType(LockType.Locked)" />
</a-menu-item>
</NcSubMenu>
</template>
<template v-if="!view.is_default">
<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" />
{{
$t('general.deleteEntity', {

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

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

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

@ -6,7 +6,6 @@ import {
CellValueInj,
ColumnInj,
IsFormInj,
IsLockedInj,
IsUnderLookupInj,
ReadonlyInj,
ReloadRowDataHookInj,
@ -35,8 +34,6 @@ const readOnly = inject(ReadonlyInj, ref(false))
const isForm = inject(IsFormInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useRoles()
@ -103,7 +100,7 @@ const belongsToColumn = computed(
</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"
>
<GeneralIcon

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

@ -5,7 +5,6 @@ import {
CellValueInj,
ColumnInj,
IsFormInj,
IsLockedInj,
IsUnderLookupInj,
ReadonlyInj,
ReloadRowDataHookInj,
@ -31,8 +30,6 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj)
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const listItemsDlg = ref(false)
@ -121,7 +118,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</template>
</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
icon="expand"
class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"

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

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

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

@ -110,9 +110,13 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
>
<template v-if="lookupColumn">
<!-- 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
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
v-for="(v, i) of arrValue"

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

@ -6,7 +6,6 @@ import {
CellValueInj,
ColumnInj,
IsFormInj,
IsLockedInj,
IsUnderLookupInj,
ReadonlyInj,
ReloadRowDataHookInj,
@ -33,8 +32,6 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj)
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const listItemsDlg = ref(false)
@ -123,7 +120,7 @@ const m2mColumn = computed(
</template>
</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
icon="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` }"
:src="qrCode"
:alt="$t('title.qrCode')"
class="min-w-[1.4em]"
@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 v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs">
{{ $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 {
ActiveCellInj,
IsFormInj,
IsLockedInj,
ReadonlyInj,
iconMap,
inject,
@ -37,13 +36,11 @@ const active = inject(ActiveCellInj, ref(false))
const isForm = inject(IsFormInj)!
const isLocked = inject(IsLockedInj, ref(false))
const { open } = useExpandedFormDetached()
function openExpandedForm() {
const rowId = extractPkFromRow(item, relatedTableMeta.value.columns as ColumnType[])
if (!readOnly.value && !isLocked.value && !readonlyProp && rowId) {
if (!readOnly.value && !readonlyProp && rowId) {
open({
isOpen: true,
row: { row: item, rowMeta: {}, oldRow: { ...item } },
@ -98,11 +95,7 @@ export default {
</template>
</span>
<div
v-show="active || isForm"
v-if="showUnlinkButton && !readOnly && !isLocked && isUIAllowed('dataEdit')"
class="flex items-center"
>
<div v-show="active || isForm" v-if="showUnlinkButton && !readOnly && isUIAllowed('dataEdit')" class="flex items-center">
<component
:is="iconMap.closeThick"
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) => {
if (isSharedBase.value) return
if (readonly.value) return
if (isPublic.value && !isForm.value) return
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,
'!border-1': isLinked && !isLoading,
'!hover:border-gray-400': !isLinked,
'!cursor-auto !hover:bg-white': readonly,
}"
:body-style="{ padding: 0 }"
:hoverable="false"
@ -109,7 +110,7 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
v-if="isLinked && !isLoading"
class="text-brand-500 text-0.875"
:class="{
'!group-hover:mr-12': fields.length === 0,
'!group-hover:mr-12': fields.length === 0 && !readonly,
}"
>
<LinkIcon class="w-4 h-4" />
@ -118,7 +119,7 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
<MdiLoading
v-else-if="isLoading"
: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"
/>

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

@ -34,10 +34,6 @@ export function useData(args: {
}) {
const { meta, viewMeta, formattedData, paginationData, callbacks } = args
if (!meta) {
throw new Error('Table meta is not available')
}
const { t } = useI18n()
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',
disableEmailAuth: false,
dashboardPath: '/dashboard',
inviteOnlySignup: false,
})
/** reactive token payload */

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

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

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

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

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

@ -1,5 +1,7 @@
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import type { SidebarTableNode } from '~/lib'
import {
Modal,
SYSTEM_COLUMNS,
@ -58,7 +60,7 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
const tables = computed(() => baseTables.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
let base = bases.value.get(table.base_id)
@ -82,8 +84,17 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
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,
})
}
table.isViewsLoading = true
try {
await loadViews({ tableId: table.id as string })
const views = viewsByTable.value.get(table.id as string) ?? []
@ -95,11 +106,27 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
path: `/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}/${defaultView.id}/${openedViewsTab.value}`,
query: route.value.query,
})
} else
await navigateTo({
path: `/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}`,
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 () => {

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

@ -42,10 +42,6 @@ export function useViewData(
const meta = computed(() => _meta.value || activeTable.value)
const metaId = computed(() => _meta.value?.id || activeTableId.value)
if (!meta.value) {
throw new Error('Table meta is not available')
}
const { t } = useI18n()
const optimisedQuery = useState('optimisedQuery', () => true)
@ -190,6 +186,8 @@ export function useViewData(
controller.value = CancelToken.source()
isPaginationLoading.value = true
const response = !isPublic.value
? await api.dbViewRow.list(
'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",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "إرسال",
"create": "إنشاء",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplicate",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "الذهاب إلى لوحة التحكم",
"importing": "الاستيراد",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "متداخلة متقطعة",
"downloadAllowed": "التحميل المسموح به",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "জমিন",
"create": "সি",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplicate",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

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

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Indsend",
"create": "Opret",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplikat",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Gå til Dashboard",
"importing": "Import af",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flade, indlejrede",
"downloadAllowed": "Download tilladt",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Indsæt er ikke understøttet på den aktive celle",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Bestätigen",
"create": "Erstellen",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplizieren",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Zum Dashboard gehen",
"importing": "Wird importiert",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Verflachen Verschachtelt",
"downloadAllowed": "Download erlaubt",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Der Vorgang Einfügen wird auf der aktiven Zelle nicht unterstützt",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Submit",
"create": "Create",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplicate",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -1033,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

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

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Bidali",
"create": "Sortu",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Bikoiztu",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "ارسال",
"create": "ایجاد",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "کپی کردن",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "رفتن به داشبورد",
"importing": "در حال ایمپورت",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "مسطح کردن تو در تو",
"downloadAllowed": "اجازه دانلود",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "عملیات جایگذاری در سلول فعال پشتیبانی نمیشود",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Lähetä",
"create": "Luoda",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplikaatti",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Siirry kojelautaan",
"importing": "Tuo",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Lataa sallittu",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Liitä-toimintoa ei tueta aktiivisessa solussa.",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Soumettre",
"create": "Créer",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Dupliquer",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Accéder au tableau de bord",
"importing": "Importation de",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Aplatir imbriqué",
"downloadAllowed": "Téléchargement autorisé",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "L'opération de collage n'est pas prise en charge sur la cellule active",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "שלח",
"create": "צור",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "שכפל",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard",
"importing": "מייבא",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "परसत करन",
"create": "सजन करन",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplicate",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "podnijeti",
"create": "Stvoriti",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Dupliciraj",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Idi na nadzornu ploču",
"importing": "Uvozim",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Ispravi ugniježđene strukture",
"downloadAllowed": "Preuzimanje dozvoljeno",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operacija \"lijepljenje\" nije podržana u aktivnoj ćeliji",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Kirim",
"create": "Membuat",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplikat",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Buka Dasbor",
"importing": "Mengimpor",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Ratakan Bersarang",
"downloadAllowed": "Unduh diizinkan",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operasi tempel tidak didukung pada sel aktif",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Invia",
"create": "Crea",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplicato",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Vada alla Dashboard",
"importing": "Importazione",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Appiattisci annidato",
"downloadAllowed": "Download consentito",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "L'operazione di incollamento non è supportata sulla cella attiva.",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "送信",
"create": "作成",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "複製",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "ダッシュボードに移動",
"importing": "インポート中",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "入れ子を平坦化",
"downloadAllowed": "ダウンロードを許可",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "現在アクティブなセルでは貼り付けはサポートされていません",
"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",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Iesniegt",
"create": "Izveidot",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Dublikāts",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Dodieties uz paneli",
"importing": "Importēšana",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Izlīdzināt ligzdotās",
"downloadAllowed": "Lejupielādēt atļauts",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Aktīvajā šūnā netiek atbalstīta operācija \"ielīmēt\".",
"roles": {

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

@ -11,11 +11,11 @@
"buttonActionTypes": {
"open_external_url": "Open external link",
"delete_record": "Delete record",
"update_record": "Update record",
"update_record": "Update Record",
"open_layout": "Open layout"
},
"widgets": {
"static_text": "Text",
"static_text": "Tekst",
"chart": "Chart",
"table": "Table",
"image": "Image",
@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Indienen",
"create": "Creëren",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Dubbele",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Ga naar het dashboard",
"importing": "Importeren",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Afvlakken genest",
"downloadAllowed": "Downloaden toegestaan",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Plakken wordt niet ondersteund op de actieve cel",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Sende inn",
"create": "Opprett",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplicate",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Zapisz",
"create": "Utwórz",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplikuj",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Przejdź do panelu",
"importing": "Importowanie",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Spłaszcz zagnieżdżone",
"downloadAllowed": "Pobieranie dozwolone",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operacja wklejania nie jest obsługiwana w aktywnej komórce",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Enviar",
"create": "Criar",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplicado",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Ir para Painel de controlo",
"importing": "Importação",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Ninhada achatada",
"downloadAllowed": "Download permitido",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "A operação de colar não é suportada na célula activa",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Enviar",
"create": "Criar",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplicar",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Ir para o Painel de Controle",
"importing": "Importando",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Achatamento aninhado",
"downloadAllowed": "Download permitido",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "A operação de colar não é suportada na célula activa",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Отправить",
"create": "Создать",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Копировать",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Перейти к Дашборду",
"importing": "Импорт",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Вложенные",
"downloadAllowed": "Скачивание разрешено",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Операция Вставки не поддерживается в выделенной ячейке",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Odoslať",
"create": "Vytvoriť stránku",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplikát",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Prejsť na prístrojový panel",
"importing": "Importovanie",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Sploštenie vnorených",
"downloadAllowed": "Stiahnuť povolené",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operácia Vložiť nie je podporovaná v aktívnej bunke",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Pošlji",
"create": "Ustvari",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Duplikatni",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Pojdite na nadzorno ploščo",
"importing": "Uvoz",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Ploščati vgnezdeno",
"downloadAllowed": "Prenos dovoljen",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Operacija Vstavi ni podprta v aktivni celici",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Skicka in",
"create": "Skapa",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Dubbla",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Gå till instrumentpanelen",
"importing": "Importera",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Platta och inbäddade",
"downloadAllowed": "Nedladdning tillåten",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Klistra in stöds inte på den aktiva cellen.",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "สง",
"create": "สราง",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "คดลอก",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "อนญาตใหดาวนโหลด",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Gönder",
"create": "Oluştur",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Yinelenen",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Gösterge Tablosuna Git",
"importing": "İthalat",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Yuvalanmış Düzleştir",
"downloadAllowed": "İndirme izni verildi",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Etkin hücre üzerinde yapıştırma işlemi desteklenmiyor",
"roles": {

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

@ -1,15 +1,15 @@
{
"dashboards": {
"create_new_dashboard_project": "Create New Interface",
"connect_data_sources": "Connect data sources",
"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.",
"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",
"project_name": "Base Name",
"connect": "Connect",
"create_new_dashboard_project": "Створити новий інтерфейс",
"connect_data_sources": "Підключення джерел даних",
"alert": "Оповіщення",
"alert-message": "Немає баз даних підключено. З’єднайте бази даних для створення інтерфейсів. Пропустіть цей крок і додайте бази даних з основної домашньої сторінки пізніше.",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Виберіть базу даних, які Ви хочете прив'язати до цього інтерфейсу.",
"create_interface": "Створення інтерфейсу",
"project_name": "Назва бази",
"connect": "Підключитися",
"buttonActionTypes": {
"open_external_url": "Open external link",
"open_external_url": "Відкрити зовнішнє посилання",
"delete_record": "Delete record",
"update_record": "Update record",
"open_layout": "Open layout"
@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Надіслати",
"create": "Створити",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Дублювати",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Панель керування",
"importing": "Імпорт",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Вкладені",
"downloadAllowed": "Завантаження дозволене",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Операція вставки не підтримується в активній комірці",
"roles": {

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

@ -1,6 +1,6 @@
{
"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",
"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.",
@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "Nộp",
"create": "Tạo ra",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "Tạo bản sao",
"duplicating": "Duplicating",
"activate": "Activate",
@ -163,7 +165,7 @@
"type": "Type",
"name": "Name",
"changes": "Changes",
"new": "New",
"new": "Mới",
"old": "Old",
"data": "Data",
"source": "Source",
@ -339,7 +341,7 @@
"hideSidebar": "Hide Sidebar",
"creatingTable": "Creating Table",
"erdView": "ERD View",
"newBase": "New Data Source",
"newBase": "Nguồn dữ liệu mới",
"newProj": "Dự án mới",
"createBase": "Create Base",
"myProject": "Những dự án của tôi",
@ -390,7 +392,7 @@
"generateRandomName": "Generate Random Name",
"findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management",
"addNewToken": "Add new token",
"addNewToken": "Thêm mới token",
"accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password",
"tokens": "Tokens",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
@ -602,7 +617,7 @@
"extraConnectionParameters": "Extra connection parameters",
"commentsOnly": "Comments only",
"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}",
"signInWithProvider": "Sign in with {provider}",
"agreeToTos": "By signing up, you agree to the Terms of Service",
@ -799,7 +814,7 @@
"clearCell": "Clear cell",
"addFilterGroup": "Add Filter Group",
"linkRecord": "Link record",
"addNewRecord": "Add new record",
"addNewRecord": "Thêm bản ghi mới",
"newRecord": "New record",
"tableNameCreateNewRecord": "{tableName}: Create new record",
"gotSavedLinkedSuccessfully": "{tableName} '{recordTitle}' got saved & linked successfully",
@ -920,7 +935,7 @@
"clickToCopyFieldId": "Click to copy Field Id",
"enterPassword": "Enter password",
"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",
"thisSharedViewIsProtected": "This shared view is protected",
"successfullySubmittedFormData": "Successfully submitted form data",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

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

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

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

@ -75,6 +75,7 @@
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote",
"submit": "提交",
"create": "建立",
"createEntity": "Create {entity}",
@ -82,6 +83,7 @@
"creatingEntity": "Creating {entity}",
"details": "Details",
"skip": "Skip",
"code": "Code",
"duplicate": "複製",
"duplicating": "Duplicating",
"activate": "Activate",
@ -411,7 +413,18 @@
}
},
"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",
"blockQuote": "Block Quote",
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached",
@ -423,6 +436,7 @@
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"enableRichText": "Enable Rich Text",
"idColon": "Id:",
"copiedRecordURL": "Copied Record URL",
"copyRecordURL": "Copy Record URL",
@ -591,6 +605,7 @@
"goToDashboard": "前往儀表板",
"importing": "匯入中",
"formatJson": "Format JSON",
"autoSelectFieldTypes": "Auto-Select Field Types",
"firstRowAsHeaders": "Use First Record as Headers",
"flattenNested": "展平嵌套",
"downloadAllowed": "允許下載",
@ -1032,6 +1047,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "不支援在啟用的儲存格中執行貼上",
"roles": {

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

@ -84,6 +84,7 @@ const rolePermissions = {
filterChildrenRead: true,
viewFieldEdit: true,
csvTableImport: true,
excelTableImport: true,
},
},
[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 { Theme as AntTheme } from 'ant-design-vue/es/config-provider'
import type { UploadFile } from 'ant-design-vue'
@ -171,6 +171,11 @@ type ViewPageType = 'view' | 'webhook' | 'api' | 'field' | 'relation'
type NcButtonSize = 'xxsmall' | 'xsmall' | 'small' | 'medium'
interface SidebarTableNode extends TableType {
isMetaLoading?: boolean
isViewsLoading?: boolean
}
export {
User,
ProjectMetaInfo,
@ -195,4 +200,5 @@ export {
Users,
ViewPageType,
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 { TabType } from './composables'
declare module '#app/nuxt' {
declare module '#app' {
interface NuxtApp {
$api: BaseAPI<any>
/** {@link import('./plugins/tele') Telemetry} */

68
packages/nc-gui/package.json

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

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

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

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

@ -1,6 +1,7 @@
import { acceptHMRUpdate, defineStore } from 'pinia'
import type { TableType } from 'nocodb-sdk'
import { useTitle } from '@vueuse/core'
import type { SidebarTableNode } from '~/lib'
export const useTablesStore = defineStore('tablesStore', () => {
const { includeM2M, ncNavigateTo } = useGlobal()
@ -12,7 +13,7 @@ export const useTablesStore = defineStore('tablesStore', () => {
const router = useRouter()
const route = router.currentRoute
const baseTables = ref<Map<string, TableType[]>>(new Map())
const baseTables = ref<Map<string, SidebarTableNode[]>>(new Map())
const basesStore = useBases()
// 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
const isPaginationLoading = ref(true)
@ -184,9 +186,13 @@ export const useViewsStore = defineStore('viewsStore', () => {
isViewDataLoading.value = true
try {
if (tablesStore.activeTable) tablesStore.activeTable.isViewsLoading = true
await loadViews()
} catch (e) {
console.error(e)
} finally {
if (tablesStore.activeTable) tablesStore.activeTable.isViewsLoading = false
}
},
{ immediate: true },
@ -271,10 +277,6 @@ export const useViewsStore = defineStore('viewsStore', () => {
}
}
watch(activeViewTitleOrId, () => {
isPaginationLoading.value = true
})
watch(activeView, (view) => {
if (!view) return
if (!view.base_id) return
@ -317,6 +319,7 @@ export const useViewsStore = defineStore('viewsStore', () => {
removeFromRecentViews,
activeSorts,
activeNestedFilters,
isActiveViewLocked,
}
})

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

@ -416,6 +416,208 @@ const formulas: Record<string, any> = {
syntax: 'WEEKDAY(date, [startDayOfWeek])',
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)

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

@ -6,8 +6,15 @@ export const replaceUrlsWithLink = (text: string): boolean | string => {
}
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
const out = rawText.replace(/URI::\((.*?)\)/g, (_, url) => {
const out = sanitisedText.replace(/URI::\((.*?)\)/g, (_, url) => {
found = true
const a = document.createElement('a')
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'
description: 'This article explains various numeric and logical operators that can be used in formula fields.'
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}` | Not equal to |
### String operators
| Operator | Sample | Description |
|----------|--------------------------|--------------------------|
| `&` | `{field1} & {field2}` | String concatenation |
## Related Articles
- [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