diff --git a/apps/nextjs-app/src/features/app/utils/clipboard.spec.ts b/apps/nextjs-app/src/features/app/utils/clipboard.spec.ts index 9d1d02da5..b63cafd13 100644 --- a/apps/nextjs-app/src/features/app/utils/clipboard.spec.ts +++ b/apps/nextjs-app/src/features/app/utils/clipboard.spec.ts @@ -25,9 +25,18 @@ vi.mock('@teable/core', () => { }; }); +vi.mock('zod', () => { + return { + z: { + array: () => ({ + safeParse: (data: string) => ({ success: true, data }), + }), + }, + }; +}); + describe('clipboard', () => { - const html = - '
NameCountStatus
John20light
Tom30medium
Bob40heavy
'; + const html = `
John20light
Tom30medium
Bob40heavy
`; const expectedHeader = [ { diff --git a/apps/nextjs-app/src/features/app/utils/clipboard.ts b/apps/nextjs-app/src/features/app/utils/clipboard.ts index a9480a0c2..2db925393 100644 --- a/apps/nextjs-app/src/features/app/utils/clipboard.ts +++ b/apps/nextjs-app/src/features/app/utils/clipboard.ts @@ -1,7 +1,9 @@ import { FieldType, fieldVoSchema, parseClipboardText, type IFieldVo } from '@teable/core'; +import { z } from 'zod'; import { fromZodError } from 'zod-validation-error'; const teableHtmlMarker = 'data-teable-html-marker'; +const teableHeader = 'data-teable-html-header'; export const serializerHtml = (data: string, headers: IFieldVo[]) => { const tableData = parseClipboardText(data); @@ -18,13 +20,8 @@ export const serializerHtml = (data: string, headers: IFieldVo[]) => { .join('')}`; }) .join(''); - const headerContent = headers - .map((header) => { - return `${header.name}`; - }) - .join(''); - return `${headerContent}${bodyContent}
`; + return `${bodyContent}
`; }; export const extractTableHeader = (html?: string) => { @@ -34,23 +31,16 @@ export const extractTableHeader = (html?: string) => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const table = doc.querySelector('table'); - const headerRow = table?.querySelector('thead tr'); - const headerCells = headerRow?.querySelectorAll('th') || []; - - const headers = Array.from(headerCells); - let error = ''; - const result = headers.map((cell) => { - const fieldVoStr = cell.getAttribute('data-field'); - const fieldVo = fieldVoStr ? JSON.parse(decodeURIComponent(fieldVoStr)) : undefined; - - const validate = fieldVoSchema.safeParse(fieldVo); - if (validate.success) { - return fieldVo; - } - error = fromZodError(validate.error).message; - return undefined; - }) as IFieldVo[]; - return error ? { result: undefined, error } : { result }; + const headerStr = table?.getAttribute(teableHeader); + const headers = headerStr ? JSON.parse(decodeURIComponent(headerStr)) : undefined; + if (!headers) { + return { result: undefined }; + } + const validate = z.array(fieldVoSchema).safeParse(headers); + if (!validate.success) { + return { result: undefined, error: fromZodError(validate.error).message }; + } + return { result: validate.data }; }; export const isTeableHTML = (html: string) => { diff --git a/plugins/next.config.mjs b/plugins/next.config.mjs index d6c041c4a..ac117984d 100644 --- a/plugins/next.config.mjs +++ b/plugins/next.config.mjs @@ -29,8 +29,8 @@ const nextConfig = { } }), { - key: 'X-Frame-Options', - value: 'SAMEORIGIN' + key: 'Content-Security-Policy', + value: 'frame-ancestors *' }, { key: 'Cross-Origin-Opener-Policy', value: isProd ? 'same-origin' : 'unsafe-none' }, { key: 'Cross-Origin-Embedder-Policy', value: isProd ? 'same-origin' : 'unsafe-none' }