diff --git a/.github/uffizzi/docker-compose.uffizzi.yml b/.github/uffizzi/docker-compose.uffizzi.yml index 318ad64367..ae78efb590 100644 --- a/.github/uffizzi/docker-compose.uffizzi.yml +++ b/.github/uffizzi/docker-compose.uffizzi.yml @@ -31,7 +31,7 @@ services: MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: password MYSQL_USER: noco - image: "mysql:8.0.32" + image: "mysql:8.0.35" deploy: resources: limits: diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index bf8ccc2322..5a6c5cd476 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -92,7 +92,7 @@ jobs: export NODE_OPTIONS="--max_old_space_size=16384" NOCODB_SDK_PKG_NAME=nocodb-sdk-daily targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} targetVersion=${{ github.event.inputs.tag || inputs.tag }} node scripts/bumpNocodbSdkVersion.js && - pnpm --filter=${NOCODB_SDK_PKG_NAME} install --ignore-scripts --no-frozen-lockfile && pnpm --filter=${NOCODB_SDK_PKG_NAME} run build && + pnpm --filter=${NOCODB_SDK_PKG_NAME} install --ignore-scripts --no-frozen-lockfile --ignore-workspace && pnpm --filter=${NOCODB_SDK_PKG_NAME} run build && targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} node scripts/upgradeNocodbSdk.js && targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} targetVersion=${{ github.event.inputs.tag || inputs.tag }} node scripts/bumpNcGuiVersion.js && pnpm --filter=nc-gui install --ignore-scripts --no-frozen-lockfile && diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index d2b25a696c..fd8e963f72 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -61,7 +61,7 @@ jobs: fi echo $NOCODB_SDK_PKG_NAME targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} targetVersion=${{ github.event.inputs.tag || inputs.tag }} node scripts/bumpNocodbSdkVersion.js && - pnpm --filter=${NOCODB_SDK_PKG_NAME} install --ignore-scripts --no-frozen-lockfile && pnpm --filter=${NOCODB_SDK_PKG_NAME} run build && pnpm --filter=${NOCODB_SDK_PKG_NAME} publish --no-git-checks && + pnpm --filter=${NOCODB_SDK_PKG_NAME} install --ignore-scripts --no-frozen-lockfile --ignore-workspace && pnpm --filter=${NOCODB_SDK_PKG_NAME} run build && pnpm --filter=${NOCODB_SDK_PKG_NAME} publish --no-git-checks && sleep 90 && targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} node scripts/upgradeNocodbSdk.js && targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} targetVersion=${{ github.event.inputs.tag || inputs.tag }} node scripts/bumpNcGuiVersion.js && diff --git a/README.md b/README.md index fbeab2db7d..941a7e8934 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadshe --> -[![Stargazers repo roster for @nocodb/nocodb](https://reporoster.com/stars/nocodb/nocodb)](https://github.com/nocodb/nocodb/stargazers) +[![Stargazers repo roster for @nocodb/nocodb](http://reporoster.com/stars/nocodb/nocodb)](https://github.com/nocodb/nocodb/stargazers) # Quick try diff --git a/docker-compose/mysql/docker-compose.yml b/docker-compose/mysql/docker-compose.yml index 76e2e232cc..323c00d3a8 100644 --- a/docker-compose/mysql/docker-compose.yml +++ b/docker-compose/mysql/docker-compose.yml @@ -27,7 +27,7 @@ services: - "-h" - localhost timeout: 20s - image: "mysql:8.0.32" + image: "mysql:8.0.35" restart: always volumes: - "db_data:/var/lib/mysql" diff --git a/docker-compose/nginx-proxy-manager/docker-compose.yml b/docker-compose/nginx-proxy-manager/docker-compose.yml index 89409c77c9..62aec02a06 100644 --- a/docker-compose/nginx-proxy-manager/docker-compose.yml +++ b/docker-compose/nginx-proxy-manager/docker-compose.yml @@ -46,7 +46,7 @@ services: - "-h" - localhost timeout: 20s - image: "mysql:8.0.32" + image: "mysql:8.0.35" networks: - default restart: always diff --git a/markdown/readme/languages/spanish.md b/markdown/readme/languages/spanish.md index 12692193c2..935bc07e08 100644 --- a/markdown/readme/languages/spanish.md +++ b/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. diff --git a/package.json b/package.json index 6242a7000d..e8a13f0c66 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "devDependencies": { "fs": "0.0.1-security", "lerna": "^7.0.2", - "husky": "^8.0.0", + "husky": "^8.0.3", "xlsx": "^0.17.4" }, "husky": { diff --git a/packages/nc-gui/assets/nc-icons/credit-card.svg b/packages/nc-gui/assets/nc-icons/credit-card.svg index 06b6025ea3..213df64d29 100644 --- a/packages/nc-gui/assets/nc-icons/credit-card.svg +++ b/packages/nc-gui/assets/nc-icons/credit-card.svg @@ -1,4 +1,4 @@ - - + + diff --git a/packages/nc-gui/assets/nc-icons/layers.svg b/packages/nc-gui/assets/nc-icons/layers.svg index 1d23789b83..82faffa520 100644 --- a/packages/nc-gui/assets/nc-icons/layers.svg +++ b/packages/nc-gui/assets/nc-icons/layers.svg @@ -1,8 +1,8 @@ - - - + + + diff --git a/packages/nc-gui/assets/style.scss b/packages/nc-gui/assets/style.scss index 4a620c5276..7d9c703132 100644 --- a/packages/nc-gui/assets/style.scss +++ b/packages/nc-gui/assets/style.scss @@ -678,3 +678,7 @@ input[type='number'] { @apply xs:(visible opacity-100 !text-gray-500) } } + +.ant-message-notice-content { + @apply !rounded-md; +} \ No newline at end of file diff --git a/packages/nc-gui/components.d.ts b/packages/nc-gui/components.d.ts index 57affe1553..70144a9a06 100644 --- a/packages/nc-gui/components.d.ts +++ b/packages/nc-gui/components.d.ts @@ -26,7 +26,6 @@ declare module '@vue/runtime-core' { AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] ADivider: typeof import('ant-design-vue/es')['Divider'] - ADrawer: typeof import('ant-design-vue/es')['Drawer'] ADropdown: typeof import('ant-design-vue/es')['Dropdown'] ADropdownButton: typeof import('ant-design-vue/es')['DropdownButton'] AEmpty: typeof import('ant-design-vue/es')['Empty'] @@ -47,7 +46,6 @@ declare module '@vue/runtime-core' { AMenu: typeof import('ant-design-vue/es')['Menu'] AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] - AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup'] AModal: typeof import('ant-design-vue/es')['Modal'] APagination: typeof import('ant-design-vue/es')['Pagination'] APopover: typeof import('ant-design-vue/es')['Popover'] @@ -64,7 +62,6 @@ declare module '@vue/runtime-core' { ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] ASwitch: typeof import('ant-design-vue/es')['Switch'] ATable: typeof import('ant-design-vue/es')['Table'] - ATableColumn: typeof import('ant-design-vue/es')['TableColumn'] ATabPane: typeof import('ant-design-vue/es')['TabPane'] ATabs: typeof import('ant-design-vue/es')['Tabs'] ATag: typeof import('ant-design-vue/es')['Tag'] @@ -79,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'] @@ -126,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'] @@ -133,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'] diff --git a/packages/nc-gui/components/cell/DatePicker.vue b/packages/nc-gui/components/cell/DatePicker.vue index abf01f6584..fc0c16ea25 100644 --- a/packages/nc-gui/components/cell/DatePicker.vue +++ b/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 diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 11ccba81b6..fa8a588a4b 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/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)}` diff --git a/packages/nc-gui/components/cell/Json.vue b/packages/nc-gui/components/cell/Json.vue index 18326296f9..b4eaf90dac 100644 --- a/packages/nc-gui/components/cell/Json.vue +++ b/packages/nc-gui/components/cell/Json.vue @@ -72,13 +72,7 @@ const clear = () => { const formatJson = (json: string) => { try { - json = json - .trim() - .replace(/^\{\s*|\s*\}$/g, '') - .replace(/\n\s*/g, '') - json = `{${json}}` - - return json + return JSON.stringify(JSON.parse(json)) } catch (e) { console.log(e) return json diff --git a/packages/nc-gui/components/cell/MultiSelect.vue b/packages/nc-gui/components/cell/MultiSelect.vue index 7a64c35a1a..d02db8decc 100644 --- a/packages/nc-gui/components/cell/MultiSelect.vue +++ b/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,14 +146,13 @@ const selectedTitles = computed(() => } return 0 }) - : modelValue.split(',').map((el) => el.trim()) - : modelValue.map((el) => el.trim()) + : modelValue.toString().split(',') : [], ) onMounted(() => { selectedIds.value = selectedTitles.value.flatMap((el) => { - const item = options.value.find((op) => op.title === el) + const item = options.value.find((op) => op.title === el || op.title === el?.trim()) const itemIdOrTitle = item?.id || item?.title if (itemIdOrTitle) { return [itemIdOrTitle] @@ -165,7 +166,7 @@ watch( () => modelValue, () => { selectedIds.value = selectedTitles.value.flatMap((el) => { - const item = options.value.find((op) => op.title === el) + const item = options.value.find((op) => op.title === el || op.title === el?.trim()) if (item && (item.id || item.title)) { return [(item.id || item.title)!] } @@ -343,11 +344,7 @@ 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" diff --git a/packages/nc-gui/components/cell/RichText.vue b/packages/nc-gui/components/cell/RichText.vue new file mode 100644 index 0000000000..7f48430ea8 --- /dev/null +++ b/packages/nc-gui/components/cell/RichText.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/packages/nc-gui/components/cell/RichText/LinkOptions.vue b/packages/nc-gui/components/cell/RichText/LinkOptions.vue new file mode 100644 index 0000000000..ddae15f26b --- /dev/null +++ b/packages/nc-gui/components/cell/RichText/LinkOptions.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue b/packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue new file mode 100644 index 0000000000..51b939b264 --- /dev/null +++ b/packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue @@ -0,0 +1,381 @@ + + + + + diff --git a/packages/nc-gui/components/cell/RichText/SelectedBubbleMenuPopup.vue b/packages/nc-gui/components/cell/RichText/SelectedBubbleMenuPopup.vue new file mode 100644 index 0000000000..63132a18f8 --- /dev/null +++ b/packages/nc-gui/components/cell/RichText/SelectedBubbleMenuPopup.vue @@ -0,0 +1,68 @@ + + + diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue index 6d132cf0b0..9acccd11e2 100644 --- a/packages/nc-gui/components/cell/SingleSelect.vue +++ b/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)) @@ -104,7 +102,7 @@ const hasEditRoles = computed(() => isUIAllowed('dataEdit')) const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value) const vModel = computed({ - get: () => tempSelectedOptState.value ?? modelValue?.trim(), + get: () => tempSelectedOptState.value ?? modelValue, set: (val) => { if (val && isNewOptionCreateEnabled.value && (options.value ?? []).every((op) => op.title !== val)) { tempSelectedOptState.value = val @@ -259,16 +257,12 @@ const handleClose = (e: MouseEvent) => { useEventListener(document, 'click', handleClose, true) const selectedOpt = computed(() => { - return options.value.find((o) => o.value === vModel.value) + return options.value.find((o) => o.value === vModel.value || o.value === vModel.value?.trim()) })