diff --git a/packages/nc-gui/components/smartsheet/Grid.vue b/packages/nc-gui/components/smartsheet/Grid.vue
index d007bfb074..43726b0b8b 100644
--- a/packages/nc-gui/components/smartsheet/Grid.vue
+++ b/packages/nc-gui/components/smartsheet/Grid.vue
@@ -162,15 +162,8 @@ const getContainerScrollForElement = (
return scroll
}
-const { selectCell, selectBlock, selectedRange, clearRangeRows, startSelectRange, selected } = useMultiSelect(
- fields,
- data,
- $$(editEnabled),
- isPkAvail,
- clearCell,
- makeEditable,
- scrollToCell,
- (e: KeyboardEvent) => {
+const { selectCell, startSelectRange, endSelectRange, clearSelectedRange, copyValue, isCellSelected, selectedCell } =
+ useMultiSelect(fields, data, $$(editEnabled), isPkAvail, clearCell, makeEditable, scrollToCell, (e: KeyboardEvent) => {
// ignore navigating if picker(Date, Time, DateTime, Year)
// or single/multi select options is open
const activePickerOrDropdownEl = document.querySelector(
@@ -188,9 +181,9 @@ const { selectCell, selectBlock, selectedRange, clearRangeRows, startSelectRange
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
if (e.key === ' ') {
- if (selected.row !== null && !editEnabled) {
+ if (selectedCell.row !== null && !editEnabled) {
e.preventDefault()
- const row = data.value[selected.row]
+ const row = data.value[selectedCell.row]
expandForm(row)
return true
}
@@ -208,37 +201,37 @@ const { selectCell, selectBlock, selectedRange, clearRangeRows, startSelectRange
if (cmdOrCtrl) {
switch (e.key) {
case 'ArrowUp':
- selected.row = 0
- selected.col = selected.col ?? 0
+ selectedCell.row = 0
+ selectedCell.col = selectedCell.col ?? 0
scrollToCell?.()
editEnabled = false
return true
case 'ArrowDown':
- selected.row = data.value.length - 1
- selected.col = selected.col ?? 0
+ selectedCell.row = data.value.length - 1
+ selectedCell.col = selectedCell.col ?? 0
scrollToCell?.()
editEnabled = false
return true
case 'ArrowRight':
- selected.row = selected.row ?? 0
- selected.col = fields.value?.length - 1
+ selectedCell.row = selectedCell.row ?? 0
+ selectedCell.col = fields.value?.length - 1
scrollToCell?.()
editEnabled = false
return true
case 'ArrowLeft':
- selected.row = selected.row ?? 0
- selected.col = 0
+ selectedCell.row = selectedCell.row ?? 0
+ selectedCell.col = 0
scrollToCell?.()
editEnabled = false
return true
}
}
- },
-)
+ })
function scrollToCell(row?: number | null, col?: number | null) {
- row = row ?? selected.row
- col = col ?? selected.col
+ row = row ?? selectedCell.row
+ col = col ?? selectedCell.col
+
if (row !== undefined && col !== undefined && row !== null && col !== null) {
// get active cell
const rows = tbodyEl.value?.querySelectorAll('tr')
@@ -393,10 +386,12 @@ useEventListener(document, 'keyup', async (e: KeyboardEvent) => {
/** On clicking outside of table reset active cell */
const smartTable = ref(null)
onClickOutside(smartTable, (e) => {
- clearRangeRows()
- if (selected.col === null) return
+ // do nothing if context menu was open
+ if (contextMenu.value) return
+ clearSelectedRange()
+ if (selectedCell.col === null) return
- const activeCol = fields.value[selected.col]
+ const activeCol = fields.value[selectedCell.col]
if (editEnabled && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return
@@ -417,25 +412,25 @@ onClickOutside(smartTable, (e) => {
return
}
- selected.row = null
- selected.col = null
+ selectedCell.row = null
+ selectedCell.col = null
})
const onNavigate = (dir: NavigateDir) => {
- if (selected.row === null || selected.col === null) return
+ if (selectedCell.row === null || selectedCell.col === null) return
editEnabled = false
switch (dir) {
case NavigateDir.NEXT:
- if (selected.row < data.value.length - 1) {
- selected.row++
+ if (selectedCell.row < data.value.length - 1) {
+ selectedCell.row++
} else {
addEmptyRow()
- selected.row++
+ selectedCell.row++
}
break
case NavigateDir.PREV:
- if (selected.row > 0) {
- selected.row--
+ if (selectedCell.row > 0) {
+ selectedCell.row--
}
break
}
@@ -696,9 +691,7 @@ watch(
:key="columnObj.id"
class="cell relative cursor-pointer nc-grid-cell"
:class="{
- 'active':
- (hasEditPermission && selected.col === colIndex && selected.row === rowIndex) ||
- (hasEditPermission && selectedRange(rowIndex, colIndex)),
+ 'active': hasEditPermission && isCellSelected(rowIndex, colIndex),
'nc-required-cell': isColumnRequiredAndNull(columnObj, row.row),
}"
:data-testid="`cell-${columnObj.title}-${rowIndex}`"
@@ -708,7 +701,7 @@ watch(
@click="selectCell(rowIndex, colIndex)"
@dblclick="makeEditable(row, columnObj)"
@mousedown="startSelectRange($event, rowIndex, colIndex)"
- @mouseover="selectBlock(rowIndex, colIndex)"
+ @mouseover="endSelectRange(rowIndex, colIndex)"
@contextmenu="showContextMenu($event, { row: rowIndex, col: colIndex })"
>
@@ -716,7 +709,7 @@ watch(
v-if="isVirtualCol(columnObj)"
v-model="row.row[columnObj.title]"
:column="columnObj"
- :active="selected.col === colIndex && selected.row === rowIndex"
+ :active="selectedCell.col === colIndex && selectedCell.row === rowIndex"
:row="row"
@navigate="onNavigate"
/>
@@ -726,10 +719,10 @@ watch(
v-model="row.row[columnObj.title]"
:column="columnObj"
:edit-enabled="
- !!hasEditPermission && !!editEnabled && selected.col === colIndex && selected.row === rowIndex
+ !!hasEditPermission && !!editEnabled && selectedCell.col === colIndex && selectedCell.row === rowIndex
"
:row-index="rowIndex"
- :active="selected.col === colIndex && selected.row === rowIndex"
+ :active="selectedCell.col === colIndex && selectedCell.row === rowIndex"
@update:edit-enabled="editEnabled = $event"
@save="updateOrSaveRow(row, columnObj.title, state)"
@navigate="onNavigate"
@@ -794,6 +787,13 @@ watch(
{{ $t('activity.insertRow') }}
+
+
+
+
diff --git a/packages/nc-gui/composables/useMultiSelect/cellRange.ts b/packages/nc-gui/composables/useMultiSelect/cellRange.ts
new file mode 100644
index 0000000000..c059cbfe12
--- /dev/null
+++ b/packages/nc-gui/composables/useMultiSelect/cellRange.ts
@@ -0,0 +1,54 @@
+export interface Cell {
+ row: number | null
+ col: number | null
+}
+
+export class CellRange {
+ _start: Cell | null
+ _end: Cell | null
+
+ constructor(start = null, end = null) {
+ this._start = start
+ this._end = end ?? this._start
+ }
+
+ get start() {
+ return {
+ row: Math.min(this._start?.row ?? NaN, this._end?.row ?? NaN),
+ col: Math.min(this._start?.col ?? NaN, this._end?.col ?? NaN),
+ }
+ }
+
+ get end() {
+ return {
+ row: Math.max(this._start?.row ?? NaN, this._end?.row ?? NaN),
+ col: Math.max(this._start?.col ?? NaN, this._end?.col ?? NaN),
+ }
+ }
+
+ startRange(value: Cell) {
+ if (value == null) {
+ return
+ }
+
+ this._start = value
+ this._end = value
+ }
+
+ endRange(value: Cell) {
+ if (value == null) {
+ return
+ }
+
+ this._end = value
+ }
+
+ clear() {
+ this._start = null
+ this._end = null
+ }
+
+ isEmpty() {
+ return this._start == null || this._end == null
+ }
+}
diff --git a/packages/nc-gui/composables/useMultiSelect/copyValue.ts b/packages/nc-gui/composables/useMultiSelect/copyValue.ts
new file mode 100644
index 0000000000..fec82fb7c9
--- /dev/null
+++ b/packages/nc-gui/composables/useMultiSelect/copyValue.ts
@@ -0,0 +1,29 @@
+import { UITypes } from '../../../nocodb-sdk'
+import type { ColumnType } from '../../../nocodb-sdk'
+import type { Row } from '~/lib'
+
+export const copyTable = async (rows: Row[], cols: ColumnType[]) => {
+ let copyHTML = ''
+ let copyPlainText = ''
+
+ rows.forEach((row) => {
+ let copyRow = ''
+ cols.forEach((col) => {
+ let value = (col.title && row.row[col.title]) ?? ''
+ if (typeof value === 'object') {
+ value = JSON.stringify(value)
+ }
+ copyRow += `${value} | `
+ copyPlainText = `${copyPlainText} ${value} \t`
+ })
+ copyHTML += `${copyRow}
`
+ copyPlainText = `${copyPlainText.trim()}\n`
+ })
+ copyHTML += '
'
+ copyPlainText.trim()
+
+ const blobHTML = new Blob([copyHTML], { type: 'text/html' })
+ const blobPlainText = new Blob([copyPlainText], { type: 'text/plain' })
+
+ return navigator.clipboard.write([new ClipboardItem({ [blobHTML.type]: blobHTML, [blobPlainText.type]: blobPlainText })])
+}
diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts
index 9b08d29554..c27ea269ed 100644
--- a/packages/nc-gui/composables/useMultiSelect/index.ts
+++ b/packages/nc-gui/composables/useMultiSelect/index.ts
@@ -1,18 +1,16 @@
import type { MaybeRef } from '@vueuse/core'
-import { UITypes } from 'nocodb-sdk'
-import { message, reactive, ref, unref, useCopy, useEventListener, useI18n } from '#imports'
-
-interface SelectedBlock {
- row: number | null
- col: number | null
-}
+import type { ColumnType } from 'nocodb-sdk'
+import type { Cell } from './cellRange'
+import { CellRange } from './cellRange'
+import { copyTable, message, reactive, ref, unref, useCopy, useEventListener, useI18n } from '#imports'
+import type { Row } from '~/lib'
/**
* Utility to help with multi-selecting rows/cells in the smartsheet
*/
export function useMultiSelect(
- fields: MaybeRef,
- data: MaybeRef,
+ fields: MaybeRef,
+ data: MaybeRef,
_editEnabled: MaybeRef,
isPkAvail: MaybeRef,
clearCell: Function,
@@ -26,99 +24,92 @@ export function useMultiSelect(
const editEnabled = ref(_editEnabled)
- const selected = reactive({ row: null, col: null })
-
- // save the first and the last column where the mouse is down while the value isSelectedRow is true
- const selectedRows = reactive({ startCol: NaN, endCol: NaN, startRow: NaN, endRow: NaN })
-
- // calculate the min and the max column where the mouse is down while the value isSelectedRow is true
- const rangeRows = reactive({ minRow: NaN, maxRow: NaN, minCol: NaN, maxCol: NaN })
-
- // check if mouse is down or up false=mouseup and true=mousedown
- let isSelectedBlock = $ref(false)
+ const selectedCell = reactive({ row: null, col: null })
+ const selectedRange = reactive(new CellRange())
+ let isMouseDown = $ref(false)
const columnLength = $computed(() => unref(fields)?.length)
+ async function copyValue(ctx?: Cell) {
+ try {
+ if (!selectedRange.isEmpty()) {
+ const cprows = unref(data).slice(selectedRange.start.row, selectedRange.end.row + 1) // slice the selected rows for copy
+ const cpcols = unref(fields).slice(selectedRange.start.col, selectedRange.end.col + 1) // slice the selected cols for copy
+
+ await copyTable(cprows, cpcols)
+ message.success(t('msg.info.copiedToClipboard'))
+ } else {
+ // if copy was called with context (right click position) - copy value from context
+ // else if there is just one selected cell, copy it's value
+ const cpRow = ctx?.row ?? selectedCell?.row
+ const cpCol = ctx?.col ?? selectedCell?.col
+
+ if (cpRow != null && cpCol != null) {
+ const rowObj = unref(data)[cpRow]
+ const columnObj = unref(fields)[cpCol]
+
+ let textToCopy = (columnObj.title && rowObj.row[columnObj.title]) || ''
+ if (typeof textToCopy === 'object') {
+ textToCopy = JSON.stringify(textToCopy)
+ }
+ await copy(textToCopy)
+ message.success(t('msg.info.copiedToClipboard'))
+ }
+ }
+ } catch {
+ message.error(t('msg.error.copyToClipboardError'))
+ }
+ }
+
function selectCell(row: number, col: number) {
- clearRangeRows()
- if (selected.row === row && selected.col === col) return
+ selectedRange.clear()
+ if (selectedCell.row === row && selectedCell.col === col) return
editEnabled.value = false
- selected.row = row
- selected.col = col
+ selectedCell.row = row
+ selectedCell.col = col
}
- function selectBlock(row: number, col: number) {
- // if selected.col and selected.row are null and isSelectedBlock is true that means you are selecting a block
- if (selected.col === null || selected.row === null) {
- if (isSelectedBlock) {
- // save the next value after the selectionStart
- selectedRows.endCol = col
- selectedRows.endRow = row
- }
- } else if (selected.col !== col || selected.row !== row) {
- // if selected.col and selected.row is not null but the selected col and row is not equal at the row and col where the mouse is clicking
- // and isSelectedBlock is true that means you are selecting a block
- if (isSelectedBlock) {
- selected.col = null
- selected.row = null
- // save the next value after the selectionStart
- selectedRows.endCol = col
- selectedRows.endRow = row
- }
+ function endSelectRange(row: number, col: number) {
+ if (!isMouseDown) {
+ return
}
+ selectedCell.row = null
+ selectedCell.col = null
+ selectedRange.endRange({ row, col })
}
- function selectedRange(row: number, col: number) {
- if (
- !isNaN(selectedRows.startRow) &&
- !isNaN(selectedRows.startCol) &&
- !isNaN(selectedRows.endRow) &&
- !isNaN(selectedRows.endCol)
- ) {
- // check if column selection is up or down
- rangeRows.minRow = Math.min(selectedRows.startRow, selectedRows.endRow)
- rangeRows.maxRow = Math.max(selectedRows.startRow, selectedRows.endRow)
- rangeRows.minCol = Math.min(selectedRows.startCol, selectedRows.endCol)
- rangeRows.maxCol = Math.max(selectedRows.startCol, selectedRows.endCol)
+ function isCellSelected(row: number, col: number) {
+ if (selectedCell?.row === row && selectedCell?.col === col) {
+ return true
+ }
- // return if the column is in between the selection
- return col >= rangeRows.minCol && col <= rangeRows.maxCol && row >= rangeRows.minRow && row <= rangeRows.maxRow
- } else {
+ if (selectedRange.isEmpty()) {
return false
}
+
+ return (
+ col >= selectedRange.start.col &&
+ col <= selectedRange.end.col &&
+ row >= selectedRange.start.row &&
+ row <= selectedRange.end.row
+ )
}
function startSelectRange(event: MouseEvent, row: number, col: number) {
- // if editEnabled but the selected col or the selected row is not equal like the actual row or col, enabled selected multiple rows
- if (unref(editEnabled) && (selected.col !== col || selected.row !== row)) {
- event.preventDefault()
- } else if (!unref(editEnabled)) {
- // if editEnabled is not true, enabled selected multiple rows
- event.preventDefault()
+ // if there was a right click on selected range, don't restart the selection
+ const leftClickButton = 0
+ if (event?.button !== leftClickButton && isCellSelected(row, col)) {
+ return
}
- // clear the selection when the mouse is down
- selectedRows.startCol = NaN
- selectedRows.endCol = NaN
- selectedRows.startRow = NaN
- selectedRows.endRow = NaN
- // asing where the selection start
- selectedRows.startCol = col
- selectedRows.startRow = row
- isSelectedBlock = true
- }
+ if (unref(editEnabled)) {
+ event.preventDefault()
+ return
+ }
- function clearRangeRows() {
- // when the selection starts or ends or when enter/arrow/tab is pressed
- // this clear the previous selection
- rangeRows.minCol = NaN
- rangeRows.maxCol = NaN
- rangeRows.minRow = NaN
- rangeRows.maxRow = NaN
- selectedRows.startRow = NaN
- selectedRows.startCol = NaN
- selectedRows.endRow = NaN
- selectedRows.endCol = NaN
+ isMouseDown = true
+ selectedRange.clear()
+ selectedRange.startRange({ row, col })
}
useEventListener(document, 'mouseup', (e) => {
@@ -127,7 +118,7 @@ export function useMultiSelect(
e.preventDefault()
}
- isSelectedBlock = false
+ isMouseDown = false
})
const onKeyDown = async (e: KeyboardEvent) => {
@@ -136,41 +127,36 @@ export function useMultiSelect(
return true
}
- if (
- !isNaN(selectedRows.startRow) &&
- !isNaN(selectedRows.startCol) &&
- !isNaN(selectedRows.endRow) &&
- !isNaN(selectedRows.endCol)
- ) {
+ if (!selectedRange.isEmpty()) {
// In case the user press tabs or arrows keys
- selected.row = selectedRows.startRow
- selected.col = selectedRows.startCol
+ selectedCell.row = selectedRange.start.row
+ selectedCell.col = selectedRange.start.col
}
- if (selected.row === null || selected.col === null) return
+ if (selectedCell.row === null || selectedCell.col === null) return
/** on tab key press navigate through cells */
switch (e.key) {
case 'Tab':
e.preventDefault()
- clearRangeRows()
+ selectedRange.clear()
if (e.shiftKey) {
- if (selected.col > 0) {
- selected.col--
+ if (selectedCell.col > 0) {
+ selectedCell.col--
editEnabled.value = false
- } else if (selected.row > 0) {
- selected.row--
- selected.col = unref(columnLength) - 1
+ } else if (selectedCell.row > 0) {
+ selectedCell.row--
+ selectedCell.col = unref(columnLength) - 1
editEnabled.value = false
}
} else {
- if (selected.col < unref(columnLength) - 1) {
- selected.col++
+ if (selectedCell.col < unref(columnLength) - 1) {
+ selectedCell.col++
editEnabled.value = false
- } else if (selected.row < unref(data).length - 1) {
- selected.row++
- selected.col = 0
+ } else if (selectedCell.row < unref(data).length - 1) {
+ selectedCell.row++
+ selectedCell.col = 0
editEnabled.value = false
}
}
@@ -179,90 +165,63 @@ export function useMultiSelect(
/** on enter key press make cell editable */
case 'Enter':
e.preventDefault()
- clearRangeRows()
- makeEditable(unref(data)[selected.row], unref(fields)[selected.col])
+ selectedRange.clear()
+ makeEditable(unref(data)[selectedCell.row], unref(fields)[selectedCell.col])
break
/** on delete key press clear cell */
case 'Delete':
e.preventDefault()
- clearRangeRows()
- await clearCell(selected as { row: number; col: number })
+ selectedRange.clear()
+ await clearCell(selectedCell as { row: number; col: number })
break
/** on arrow key press navigate through cells */
case 'ArrowRight':
e.preventDefault()
- clearRangeRows()
- if (selected.col < unref(columnLength) - 1) {
- selected.col++
+ selectedRange.clear()
+ if (selectedCell.col < unref(columnLength) - 1) {
+ selectedCell.col++
scrollToActiveCell?.()
editEnabled.value = false
}
break
case 'ArrowLeft':
- clearRangeRows()
+ selectedRange.clear()
e.preventDefault()
- clearRangeRows()
- if (selected.col > 0) {
- selected.col--
+ if (selectedCell.col > 0) {
+ selectedCell.col--
scrollToActiveCell?.()
editEnabled.value = false
}
break
case 'ArrowUp':
- clearRangeRows()
+ selectedRange.clear()
e.preventDefault()
- clearRangeRows()
- if (selected.row > 0) {
- selected.row--
+ if (selectedCell.row > 0) {
+ selectedCell.row--
scrollToActiveCell?.()
editEnabled.value = false
}
break
case 'ArrowDown':
- clearRangeRows()
+ selectedRange.clear()
e.preventDefault()
- clearRangeRows()
- if (selected.row < unref(data).length - 1) {
- selected.row++
+ if (selectedCell.row < unref(data).length - 1) {
+ selectedCell.row++
scrollToActiveCell?.()
editEnabled.value = false
}
break
default:
{
- const rowObj = unref(data)[selected.row]
+ const rowObj = unref(data)[selectedCell.row]
- const columnObj = unref(fields)[selected.col]
-
- let cptext = '' // variable for save the text to be copy
-
- if (!isNaN(rangeRows.minRow) && !isNaN(rangeRows.maxRow) && !isNaN(rangeRows.minCol) && !isNaN(rangeRows.maxCol)) {
- const cprows = unref(data).slice(rangeRows.minRow, rangeRows.maxRow + 1) // slice the selected rows for copy
-
- const cpcols = unref(fields).slice(rangeRows.minCol, rangeRows.maxCol + 1) // slice the selected cols for copy
-
- cprows.forEach((row) => {
- cpcols.forEach((col) => {
- // todo: JSON stringify the attachment cell and LTAR contents for copy
- // filter attachment cells and LATR cells from copy
- if (col.uidt !== UITypes.Attachment && col.uidt !== UITypes.LinkToAnotherRecord) {
- cptext = `${cptext} ${row.row[col.title]} \t`
- }
- })
-
- cptext = `${cptext.trim()}\n`
- })
-
- cptext.trim()
- } else {
- cptext = rowObj.row[columnObj.title] || ''
- }
+ const columnObj = unref(fields)[selectedCell.col]
if ((!unref(editEnabled) && e.metaKey) || e.ctrlKey) {
switch (e.keyCode) {
// copy - ctrl/cmd +c
case 67:
- await copy(cptext)
+ await copyValue()
break
}
}
@@ -277,7 +236,7 @@ export function useMultiSelect(
// Update not allowed for table which doesn't have primary Key
return message.info(t('msg.info.updateNotAllowedWithoutPK'))
}
- if (makeEditable(rowObj, columnObj)) {
+ if (makeEditable(rowObj, columnObj) && columnObj.title) {
rowObj.row[columnObj.title] = ''
}
// editEnabled = true
@@ -291,12 +250,11 @@ export function useMultiSelect(
return {
selectCell,
- selectBlock,
- selectedRange,
- clearRangeRows,
startSelectRange,
- selected,
- selectedRows,
- rangeRows,
+ endSelectRange,
+ clearSelectedRange: selectedRange.clear.bind(selectedRange),
+ copyValue,
+ isCellSelected,
+ selectedCell,
}
}
diff --git a/packages/nc-gui/context/index.ts b/packages/nc-gui/context/index.ts
index 21278b9876..5cbff053fa 100644
--- a/packages/nc-gui/context/index.ts
+++ b/packages/nc-gui/context/index.ts
@@ -26,7 +26,7 @@ export const ReloadViewDataHookInj: InjectionKey> = Sy
export const ReloadViewMetaHookInj: InjectionKey> = Symbol('reload-view-meta-injection')
export const ReloadRowDataHookInj: InjectionKey> = Symbol('reload-row-data-injection')
export const OpenNewRecordFormHookInj: InjectionKey> = Symbol('open-new-record-form-injection')
-export const FieldsInj: InjectionKey[> = Symbol('fields-injection')
+export const FieldsInj: InjectionKey][> = Symbol('fields-injection')
export const EditModeInj: InjectionKey][> = Symbol('edit-mode-injection')
export const SharedViewPasswordInj: InjectionKey][> = Symbol('shared-view-password-injection')
export const CellUrlDisableOverlayInj: InjectionKey][> = Symbol('cell-url-disable-url')
diff --git a/packages/nc-gui/lang/ar.json b/packages/nc-gui/lang/ar.json
index 4b065ce319..51d9564f32 100644
--- a/packages/nc-gui/lang/ar.json
+++ b/packages/nc-gui/lang/ar.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "تصدير البيانات الوصفية للمشروع بنجاح",
diff --git a/packages/nc-gui/lang/bn_IN.json b/packages/nc-gui/lang/bn_IN.json
index aaa7d443f5..6bd3dbea4f 100644
--- a/packages/nc-gui/lang/bn_IN.json
+++ b/packages/nc-gui/lang/bn_IN.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "প্রকল্প মেটাডেটা সফলভাবে রফতানি করেছে",
diff --git a/packages/nc-gui/lang/da.json b/packages/nc-gui/lang/da.json
index 5e07bf3d1f..25b4a074a2 100644
--- a/packages/nc-gui/lang/da.json
+++ b/packages/nc-gui/lang/da.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Project Metadata eksporteres med succes",
diff --git a/packages/nc-gui/lang/de.json b/packages/nc-gui/lang/de.json
index 79ef0efc39..f4e43c0726 100644
--- a/packages/nc-gui/lang/de.json
+++ b/packages/nc-gui/lang/de.json
@@ -667,7 +667,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Projektmetadaten erfolgreich exportiert",
diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json
index e26dd5bc9e..50b78578ca 100644
--- a/packages/nc-gui/lang/en.json
+++ b/packages/nc-gui/lang/en.json
@@ -674,7 +674,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Project metadata exported successfully",
diff --git a/packages/nc-gui/lang/es.json b/packages/nc-gui/lang/es.json
index 8e98ddc391..a98475ea72 100644
--- a/packages/nc-gui/lang/es.json
+++ b/packages/nc-gui/lang/es.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Metadatos del proyecto exportados con éxito.",
diff --git a/packages/nc-gui/lang/fa.json b/packages/nc-gui/lang/fa.json
index 5fe34c4a89..5207705649 100644
--- a/packages/nc-gui/lang/fa.json
+++ b/packages/nc-gui/lang/fa.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "فراداده پروژه با موفقیت خارج شد",
diff --git a/packages/nc-gui/lang/fi.json b/packages/nc-gui/lang/fi.json
index e19380b494..02bfe2c8dc 100644
--- a/packages/nc-gui/lang/fi.json
+++ b/packages/nc-gui/lang/fi.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Project Metadata viedään onnistuneesti",
diff --git a/packages/nc-gui/lang/fr.json b/packages/nc-gui/lang/fr.json
index 188d120826..4aea9a7be6 100644
--- a/packages/nc-gui/lang/fr.json
+++ b/packages/nc-gui/lang/fr.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Les métadonnées de projet sont exportées avec succès",
diff --git a/packages/nc-gui/lang/he.json b/packages/nc-gui/lang/he.json
index f9501712f7..b023a59001 100644
--- a/packages/nc-gui/lang/he.json
+++ b/packages/nc-gui/lang/he.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "פרויקט Metadata מיוצא בהצלחה",
diff --git a/packages/nc-gui/lang/hi.json b/packages/nc-gui/lang/hi.json
index fdd21d3c66..3036ba6bc7 100644
--- a/packages/nc-gui/lang/hi.json
+++ b/packages/nc-gui/lang/hi.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "परियोजना मेटाडेटा सफलतापूर्वक निर्यात की गई",
diff --git a/packages/nc-gui/lang/hr.json b/packages/nc-gui/lang/hr.json
index 01cd148906..44d3e0bdc6 100644
--- a/packages/nc-gui/lang/hr.json
+++ b/packages/nc-gui/lang/hr.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Projektni metapodaci su uspješno izvozili",
diff --git a/packages/nc-gui/lang/id.json b/packages/nc-gui/lang/id.json
index afe46061b1..15657f7320 100644
--- a/packages/nc-gui/lang/id.json
+++ b/packages/nc-gui/lang/id.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Metadata proyek berhasil diekspor",
diff --git a/packages/nc-gui/lang/it.json b/packages/nc-gui/lang/it.json
index 8714d8618a..76864f1326 100644
--- a/packages/nc-gui/lang/it.json
+++ b/packages/nc-gui/lang/it.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Metadati del progetto esportati con successo",
diff --git a/packages/nc-gui/lang/ja.json b/packages/nc-gui/lang/ja.json
index b2806c2065..cb0333f206 100644
--- a/packages/nc-gui/lang/ja.json
+++ b/packages/nc-gui/lang/ja.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "プロジェクトメタデータは正常にエクスポートされました",
diff --git a/packages/nc-gui/lang/ko.json b/packages/nc-gui/lang/ko.json
index 959693e71e..b91b3ea389 100644
--- a/packages/nc-gui/lang/ko.json
+++ b/packages/nc-gui/lang/ko.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "프로젝트 메타 데이터를 성공적으로 내보냈습니다.",
diff --git a/packages/nc-gui/lang/lv.json b/packages/nc-gui/lang/lv.json
index 0fa407eab1..07081cb1bd 100644
--- a/packages/nc-gui/lang/lv.json
+++ b/packages/nc-gui/lang/lv.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Projekta metadati eksportēti veiksmīgi",
diff --git a/packages/nc-gui/lang/nl.json b/packages/nc-gui/lang/nl.json
index 130f087c3e..9c714e9ec1 100644
--- a/packages/nc-gui/lang/nl.json
+++ b/packages/nc-gui/lang/nl.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Project metadata met succes geëxporteerd",
diff --git a/packages/nc-gui/lang/no.json b/packages/nc-gui/lang/no.json
index bc4f661903..a30561c1c0 100644
--- a/packages/nc-gui/lang/no.json
+++ b/packages/nc-gui/lang/no.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Prosjektmetadata eksporteres vellykket",
diff --git a/packages/nc-gui/lang/pl.json b/packages/nc-gui/lang/pl.json
index e089c10f0d..6b8e0bca13 100644
--- a/packages/nc-gui/lang/pl.json
+++ b/packages/nc-gui/lang/pl.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Klucz parametru nie może być pusty",
"duplicateParameterKeysAreNotAllowed": "Zduplikowane klucze parametrów są niedozwolone",
"fieldRequired": "{value} nie może być puste.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Pomyślnie wyeksportowano metadane projektu",
diff --git a/packages/nc-gui/lang/pt.json b/packages/nc-gui/lang/pt.json
index 1d340c27c4..9de8d9c21f 100644
--- a/packages/nc-gui/lang/pt.json
+++ b/packages/nc-gui/lang/pt.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Metadados do projeto exportado com sucesso",
diff --git a/packages/nc-gui/lang/pt_BR.json b/packages/nc-gui/lang/pt_BR.json
index 96e7305f29..932669389f 100644
--- a/packages/nc-gui/lang/pt_BR.json
+++ b/packages/nc-gui/lang/pt_BR.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Metadados do projeto exportado com sucesso",
diff --git a/packages/nc-gui/lang/ru.json b/packages/nc-gui/lang/ru.json
index d175e86a89..70df80d496 100644
--- a/packages/nc-gui/lang/ru.json
+++ b/packages/nc-gui/lang/ru.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Метаданные проекта успешно экспортированы",
diff --git a/packages/nc-gui/lang/sl.json b/packages/nc-gui/lang/sl.json
index 02504f569e..c051e52cb3 100644
--- a/packages/nc-gui/lang/sl.json
+++ b/packages/nc-gui/lang/sl.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Projekt Metapodatki se je uspešno izvozil",
diff --git a/packages/nc-gui/lang/sv.json b/packages/nc-gui/lang/sv.json
index 90c5518c41..05e67399e3 100644
--- a/packages/nc-gui/lang/sv.json
+++ b/packages/nc-gui/lang/sv.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Projektmetadata exporterades framgångsrikt",
diff --git a/packages/nc-gui/lang/th.json b/packages/nc-gui/lang/th.json
index ff5be0bf66..e2e51c7050 100644
--- a/packages/nc-gui/lang/th.json
+++ b/packages/nc-gui/lang/th.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "ข้อมูลเมตาของโครงการส่งออกเรียบร้อยแล้ว",
diff --git a/packages/nc-gui/lang/tr.json b/packages/nc-gui/lang/tr.json
index 83ea91fea2..cbdf0ec321 100644
--- a/packages/nc-gui/lang/tr.json
+++ b/packages/nc-gui/lang/tr.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Proje metaverileri başarıyla dışa aktarıldı",
diff --git a/packages/nc-gui/lang/uk.json b/packages/nc-gui/lang/uk.json
index 9b24d55df9..0c8d5d034d 100644
--- a/packages/nc-gui/lang/uk.json
+++ b/packages/nc-gui/lang/uk.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Метадані проекту успішно експортується",
diff --git a/packages/nc-gui/lang/vi.json b/packages/nc-gui/lang/vi.json
index 6a8806bfc0..87d7ca6301 100644
--- a/packages/nc-gui/lang/vi.json
+++ b/packages/nc-gui/lang/vi.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "Metadata dự án xuất khẩu thành công",
diff --git a/packages/nc-gui/lang/zh-Hans.json b/packages/nc-gui/lang/zh-Hans.json
index 017bd56ede..078389b1e0 100644
--- a/packages/nc-gui/lang/zh-Hans.json
+++ b/packages/nc-gui/lang/zh-Hans.json
@@ -368,7 +368,7 @@
"setPrimary": "设置为主要值",
"addRow": "添加新行",
"saveRow": "保存行",
- "saveAndExit": "Save & Exit",
+ "saveAndExit": "保存并退出",
"saveAndStay": "Save & Stay",
"insertRow": "插入新行",
"deleteRow": "删除行",
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "参数键不能为空",
"duplicateParameterKeysAreNotAllowed": "不允许重复的参数键",
"fieldRequired": "{value} 不能为空。",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "项目元数据成功导出",
diff --git a/packages/nc-gui/lang/zh-Hant.json b/packages/nc-gui/lang/zh-Hant.json
index 708ce4e23e..2682ea92ce 100644
--- a/packages/nc-gui/lang/zh-Hant.json
+++ b/packages/nc-gui/lang/zh-Hant.json
@@ -666,7 +666,8 @@
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
- "projectNotAccessible": "Project not accessible"
+ "projectNotAccessible": "Project not accessible",
+ "copyToClipboardError": "Failed to copy to clipboard"
},
"toast": {
"exportMetadata": "專案中繼資料已成功匯出",
diff --git a/packages/nc-gui/package-lock.json b/packages/nc-gui/package-lock.json
index 1c5596e655..4198863821 100644
--- a/packages/nc-gui/package-lock.json
+++ b/packages/nc-gui/package-lock.json
@@ -33,6 +33,7 @@
"sortablejs": "^1.15.0",
"tinycolor2": "^1.4.2",
"unique-names-generator": "^4.7.1",
+ "validator": "^13.7.0",
"vue-dompurify-html": "^3.0.0",
"vue-github-button": "^3.0.3",
"vue-i18n": "^9.2.2",
@@ -63,6 +64,7 @@
"@types/papaparse": "^5.3.2",
"@types/sortablejs": "^1.13.0",
"@types/tinycolor2": "^1.4.3",
+ "@types/validator": "^13.7.10",
"@vitest/ui": "^0.18.0",
"@vue/compiler-sfc": "^3.2.37",
"@vue/test-utils": "^2.0.2",
@@ -85,6 +87,28 @@
"windicss": "^3.5.6"
}
},
+ "../nocodb-sdk": {
+ "version": "0.99.2",
+ "license": "AGPL-3.0-or-later",
+ "dependencies": {
+ "axios": "^0.21.1",
+ "jsep": "^1.3.6"
+ },
+ "devDependencies": {
+ "@typescript-eslint/eslint-plugin": "^4.0.1",
+ "@typescript-eslint/parser": "^4.0.1",
+ "cspell": "^4.1.0",
+ "eslint": "^7.8.0",
+ "eslint-config-prettier": "^6.11.0",
+ "eslint-plugin-eslint-comments": "^3.2.0",
+ "eslint-plugin-functional": "^3.0.2",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-prettier": "^4.0.0",
+ "npm-run-all": "^4.1.5",
+ "prettier": "^2.1.1",
+ "typescript": "^4.0.2"
+ }
+ },
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@@ -3078,6 +3102,12 @@
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
"dev": true
},
+ "node_modules/@types/validator": {
+ "version": "13.7.10",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz",
+ "integrity": "sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ==",
+ "dev": true
+ },
"node_modules/@types/web-bluetooth": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz",
@@ -8383,6 +8413,7 @@
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
+ "devOptional": true,
"funding": [
{
"type": "individual",
@@ -11738,21 +11769,8 @@
}
},
"node_modules/nocodb-sdk": {
- "version": "0.99.2",
- "resolved": "file:../nocodb-sdk",
- "license": "AGPL-3.0-or-later",
- "dependencies": {
- "axios": "^0.21.1",
- "jsep": "^1.3.6"
- }
- },
- "node_modules/nocodb-sdk/node_modules/axios": {
- "version": "0.21.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
- "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
- "dependencies": {
- "follow-redirects": "^1.14.0"
- }
+ "resolved": "../nocodb-sdk",
+ "link": true
},
"node_modules/node-abi": {
"version": "3.23.0",
@@ -16047,6 +16065,14 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "node_modules/validator": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
+ "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/vite": {
"version": "2.9.15",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.15.tgz",
@@ -19769,6 +19795,12 @@
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
"dev": true
},
+ "@types/validator": {
+ "version": "13.7.10",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz",
+ "integrity": "sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ==",
+ "dev": true
+ },
"@types/web-bluetooth": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz",
@@ -23561,7 +23593,8 @@
"follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
- "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
+ "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
+ "devOptional": true
},
"form-data": {
"version": "4.0.0",
@@ -26029,20 +26062,22 @@
}
},
"nocodb-sdk": {
- "version": "0.99.2",
+ "version": "file:../nocodb-sdk",
"requires": {
+ "@typescript-eslint/eslint-plugin": "^4.0.1",
+ "@typescript-eslint/parser": "^4.0.1",
"axios": "^0.21.1",
- "jsep": "^1.3.6"
- },
- "dependencies": {
- "axios": {
- "version": "0.21.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
- "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
- "requires": {
- "follow-redirects": "^1.14.0"
- }
- }
+ "cspell": "^4.1.0",
+ "eslint": "^7.8.0",
+ "eslint-config-prettier": "^6.11.0",
+ "eslint-plugin-eslint-comments": "^3.2.0",
+ "eslint-plugin-functional": "^3.0.2",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-prettier": "^4.0.0",
+ "jsep": "^1.3.6",
+ "npm-run-all": "^4.1.5",
+ "prettier": "^2.1.1",
+ "typescript": "^4.0.2"
}
},
"node-abi": {
@@ -29245,6 +29280,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "validator": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
+ "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw=="
+ },
"vite": {
"version": "2.9.15",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.15.tgz",
diff --git a/packages/nc-gui/package.json b/packages/nc-gui/package.json
index a1cb82564b..ab0ed240b7 100644
--- a/packages/nc-gui/package.json
+++ b/packages/nc-gui/package.json
@@ -56,6 +56,7 @@
"sortablejs": "^1.15.0",
"tinycolor2": "^1.4.2",
"unique-names-generator": "^4.7.1",
+ "validator": "^13.7.0",
"vue-dompurify-html": "^3.0.0",
"vue-github-button": "^3.0.3",
"vue-i18n": "^9.2.2",
@@ -86,6 +87,7 @@
"@types/papaparse": "^5.3.2",
"@types/sortablejs": "^1.13.0",
"@types/tinycolor2": "^1.4.3",
+ "@types/validator": "^13.7.10",
"@vitest/ui": "^0.18.0",
"@vue/compiler-sfc": "^3.2.37",
"@vue/test-utils": "^2.0.2",
diff --git a/packages/nc-gui/utils/cell.ts b/packages/nc-gui/utils/cell.ts
index 9214578bf2..4b516e7d6d 100644
--- a/packages/nc-gui/utils/cell.ts
+++ b/packages/nc-gui/utils/cell.ts
@@ -46,8 +46,7 @@ export const isAutoSaved = (column: ColumnType) =>
UITypes.Duration,
].includes(column.uidt as UITypes)
-export const isManualSaved = (column: ColumnType) =>
- [UITypes.Currency, UITypes.Year, UITypes.Time].includes(column.uidt as UITypes)
+export const isManualSaved = (column: ColumnType) => [UITypes.Currency].includes(column.uidt as UITypes)
export const isPrimary = (column: ColumnType) => !!column.pv
diff --git a/packages/nc-gui/utils/parsers/ExcelTemplateAdapter.ts b/packages/nc-gui/utils/parsers/ExcelTemplateAdapter.ts
index c9048cb724..d3d60b5359 100644
--- a/packages/nc-gui/utils/parsers/ExcelTemplateAdapter.ts
+++ b/packages/nc-gui/utils/parsers/ExcelTemplateAdapter.ts
@@ -132,15 +132,15 @@ export default class ExcelTemplateAdapter extends TemplateGenerator {
if (column.uidt === UITypes.SingleLineText) {
// check for long text
- if (isMultiLineTextType(rows)) {
+ if (isMultiLineTextType(rows, col)) {
column.uidt = UITypes.LongText
}
- if (isEmailType(rows)) {
+ if (isEmailType(rows, col)) {
column.uidt = UITypes.Email
}
- if (isUrlType(rows)) {
+ if (isUrlType(rows, col)) {
column.uidt = UITypes.URL
} else {
const vals = rows
@@ -148,7 +148,7 @@ export default class ExcelTemplateAdapter extends TemplateGenerator {
.map((r: any) => r[col])
.filter((v: any) => v !== null && v !== undefined && v.toString().trim() !== '')
- const checkboxType = isCheckboxType(vals)
+ const checkboxType = isCheckboxType(vals, col)
if (checkboxType.length === 1) {
column.uidt = UITypes.Checkbox
} else {
diff --git a/packages/nc-gui/utils/parsers/parserHelpers.ts b/packages/nc-gui/utils/parsers/parserHelpers.ts
index 7df86117c0..b0759389b6 100644
--- a/packages/nc-gui/utils/parsers/parserHelpers.ts
+++ b/packages/nc-gui/utils/parsers/parserHelpers.ts
@@ -20,11 +20,11 @@ const booleanOptions = [
]
const aggBooleanOptions: any = booleanOptions.reduce((obj, o) => ({ ...obj, ...o }), {})
-const getColVal = (row: any, col = null) => {
+const getColVal = (row: any, col?: number) => {
return row && col ? row[col] : row
}
-export const isCheckboxType: any = (values: [], col = null) => {
+export const isCheckboxType: any = (values: [], col?: number) => {
let options = booleanOptions
for (let i = 0; i < values.length; i++) {
const val = getColVal(values[i], col)
@@ -45,7 +45,7 @@ export const getCheckboxValue = (value: any) => {
return value && aggBooleanOptions[value]
}
-export const isMultiLineTextType = (values: [], col = null) => {
+export const isMultiLineTextType = (values: [], col?: number) => {
return values.some(
(r) => (getColVal(r, col) || '').toString().match(/[\r\n]/) || (getColVal(r, col) || '').toString().length > 255,
)
@@ -107,13 +107,15 @@ export const isDecimalType = (colData: []) =>
return v && parseInt(v) !== +v
})
-export const isEmailType = (colData: []) =>
- colData.some((v: any) => {
+export const isEmailType = (colData: [], col?: number) =>
+ colData.some((r: any) => {
+ const v = getColVal(r, col)
return v && validateEmail(v)
})
-export const isUrlType = (colData: []) =>
- colData.some((v: any) => {
+export const isUrlType = (colData: [], col?: number) =>
+ colData.some((r: any) => {
+ const v = getColVal(r, col)
return v && isValidURL(v)
})
diff --git a/packages/nc-gui/utils/urlUtils.ts b/packages/nc-gui/utils/urlUtils.ts
index e6bf9d8c74..d9d807d446 100644
--- a/packages/nc-gui/utils/urlUtils.ts
+++ b/packages/nc-gui/utils/urlUtils.ts
@@ -1,3 +1,5 @@
+import isURL from 'validator/lib/isURL'
+
export const replaceUrlsWithLink = (text: string): boolean | string => {
if (!text) {
return false
@@ -17,12 +19,8 @@ export const replaceUrlsWithLink = (text: string): boolean | string => {
return found && out
}
-// ref : https://stackoverflow.com/a/5717133
export const isValidURL = (str: string) => {
- const pattern =
- /^(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00A1-\uFFFF0-9]-*)*[a-z\u00A1-\uFFFF0-9]+)(?:\.(?:[a-z\u00A1-\uFFFF0-9]-*)*[a-z\u00A1-\uFFFF0-9]+)*(?:\.(?:[a-z\u00A1-\uFFFF]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i
-
- return pattern.test(str)
+ return isURL(str)
}
export const openLink = (path: string, baseURL?: string, target = '_blank') => {
diff --git a/packages/nc-gui/windi.config.ts b/packages/nc-gui/windi.config.ts
index 306b909724..7508c05def 100644
--- a/packages/nc-gui/windi.config.ts
+++ b/packages/nc-gui/windi.config.ts
@@ -20,7 +20,7 @@ export default defineConfig({
},
darkMode: 'class',
-
+ safelist: ['text-yellow-500', 'text-sky-500', 'text-red-500'],
plugins: [
scrollbar,
animations,
diff --git a/packages/nocodb/package-lock.json b/packages/nocodb/package-lock.json
index 79d260b449..5586097908 100644
--- a/packages/nocodb/package-lock.json
+++ b/packages/nocodb/package-lock.json
@@ -151,6 +151,28 @@
"vuedraggable": "^2.24.3"
}
},
+ "../nocodb-sdk": {
+ "version": "0.99.2",
+ "license": "AGPL-3.0-or-later",
+ "dependencies": {
+ "axios": "^0.21.1",
+ "jsep": "^1.3.6"
+ },
+ "devDependencies": {
+ "@typescript-eslint/eslint-plugin": "^4.0.1",
+ "@typescript-eslint/parser": "^4.0.1",
+ "cspell": "^4.1.0",
+ "eslint": "^7.8.0",
+ "eslint-config-prettier": "^6.11.0",
+ "eslint-plugin-eslint-comments": "^3.2.0",
+ "eslint-plugin-functional": "^3.0.2",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-prettier": "^4.0.0",
+ "npm-run-all": "^4.1.5",
+ "prettier": "^2.1.1",
+ "typescript": "^4.0.2"
+ }
+ },
"node_modules/@azure/abort-controller": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
@@ -10634,13 +10656,8 @@
"dev": true
},
"node_modules/nocodb-sdk": {
- "version": "0.99.2",
- "resolved": "file:../nocodb-sdk",
- "license": "AGPL-3.0-or-later",
- "dependencies": {
- "axios": "^0.21.1",
- "jsep": "^1.3.6"
- }
+ "resolved": "../nocodb-sdk",
+ "link": true
},
"node_modules/node-abort-controller": {
"version": "3.0.1",
@@ -26042,10 +26059,22 @@
"dev": true
},
"nocodb-sdk": {
- "version": "0.99.2",
+ "version": "file:../nocodb-sdk",
"requires": {
+ "@typescript-eslint/eslint-plugin": "^4.0.1",
+ "@typescript-eslint/parser": "^4.0.1",
"axios": "^0.21.1",
- "jsep": "^1.3.6"
+ "cspell": "^4.1.0",
+ "eslint": "^7.8.0",
+ "eslint-config-prettier": "^6.11.0",
+ "eslint-plugin-eslint-comments": "^3.2.0",
+ "eslint-plugin-functional": "^3.0.2",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-prettier": "^4.0.0",
+ "jsep": "^1.3.6",
+ "npm-run-all": "^4.1.5",
+ "prettier": "^2.1.1",
+ "typescript": "^4.0.2"
}
},
"node-abort-controller": {
] |