mirror of https://github.com/nocodb/nocodb
32 changed files with 1159 additions and 206 deletions
@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup> |
||||
import type { Row } from '~/composables' |
||||
import { useProvideSmartsheetRowStore, useSmartsheetStoreOrThrow } from '#imports' |
||||
|
||||
interface Props { |
||||
row: Row |
||||
} |
||||
|
||||
const props = defineProps<Props>() |
||||
const currentRow = toRef(props, 'row') |
||||
|
||||
const { meta } = useSmartsheetStoreOrThrow() |
||||
const { isNew, state, syncLTARRefs } = useProvideSmartsheetRowStore(meta, currentRow) |
||||
|
||||
// on changing isNew(new record insert) status sync LTAR cell values |
||||
watch(isNew, async (nextVal, prevVal) => { |
||||
if (prevVal && !nextVal) { |
||||
await syncLTARRefs(currentRow.value.row) |
||||
// update row values without invoking api |
||||
currentRow.value.row = { ...currentRow.value.row, ...state.value } |
||||
currentRow.value.oldRow = { ...currentRow.value.row, ...state.value } |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<slot :state="state" /> |
||||
</template> |
@ -0,0 +1,93 @@
|
||||
<script setup lang="ts"> |
||||
import { nextTick, useExpandedFormStoreOrThrow } from '#imports' |
||||
import { enumColor, timeAgo } from '~/utils' |
||||
import MdiAccountIcon from '~icons/mdi/account-circle' |
||||
|
||||
const { loadCommentsAndLogs, commentsAndLogs, isCommentsLoading, commentsOnly, saveComment, isYou, comment } = |
||||
useExpandedFormStoreOrThrow() |
||||
|
||||
const commentsWrapperEl = ref<HTMLDivElement>() |
||||
|
||||
await loadCommentsAndLogs() |
||||
|
||||
watch( |
||||
commentsAndLogs, |
||||
() => { |
||||
nextTick(() => { |
||||
if (commentsWrapperEl.value) commentsWrapperEl.value.scrollTop = commentsWrapperEl.value?.scrollHeight |
||||
}) |
||||
}, |
||||
{ immediate: true }, |
||||
) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="h-full d-flex flex-column w-full"> |
||||
<div ref="commentsWrapperEl" class="flex-grow-1 min-h-[100px] overflow-y-auto scrollbar-thin-primary p-2"> |
||||
<v-skeleton-loader v-if="isCommentsLoading && !commentsAndLogs" type="list-item-avatar-two-line@8" /> |
||||
|
||||
<template v-else> |
||||
<div v-for="log of commentsAndLogs" :key="log.id" class="flex gap-1 text-xs"> |
||||
<MdiAccountIcon class="row-span-2" :class="isYou(log.user) ? 'text-pink-300' : 'text-blue-300 '" /> |
||||
<div class="flex-grow"> |
||||
<p class="mb-1 caption edited-text text-[10px] text-gray"> |
||||
{{ isYou(log.user) ? 'You' : log.user == null ? 'Shared base' : log.user }} |
||||
{{ log.op_type === 'COMMENT' ? 'commented' : log.op_sub_type === 'INSERT' ? 'created' : 'edited' }} |
||||
</p> |
||||
<p |
||||
v-if="log.op_type === 'COMMENT'" |
||||
class="caption mb-0 nc-chip w-full min-h-20px" |
||||
:style="{ backgroundColor: enumColor.light[2] }" |
||||
> |
||||
{{ log.description }} |
||||
</p> |
||||
|
||||
<p v-else v-dompurify-html="log.details" class="caption mb-0" style="word-break: break-all" /> |
||||
|
||||
<p class="time text-right text-[10px] mb-0"> |
||||
{{ timeAgo(log.created_at) }} |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
<div class="border-1 my-2 w-full ml-6" /> |
||||
<div class="p-0"> |
||||
<div class="flex justify-center"> |
||||
<a-checkbox v-model:checked="commentsOnly" @change="loadCommentsAndLogs" |
||||
><span class="text-[11px] text-gray-500">Comments only</span> |
||||
</a-checkbox> |
||||
</div> |
||||
<div class="flex-shrink-1 mt-2 d-flex pl-4"> |
||||
<a-input |
||||
v-model:value="comment" |
||||
class="!text-xs" |
||||
ghost |
||||
:class="{ focus: showborder }" |
||||
@focusin="showborder = true" |
||||
@focusout="showborder = false" |
||||
@keyup.enter.prevent="saveComment" |
||||
> |
||||
<template #addonBefore> |
||||
<div class="flex align-center"> |
||||
<mdi-account-circle class="text-lg text-pink-300" small @click="saveComment" /> |
||||
</div> |
||||
</template> |
||||
<template #suffix> |
||||
<mdi-keyboard-return v-if="comment" class="text-sm" small @click="saveComment" /> |
||||
</template> |
||||
</a-input> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped> |
||||
:deep(.red.lighten-4) { |
||||
@apply bg-red-100; |
||||
} |
||||
|
||||
:deep(.green.lighten-4) { |
||||
@apply bg-green-100; |
||||
} |
||||
</style> |
@ -0,0 +1,60 @@
|
||||
<script lang="ts" setup> |
||||
import { |
||||
computed, |
||||
useExpandedFormStoreOrThrow, |
||||
useSmartsheetRowStoreOrThrow, |
||||
useSmartsheetStoreOrThrow, |
||||
useUIPermission, |
||||
} from '#imports' |
||||
import MdiDoorOpen from '~icons/mdi/door-open' |
||||
import MdiDoorClosed from '~icons/mdi/door-closed' |
||||
|
||||
const emit = defineEmits(['cancel']) |
||||
const { meta } = useSmartsheetStoreOrThrow() |
||||
const { commentsDrawer, primaryValue, save: _save } = useExpandedFormStoreOrThrow() |
||||
const { isNew, syncLTARRefs } = useSmartsheetRowStoreOrThrow() |
||||
const { isUIAllowed } = useUIPermission() |
||||
|
||||
const save = async () => { |
||||
if (isNew.value) { |
||||
const data = await _save() |
||||
await syncLTARRefs(data) |
||||
} else { |
||||
await _save() |
||||
} |
||||
} |
||||
|
||||
const drawerToggleIcon = computed(() => (commentsDrawer.value ? MdiDoorOpen : MdiDoorClosed)) |
||||
|
||||
// todo: accept as a prop / inject |
||||
const iconColor = '#1890ff' |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex p-2 align-center gap-2"> |
||||
<h5 class="text-lg font-weight-medium flex align-center gap-1 mb-0"> |
||||
<mdi-table-arrow-right :style="{ color: iconColor }" /> |
||||
|
||||
<template v-if="meta"> |
||||
{{ meta.title }} |
||||
</template> |
||||
<template v-else> |
||||
{{ table }} |
||||
</template> |
||||
<template v-if="primaryValue">: {{ primaryValue }}</template> |
||||
</h5> |
||||
<div class="flex-grow" /> |
||||
<mdi-reload class="cursor-pointer select-none" /> |
||||
<component :is="drawerToggleIcon" class="cursor-pointer select-none" @click="commentsDrawer = !commentsDrawer" /> |
||||
<a-button size="small" class="!text" @click="emit('cancel')"> |
||||
<!-- Cancel --> |
||||
{{ $t('general.cancel') }} |
||||
</a-button> |
||||
<a-button size="small" :disabled="!isUIAllowed('tableRowUpdate')" type="primary" @click="save"> |
||||
<!-- Save Row --> |
||||
{{ $t('activity.saveRow') }} |
||||
</a-button> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped></style> |
@ -0,0 +1,139 @@
|
||||
<script setup lang="ts"> |
||||
import type { ColumnType, TableType } from 'nocodb-sdk' |
||||
import { isVirtualCol } from 'nocodb-sdk' |
||||
import Comments from './Comments.vue' |
||||
import Header from './Header.vue' |
||||
import { |
||||
computedInject, |
||||
provide, |
||||
toRef, |
||||
useNuxtApp, |
||||
useProvideExpandedFormStore, |
||||
useProvideSmartsheetStore, |
||||
useVModel, |
||||
watch, |
||||
} from '#imports' |
||||
import { NOCO } from '~/lib' |
||||
import { extractPkFromRow } from '~/utils' |
||||
import type { Row } from '~/composables' |
||||
import { FieldsInj, IsFormInj, MetaInj } from '~/context' |
||||
|
||||
interface Props { |
||||
modelValue: string | null |
||||
row: Row |
||||
state?: Record<string, any> | null |
||||
meta: TableType |
||||
loadRow?: boolean |
||||
useMetaFields?: boolean |
||||
} |
||||
|
||||
const props = defineProps<Props>() |
||||
const emits = defineEmits(['update:modelValue']) |
||||
const row = toRef(props, 'row') |
||||
const state = toRef(props, 'state') |
||||
const meta = toRef(props, 'meta') |
||||
|
||||
const _fields = computedInject(FieldsInj, (_fields) => { |
||||
if (props.useMetaFields) { |
||||
return meta.value.columns ?? [] |
||||
} |
||||
return _fields?.value ?? [] |
||||
}) |
||||
|
||||
provide(MetaInj, meta) |
||||
|
||||
const { commentsDrawer, changedColumns, state: rowState } = useProvideExpandedFormStore(meta, row) |
||||
|
||||
const { $api } = useNuxtApp() |
||||
if (props.loadRow) { |
||||
const { project } = useProject() |
||||
row.value.row = await $api.dbTableRow.read( |
||||
NOCO, |
||||
project.value.id as string, |
||||
meta.value.title, |
||||
extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]), |
||||
) |
||||
row.value.oldRow = { ...row.value.row } |
||||
row.value.rowMeta = {} |
||||
} |
||||
|
||||
useProvideSmartsheetStore(ref({}) as any, meta) |
||||
|
||||
provide(IsFormInj, true) |
||||
|
||||
// accept as a prop |
||||
// const row: Row = { row: {}, rowMeta: {}, oldRow: {} } |
||||
|
||||
watch( |
||||
state, |
||||
() => { |
||||
if (state.value) { |
||||
rowState.value = state.value |
||||
} else { |
||||
rowState.value = {} |
||||
} |
||||
}, |
||||
{ immediate: true }, |
||||
) |
||||
|
||||
const isExpanded = useVModel(props, 'modelValue', emits) |
||||
</script> |
||||
|
||||
<template> |
||||
<a-modal v-model:visible="isExpanded" :footer="null" width="min(90vw,1000px)" :body-style="{ padding: 0 }" :closable="false"> |
||||
<Header @cancel="isExpanded = false" /> |
||||
<a-card class="!bg-gray-100"> |
||||
<div class="flex h-full nc-form-wrapper items-stretch"> |
||||
<div class="flex-grow overflow-auto scrollbar-thin-primary"> |
||||
<div class="w-[500px] mx-auto"> |
||||
<div v-for="col in fields" :key="col.title" class="mt-2"> |
||||
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" /> |
||||
<SmartsheetHeaderCell v-else :column="col" /> |
||||
|
||||
<div class="!bg-white rounded px-1 min-h-[35px] flex align-center"> |
||||
<SmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row" :column="col" /> |
||||
<SmartsheetCell |
||||
v-else |
||||
v-model="row.row[col.title]" |
||||
:column="col" |
||||
:edit-enabled="true" |
||||
@update:model-value="changedColumns.add(col.title)" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="nc-comments-drawer min-w-0 min-h-full max-h-full" :class="{ active: commentsDrawer }"> |
||||
<div class="h-full"> |
||||
<Comments v-if="commentsDrawer" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</a-card> |
||||
</a-modal> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
:deep(input, select, textarea) { |
||||
@apply !bg-white; |
||||
} |
||||
|
||||
:deep(.ant-modal-body) { |
||||
@apply !bg-gray-100; |
||||
} |
||||
|
||||
.nc-comments-drawer { |
||||
@apply w-0 transition-width ease-in-out duration-200; |
||||
overflow: hidden; |
||||
|
||||
&.active { |
||||
@apply w-[250px] border-left-1; |
||||
} |
||||
} |
||||
|
||||
.nc-form-wrapper { |
||||
max-height: max(calc(90vh - 100px), 600px); |
||||
height: max-content !important; |
||||
} |
||||
</style> |
@ -0,0 +1,209 @@
|
||||
import { UITypes } from 'nocodb-sdk' |
||||
import type { ColumnType, TableType } from 'nocodb-sdk' |
||||
import type { Ref } from 'vue' |
||||
import { message, notification } from 'ant-design-vue' |
||||
import dayjs from 'dayjs' |
||||
import { useApi, useInjectionState, useProject, useProvideSmartsheetRowStore } from '#imports' |
||||
import { NOCO } from '~/lib' |
||||
import { useNuxtApp } from '#app' |
||||
import type { Row } from '~/composables/useViewData' |
||||
import { extractPkFromRow, extractSdkResponseErrorMsg } from '~/utils' |
||||
|
||||
const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((meta: Ref<TableType>, row: Ref<Row>) => { |
||||
const { $e, $state, $api } = useNuxtApp() |
||||
const { api, isLoading: isCommentsLoading, error: commentsError } = useApi() |
||||
// { useGlobalInstance: true },
|
||||
// state
|
||||
const commentsOnly = ref(false) |
||||
const commentsAndLogs = ref([]) |
||||
const comment = ref('') |
||||
const commentsDrawer = ref(false) |
||||
const changedColumns = ref(new Set<string>()) |
||||
const { project } = useProject() |
||||
const rowStore = useProvideSmartsheetRowStore(meta, row) |
||||
// todo
|
||||
// const activeView = inject(ActiveViewInj)
|
||||
|
||||
// const { updateOrSaveRow, insertRow } = useViewData(meta, activeView as any)
|
||||
|
||||
// getters
|
||||
const primaryValue = computed(() => { |
||||
if (row?.value?.row) { |
||||
const col = meta?.value?.columns?.find((c) => c.pv) |
||||
if (!col) { |
||||
return |
||||
} |
||||
const value = row.value.row?.[col.title as string] |
||||
const uidt = col.uidt |
||||
if (uidt === UITypes.Date) { |
||||
return (/^\d+$/.test(value) ? dayjs(+value) : dayjs(value)).format('YYYY-MM-DD') |
||||
} else if (uidt === UITypes.DateTime) { |
||||
return (/^\d+$/.test(value) ? dayjs(+value) : dayjs(value)).format('YYYY-MM-DD HH:mm') |
||||
} else if (uidt === UITypes.Time) { |
||||
let dateTime = dayjs(value) |
||||
if (!dateTime.isValid()) { |
||||
dateTime = dayjs(value, 'HH:mm:ss') |
||||
} |
||||
if (!dateTime.isValid()) { |
||||
dateTime = dayjs(`1999-01-01 ${value}`) |
||||
} |
||||
if (!dateTime.isValid()) { |
||||
return value |
||||
} |
||||
return dateTime.format('HH:mm:ss') |
||||
} |
||||
return value |
||||
} |
||||
}) |
||||
|
||||
// actions
|
||||
const loadCommentsAndLogs = async () => { |
||||
if (!row.value) return |
||||
const rowId = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]) |
||||
if (!rowId) return |
||||
commentsAndLogs.value = |
||||
( |
||||
await api.utils.commentList({ |
||||
row_id: rowId, |
||||
fk_model_id: meta.value.id as string, |
||||
comments_only: commentsOnly.value, |
||||
}) |
||||
)?.reverse?.() || [] |
||||
} |
||||
|
||||
const isYou = (email: string) => { |
||||
return $state.user?.value?.email === email |
||||
} |
||||
|
||||
const saveComment = async () => { |
||||
try { |
||||
if (!row.value || !comment.value) return |
||||
const rowId = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]) |
||||
if (!rowId) return |
||||
|
||||
await api.utils.commentRow({ |
||||
fk_model_id: meta.value?.id as string, |
||||
row_id: rowId, |
||||
// todo: swagger type correction
|
||||
description: comment.value, |
||||
} as any) |
||||
|
||||
comment.value = '' |
||||
message.success('Comment added successfully') |
||||
await loadCommentsAndLogs() |
||||
} catch (e: any) { |
||||
message.error(e.message) |
||||
} |
||||
|
||||
$e('a:row-expand:comment') |
||||
} |
||||
|
||||
const save = async () => { |
||||
let data |
||||
try { |
||||
// todo:
|
||||
// if (this.presetValues) {
|
||||
// // cater presetValues
|
||||
// for (const k in this.presetValues) {
|
||||
// this.$set(this.changedColumns, k, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
const updateOrInsertObj = [...changedColumns.value].reduce((obj, col) => { |
||||
obj[col] = row.value.row[col] |
||||
return obj |
||||
}, {} as Record<string, any>) |
||||
|
||||
if (row.value.rowMeta.new) { |
||||
data = await $api.dbTableRow.create('noco', project.value.title as string, meta.value.title, updateOrInsertObj) |
||||
|
||||
/* todo: |
||||
// save hasmany and manytomany relations from local state
|
||||
if (this.$refs.virtual && Array.isArray(this.$refs.virtual)) { |
||||
for (const vcell of this.$refs.virtual) { |
||||
if (vcell.save) { |
||||
await vcell.save(this.localState); |
||||
} |
||||
} |
||||
} */ |
||||
row.value = { |
||||
row: data, |
||||
rowMeta: {}, |
||||
oldRow: { ...data }, |
||||
} |
||||
|
||||
/// todo:
|
||||
// await this.reload();
|
||||
} else if (Object.keys(updateOrInsertObj).length) { |
||||
const id = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]) |
||||
|
||||
if (!id) { |
||||
return message.info("Update not allowed for table which doesn't have primary Key") |
||||
} |
||||
await $api.dbTableRow.update(NOCO, project.value.title as string, meta.value.title, id, updateOrInsertObj) |
||||
for (const key of Object.keys(updateOrInsertObj)) { |
||||
// audit
|
||||
$api.utils |
||||
.auditRowUpdate(id, { |
||||
fk_model_id: meta.value.id, |
||||
column_name: key, |
||||
row_id: id, |
||||
value: getPlainText(updateOrInsertObj[key]), |
||||
prev_value: getPlainText(row.value.oldRow[key]), |
||||
}) |
||||
.then(() => {}) |
||||
} |
||||
} else { |
||||
return message.info('No columns to update') |
||||
} |
||||
|
||||
// this.$emit('update:oldRow', { ...this.localState });
|
||||
// this.changedColumns = {};
|
||||
// this.$emit('input', this.localState);
|
||||
// this.$emit('update:isNew', false);
|
||||
|
||||
notification.success({ |
||||
message: `${primaryValue.value || 'Row'} updated successfully.`, |
||||
// position: 'bottom-right',
|
||||
}) |
||||
|
||||
changedColumns.value = new Set() |
||||
} catch (e: any) { |
||||
notification.error({ message: `Failed to update row`, description: await extractSdkResponseErrorMsg(e) }) |
||||
} |
||||
$e('a:row-expand:add') |
||||
return data |
||||
} |
||||
|
||||
return { |
||||
...rowStore, |
||||
commentsOnly, |
||||
loadCommentsAndLogs, |
||||
commentsAndLogs, |
||||
isCommentsLoading, |
||||
commentsError, |
||||
saveComment, |
||||
comment, |
||||
isYou, |
||||
commentsDrawer, |
||||
row, |
||||
primaryValue, |
||||
save, |
||||
changedColumns, |
||||
} |
||||
}, 'expanded-form-store') |
||||
|
||||
export { useProvideExpandedFormStore } |
||||
|
||||
export function useExpandedFormStoreOrThrow() { |
||||
const expandedFormStore = useExpandedFormStore() |
||||
if (expandedFormStore == null) throw new Error('Please call `useExpandedFormStore` on the appropriate parent component') |
||||
return expandedFormStore |
||||
} |
||||
|
||||
// todo: move to utils
|
||||
function getPlainText(htmlString: string) { |
||||
const div = document.createElement('div') |
||||
div.textContent = htmlString || '' |
||||
return div.innerHTML |
||||
} |
@ -0,0 +1,105 @@
|
||||
import { notification } from 'ant-design-vue' |
||||
import { UITypes } from 'nocodb-sdk' |
||||
import type { ColumnType, LinkToAnotherRecordType, RelationTypes, TableType } from 'nocodb-sdk' |
||||
import type { Ref } from 'vue' |
||||
import { useNuxtApp } from '#app' |
||||
import { useInjectionState, useMetas, useProject, useVirtualCell } from '#imports' |
||||
import type { Row } from '~/composables/useViewData' |
||||
import { NOCO } from '~/lib' |
||||
import { extractPkFromRow, extractSdkResponseErrorMsg } from '~/utils' |
||||
|
||||
const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState((meta: Ref<TableType>, row: Ref<Row>) => { |
||||
const { $api } = useNuxtApp() |
||||
const { project } = useProject() |
||||
const { metas } = useMetas() |
||||
|
||||
// state
|
||||
const state = ref<Record<string, Record<string, any> | Record<string, any>[] | null>>({}) |
||||
|
||||
// getters
|
||||
const isNew = computed(() => row.value?.rowMeta?.new ?? false) |
||||
|
||||
// actions
|
||||
const addLTARRef = async (value: Record<string, any>, column: ColumnType) => { |
||||
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column))) |
||||
if (isHm || isMm) { |
||||
state.value[column.title!] = state.value[column.title!] || [] |
||||
state.value[column.title!]!.push(value) |
||||
} else if (isBt) { |
||||
state.value[column.title!] = value |
||||
} |
||||
} |
||||
|
||||
// actions
|
||||
const removeLTARRef = async (value: Record<string, any>, column: ColumnType) => { |
||||
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column))) |
||||
if (isHm || isMm) { |
||||
state.value[column.title!]?.splice(state.value[column.title!]?.indexOf(value), 1) |
||||
} else if (isBt) { |
||||
state.value[column.title!] = null |
||||
} |
||||
} |
||||
|
||||
const linkRecord = async (rowId: string, relatedRowId: string, column: ColumnType, type: RelationTypes) => { |
||||
try { |
||||
await $api.dbTableRow.nestedAdd( |
||||
NOCO, |
||||
project.value.title as string, |
||||
meta.value.title as string, |
||||
rowId, |
||||
type, |
||||
column.title as string, |
||||
relatedRowId, |
||||
) |
||||
} catch (e: any) { |
||||
notification.error({ |
||||
message: 'Linking failed', |
||||
description: await extractSdkResponseErrorMsg(e), |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/** sync LTAR relations kept in local state */ |
||||
const syncLTARRefs = async (row: Record<string, any>) => { |
||||
const id = extractPkFromRow(row, meta.value.columns as ColumnType[]) |
||||
for (const column of meta?.value?.columns ?? []) { |
||||
if (column.uidt !== UITypes.LinkToAnotherRecord) continue |
||||
const colOptions = column?.colOptions as LinkToAnotherRecordType |
||||
|
||||
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column))) |
||||
const relatedTableMeta = metas.value?.[colOptions?.fk_related_model_id as string] |
||||
|
||||
if (isHm || isMm) { |
||||
const relatedRows = (state.value?.[column.title!] ?? []) as Record<string, any>[] |
||||
for (const relatedRow of relatedRows) { |
||||
await linkRecord(id, extractPkFromRow(relatedRow, relatedTableMeta.columns as ColumnType[]), column, colOptions.type) |
||||
} |
||||
} else if (isBt && state?.value?.[column.title!]) { |
||||
await linkRecord( |
||||
id, |
||||
extractPkFromRow(state.value?.[column.title!] as Record<string, any>, relatedTableMeta.columns as ColumnType[]), |
||||
column, |
||||
colOptions.type, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return { |
||||
row, |
||||
state, |
||||
isNew, |
||||
// todo: use better name
|
||||
addLTARRef, |
||||
removeLTARRef, |
||||
syncLTARRefs, |
||||
} |
||||
}, 'smartsheet-row-store') |
||||
|
||||
export { useProvideSmartsheetRowStore } |
||||
|
||||
export function useSmartsheetRowStoreOrThrow() { |
||||
const smartsheetRowStore = useSmartsheetRowStore() |
||||
if (smartsheetRowStore == null) throw new Error('Please call `useSmartsheetRowStore` on the appropriate parent component') |
||||
return smartsheetRowStore |
||||
} |
@ -0,0 +1,6 @@
|
||||
import VueDOMPurifyHTML from 'vue-dompurify-html' |
||||
import { defineNuxtPlugin } from 'nuxt/app' |
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => { |
||||
nuxtApp.vueApp.use(VueDOMPurifyHTML) |
||||
}) |
@ -0,0 +1,11 @@
|
||||
import type { ColumnType } from 'nocodb-sdk' |
||||
|
||||
export const extractPkFromRow = (row: Record<string, any>, columns: ColumnType[]) => { |
||||
return ( |
||||
row && |
||||
columns |
||||
?.filter((c) => c.pk) |
||||
.map((c) => row?.[c.title as string]) |
||||
.join('___') |
||||
) |
||||
} |
Loading…
Reference in new issue