Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): global copy paste #6856

Merged
merged 96 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
ce6c5c3
feat(form): copy paste of document and fields prototype
sjelfull May 8, 2024
b72a750
feat(core): add global copy paste provider
sjelfull May 16, 2024
da90918
refactor(dev): use new copy paste api in actions
sjelfull May 16, 2024
735a814
refactor(core): move value transfer to own function (tbc)
skogsmaskin May 28, 2024
4877f76
test(core): add value transfer test for global copy/paste
skogsmaskin May 28, 2024
407568f
refactor: prepare for using clipboard insted of LS
sjelfull May 29, 2024
5b053dc
refactor(core): use clipboard when handling onCopy/onPaste
sjelfull May 29, 2024
a37468f
feat(core): add resolveSchemaTypeForPath utility
sjelfull May 31, 2024
3120849
fix(core): pass onChange as props instead of importing from structure
sjelfull May 31, 2024
b0df84d
test(core): add missing _type to test
sjelfull May 31, 2024
bd646f6
feat(core): add support for copy/paste via ctrl-c/v
sjelfull Jun 5, 2024
09f0dc2
refactor(test-studio): use new copy paste signature in doc actions
sjelfull Jun 5, 2024
5fd4174
feat(structure): add copy/paste actions into structure
sjelfull Jun 5, 2024
162a349
refactor(core): use new value transfer function for copy/paste + test…
skogsmaskin Jun 10, 2024
e68ac55
refactor(core): support multiple sources and targets for copy/paste
skogsmaskin Jun 10, 2024
29e5c9c
fix(core): change copy on copy/paste messaging
skogsmaskin Jun 11, 2024
2cf7333
fix(core): set new keys on object for copy/paste
skogsmaskin Jun 12, 2024
5d69c2f
feat(core): add focus handling to reference previews
sjelfull Jun 11, 2024
1fe4b02
feat(core): allow focus on objects
sjelfull Jun 11, 2024
d09bd77
fix(core): skip handling copy event on selections
sjelfull Jun 11, 2024
a6e909a
feat(core): highlight border on focused objects
sjelfull Jun 12, 2024
ed48e33
fix(form): remove focus terminator from cdr focuspath
sjelfull Jun 12, 2024
c451dc6
feat(core): move copy paste field actions to core
sjelfull Jun 12, 2024
b90cdb6
fix(core): fix imports in actions
sjelfull Jun 13, 2024
eff2398
feat(core): add telemetry to copy paste hook
sjelfull Jun 13, 2024
5f22ddc
fix(core): limit object focus to children incld in array modals
sjelfull Jun 13, 2024
d90c526
test(form): add tests for copy pasting fields
sjelfull Jun 13, 2024
9d5b214
refactor(core): don't interfere with native editable elements + use c…
skogsmaskin Jun 14, 2024
93f68b8
fix(core): don't show paste field action on readonly fields
skogsmaskin Jun 14, 2024
22cd8cd
fix(core): copy paste string field must account for string lists
skogsmaskin Jun 14, 2024
e5036a5
fix(core): don't iterate on target schemaType if sourceValue is empty
skogsmaskin Jun 17, 2024
9ed3247
fix(core): quote copied fields in toast msg
skogsmaskin Jun 19, 2024
48c5f39
fix(core): adjust for weak/hard refs when pasting ref. values
skogsmaskin Jun 20, 2024
803d76f
fix(core): fallback to text/plain clipboard item for Webkit
skogsmaskin Jun 23, 2024
469f561
fix(core): add check for non-browser env in helper
sjelfull Jun 25, 2024
8e2c189
test(form): add e2e tests for copy paste
sjelfull Jun 13, 2024
3c843ae
feat(form): add test ids to field actions
sjelfull Jun 17, 2024
c31be24
fix(form): remove blur handling from object
sjelfull Jun 18, 2024
19fe3ae
fix(form): trigger keypress on object wrapper
sjelfull Jun 19, 2024
4ebdc71
test(form): attempt to stabilise e2e tests for copy paste
sjelfull Jun 19, 2024
4a88043
chore: upgrade playwright deps
sjelfull Jun 28, 2024
5dcbef3
test(form): add component tests for copy paste
sjelfull Jun 20, 2024
756aceb
fix(test): make sure fixure awaits setup
sjelfull Jun 21, 2024
33679b9
fix(test): allow. copy pasting permissions in playwright-ct config
sjelfull Jun 21, 2024
b7b2bc8
test(form): stabilise copy paste e2e tests
sjelfull Jun 21, 2024
47dc2db
fix(test): fix clipboard.writeText mock
sjelfull Jun 21, 2024
2371805
refactor(test): remove e2e copypaste test in favour of component test
sjelfull Jun 21, 2024
b6020f5
fix(test): fix assertion texts
sjelfull Jun 21, 2024
f8a0f16
refactor(form): align updated toast msg with the copy toast
sjelfull Jun 21, 2024
ce0c8c3
fix(test): await filling out string inputs
sjelfull Jun 24, 2024
0a4f0b7
chore(test): enable trace/video on retries in CI
sjelfull Jun 24, 2024
f04e529
feat(playwright-ct): add debug fixture
sjelfull Jun 25, 2024
3605c8d
fix(test): more work to stabilise object tests
sjelfull Jun 25, 2024
ba42054
fix(form): filter out epmty ref objects
sjelfull Jun 26, 2024
a7cdbd8
fix(form): ignore copy event on text selection
sjelfull Jun 26, 2024
b2a1237
refactor(form): move copy/paste into document field action
sjelfull Jun 26, 2024
c2f4f14
fix(form): prevent pasting image/file into the opposite type
sjelfull Jun 26, 2024
75022e3
fix(form): validate option.accept on paste
sjelfull Jun 26, 2024
f70ff1e
fix(form): fix styled import
sjelfull Jun 26, 2024
a2decdb
fix(core): added suite of tests + fixes for coercions
sjelfull Jun 27, 2024
d3d44e1
feat(core): translate copy-paste
hermanwikner Jun 27, 2024
f4efe6a
feat(core): translate MIME type copy-paste validation messages
hermanwikner Jun 27, 2024
4b13ca8
test(core): update `valueTransfer` test
hermanwikner Jun 27, 2024
f3f1da5
fix(core): copy-paste unknown copy error translation
hermanwikner Jun 28, 2024
fd69a81
fix(form): transform error path to string with path utils
sjelfull Jun 28, 2024
fb8e83a
fix(form): handle deeply nested paths in arrays
sjelfull Jun 28, 2024
8d7afbf
test(form): add failing test for deeply nested arrays
sjelfull Jun 28, 2024
9a6d6c6
refactor(core): split out test schema
sjelfull Jun 28, 2024
4dfc12e
test(core): add test for copying documents with booleans
sjelfull Jun 28, 2024
eefe504
refactor(form): only copy and paste defined focus path
sjelfull Jul 1, 2024
1f9aefa
chore(form): rename valueTransfer -> transferValue
sjelfull Jul 1, 2024
b6900ed
fix(core): pass client options to remove notice
sjelfull Jul 1, 2024
d2d202b
refactor(core): make reference type and image mime checks for copy/pa…
skogsmaskin Jul 1, 2024
cd7e385
test(core): update tests for copy/paste
skogsmaskin Jul 1, 2024
8534c24
test(core): validate schema before test runs
sjelfull Jul 2, 2024
a9b06c4
fix(core): skip pasting on empty focus path
sjelfull Jul 2, 2024
b8a3afb
refactor(core): only check read-only on root level schema
sjelfull Jul 2, 2024
4618797
fix(core): fix type validation for primitive array target
sjelfull Jul 2, 2024
33652e2
fix(form): fix asset schema compatibility check on root
sjelfull Jul 2, 2024
ff503b6
fix(core): fix potential race condition when setting document meta
sjelfull Jul 2, 2024
de0d441
fix(core): make a reference weak if _strengthenOnPublish is set
sjelfull Jul 2, 2024
2d5da09
fix(core): retain relationship between marks and markDefs
sjelfull Jul 2, 2024
dafbba3
feat(core): validate pasted reference against filter
sjelfull Jul 3, 2024
13f4713
refactor(core): clean up document pane event handler
sjelfull Jul 3, 2024
ae8cdca
refactor(form): add subtle transition on object focus
sjelfull Jul 4, 2024
e2fdcd1
refactor(core): serialize clipboard into HTML for safari and firefox
ricokahler Jul 9, 2024
8a08c4d
refactor(core): rename and simplify `CopyActionResult` to `SanityClip…
ricokahler Jul 9, 2024
d9ebef5
refactor(core): remove unused `copyResult`
ricokahler Jul 9, 2024
1f007ff
refactor(core): lift onCopy and onPaste into Provider
ricokahler Jul 9, 2024
db38dfc
refactor(core): remove unexpected cases
ricokahler Jul 9, 2024
ae560ad
fix(core): update tests
ricokahler Jul 10, 2024
e656e19
fix: add missing return
ricokahler Jul 10, 2024
083ee25
test: skip copy/paste tests for now
ricokahler Jul 10, 2024
ebbbe69
refactor: remove unused interface
ricokahler Jul 11, 2024
f3f37bb
test: update field to prevent collisions
ricokahler Jul 11, 2024
768a5bd
test: update component test timeouts
ricokahler Jul 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions dev/studio-e2e-testing/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import {muxInput} from 'sanity-plugin-mux-input'
import {imageAssetSource} from 'sanity-test-studio/assetSources'
import {resolveDocumentActions as documentActions} from 'sanity-test-studio/documentActions'
import {assistFieldActionGroup} from 'sanity-test-studio/fieldActions/assistFieldActionGroup'
import {copyAction} from 'sanity-test-studio/fieldActions/copyAction'
import {pasteAction} from 'sanity-test-studio/fieldActions/pasteAction'
import {resolveInitialValueTemplates} from 'sanity-test-studio/initialValueTemplates'
import {customInspector} from 'sanity-test-studio/inspectors/custom'
import {languageFilter} from 'sanity-test-studio/plugins/language-filter'
Expand Down Expand Up @@ -53,7 +51,7 @@ export default defineConfig({
},
unstable_fieldActions: (prev, ctx) => {
if (['fieldActionsTest', 'stringsTest'].includes(ctx.documentType)) {
return [...prev, assistFieldActionGroup, copyAction, pasteAction]
return [...prev, assistFieldActionGroup]
}

return prev
Expand Down
22 changes: 0 additions & 22 deletions dev/test-studio/fieldActions/copyAction.ts

This file was deleted.

22 changes: 0 additions & 22 deletions dev/test-studio/fieldActions/pasteAction.ts

This file was deleted.

8 changes: 4 additions & 4 deletions dev/test-studio/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ import {
import {GoogleLogo, TailwindLogo, VercelLogo} from './components/workspaceLogos'
import {resolveDocumentActions as documentActions} from './documentActions'
import {assistFieldActionGroup} from './fieldActions/assistFieldActionGroup'
import {copyAction} from './fieldActions/copyAction'
import {pasteAction} from './fieldActions/pasteAction'
import {resolveInitialValueTemplates} from './initialValueTemplates'
import {customInspector} from './inspectors/custom'
import {testStudioLocaleBundles} from './locales'
Expand Down Expand Up @@ -89,11 +87,13 @@ const sharedSettings = definePlugin({
return prev
},
unstable_fieldActions: (prev, ctx) => {
const defaultActions = [...prev]

if (['fieldActionsTest', 'stringsTest'].includes(ctx.documentType)) {
return [...prev, assistFieldActionGroup, copyAction, pasteAction]
return [...defaultActions, assistFieldActionGroup]
}

return prev
return defaultActions
},
newDocumentOptions,
comments: {
Expand Down
32 changes: 27 additions & 5 deletions dev/test-studio/schema/standard/arrays.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ImageIcon, OlistIcon} from '@sanity/icons'
import {defineField, defineType} from 'sanity'
import {defineArrayMember, defineField, defineType} from 'sanity'

export const topLevelArrayType = defineType({
name: 'topLevelArrayType',
Expand Down Expand Up @@ -143,7 +143,7 @@ export default defineType({
},
],
},
{
defineField({
name: 'arrayOfMultipleTypes',
title: 'Array of multiple types',
type: 'array',
Expand All @@ -155,7 +155,7 @@ export default defineType({
{
type: 'book',
},
{
defineArrayMember({
type: 'object',
name: 'color',
title: 'Color with a long title',
Expand All @@ -174,10 +174,32 @@ export default defineType({
name: 'name',
type: 'string',
},
defineField({
name: 'nestedArray',
title: 'Nested array',
type: 'array',
of: [
defineArrayMember({
type: 'object',
name: 'color',
title: 'Color with a long title',
fields: [
{
name: 'title',
type: 'string',
},
{
name: 'name',
type: 'string',
},
],
}),
],
}),
],
},
}),
],
},
}),
{
name: 'arrayOfMultipleTypesPopover',
title: 'Array of multiple types (modal.type=popover)',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,31 @@ export const ptAllTheBellsAndWhistlesType = defineType({
}),
defineField({
title: 'Box Content',
name: 'body',
name: 'content',
type: 'array',
of: [{type: 'block'}],
validation: (rule) => rule.required().error('Must have content'),
}),
defineField({
title: 'Nested object',
name: 'nestedObject',
type: 'object',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
validation: (rule) => rule.required().warning('Should have a title'),
}),
defineField({
title: 'Box Content',
name: 'body',
type: 'array',
of: [{type: 'block'}],
validation: (rule) => rule.required().error('Must have content'),
}),
],
}),
],
components: {
preview: InfoBoxPreview as any,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"@bjoerge/mutiny": "^0.5.3",
"@google-cloud/storage": "^7.11.0",
"@jest/globals": "^29.7.0",
"@playwright/test": "1.41.2",
"@playwright/test": "1.44.1",
"@repo/package.config": "workspace:*",
"@repo/tsconfig": "workspace:*",
"@sanity/client": "^6.21.0",
Expand Down
12 changes: 12 additions & 0 deletions packages/@sanity/types/src/schema/asserters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
type BooleanSchemaType,
type DeprecatedSchemaType,
type DeprecationConfiguration,
type FileSchemaType,
type ImageSchemaType,
type NumberSchemaType,
type ObjectSchemaType,
type ReferenceSchemaType,
Expand Down Expand Up @@ -108,6 +110,16 @@ export function isReferenceSchemaType(type: unknown): type is ReferenceSchemaTyp
return isRecord(type) && (type.name === 'reference' || isReferenceSchemaType(type.type))
}

/** @internal */
export function isImageSchemaType(type: unknown): type is ImageSchemaType {
return isRecord(type) && (type.name === 'image' || isImageSchemaType(type.type))
}

/** @internal */
export function isFileSchemaType(type: unknown): type is FileSchemaType {
return isRecord(type) && (type.name === 'file' || isFileSchemaType(type.type))
}

/** @internal */
export function isDeprecatedSchemaType<TSchemaType extends BaseSchemaType>(
type: TSchemaType,
Expand Down
4 changes: 2 additions & 2 deletions packages/sanity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@
"devDependencies": {
"@jest/expect": "^29.7.0",
"@jest/globals": "^29.7.0",
"@playwright/experimental-ct-react": "1.41.2",
"@playwright/test": "1.41.2",
"@playwright/experimental-ct-react": "1.44.1",
"@playwright/test": "1.44.1",
"@repo/package.config": "workspace:*",
"@sanity/codegen": "3.49.0",
"@sanity/generate-help-url": "^3.0.0",
Expand Down
33 changes: 28 additions & 5 deletions packages/sanity/playwright-ct.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {defineConfig, devices} from '@playwright/experimental-ct-react'
const TESTS_PATH = path.join(__dirname, 'playwright-ct', 'tests')
const HTML_REPORT_PATH = path.join(__dirname, 'playwright-ct', 'report')
const ARTIFACT_OUTPUT_PATH = path.join(__dirname, 'playwright-ct', 'results')
const isCI = !!process.env.CI

/**
* See https://playwright.dev/docs/test-configuration.
Expand Down Expand Up @@ -36,18 +37,19 @@ export default defineConfig({
],

/* Maximum time one test can run for. */
timeout: 10 * 1000,
timeout: 30 * 1000,
expect: {
// Maximum time expect() should wait for the condition to be met.
timeout: 5 * 1000,
timeout: 10 * 1000,
},

/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 40 * 1000,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
trace: isCI ? 'on-all-retries' : 'retain-on-failure',
video: isCI ? 'on-first-retry' : 'retain-on-failure',
/* Port to use for Playwright component endpoint. */
ctPort: 3100,
/* Configure Playwright vite config */
Expand All @@ -69,8 +71,29 @@ export default defineConfig({

/* Configure projects for major browsers */
projects: [
{name: 'chromium', use: {...devices['Desktop Chrome']}},
{name: 'firefox', use: {...devices['Desktop Firefox']}},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
permissions: ['clipboard-read', 'clipboard-write'],
contextOptions: {
// chromium-specific permissions
permissions: ['clipboard-read', 'clipboard-write'],
},
},
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
launchOptions: {
firefoxUserPrefs: {
'dom.events.asyncClipboard.readText': true,
'dom.events.testing.asyncClipboard': true,
},
},
},
},
{name: 'webkit', use: {...devices['Desktop Safari']}},
],
})
89 changes: 89 additions & 0 deletions packages/sanity/playwright-ct/tests/fixtures/copyPasteFixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {test as base} from '@playwright/experimental-ct-react'

export const test = base.extend<{
getClipboardItemByMimeTypeAsText: (mimeType: string) => Promise<string | null>
setClipboardItems: (items: ClipboardItem[]) => Promise<void>
getClipboardItems: () => Promise<ClipboardItem[]>
getClipboardItemsAsText: () => Promise<string>
}>({
page: async ({page}, use) => {
const setupClipboardMocks = async () => {
await page.addInitScript(() => {
const mockClipboard = {
read: () => {
return Promise.resolve((window as any).__clipboardItems)
},
write: (newItems: ClipboardItem[]) => {
;(window as any).__clipboardItems = newItems

return Promise.resolve()
},
readText: () => {
const items = (window as any).__clipboardItems as ClipboardItem[]
const textItem = items.find((item) => item.types.includes('text/plain'))
return textItem
? textItem.getType('text/plain').then((blob: Blob) => blob.text())
: Promise.resolve('')
},
writeText: (text: string) => {
const textBlob = new Blob([text], {type: 'text/plain'})
;(window as any).__clipboardItems = [new ClipboardItem({'text/plain': textBlob})]
return Promise.resolve()
},
}
Object.defineProperty(Object.getPrototypeOf(navigator), 'clipboard', {
value: mockClipboard,
writable: false,
})
;(window as any).__clipboardItems = []
})
}

await setupClipboardMocks()

page.on('framenavigated', async () => {
await setupClipboardMocks()
})

await use(page)
},

setClipboardItems: async ({page}, use) => {
await use(async (items: ClipboardItem[]) => {
;(window as any).__clipboardItems = items
})
},

getClipboardItems: async ({page}, use) => {
await use(() => {
return page.evaluate(() => navigator.clipboard.read())
})
},

getClipboardItemsAsText: async ({page}, use) => {
await use(async () => {
return page.evaluate(async () => {
const items = await navigator.clipboard.read()
const textItem = items.find((item) => item.types.includes('text/plain'))

return textItem
? textItem.getType('text/plain').then((blob: Blob) => blob.text())
: Promise.resolve('')
})
})
},

getClipboardItemByMimeTypeAsText: async ({page}, use) => {
await use(async (mimeType: string) => {
return page.evaluate(async (mime) => {
const items = await navigator.clipboard.read()
const textItem = items.find((item) => item.types.includes(mime))
const content = textItem ? textItem.getType(mime).then((blob: Blob) => blob.text()) : null

return content
}, mimeType)
})
},
})

export const {expect} = test
Loading
Loading