Browse Source

Merge branch 'develop' into enhancement/filters

pull/5106/head
Wing-Kam Wong 2 years ago
parent
commit
39e15bcbe6
  1. 28
      packages/nc-gui/components/cell/attachment/Carousel.vue
  2. 25
      packages/nc-gui/components/cell/attachment/Image.vue
  3. 16
      packages/nc-gui/components/cell/attachment/Modal.vue
  4. 33
      packages/nc-gui/components/cell/attachment/index.vue
  5. 33
      packages/nc-gui/components/cell/attachment/utils.ts
  6. 8
      packages/nc-gui/components/dashboard/TreeView.vue
  7. 20
      packages/nc-gui/components/smartsheet/Gallery.vue
  8. 20
      packages/nc-gui/components/smartsheet/Kanban.vue
  9. 4
      packages/nc-gui/composables/useApi/interceptors.ts
  10. 40
      packages/nc-gui/composables/useAttachment.ts
  11. 6
      packages/nc-gui/composables/useCellUrlConfig.ts
  12. 6
      packages/nc-gui/composables/useDashboard.ts
  13. 62
      packages/nc-gui/composables/useKanbanViewStore.ts
  14. 6
      packages/nc-gui/composables/useLTARStore.ts
  15. 33
      packages/nc-gui/composables/useProject.ts
  16. 18
      packages/nc-gui/composables/useTabs.ts
  17. 63
      packages/nc-gui/composables/useViewData.ts
  18. 4
      packages/nc-gui/plugins/tele.ts
  19. 1
      packages/nc-gui/utils/index.ts
  20. 714
      packages/nc-gui/utils/mimeTypeUtils.ts
  21. 4
      packages/noco-docs/content/en/engineering/unit-testing.md
  22. 9
      packages/nocodb/src/lib/meta/api/attachmentApis.ts
  23. 22
      tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
  24. 1
      tests/playwright/tests/columnAttachments.spec.ts

28
packages/nc-gui/components/cell/attachment/Carousel.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { onKeyDown } from '@vueuse/core'
import { useAttachmentCell } from './utils'
import { computed, isImage, onClickOutside, ref } from '#imports'
import { computed, isImage, onClickOutside, ref, useAttachment } from '#imports'
const { selectedImage, visibleItems, downloadFile } = useAttachmentCell()!
@ -9,6 +9,8 @@ const carouselRef = ref()
const imageItems = computed(() => visibleItems.value.filter((item) => isImage(item.title, item.mimetype)))
const { getPossibleAttachmentSrc } = useAttachment()
/** navigate to previous image on button click */
onKeyDown(
(e) => ['Left', 'ArrowLeft', 'A'].includes(e.key),
@ -81,22 +83,16 @@ onClickOutside(carouselRef, () => {
</template>
<template #customPaging="props">
<a>
<LazyNuxtImg
quality="90"
placeholder
class="!block"
<div class="cursor-pointer h-full nc-attachment-img-wrapper">
<LazyCellAttachmentImage
class="!block m-auto h-full w-full"
:alt="imageItems[props.i].title || `#${props.i}`"
:src="imageItems[props.i].url || imageItems[props.i].data"
:srcs="getPossibleAttachmentSrc(imageItems[props.i])"
/>
</a>
</div>
</template>
<div v-for="item of imageItems" :key="item.url">
<div
:style="{ backgroundImage: `url('${item.url || item.data}')` }"
class="min-w-70vw min-h-70vh w-full h-full bg-contain bg-center bg-no-repeat"
/>
<div v-for="(item, idx) of imageItems" :key="idx">
<LazyCellAttachmentImage :srcs="getPossibleAttachmentSrc(item)" class="max-w-70vw max-h-70vh" />
</div>
</a-carousel>
</div>
@ -146,4 +142,8 @@ onClickOutside(carouselRef, () => {
.ant-carousel :deep(.custom-slick-arrow:hover) {
opacity: 0.5;
}
.nc-attachment-img-wrapper {
width: fit-content !important;
}
</style>

25
packages/nc-gui/components/cell/attachment/Image.vue

@ -0,0 +1,25 @@
<script setup lang="ts">
interface Props {
srcs: string[]
alt?: string
}
const props = defineProps<Props>()
const index = ref(0)
const onError = () => index.value++
</script>
<template>
<LazyNuxtImg
v-if="index < props.srcs.length"
class="m-auto"
:src="props.srcs[index]"
:alt="props?.alt || ''"
placeholder
quality="75"
@error="onError"
/>
<MdiFileImageBox v-else />
</template>

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

@ -2,7 +2,7 @@
import { onKeyDown } from '@vueuse/core'
import { useAttachmentCell } from './utils'
import { useSortable } from './sort'
import { isImage, openLink, ref, useDropZone, useUIPermission, watch } from '#imports'
import { isImage, ref, useAttachment, useDropZone, useUIPermission, watch } from '#imports'
const { isUIAllowed } = useUIPermission()
@ -38,6 +38,8 @@ const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
const { isSharedForm } = useSmartsheetStoreOrThrow()
const { getPossibleAttachmentSrc, openAttachment } = useAttachment()
onKeyDown('Escape', () => {
modalVisible.value = false
isOverDropZone.value = false
@ -159,12 +161,12 @@ function onRemoveFileClick(title: any, i: number) {
<div
:class="[dragging ? 'cursor-move' : 'cursor-pointer']"
class="nc-attachment h-full w-full flex items-center justify-center"
class="nc-attachment h-full w-full flex items-center justify-center overflow-hidden"
>
<div
<LazyCellAttachmentImage
v-if="isImage(item.title, item.mimetype)"
:style="{ backgroundImage: `url('${item.url || item.data}')` }"
class="w-full h-full bg-contain bg-center bg-no-repeat"
:srcs="getPossibleAttachmentSrc(item)"
class="max-w-full max-h-full m-auto justify-center"
@click.stop="onClick(item)"
/>
@ -173,10 +175,10 @@ function onRemoveFileClick(title: any, i: number) {
v-else-if="item.icon"
height="150"
width="150"
@click.stop="openLink(item.url || item.data)"
@click.stop="openAttachment(item)"
/>
<IcOutlineInsertDriveFile v-else height="150" width="150" @click.stop="openLink(item.url || item.data)" />
<IcOutlineInsertDriveFile v-else height="150" width="150" @click.stop="openAttachment(item)" />
</div>
</a-card>

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

@ -10,8 +10,8 @@ import {
inject,
isImage,
nextTick,
openLink,
ref,
useAttachment,
useDropZone,
useSelectedCellKeyupListener,
useSmartsheetRowStoreOrThrow,
@ -46,6 +46,8 @@ const currentCellRef = ref<Element | undefined>(dropZoneInjection.value)
const { cellRefs, isSharedForm } = useSmartsheetStoreOrThrow()!
const { getPossibleAttachmentSrc, openAttachment } = useAttachment()
const {
isPublic,
isForm,
@ -60,7 +62,6 @@ const {
selectedImage,
isReadonly,
storedFiles,
getAttachmentUrl,
} = useProvideAttachmentCell(updateModelValue)
watch(
@ -101,16 +102,7 @@ watch(
async (nextModel) => {
if (nextModel) {
try {
let nextAttachments = ((typeof nextModel === 'string' ? JSON.parse(nextModel) : nextModel) || []).filter(Boolean)
// reconstruct the url
// See /packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts for the details
nextAttachments = await Promise.all(
nextAttachments.map(async (attachment: any) => ({
...attachment,
url: await getAttachmentUrl(attachment),
})),
)
const nextAttachments = ((typeof nextModel === 'string' ? JSON.parse(nextModel) : nextModel) || []).filter(Boolean)
if (isPublic.value && isForm.value) {
storedFiles.value = nextAttachments
@ -229,21 +221,12 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e) => {
<template #title>
<div class="text-center w-full">{{ item.title }}</div>
</template>
<template v-if="isImage(item.title, item.mimetype ?? item.type) && (item.url || item.data)">
<div v-if="isImage(item.title, item.mimetype ?? item.type)">
<div class="nc-attachment flex items-center justify-center" @click.stop="selectedImage = item">
<LazyNuxtImg
quality="75"
placeholder
fit="cover"
:alt="item.title || `#${i}`"
:src="item.url || item.data"
class="max-w-full max-h-full"
/>
<LazyCellAttachmentImage :alt="item.title || `#${i}`" :srcs="getPossibleAttachmentSrc(item)" />
</div>
</template>
<div v-else class="nc-attachment flex items-center justify-center" @click="openLink(item.url || item.data)">
</div>
<div v-else class="nc-attachment flex items-center justify-center" @click="openAttachment(item)">
<component :is="FileIcon(item.icon)" v-if="item.icon" />
<IcOutlineInsertDriveFile v-else />

33
packages/nc-gui/components/cell/attachment/utils.ts

@ -14,6 +14,7 @@ import {
message,
ref,
useApi,
useAttachment,
useFileDialog,
useI18n,
useInjectionState,
@ -60,6 +61,8 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
const { t } = useI18n()
const { getAttachmentSrc } = useAttachment()
const defaultAttachmentMeta = {
...(appInfo.value.ee && {
// Maximum Number of Attachments per cell
@ -226,31 +229,12 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
/** download a file */
async function downloadFile(item: AttachmentType) {
;(await import('file-saver')).saveAs(item.url || item.data, item.title)
}
/** construct the attachment url
* See /packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts for the details
* */
async function getAttachmentUrl(item: AttachmentType) {
const path = item?.path
// if path doesn't exist, use `item.url`
if (path) {
// try ${appInfo.value.ncSiteUrl}/${item.path} first
const url = `${appInfo.value.ncSiteUrl}/${item.path}`
try {
const res = await fetch(url)
if (res.ok) {
// use `url` if it is accessible
return Promise.resolve(url)
}
} catch {
// for some cases, `url` is not accessible as expected
// do nothing here
}
const src = await getAttachmentSrc(item)
if (src) {
;(await import('file-saver')).saveAs(src, item.title)
} else {
message.error('Failed to download file')
}
// if it fails, use the original url
return Promise.resolve(item.url)
}
const FileIcon = (icon: string) => {
@ -294,7 +278,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
storedFiles,
bulkDownloadFiles,
defaultAttachmentMeta,
getAttachmentUrl,
}
},
'useAttachmentCell',

8
packages/nc-gui/components/dashboard/TreeView.vue

@ -35,7 +35,7 @@ const { addTab, updateTab } = useTabs()
const { $api, $e } = useNuxtApp()
const { project, loadProject, bases, tables, loadTables, isSharedBase } = useProject()
const { bases, tables, loadTables, isSharedBase } = useProject()
const { activeTab } = useTabs()
@ -324,12 +324,6 @@ const setIcon = async (icon: string, table: TableType) => {
message.error(await extractSdkResponseErrorMsg(e))
}
}
onMounted(async () => {
if (!project.value?.id) {
await loadProject()
}
})
</script>
<template>

20
packages/nc-gui/components/smartsheet/Gallery.vue

@ -18,11 +18,13 @@ import {
createEventHook,
extractPkFromRow,
inject,
isImage,
isLTAR,
nextTick,
onMounted,
provide,
ref,
useAttachment,
useViewData,
} from '#imports'
import type { Row as RowType } from '~/lib'
@ -64,6 +66,8 @@ const route = useRoute()
const router = useRouter()
const { getPossibleAttachmentSrc } = useAttachment()
const fieldsWithoutCover = computed(() => fields.value.filter((f) => f.id !== galleryData.value?.fk_cover_image_col_id))
const coverImageColumn: any = $(
@ -199,14 +203,14 @@ watch(view, async (nextView) => {
<div style="z-index: 1"></div>
</template>
<LazyNuxtImg
v-for="(attachment, index) in attachments(record)"
:key="`carousel-${record.row.id}-${index}`"
quality="90"
placeholder
class="h-52 object-contain"
:src="attachment.url"
/>
<template v-for="(attachment, index) in attachments(record)">
<LazyCellAttachmentImage
v-if="isImage(attachment.title, attachment.mimetype ?? attachment.type)"
:key="`carousel-${record.row.id}-${index}`"
class="h-52 object-contain"
:srcs="getPossibleAttachmentSrc(attachment)"
/>
</template>
</a-carousel>
<MdiFileImageBox v-else class="w-full h-48 my-4 text-cool-gray-200" />

20
packages/nc-gui/components/smartsheet/Kanban.vue

@ -13,10 +13,12 @@ import {
MetaInj,
OpenNewRecordFormHookInj,
inject,
isImage,
isLTAR,
onBeforeMount,
onBeforeUnmount,
provide,
useAttachment,
useKanbanViewStoreOrThrow,
} from '#imports'
import type { Row as RowType } from '~/lib'
@ -55,6 +57,8 @@ const route = useRoute()
const router = useRouter()
const { getPossibleAttachmentSrc } = useAttachment()
const {
loadKanbanData,
loadMoreKanbanData,
@ -456,14 +460,14 @@ watch(view, async (nextView) => {
<div style="z-index: 1"></div>
</template>
<LazyNuxtImg
v-for="(attachment, index) in attachments(record)"
:key="`carousel-${record.row.id}-${index}`"
quality="90"
placeholder
class="h-52 object-cover"
:src="attachment.url"
/>
<template v-for="(attachment, index) in attachments(record)">
<LazyCellAttachmentImage
v-if="isImage(attachment.title, attachment.mimetype ?? attachment.type)"
:key="`carousel-${record.row.id}-${index}`"
class="h-52 object-cover"
:srcs="getPossibleAttachmentSrc(attachment)"
/>
</template>
</a-carousel>
<MdiFileImageBox v-else class="w-full h-48 my-4 text-cool-gray-200" />

4
packages/nc-gui/composables/useApi/interceptors.ts

@ -1,12 +1,12 @@
import type { Api } from 'nocodb-sdk'
import { navigateTo, useGlobal, useRoute, useRouter } from '#imports'
import { navigateTo, useGlobal, useRouter } from '#imports'
const DbNotFoundMsg = 'Database config not found'
export function addAxiosInterceptors(api: Api<any>) {
const state = useGlobal()
const router = useRouter()
const route = useRoute()
const route = $(router.currentRoute)
api.instance.interceptors.request.use((config) => {
config.headers['xc-gui'] = 'true'

40
packages/nc-gui/composables/useAttachment.ts

@ -0,0 +1,40 @@
import { mimeTypes, openLink, useGlobal } from '#imports'
const useAttachment = () => {
const { appInfo } = useGlobal()
const getPossibleAttachmentSrc = (item: Record<string, any>) => {
const res: string[] = []
if (item?.path) res.push(`${appInfo.value.ncSiteUrl}/${item.path}`)
if (item?.url) res.push(item.url)
return res
}
const getAttachmentSrc = async (item: Record<string, any>) => {
if (item?.data) {
return item.data
}
const sources = getPossibleAttachmentSrc(item)
const mimeType = mimeTypes[item?.mimetype?.split('/')?.pop() || 'txt']
for (const source of sources) {
// test if the source is accessible or not
const res = await fetch(source)
if (res.ok && res.headers.get('Content-Type') === mimeType) {
return source
}
}
return null
}
const openAttachment = async (item: Record<string, any>) => {
openLink(await getAttachmentSrc(item))
}
return {
getAttachmentSrc,
getPossibleAttachmentSrc,
openAttachment,
}
}
export default useAttachment

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

@ -1,5 +1,5 @@
import type { MaybeRef } from '@vueuse/core'
import { computed, unref, useRoute } from '#imports'
import { computed, unref, useRouter } from '#imports'
export interface CellUrlOptions {
behavior?: string
@ -21,7 +21,9 @@ const parseUrlRules = (serialized?: string): ParsedRules[] | undefined => {
}
export function useCellUrlConfig(url?: MaybeRef<string>) {
const route = useRoute()
const router = useRouter()
const route = $(router.currentRoute)
const config = $computed(() => ({
behavior: route.query.url_behavior as string | undefined,

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

@ -1,7 +1,9 @@
import { computed, useRoute } from '#imports'
import { computed, useRouter } from '#imports'
export function useDashboard() {
const route = useRoute()
const router = useRouter()
const route = $(router.currentRoute)
const dashboardUrl = computed(() => {
// todo: test in different scenarios

62
packages/nc-gui/composables/useKanbanViewStore.ts

@ -1,15 +1,5 @@
import type { ComputedRef, Ref } from 'vue'
import type {
Api,
AttachmentType,
ColumnType,
KanbanType,
SelectOptionType,
SelectOptionsType,
TableType,
ViewType,
} from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import type { Api, ColumnType, KanbanType, SelectOptionType, SelectOptionsType, TableType, ViewType } from 'nocodb-sdk'
import type { Row } from '~/lib'
import {
IsPublicInj,
@ -25,7 +15,6 @@ import {
ref,
useApi,
useFieldQuery,
useGlobal,
useI18n,
useInjectionState,
useNuxtApp,
@ -55,8 +44,6 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
const { $e, $api } = useNuxtApp()
const { appInfo } = useGlobal()
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()
const { sharedView, fetchSharedViewData, fetchSharedViewGroupedData } = useSharedView()
@ -91,10 +78,6 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
return where
})
const attachmentColumns = computed(() =>
(meta.value?.columns as ColumnType[])?.filter((c) => c.uidt === UITypes.Attachment).map((c) => c.title),
)
provide(SharedViewPasswordInj, password)
// kanban view meta data
@ -144,27 +127,6 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
rowMeta: {},
}))
async function getAttachmentUrl(item: AttachmentType) {
const path = item?.path
// if path doesn't exist, use `item.url`
if (path) {
// try ${appInfo.value.ncSiteUrl}/${item.path} first
const url = `${appInfo.value.ncSiteUrl}/${item.path}`
try {
const res = await fetch(url)
if (res.ok) {
// use `url` if it is accessible
return Promise.resolve(url)
}
} catch {
// for some cases, `url` is not accessible as expected
// do nothing here
}
}
// if it fails, use the original url
return Promise.resolve(item.url)
}
async function loadKanbanData() {
if ((!project?.value?.id || !meta.value?.id || !viewMeta?.value?.id || !groupingFieldColumn?.value?.id) && !isPublic.value)
return
@ -193,28 +155,8 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
}
for (const data of groupData) {
const records = []
const key = data.key
// TODO: optimize
// reconstruct the url
// See /packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts for the details
for (const record of data.value.list) {
for (const attachmentColumn of attachmentColumns.value) {
// attachment column can be hidden
if (!record[attachmentColumn!]) continue
const oldAttachment = JSON.parse(record[attachmentColumn!])
const newAttachment = []
for (const attachmentObj of oldAttachment) {
newAttachment.push({
...attachmentObj,
url: await getAttachmentUrl(attachmentObj),
})
}
record[attachmentColumn!] = newAttachment
}
records.push(record)
}
formattedData.value.set(key, formatData(records))
formattedData.value.set(key, formatData(data.value.list))
countByStack.value.set(key, data.value.pageInfo.totalRows || 0)
}
}

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

@ -16,6 +16,7 @@ import {
useMetas,
useNuxtApp,
useProject,
useRouter,
useSharedView,
watch,
} from '#imports'
@ -107,7 +108,10 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
const loadChildrenExcludedList = async () => {
try {
if (isPublic) {
const route = useRoute()
const router = useRouter()
const route = $(router.currentRoute)
childrenExcludedList.value = await $api.public.dataRelationList(
route.params.viewId as string,
column.value.id,

33
packages/nc-gui/composables/useProject.ts

@ -5,31 +5,30 @@ import {
ClientType,
computed,
createEventHook,
createSharedComposable,
ref,
useApi,
useGlobal,
useInjectionState,
useNuxtApp,
useRoles,
useRoute,
useRouter,
useTheme,
} from '#imports'
import type { ProjectMetaInfo, ThemeConfig } from '~/lib'
const [setup, use] = useInjectionState(() => {
export const useProject = createSharedComposable(() => {
const { $e } = useNuxtApp()
const { api, isLoading } = useApi()
const route = useRoute()
const router = useRouter()
const route = $(router.currentRoute)
const { includeM2M } = useGlobal()
const { setTheme, theme } = useTheme()
const router = useRouter()
const { projectRoles, loadProjectRoles } = useRoles()
const projectLoadedHook = createEventHook<ProjectType>()
@ -178,6 +177,14 @@ const [setup, use] = useInjectionState(() => {
setTheme()
}
watch(
() => route.params.projectType,
(n) => {
if (!n) reset()
},
{ immediate: true },
)
return {
project,
bases,
@ -201,16 +208,4 @@ const [setup, use] = useInjectionState(() => {
lastOpenedViewMap,
isXcdbBase,
}
}, 'useProject')
export const provideProject = setup
export function useProject() {
const state = use()
if (!state) {
return setup()
}
return state
}
})

18
packages/nc-gui/composables/useTabs.ts

@ -1,5 +1,5 @@
import type { WritableComputedRef } from '@vue/reactivity'
import { computed, navigateTo, ref, useInjectionState, useProject, useRoute, useRouter, watch } from '#imports'
import { computed, createSharedComposable, navigateTo, ref, useProject, useRouter, watch } from '#imports'
import type { TabItem } from '~/lib'
import { TabType } from '~/lib'
@ -10,13 +10,13 @@ function getPredicate(key: Partial<TabItem>) {
(!('type' in key) || tab.type === key.type)
}
const [setup, use] = useInjectionState(() => {
export const useTabs = createSharedComposable(() => {
const tabs = ref<TabItem[]>([])
const route = useRoute()
const router = useRouter()
const route = $(router.currentRoute)
const { bases, tables } = useProject()
const projectType = $computed(() => route.params.projectType as string)
@ -157,13 +157,3 @@ const [setup, use] = useInjectionState(() => {
return { tabs, addTab, activeTabIndex, activeTab, clearTabs, closeTab, updateTab }
})
export function useTabs() {
const state = use()
if (!state) {
return setup()
}
return state
}

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

@ -1,15 +1,5 @@
import { UITypes, ViewTypes } from 'nocodb-sdk'
import type {
Api,
AttachmentType,
ColumnType,
FormColumnType,
FormType,
GalleryType,
PaginatedType,
TableType,
ViewType,
} from 'nocodb-sdk'
import type { Api, ColumnType, FormColumnType, FormType, GalleryType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
import {
IsPublicInj,
@ -29,7 +19,6 @@ import {
useMetas,
useNuxtApp,
useProject,
useRoute,
useRouter,
useSharedView,
useSmartsheetStoreOrThrow,
@ -59,7 +48,7 @@ export function useViewData(
const router = useRouter()
const route = useRoute()
const route = $(router.currentRoute)
const { appInfo } = $(useGlobal())
@ -91,10 +80,6 @@ export function useViewData(
const { isUIAllowed } = useUIPermission()
const attachmentColumns = computed(() =>
(meta.value?.columns as ColumnType[])?.filter((c) => c.uidt === UITypes.Attachment).map((c) => c.title),
)
const routeQuery = $computed(() => route.query as Record<string, string>)
const paginationData = computed({
@ -201,28 +186,6 @@ export function useViewData(
}
}
// TODO: refactor
async function getAttachmentUrl(item: AttachmentType) {
const path = item?.path
// if path doesn't exist, use `item.url`
if (path) {
// try ${appInfo.value.ncSiteUrl}/${item.path} first
const url = `${appInfo.ncSiteUrl}/${item.path}`
try {
const res = await fetch(url)
if (res.ok) {
// use `url` if it is accessible
return Promise.resolve(url)
}
} catch {
// for some cases, `url` is not accessible as expected
// do nothing here
}
}
// if it fails, use the original url
return Promise.resolve(item.url)
}
async function loadData(params: Parameters<Api<any>['dbViewRow']['list']>[4] = {}) {
if ((!project?.value?.id || !meta.value?.id || !viewMeta.value?.id) && !isPublic.value) return
const response = !isPublic.value
@ -234,27 +197,7 @@ export function useViewData(
where: where?.value,
})
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value })
// reconstruct the url
// See /packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts for the details
const records = []
for (const record of response.list) {
for (const attachmentColumn of attachmentColumns.value) {
// attachment column can be hidden
if (!record[attachmentColumn!]) continue
const oldAttachment =
typeof record[attachmentColumn!] === 'string' ? JSON.parse(record[attachmentColumn!]) : record[attachmentColumn!]
const newAttachment = []
for (const attachmentObj of oldAttachment) {
newAttachment.push({
...attachmentObj,
url: await getAttachmentUrl(attachmentObj),
})
}
record[attachmentColumn!] = newAttachment
}
records.push(record)
}
formattedData.value = formatData(records)
formattedData.value = formatData(response.list)
paginationData.value = response.pageInfo
// to cater the case like when querying with a non-zero offset

4
packages/nc-gui/plugins/tele.ts

@ -1,12 +1,12 @@
import type { Socket } from 'socket.io-client'
import io from 'socket.io-client'
import { defineNuxtPlugin, useGlobal, useRoute, useRouter, watch } from '#imports'
import { defineNuxtPlugin, useGlobal, useRouter, watch } from '#imports'
// todo: ignore init if tele disabled
export default defineNuxtPlugin(async (nuxtApp) => {
const router = useRouter()
const route = useRoute()
const route = $(router.currentRoute)
const { appInfo } = $(useGlobal())

1
packages/nc-gui/utils/index.ts

@ -20,3 +20,4 @@ export * from './userUtils'
export * from './stringUtils'
export * from './memStorage'
export * from './browserUtils'
export * from './mimeTypeUtils'

714
packages/nc-gui/utils/mimeTypeUtils.ts

@ -0,0 +1,714 @@
const mimeTypes: Record<string, string> = {
'123': 'application/vnd.lotus-1-2-3',
'x3d': 'application/vnd.hzn-3d-crossword',
'3gp': 'video/3gpp',
'3g2': 'video/3gpp2',
'mseq': 'application/vnd.mseq',
'pwn': 'application/vnd.3m.post-it-notes',
'plb': 'application/vnd.3gpp.pic-bw-large',
'psb': 'application/vnd.3gpp.pic-bw-small',
'pvb': 'application/vnd.3gpp.pic-bw-var',
'tcap': 'application/vnd.3gpp2.tcap',
'7z': 'application/x-7z-compressed',
'abw': 'application/x-abiword',
'ace': 'application/x-ace-compressed',
'acc': 'application/vnd.americandynamics.acc',
'acu': 'application/vnd.acucobol',
'atc': 'application/vnd.acucorp',
'adp': 'audio/adpcm',
'aab': 'application/x-authorware-bin',
'aam': 'application/x-authorware-map',
'aas': 'application/x-authorware-seg',
'air': 'application/vnd.adobe.air-application-installer-package+zip',
'swf': 'application/x-shockwave-flash',
'fxp': 'application/vnd.adobe.fxp',
'pdf': 'application/pdf',
'ppd': 'application/vnd.cups-ppd',
'dir': 'application/x-director',
'xdp': 'application/vnd.adobe.xdp+xml',
'xfdf': 'application/vnd.adobe.xfdf',
'aac': 'audio/x-aac',
'ahead': 'application/vnd.ahead.space',
'azf': 'application/vnd.airzip.filesecure.azf',
'azs': 'application/vnd.airzip.filesecure.azs',
'azw': 'application/vnd.amazon.ebook',
'ami': 'application/vnd.amiga.ami',
'/A': 'application/andrew-inset',
'apk': 'application/vnd.android.package-archive',
'cii': 'application/vnd.anser-web-certificate-issue-initiation',
'fti': 'application/vnd.anser-web-funds-transfer-initiation',
'atx': 'application/vnd.antix.game-component',
'dmg': 'application/x-apple-diskimage',
'mpkg': 'application/vnd.apple.installer+xml',
'aw': 'application/applixware',
'les': 'application/vnd.hhe.lesson-player',
'swi': 'application/vnd.aristanetworks.swi',
's': 'text/x-asm',
'atomcat': 'application/atomcat+xml',
'atomsvc': 'application/atomsvc+xml',
'atom, .xml': 'application/atom+xml',
'ac': 'application/pkix-attr-cert',
'aif': 'audio/x-aiff',
'avi': 'video/x-msvideo',
'aep': 'application/vnd.audiograph',
'dxf': 'image/vnd.dxf',
'dwf': 'model/vnd.dwf',
'par': 'text/plain-bas',
'bcpio': 'application/x-bcpio',
'bin': 'application/octet-stream',
'bmp': 'image/bmp',
'torrent': 'application/x-bittorrent',
'cod': 'application/vnd.rim.cod',
'mpm': 'application/vnd.blueice.multipass',
'bmi': 'application/vnd.bmi',
'sh': 'application/x-sh',
'btif': 'image/prs.btif',
'rep': 'application/vnd.businessobjects',
'bz': 'application/x-bzip',
'bz2': 'application/x-bzip2',
'csh': 'application/x-csh',
'c': 'text/x-c',
'cdxml': 'application/vnd.chemdraw+xml',
'css': 'text/css',
'cdx': 'chemical/x-cdx',
'cml': 'chemical/x-cml',
'csml': 'chemical/x-csml',
'cdbcmsg': 'application/vnd.contact.cmsg',
'cla': 'application/vnd.claymore',
'c4g': 'application/vnd.clonk.c4group',
'sub': 'image/vnd.dvb.subtitle',
'cdmia': 'application/cdmi-capability',
'cdmic': 'application/cdmi-container',
'cdmid': 'application/cdmi-domain',
'cdmio': 'application/cdmi-object',
'cdmiq': 'application/cdmi-queue',
'c11amc': 'application/vnd.cluetrust.cartomobile-config',
'c11amz': 'application/vnd.cluetrust.cartomobile-config-pkg',
'ras': 'image/x-cmu-raster',
'dae': 'model/vnd.collada+xml',
'csv': 'text/csv',
'cpt': 'application/mac-compactpro',
'wmlc': 'application/vnd.wap.wmlc',
'cgm': 'image/cgm',
'ice': 'x-conference/x-cooltalk',
'cmx': 'image/x-cmx',
'xar': 'application/vnd.xara',
'cmc': 'application/vnd.cosmocaller',
'cpio': 'application/x-cpio',
'clkx': 'application/vnd.crick.clicker',
'clkk': 'application/vnd.crick.clicker.keyboard',
'clkp': 'application/vnd.crick.clicker.palette',
'clkt': 'application/vnd.crick.clicker.template',
'clkw': 'application/vnd.crick.clicker.wordbank',
'wbs': 'application/vnd.criticaltools.wbs+xml',
'cryptonote': 'application/vnd.rig.cryptonote',
'cif': 'chemical/x-cif',
'cmdf': 'chemical/x-cmdf',
'cu': 'application/cu-seeme',
'cww': 'application/prs.cww',
'curl': 'text/vnd.curl',
'dcurl': 'text/vnd.curl.dcurl',
'mcurl': 'text/vnd.curl.mcurl',
'scurl': 'text/vnd.curl.scurl',
'car': 'application/vnd.curl.car',
'pcurl': 'application/vnd.curl.pcurl',
'cmp': 'application/vnd.yellowriver-custom-menu',
'dssc': 'application/dssc+der',
'xdssc': 'application/dssc+xml',
'deb': 'application/x-debian-package',
'uva': 'audio/vnd.dece.audio',
'uvi': 'image/vnd.dece.graphic',
'uvh': 'video/vnd.dece.hd',
'uvm': 'video/vnd.dece.mobile',
'uvu': 'video/vnd.uvvu.mp4',
'uvp': 'video/vnd.dece.pd',
'uvs': 'video/vnd.dece.sd',
'uvv': 'video/vnd.dece.video',
'dvi': 'application/x-dvi',
'seed': 'application/vnd.fdsn.seed',
'dtb': 'application/x-dtbook+xml',
'res': 'application/x-dtbresource+xml',
'ait': 'application/vnd.dvb.ait',
'svc': 'application/vnd.dvb.service',
'eol': 'audio/vnd.digital-winds',
'djvu': 'image/vnd.djvu',
'dtd': 'application/xml-dtd',
'mlp': 'application/vnd.dolby.mlp',
'wad': 'application/x-doom',
'dpg': 'application/vnd.dpgraph',
'dra': 'audio/vnd.dra',
'dfac': 'application/vnd.dreamfactory',
'dts': 'audio/vnd.dts',
'dtshd': 'audio/vnd.dts.hd',
'dwg': 'image/vnd.dwg',
'geo': 'application/vnd.dynageo',
'es': 'application/ecmascript',
'mag': 'application/vnd.ecowin.chart',
'mmr': 'image/vnd.fujixerox.edmics-mmr',
'rlc': 'image/vnd.fujixerox.edmics-rlc',
'exi': 'application/exi',
'mgz': 'application/vnd.proteus.magazine',
'epub': 'application/epub+zip',
'eml': 'message/rfc822',
'nml': 'application/vnd.enliven',
'xpr': 'application/vnd.is-xpr',
'xif': 'image/vnd.xiff',
'xfdl': 'application/vnd.xfdl',
'emma': 'application/emma+xml',
'ez2': 'application/vnd.ezpix-album',
'ez3': 'application/vnd.ezpix-package',
'fst': 'image/vnd.fst',
'fvt': 'video/vnd.fvt',
'fbs': 'image/vnd.fastbidsheet',
'fe_launch': 'application/vnd.denovo.fcselayout-link',
'f4v': 'video/x-f4v',
'flv': 'video/x-flv',
'fpx': 'image/vnd.fpx',
'npx': 'image/vnd.net-fpx',
'flx': 'text/vnd.fmi.flexstor',
'fli': 'video/x-fli',
'ftc': 'application/vnd.fluxtime.clip',
'fdf': 'application/vnd.fdf',
'f': 'text/x-fortran',
'mif': 'application/vnd.mif',
'fm': 'application/vnd.framemaker',
'fh': 'image/x-freehand',
'fsc': 'application/vnd.fsc.weblaunch',
'fnc': 'application/vnd.frogans.fnc',
'ltf': 'application/vnd.frogans.ltf',
'ddd': 'application/vnd.fujixerox.ddd',
'xdw': 'application/vnd.fujixerox.docuworks',
'xbd': 'application/vnd.fujixerox.docuworks.binder',
'oas': 'application/vnd.fujitsu.oasys',
'oa2': 'application/vnd.fujitsu.oasys2',
'oa3': 'application/vnd.fujitsu.oasys3',
'fg5': 'application/vnd.fujitsu.oasysgp',
'bh2': 'application/vnd.fujitsu.oasysprs',
'spl': 'application/x-futuresplash',
'fzs': 'application/vnd.fuzzysheet',
'g3': 'image/g3fax',
'gmx': 'application/vnd.gmx',
'gtw': 'model/vnd.gtw',
'txd': 'application/vnd.genomatix.tuxedo',
'ggb': 'application/vnd.geogebra.file',
'ggt': 'application/vnd.geogebra.tool',
'gdl': 'model/vnd.gdl',
'gex': 'application/vnd.geometry-explorer',
'gxt': 'application/vnd.geonext',
'g2w': 'application/vnd.geoplan',
'g3w': 'application/vnd.geospace',
'gsf': 'application/x-font-ghostscript',
'bdf': 'application/x-font-bdf',
'gtar': 'application/x-gtar',
'texinfo': 'application/x-texinfo',
'gnumeric': 'application/x-gnumeric',
'kml': 'application/vnd.google-earth.kml+xml',
'kmz': 'application/vnd.google-earth.kmz',
'gpx': 'application/gpx+xml',
'gqf': 'application/vnd.grafeq',
'gif': 'image/gif',
'gv': 'text/vnd.graphviz',
'gac': 'application/vnd.groove-account',
'ghf': 'application/vnd.groove-help',
'gim': 'application/vnd.groove-identity-message',
'grv': 'application/vnd.groove-injector',
'gtm': 'application/vnd.groove-tool-message',
'tpl': 'application/vnd.groove-tool-template',
'vcg': 'application/vnd.groove-vcard',
'h261': 'video/h261',
'h263': 'video/h263',
'h264': 'video/h264',
'hpid': 'application/vnd.hp-hpid',
'hps': 'application/vnd.hp-hps',
'hdf': 'application/x-hdf',
'rip': 'audio/vnd.rip',
'hbci': 'application/vnd.hbci',
'jlt': 'application/vnd.hp-jlyt',
'pcl': 'application/vnd.hp-pcl',
'hpgl': 'application/vnd.hp-hpgl',
'hvs': 'application/vnd.yamaha.hv-script',
'hvd': 'application/vnd.yamaha.hv-dic',
'hvp': 'application/vnd.yamaha.hv-voice',
'sfd-hdstx': 'application/vnd.hydrostatix.sof-data',
'stk': 'application/hyperstudio',
'hal': 'application/vnd.hal+xml',
'html': 'text/html',
'irm': 'application/vnd.ibm.rights-management',
'sc': 'application/vnd.ibm.secure-container',
'ics': 'text/calendar',
'icc': 'application/vnd.iccprofile',
'ico': 'image/x-icon',
'igl': 'application/vnd.igloader',
'ief': 'image/ief',
'ivp': 'application/vnd.immervision-ivp',
'ivu': 'application/vnd.immervision-ivu',
'rif': 'application/reginfo+xml',
'3dml': 'text/vnd.in3d.3dml',
'spot': 'text/vnd.in3d.spot',
'igs': 'model/iges',
'i2g': 'application/vnd.intergeo',
'cdy': 'application/vnd.cinderella',
'xpw': 'application/vnd.intercon.formnet',
'fcs': 'application/vnd.isac.fcs',
'ipfix': 'application/ipfix',
'cer': 'application/pkix-cert',
'pki': 'application/pkixcmp',
'crl': 'application/pkix-crl',
'pkipath': 'application/pkix-pkipath',
'igm': 'application/vnd.insors.igm',
'rcprofile': 'application/vnd.ipunplugged.rcprofile',
'irp': 'application/vnd.irepository.package+xml',
'jad': 'text/vnd.sun.j2me.app-descriptor',
'jar': 'application/java-archive',
'class': 'application/java-vm',
'jnlp': 'application/x-java-jnlp-file',
'ser': 'application/java-serialized-object',
'java': 'text/x-java-source,java',
'js': 'application/javascript',
'json': 'application/json',
'joda': 'application/vnd.joost.joda-archive',
'jpm': 'video/jpm',
'jpeg': 'image/x-citrix-jpeg',
'jpg': 'image/x-citrix-jpeg',
'pjpeg': 'image/pjpeg',
'jpgv': 'video/jpeg',
'ktz': 'application/vnd.kahootz',
'mmd': 'application/vnd.chipnuts.karaoke-mmd',
'karbon': 'application/vnd.kde.karbon',
'chrt': 'application/vnd.kde.kchart',
'kfo': 'application/vnd.kde.kformula',
'flw': 'application/vnd.kde.kivio',
'kon': 'application/vnd.kde.kontour',
'kpr': 'application/vnd.kde.kpresenter',
'ksp': 'application/vnd.kde.kspread',
'kwd': 'application/vnd.kde.kword',
'htke': 'application/vnd.kenameaapp',
'kia': 'application/vnd.kidspiration',
'kne': 'application/vnd.kinar',
'sse': 'application/vnd.kodak-descriptor',
'lasxml': 'application/vnd.las.las+xml',
'latex': 'application/x-latex',
'lbd': 'application/vnd.llamagraphics.life-balance.desktop',
'lbe': 'application/vnd.llamagraphics.life-balance.exchange+xml',
'jam': 'application/vnd.jam',
'apr': 'application/vnd.lotus-approach',
'pre': 'application/vnd.lotus-freelance',
'nsf': 'application/vnd.lotus-notes',
'org': 'application/vnd.lotus-organizer',
'scm': 'application/vnd.lotus-screencam',
'lwp': 'application/vnd.lotus-wordpro',
'lvp': 'audio/vnd.lucent.voice',
'm3u': 'audio/x-mpegurl',
'm4v': 'video/x-m4v',
'hqx': 'application/mac-binhex40',
'portpkg': 'application/vnd.macports.portpkg',
'mgp': 'application/vnd.osgeo.mapguide.package',
'mrc': 'application/marc',
'mrcx': 'application/marcxml+xml',
'mxf': 'application/mxf',
'nbp': 'application/vnd.wolfram.player',
'ma': 'application/mathematica',
'mathml': 'application/mathml+xml',
'mbox': 'application/mbox',
'mc1': 'application/vnd.medcalcdata',
'mscml': 'application/mediaservercontrol+xml',
'cdkey': 'application/vnd.mediastation.cdkey',
'mwf': 'application/vnd.mfer',
'mfm': 'application/vnd.mfmp',
'msh': 'model/mesh',
'mads': 'application/mads+xml',
'mets': 'application/mets+xml',
'mods': 'application/mods+xml',
'meta4': 'application/metalink4+xml',
'mcd': 'application/vnd.mcd',
'flo': 'application/vnd.micrografx.flo',
'igx': 'application/vnd.micrografx.igx',
'es3': 'application/vnd.eszigno3+xml',
'mdb': 'application/x-msaccess',
'asf': 'video/x-ms-asf',
'exe': 'application/x-msdownload',
'cil': 'application/vnd.ms-artgalry',
'cab': 'application/vnd.ms-cab-compressed',
'ims': 'application/vnd.ms-ims',
'application': 'application/x-ms-application',
'clp': 'application/x-msclip',
'mdi': 'image/vnd.ms-modi',
'eot': 'application/vnd.ms-fontobject',
'xls': 'application/vnd.ms-excel',
'xlam': 'application/vnd.ms-excel.addin.macroenabled.12',
'xlsb': 'application/vnd.ms-excel.sheet.binary.macroenabled.12',
'xltm': 'application/vnd.ms-excel.template.macroenabled.12',
'xlsm': 'application/vnd.ms-excel.sheet.macroenabled.12',
'chm': 'application/vnd.ms-htmlhelp',
'crd': 'application/x-mscardfile',
'lrm': 'application/vnd.ms-lrm',
'mvb': 'application/x-msmediaview',
'mny': 'application/x-msmoney',
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx': 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'ppsx': 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'potx': 'application/vnd.openxmlformats-officedocument.presentationml.template',
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'obd': 'application/x-msbinder',
'thmx': 'application/vnd.ms-officetheme',
'onetoc': 'application/onenote',
'pya': 'audio/vnd.ms-playready.media.pya',
'pyv': 'video/vnd.ms-playready.media.pyv',
'ppt': 'application/vnd.ms-powerpoint',
'ppam': 'application/vnd.ms-powerpoint.addin.macroenabled.12',
'sldm': 'application/vnd.ms-powerpoint.slide.macroenabled.12',
'pptm': 'application/vnd.ms-powerpoint.presentation.macroenabled.12',
'ppsm': 'application/vnd.ms-powerpoint.slideshow.macroenabled.12',
'potm': 'application/vnd.ms-powerpoint.template.macroenabled.12',
'mpp': 'application/vnd.ms-project',
'pub': 'application/x-mspublisher',
'scd': 'application/x-msschedule',
'xap': 'application/x-silverlight-app',
'stl': 'application/vnd.ms-pki.stl',
'cat': 'application/vnd.ms-pki.seccat',
'vsd': 'application/vnd.visio',
'vsdx': 'application/vnd.visio2013',
'wm': 'video/x-ms-wm',
'wma': 'audio/x-ms-wma',
'wax': 'audio/x-ms-wax',
'wmx': 'video/x-ms-wmx',
'wmd': 'application/x-ms-wmd',
'wpl': 'application/vnd.ms-wpl',
'wmz': 'application/x-ms-wmz',
'wmv': 'video/x-ms-wmv',
'wvx': 'video/x-ms-wvx',
'wmf': 'application/x-msmetafile',
'trm': 'application/x-msterminal',
'doc': 'application/msword',
'docm': 'application/vnd.ms-word.document.macroenabled.12',
'dotm': 'application/vnd.ms-word.template.macroenabled.12',
'wri': 'application/x-mswrite',
'wps': 'application/vnd.ms-works',
'xbap': 'application/x-ms-xbap',
'xps': 'application/vnd.ms-xpsdocument',
'mid': 'audio/midi',
'mpy': 'application/vnd.ibm.minipay',
'afp': 'application/vnd.ibm.modcap',
'rms': 'application/vnd.jcp.javame.midlet-rms',
'tmo': 'application/vnd.tmobile-livetv',
'prc': 'application/x-mobipocket-ebook',
'mbk': 'application/vnd.mobius.mbk',
'dis': 'application/vnd.mobius.dis',
'plc': 'application/vnd.mobius.plc',
'mqy': 'application/vnd.mobius.mqy',
'msl': 'application/vnd.mobius.msl',
'txf': 'application/vnd.mobius.txf',
'daf': 'application/vnd.mobius.daf',
'fly': 'text/vnd.fly',
'mpc': 'application/vnd.mophun.certificate',
'mpn': 'application/vnd.mophun.application',
'mj2': 'video/mj2',
'mpga': 'audio/mpeg',
'mxu': 'video/vnd.mpegurl',
'mpeg': 'video/mpeg',
'm21': 'application/mp21',
'mp4a': 'audio/mp4',
'mp4': 'application/mp4',
'm3u8': 'application/vnd.apple.mpegurl',
'mus': 'application/vnd.musician',
'msty': 'application/vnd.muvee.style',
'mxml': 'application/xv+xml',
'ngdat': 'application/vnd.nokia.n-gage.data',
'n-gage': 'application/vnd.nokia.n-gage.symbian.install',
'ncx': 'application/x-dtbncx+xml',
'nc': 'application/x-netcdf',
'nlu': 'application/vnd.neurolanguage.nlu',
'dna': 'application/vnd.dna',
'nnd': 'application/vnd.noblenet-directory',
'nns': 'application/vnd.noblenet-sealer',
'nnw': 'application/vnd.noblenet-web',
'rpst': 'application/vnd.nokia.radio-preset',
'rpss': 'application/vnd.nokia.radio-presets',
'n3': 'text/n3',
'edm': 'application/vnd.novadigm.edm',
'edx': 'application/vnd.novadigm.edx',
'ext': 'application/vnd.novadigm.ext',
'gph': 'application/vnd.flographit',
'ecelp4800': 'audio/vnd.nuera.ecelp4800',
'ecelp7470': 'audio/vnd.nuera.ecelp7470',
'ecelp9600': 'audio/vnd.nuera.ecelp9600',
'oda': 'application/oda',
'ogx': 'application/ogg',
'oga': 'audio/ogg',
'ogv': 'video/ogg',
'dd2': 'application/vnd.oma.dd2+xml',
'oth': 'application/vnd.oasis.opendocument.text-web',
'opf': 'application/oebps-package+xml',
'qbo': 'application/vnd.intu.qbo',
'oxt': 'application/vnd.openofficeorg.extension',
'osf': 'application/vnd.yamaha.openscoreformat',
'weba': 'audio/webm',
'webm': 'video/webm',
'odc': 'application/vnd.oasis.opendocument.chart',
'otc': 'application/vnd.oasis.opendocument.chart-template',
'odb': 'application/vnd.oasis.opendocument.database',
'odf': 'application/vnd.oasis.opendocument.formula',
'odft': 'application/vnd.oasis.opendocument.formula-template',
'odg': 'application/vnd.oasis.opendocument.graphics',
'otg': 'application/vnd.oasis.opendocument.graphics-template',
'odi': 'application/vnd.oasis.opendocument.image',
'oti': 'application/vnd.oasis.opendocument.image-template',
'odp': 'application/vnd.oasis.opendocument.presentation',
'otp': 'application/vnd.oasis.opendocument.presentation-template',
'ods': 'application/vnd.oasis.opendocument.spreadsheet',
'ots': 'application/vnd.oasis.opendocument.spreadsheet-template',
'odt': 'application/vnd.oasis.opendocument.text',
'odm': 'application/vnd.oasis.opendocument.text-master',
'ott': 'application/vnd.oasis.opendocument.text-template',
'ktx': 'image/ktx',
'sxc': 'application/vnd.sun.xml.calc',
'stc': 'application/vnd.sun.xml.calc.template',
'sxd': 'application/vnd.sun.xml.draw',
'std': 'application/vnd.sun.xml.draw.template',
'sxi': 'application/vnd.sun.xml.impress',
'sti': 'application/vnd.sun.xml.impress.template',
'sxm': 'application/vnd.sun.xml.math',
'sxw': 'application/vnd.sun.xml.writer',
'sxg': 'application/vnd.sun.xml.writer.global',
'stw': 'application/vnd.sun.xml.writer.template',
'otf': 'application/x-font-otf',
'osfpvg': 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
'dp': 'application/vnd.osgi.dp',
'pdb': 'application/vnd.palm',
'p': 'text/x-pascal',
'paw': 'application/vnd.pawaafile',
'pclxl': 'application/vnd.hp-pclxl',
'efif': 'application/vnd.picsel',
'pcx': 'image/x-pcx',
'psd': 'image/vnd.adobe.photoshop',
'prf': 'application/pics-rules',
'pic': 'image/x-pict',
'chat': 'application/x-chat',
'p10': 'application/pkcs10',
'p12': 'application/x-pkcs12',
'p7m': 'application/pkcs7-mime',
'p7s': 'application/pkcs7-signature',
'p7r': 'application/x-pkcs7-certreqresp',
'p7b': 'application/x-pkcs7-certificates',
'p8': 'application/pkcs8',
'plf': 'application/vnd.pocketlearn',
'pnm': 'image/x-portable-anymap',
'pbm': 'image/x-portable-bitmap',
'pcf': 'application/x-font-pcf',
'pfr': 'application/font-tdpfr',
'pgn': 'application/x-chess-pgn',
'pgm': 'image/x-portable-graymap',
'png': 'image/x-png',
'ppm': 'image/x-portable-pixmap',
'pskcxml': 'application/pskc+xml',
'pml': 'application/vnd.ctc-posml',
'ai': 'application/postscript',
'pfa': 'application/x-font-type1',
'pbd': 'application/vnd.powerbuilder6',
'pgp': 'application/pgp-signature',
'box': 'application/vnd.previewsystems.box',
'ptid': 'application/vnd.pvi.ptid1',
'pls': 'application/pls+xml',
'str': 'application/vnd.pg.format',
'ei6': 'application/vnd.pg.osasli',
'dsc': 'text/prs.lines.tag',
'psf': 'application/x-font-linux-psf',
'qps': 'application/vnd.publishare-delta-tree',
'wg': 'application/vnd.pmi.widget',
'qxd': 'application/vnd.quark.quarkxpress',
'esf': 'application/vnd.epson.esf',
'msf': 'application/vnd.epson.msf',
'ssf': 'application/vnd.epson.ssf',
'qam': 'application/vnd.epson.quickanime',
'qfx': 'application/vnd.intu.qfx',
'qt': 'video/quicktime',
'rar': 'application/x-rar-compressed',
'ram': 'audio/x-pn-realaudio',
'rmp': 'audio/x-pn-realaudio-plugin',
'rsd': 'application/rsd+xml',
'rm': 'application/vnd.rn-realmedia',
'bed': 'application/vnd.realvnc.bed',
'mxl': 'application/vnd.recordare.musicxml',
'musicxml': 'application/vnd.recordare.musicxml+xml',
'rnc': 'application/relax-ng-compact-syntax',
'rdz': 'application/vnd.data-vision.rdz',
'rdf': 'application/rdf+xml',
'rp9': 'application/vnd.cloanto.rp9',
'jisp': 'application/vnd.jisp',
'rtf': 'application/rtf',
'rtx': 'text/richtext',
'link66': 'application/vnd.route66.link66+xml',
'rss, .xml': 'application/rss+xml',
'shf': 'application/shf+xml',
'st': 'application/vnd.sailingtracker.track',
'svg': 'image/svg+xml',
'sus': 'application/vnd.sus-calendar',
'sru': 'application/sru+xml',
'setpay': 'application/set-payment-initiation',
'setreg': 'application/set-registration-initiation',
'sema': 'application/vnd.sema',
'semd': 'application/vnd.semd',
'semf': 'application/vnd.semf',
'see': 'application/vnd.seemail',
'snf': 'application/x-font-snf',
'spq': 'application/scvp-vp-request',
'spp': 'application/scvp-vp-response',
'scq': 'application/scvp-cv-request',
'scs': 'application/scvp-cv-response',
'sdp': 'application/sdp',
'etx': 'text/x-setext',
'movie': 'video/x-sgi-movie',
'ifm': 'application/vnd.shana.informed.formdata',
'itp': 'application/vnd.shana.informed.formtemplate',
'iif': 'application/vnd.shana.informed.interchange',
'ipk': 'application/vnd.shana.informed.package',
'tfi': 'application/thraud+xml',
'shar': 'application/x-shar',
'rgb': 'image/x-rgb',
'slt': 'application/vnd.epson.salt',
'aso': 'application/vnd.accpac.simply.aso',
'imp': 'application/vnd.accpac.simply.imp',
'twd': 'application/vnd.simtech-mindmapper',
'csp': 'application/vnd.commonspace',
'saf': 'application/vnd.yamaha.smaf-audio',
'mmf': 'application/vnd.smaf',
'spf': 'application/vnd.yamaha.smaf-phrase',
'teacher': 'application/vnd.smart.teacher',
'svd': 'application/vnd.svd',
'rq': 'application/sparql-query',
'srx': 'application/sparql-results+xml',
'gram': 'application/srgs',
'grxml': 'application/srgs+xml',
'ssml': 'application/ssml+xml',
'skp': 'application/vnd.koan',
'sgml': 'text/sgml',
'sdc': 'application/vnd.stardivision.calc',
'sda': 'application/vnd.stardivision.draw',
'sdd': 'application/vnd.stardivision.impress',
'smf': 'application/vnd.stardivision.math',
'sdw': 'application/vnd.stardivision.writer',
'sgl': 'application/vnd.stardivision.writer-global',
'sm': 'application/vnd.stepmania.stepchart',
'sit': 'application/x-stuffit',
'sitx': 'application/x-stuffitx',
'sdkm': 'application/vnd.solent.sdkm+xml',
'xo': 'application/vnd.olpc-sugar',
'au': 'audio/basic',
'wqd': 'application/vnd.wqd',
'sis': 'application/vnd.symbian.install',
'smi': 'application/smil+xml',
'xsm': 'application/vnd.syncml+xml',
'bdm': 'application/vnd.syncml.dm+wbxml',
'xdm': 'application/vnd.syncml.dm+xml',
'sv4cpio': 'application/x-sv4cpio',
'sv4crc': 'application/x-sv4crc',
'sbml': 'application/sbml+xml',
'tsv': 'text/tab-separated-values',
'tiff': 'image/tiff',
'tao': 'application/vnd.tao.intent-module-archive',
'tar': 'application/x-tar',
'tcl': 'application/x-tcl',
'tex': 'application/x-tex',
'tfm': 'application/x-tex-tfm',
'tei': 'application/tei+xml',
'txt': 'text/plain',
'dxp': 'application/vnd.spotfire.dxp',
'sfs': 'application/vnd.spotfire.sfs',
'tsd': 'application/timestamped-data',
'tpt': 'application/vnd.trid.tpt',
'mxs': 'application/vnd.triscape.mxs',
't': 'text/troff',
'tra': 'application/vnd.trueapp',
'ttf': 'application/x-font-ttf',
'ttl': 'text/turtle',
'umj': 'application/vnd.umajin',
'uoml': 'application/vnd.uoml+xml',
'unityweb': 'application/vnd.unity',
'ufd': 'application/vnd.ufdl',
'uri': 'text/uri-list',
'utz': 'application/vnd.uiq.theme',
'ustar': 'application/x-ustar',
'uu': 'text/x-uuencode',
'vcs': 'text/x-vcalendar',
'vcf': 'text/x-vcard',
'vcd': 'application/x-cdlink',
'vsf': 'application/vnd.vsf',
'wrl': 'model/vrml',
'vcx': 'application/vnd.vcx',
'mts': 'model/vnd.mts',
'vtu': 'model/vnd.vtu',
'vis': 'application/vnd.visionary',
'viv': 'video/vnd.vivo',
'ccxml': 'application/ccxml+xml,',
'vxml': 'application/voicexml+xml',
'src': 'application/x-wais-source',
'wbxml': 'application/vnd.wap.wbxml',
'wbmp': 'image/vnd.wap.wbmp',
'wav': 'audio/x-wav',
'davmount': 'application/davmount+xml',
'woff': 'application/x-font-woff',
'wspolicy': 'application/wspolicy+xml',
'webp': 'image/webp',
'wtb': 'application/vnd.webturbo',
'wgt': 'application/widget',
'hlp': 'application/winhlp',
'wml': 'text/vnd.wap.wml',
'wmls': 'text/vnd.wap.wmlscript',
'wmlsc': 'application/vnd.wap.wmlscriptc',
'wpd': 'application/vnd.wordperfect',
'stf': 'application/vnd.wt.stf',
'wsdl': 'application/wsdl+xml',
'xbm': 'image/x-xbitmap',
'xpm': 'image/x-xpixmap',
'xwd': 'image/x-xwindowdump',
'der': 'application/x-x509-ca-cert',
'fig': 'application/x-xfig',
'xhtml': 'application/xhtml+xml',
'xml': 'application/xml',
'xdf': 'application/xcap-diff+xml',
'xenc': 'application/xenc+xml',
'xer': 'application/patch-ops-error+xml',
'rl': 'application/resource-lists+xml',
'rs': 'application/rls-services+xml',
'rld': 'application/resource-lists-diff+xml',
'xslt': 'application/xslt+xml',
'xop': 'application/xop+xml',
'xpi': 'application/x-xpinstall',
'xspf': 'application/xspf+xml',
'xul': 'application/vnd.mozilla.xul+xml',
'xyz': 'chemical/x-xyz',
'yaml': 'text/yaml',
'yang': 'application/yang',
'yin': 'application/yin+xml',
'zir': 'application/vnd.zul',
'zip': 'application/zip',
'zmm': 'application/vnd.handheld-entertainment+xml',
'zaz': 'application/vnd.zzazz.deck+xml',
}
const mimeIcons = {
pdf: 'mdi-pdf-box',
docx: 'mdi-file-word-outline',
dotx: 'mdi-file-word-outline',
odt: 'mdi-file-word-outline',
odm: 'mdi-file-word-outline',
ott: 'mdi-file-word-outline',
ppt: 'mdi-file-powerpoint-box',
pptx: 'mdi-file-powerpoint-box',
sldx: 'mdi-file-powerpoint-box',
ppsx: 'mdi-file-powerpoint-box',
potx: 'mdi-file-powerpoint-box',
xls: 'mdi-file-excel-outline',
xlam: 'mdi-file-excel-outline',
xlsb: 'mdi-file-excel-outline',
xltm: 'mdi-file-excel-outline',
xlsm: 'mdi-file-excel-outline',
}
export { mimeTypes, mimeIcons }

4
packages/noco-docs/content/en/engineering/unit-testing.md

@ -43,7 +43,7 @@ npm run test:unit
### Folder Structure
The root folder for unit tests is `packages/tests/unit`
The root folder for unit tests is `packages/nocodb/tests/unit`
- `rest` folder contains all the test suites for rest apis.
- `model` folder contains all the test suites for models.
@ -69,7 +69,7 @@ We will create an `Table` test suite as an example.
#### Configure test
We will configure `beforeEach` which is called before each test is executed. We will use `init` function from `nocodb/packages/tests/unit/init/index.ts`, which is a helper function which configures the test environment(i.e resetting state, etc.).
We will configure `beforeEach` which is called before each test is executed. We will use `init` function from `nocodb/packages/nocodb/tests/unit/init/index.ts`, which is a helper function which configures the test environment(i.e resetting state, etc.).
`init` does the following things -

9
packages/nocodb/src/lib/meta/api/attachmentApis.ts

@ -17,7 +17,9 @@ import { NC_ATTACHMENT_FIELD_SIZE } from '../../constants';
const isUploadAllowed = async (req: Request, _res: Response, next: any) => {
if (!req['user']?.id) {
NcError.unauthorized('Unauthorized');
if (!req['user']?.isPublicBase) {
NcError.unauthorized('Unauthorized');
}
}
try {
@ -25,6 +27,7 @@ const isUploadAllowed = async (req: Request, _res: Response, next: any) => {
if (
req['user'].roles?.includes(OrgUserRoles.SUPER_ADMIN) ||
req['user'].roles?.includes(OrgUserRoles.CREATOR) ||
req['user'].roles?.includes(ProjectRoles.EDITOR) ||
// if viewer then check at-least one project have editor or higher role
// todo: cache
!!(await Noco.ncMeta
@ -54,7 +57,7 @@ export async function upload(req: Request, res: Response) {
(req as any).files?.map(async (file) => {
const fileName = `${nanoid(18)}${path.extname(file.originalname)}`;
let url = await storageAdapter.fileCreate(
const url = await storageAdapter.fileCreate(
slash(path.join(destPath, fileName)),
file
);
@ -98,7 +101,7 @@ export async function uploadViaURL(req: Request, res: Response) {
const fileName = `${nanoid(18)}${_fileName || url.split('/').pop()}`;
let attachmentUrl = await (storageAdapter as any).fileCreateByUrl(
const attachmentUrl = await (storageAdapter as any).fileCreateByUrl(
slash(path.join(destPath, fileName)),
url
);

22
tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts

@ -48,15 +48,21 @@ export class AttachmentCellPageObject extends BasePage {
}
async verifyFileCount({ index, columnHeader, count }: { index: number; columnHeader: string; count: number }) {
const attachments = await this.get({ index, columnHeader }).locator(
'.nc-cell > .nc-attachment-cell > .flex > .nc-attachment'
);
// retry below logic for 5 times, with 1 second delay
let retryCount = 0;
while (retryCount < 5) {
const attachments = await this.get({ index, columnHeader }).locator('.nc-attachment');
console.log(await attachments.count());
if ((await attachments.count()) === count) {
break;
}
retryCount++;
await this.rootPage.waitForTimeout(1000);
console.log(await attachments.count());
expect(await attachments.count()).toBe(count);
// attachments should be of count 'count'
// await expect(await attachments.count()).toBe(count);
if (retryCount === 5) {
expect(await attachments.count()).toBe(count);
}
}
}
async expandModalClose() {

1
tests/playwright/tests/columnAttachments.spec.ts

@ -138,7 +138,6 @@ test.describe('Attachment column', () => {
columnHeader: 'testAttach',
filePath: twoFileArray,
});
await dashboard.rootPage.waitForTimeout(2000);
await dashboard.grid.cell.attachment.verifyFileCount({
index: 1,
columnHeader: 'testAttach',

Loading…
Cancel
Save